Commit Graph
19 Commits
Author SHA1 Message Date
mayatnikovandClaude Opus 4.7 0d97ccccfa feat(v0.3.0): paradigm shift — TimeWeb-only LLM + persistent advisor trail + improvement queue
This is the rc.4 batch the user requested:

  1. Emergency triggers (low HP + close hostile, lava-under-foot)
     bypass the long cooldown so the LLM is consulted BEFORE the bot
     dies, not after.
  2. Active manifesto need is now included in the advisor user prompt
     — the LLM picks suggestions that satisfy the bot's current
     concrete need (L2 tools_wood → "gather logs nearby" not
     "explore further").
  3. Every advisor recommendation is persisted to SQLite
     (advisor_recommendations table) with full token usage. The
     reflex marks 'applied=1' when it dispatches and updates
     outcome_ok/code when the dispatch completes. Ground truth for
     "is the LLM actually helping" lives in the DB, not in logs.
  4. Pi CLI is OUT of every background loop. coach/postmortem and
     coach/reflect now go through the same TimeWeb endpoint
     fast-advisor uses, via the shared coach/llm-call.js helper.
     Pi is reserved for manual operator commands.
  5. The LLM (postmortem, reflect, advisor) can flag "structural
     gaps" — missing skills/features the operator should implement.
     These land in the new improvement_requests table. Dedup by
     title bumps `votes` instead of inserting duplicates so the
     queue doesn't bloat. Operator views via
     `node scripts/list-improvements.js`.
  6. A deterministic trigger-tuner runs hourly: reads 24h of
     recommendation stats, flags triggers whose success rate is
     below 25% (sample ≥ 5) or whose prompts are expensive (>1000
     input tokens) with mediocre payoff. Improvements get
     source="tuner", category="tuning". No LLM call.

New files:
  runtime/coach/llm-call.js        — askAnalytical() helper
  runtime/coach/trigger-tuner.js   — stats → improvements
  runtime/coach/trigger-tuner.test.js
  scripts/list-improvements.js     — operator CLI

Schema additions:
  advisor_recommendations: id, ts, trigger_reason, planned_skill,
    recommended_skill, action, rationale, active_need, tokens_in,
    tokens_out, latency_ms, applied, outcome_ok, outcome_code, outcome_at
  improvement_requests: id, ts, source, category, title, description,
    context, priority, status, duplicate_of, votes, implemented_at, notes

Renamed env-var consumers:
  Pi-coach drainOnce({ askPi })   → drainOnce({ askAnalyticalFn? })
  Pi-reflect runOnce({ askPi })   → runOnce({ askAnalyticalFn? })
  bot.js attachCoach/attachReflect no longer pass askPi
  attachTuner() added to bot.js spawn handler
  lessons.source 'pi-coach'   → 'timeweb-coach'
  lessons.source 'pi-reflect' → 'timeweb-reflect'

Token cost measured live:
  ~705 input + 45 output = ~750 total per advisor call
  worst case @ 6 calls/hour rate cap = ~108K tokens/day
  OpenAI gpt-5-mini reference price: ~$0.60/month

Operator usage:
  node scripts/list-improvements.js                # open queue
  node scripts/list-improvements.js --stats        # advisor performance
  node scripts/list-improvements.js --done 17 "shipped in 0.3.1"
  node scripts/list-improvements.js --reject 18 "duplicate"

Tests: 360 green (was 332, +28 new).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 19:12:48 +03:00
mayatnikovandClaude Opus 4.7 bc381b2a4b feat(v0.3.0): auto-trigger fast-advisor + token usage tracking
Closes the awareness → LLM → action loop that the rc.1/2/3 sequence
left as a followup. When the bot is wedged, looping, or just suffered
a preempt-then-retry, the reflex fires advise() in the background;
when the recommendation lands it overrides the next dispatch.

Async by design: advise() takes 5-15s on TimeWeb's hosted endpoint —
too slow for a synchronous reflex tick. tickAdvisor() is fire-and-
forget, the result lands on ctx.advisorRecommendation, and the *next*
tick reads and consumes it. Recommendations age out after 60s.

Components:

- runtime/coach/advisor-trigger.js — policy + async fire path
  - tickAdvisor(ctx, {plannedSkillId}) checks three triggers:
    1. wedged > 60s (no significant move)
    2. last 4+ dispatches are the same skill AND it's planned again
    3. preempt within last 30s + same skill being retried
  - 90s trigger cooldown, single-in-flight guard
  - consumeFreshRecommendation(ctx) reads/clears the cache
- runtime/reflex.js — curriculumReflex calls tickAdvisor() every tick
  and consumes a fresh recommendation BEFORE dispatching. ctx flag
  disableAdvisor=true for tests.
- runtime/bot.js — dispatchAction maintains a rolling 8-slot
  reflexCtx.recentSkillIds for the loop-detection trigger.

Token usage:

- runtime/llm/provider.js — normaliseUsage() reads OpenAI/TimeWeb-
  style {prompt_tokens, completion_tokens, total_tokens} from the
  response. Returned on every complete() result and logged at info
  level as "in=Nt/out=Mt".
- runtime/coach/fast-advisor.js — getUsageSnapshot() aggregates
  total tokens across all calls in the session.

Measured on live TimeWeb endpoint (gpt-5.4-mini agent):
  per call: ~705 input + 45 output = ~750 tokens
  rate limit: 6 calls/hour
  worst case at full budget: ~108K tokens/day
  estimated cost (OpenAI gpt-5-mini reference price): ~$0.60/month

Well within any reasonable budget — model can run hot 24/7.

Smoke verified: scripts/check-timeweb.js probe 4 produces
  trigger fired: true (wedged_90s)
  recommendation: recovery.tunnel-out
  rationale: "Stuck wedged for 90s; exploration is failing."
  latency: 5302ms

Tests: 345 green (was 332, +13 advisor-trigger).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 18:44:30 +03:00
mayatnikovandClaude Opus 4.7 ddc67a5031 v0.3.0-rc.2: manifesto / needs ladder L0-L10
Adds an explicit hierarchical needs catalogue that the reflex consults
on every tick. The bot now pursues tangible intermediate goals (food,
wood tools, shelter, stone tools, ...) instead of inheriting whatever
the curriculum thought was "next".

Ladder:
  L0  alive          HP>5, food>0, not in lava, not panic-near hostile
  L1  food           ≥6 food items in inventory (or sated + any food)
  L2  tools_wood     wooden_pickaxe + wooden_axe + wooden_sword
  L3  shelter_basic  bed placed nearby or in inventory
  L4  tools_stone    stone-tier triplet
  L5  armor_basic    any chestplate (pursue=null until craft.leather-*
                     lands; ladder gracefully skips)
  L6  food_security  ≥16 food items
  L7  tools_iron     iron-tier triplet (pursue=gather.stone for now)
  L8  armor_iron     iron chestplate (pursue=null for now)
  L9  village_seed   bed + chest in nearby blocks
  L10 village_full   never detected, falls through to curriculum

Each need has detect(snapshot) → bool and pursue(snapshot) →
{skillId, args} | null. The ladder picks the LOWEST unsatisfied
pursuable need. Needs whose pursue is null get recorded as
blockedNeeds and the walk continues — no stalling on missing skills.

Wired into curriculumReflex: manifesto takes precedence over
curriculum.plan when it has a concrete suggestion. Tests can pass
ctx.disableManifesto=true to exercise the curriculum branch
in isolation (existing reflex tests keep passing this way).

Pi self-reflection prompt now includes
"activeNeed (Maslow ladder L0-L10): L2 tools_wood → gather.logs"
so Pi advises at the right level instead of giving generic guidance.

skillId returned by pursue() is validated against the live registry
(rc.1 plumbing) — manifesto cannot accidentally dispatch a
hallucinated skill name.

Tests: 315 green (was 279 on rc.1, +36 new):
- runtime/manifesto/needs.test.js — 24 tests (per-need detect/pursue,
  helper sums)
- runtime/manifesto/state.test.js — 10 tests (ladder walk, hostile
  takeover at L0, armor skipping, caching)
- runtime/reflex.test.js — 2 integration tests (manifesto overrides
  curriculum plan; well-fed bot pursues tools_stone)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 17:50:05 +03:00
865aae1213 v0.2.0-rc.3: pillar-up escape + advice in fallback + danger POI (#22)
* v0.2.0-rc.3: pillar-up escape + advice everywhere + danger POI

Closes the gap rc.2 left open. Live observation showed:
- Pi-coach extracted 5 high-quality lessons (do not explore.far at
  night near zombies, etc.) but none of them fired (applied_count=0
  across the board). Root cause: dispatcher consulted advice only on
  the main curriculum path; the bot was falling into the wander/
  explore.far FALLBACK after each gather attempt bailed, which
  bypassed consult().
- Bot was wedged in a pit on (608, 90) with stone walls. recovery.
  tunnel-out kept failing ("Digging aborted") because mining stone
  with fists takes ~10s/block; pathfinder watchdog kills it.

This patch:

1. survive.pillar-up (runtime/skills/pillar-up.js) — new escape skill.
   Places a placeable block under the bot and jumps onto it; repeats
   up to 8 steps. No pickaxe required. Works in dirt/cobble/planks/
   sand/gravel/wool/etc. The bot's vertical exit from any pit it can
   stand in.

2. Wedged-emergency reflex (runtime/reflex.js). At the top of
   curriculumReflex, if noProgressReason is wedged-like AND position
   hasn't shifted ≥16 blocks in 60s AND no hostile in 6m AND pillar
   block in inventory → dispatch survive.pillar-up. 2-min cooldown
   between attempts.

3. consult() now also runs on the WANDER/explore.far fallback path
   (runtime/reflex.js curriculumReflex). Pi-coach lessons can finally
   take effect. If the fallback skill is overridden to a non-eligible
   skill but the bot has a placeable block, falls back to pillar-up.
   Outcomes feed reportAdviceOutcome so confidence stays grounded.

4. recordPOI("danger") on death (runtime/coach/postmortem.js). Spatial
   memory now flags where the bot died, expires after 6h. POI table
   was empty in rc.2.

5. SAFE_OVERRIDES extended (runtime/coach/advice.js): adds
   survive.pillar-up and village.choose-base so coach lessons can
   route there.

Tests: 255/255 green (+9 pillar-up).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

* v0.2.0-rc.3 fixup: relax wedged-escape trigger

Drop the WEDGED_REASONS check — noProgressReason is a string that
may or may not be set when the bot is stuck. Fire pillar-up purely on
"no horizontal progress ≥ 60s, no hostile in 6m, placeable block in
inv". Pillar-up is a constructive no-op when it's not needed (places
one dirt under self) so the false-positive cost is small.

Live observation: rc.3 was deployed and bot was wedged with tunnel-out
repeatedly aborted on stone, but wedged-escape never fired because
the runtime's noProgressReason wasn't in my whitelist. Removing the
gate lets the trigger actually engage.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>

---------

Co-authored-by: Yuriy Mayatnikov <mayatnikov@me.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 15:31:21 +03:00
e84148d189 v0.2.0-rc.2: P0 hardening — Pi headless, test state isolation, advice fixes (#21)
P0 (correctness):

1. PEPA_HEADLESS=1 guard in extensions/mineflayer-bridge.ts. When `pi -p`
   spawns a subprocess (banter, coach, planner, reflect, auto-patch), the
   bridge no longer attempts a second MC connect — the hybrid runtime
   already owns the nickname. runtime/pi-bridge.js sets the env var on
   every spawn. Root cause of the "two pepa_bot's racing for the slot"
   bug seen in reply-pi stderr.

2. Test state isolation in runtime/config.js. When running under the node
   test runner (detected via execArgv/argv) — or when PEPA_STATE_DIR is
   set — stateDir redirects to /tmp/pepa-test-state-<pid>/. log.js,
   scenario-memory, world-journal, and knowledge.db all follow.
   `npm test` no longer pollutes live scenarios.jsonl, world-journal.jsonl,
   or daily log files. Verified empirically: post-fix run added 0 test
   rows to the live scenarios file. Cleaned ~550 historical test rows
   from live state in the same change.

3. defendReflex outcome reporting (runtime/reflex.js). Previously a
   creeper-rule override marked the lesson succeeded=false BEFORE the
   flee skill returned. Now dispatchDefendFlee accepts {lessonId} and
   the onComplete fires reportAdviceOutcome with the actual flee result.

4. Mode-name → skill-id translation in runtime/coach/advice.js. Pi-coach
   occasionally returns prefer_skill values that are mode names
   ("night_shelter", "self_preservation", "hunger"). normalisePreferSkill
   maps these to SAFE_OVERRIDES entries before dispatch. Also handles
   "tunnel-out", "survive_flee", "survive flee" shapes.

New behavior:

5. Self-reflection loop (runtime/coach/reflect.js). Every 30 min, the
   bot asks Pi: "Are you making progress, or stuck in a loop? What
   should you do differently?" Pi answers with a verdict
   (progress/loop/recovering/idle/emergency), summary, next-action, and
   0-N new lessons. The reflection is written to
   state/<host>/reflections/<ts>.md and lessons land in the DB with
   source="pi-reflect". Rate-limited to 2 calls/hour. Wired through
   bot.js with the existing askPi + lastSnapshot accessor.

Tests: 246/246 green (+9 new: 6 advice mode-name + 4 reflect).

Co-authored-by: Yuriy Mayatnikov <mayatnikov@me.com>
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 14:10:28 +03:00
mayatnikovandClaude Opus 4.7 e183aaec4e feat(runtime/coach,reflex): retrieval-augmented dispatch via learned lessons
This closes the learning loop. Lessons in knowledge.db now actually
influence behaviour:

- runtime/coach/advice.js: consult({plannedSkillId, snapshot}) reads
  knowledge.topAdvice() and returns 'override' / 'avoid' / 'proceed'.
  When a lesson says "avoid <skill>" with prefer="survive.flee" (etc.),
  the dispatcher swaps in the alternative.

- runtime/reflex.js:
  * curriculumReflex now consults advice before dispatch; on 'avoid'
    backs off the planned skill + sets wander hint; on 'override'
    dispatches the lesson's preferred alternative.
  * defendReflex (dist≤4 melee branch) consults advice too — so a
    creeper at 4m honours the starter rule "attack creeper → flee".
    Failure outcomes feed back via markApplied so confidence stays
    grounded.

SAFE_OVERRIDES whitelist contains only known runSkill targets
(survive.flee, survive.sleep, survive.eat, recovery.tunnel-out,
explore.far/wander, village.build-shelter); unknown prefers fall back
to plain 'avoid'.

7 advice tests; total suite 237 green.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 13:23:20 +03:00
mayatnikovandClaude Opus 4.7 86e5294bb8 chore: snapshot pre-v0.2.0 WIP (pathfinder/reflex/metrics/skills improvements)
Baseline for the v0.2.0 self-learning iteration. All 205 tests pass on this
state. Subsequent commits in this branch layer the knowledge base,
post-mortem coach, and persona narration on top.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-27 13:05:12 +03:00
mayatnikov f13799de28 fix(runtime/reflex): retreat after repeated melee clears 2026-05-26 16:13:11 +03:00
mayatnikov b23aad2128 fix(runtime/reflex): verify melee clears hostile 2026-05-26 16:09:21 +03:00
mayatnikovandClaude Opus 4.7 4ae63dabe1 feat(runtime): v0.1.0 — adopt Voyager critic + Mindcraft modes/library/lint
Five concrete patterns from Voyager and Mindcraft, applied in our shape
without abandoning the git-as-evolution-substrate that makes pepa
distinct. Plus a first multi-agent surface so two bots from the same
repo can share intent.

1. runtime/critic.js (Voyager critic.txt)
   - Spawns `pi -p` with a JSON-only critic prompt before a proposal is
     written. {reasoning, success, critique}.
   - success=true short-circuits the proposal (bot recovered between
     detector tripping and now), saving Pi tokens on false positives.
   - critique is spliced into the proposal body via attachCritique() so
     the downstream auto-patcher has a sharp spec.
   - Graceful: pi missing / timeout / unparseable JSON → proposal still
     filed without the critic block.

2. scripts/lint-patch.js (Mindcraft coder._lintCode)
   - Pre-flight gate between Pi commit and npm test: node --check, dynamic
     import (catches missing named exports), regex extraction of
     runSkill("id") calls cross-checked against the live registry.
   - Cheaper than npm test, fails fast with a clear reason.

3. runtime/stuck-incident.renderActionTemplate (Voyager action_template.txt)
   - All proposal bodies now follow the same fixed-section layout: Task /
     Last result / Execution error / State / Metrics / Journal /
     Scenarios / Critique / Fix / Edit scope / Forbidden.

4. runtime/skill-library.js (Mindcraft skill_library.getRelevantSkillDocs)
   - Word-overlap ranking (Mindcraft's offline fallback) — zero deps,
     deterministic. auto-patch.js injects top-3 similar skills into the
     Pi prompt as "look at these patterns".

5. runtime/modes.js (Mindcraft modes.js)
   - Declarative {name, interrupts, on, active, update(ctx)} chain that
     runs BEFORE the curriculum each tick.
   - Ships self_preservation (low HP → eat/flee), hunger (food<14 → eat),
     night_shelter (night + bed in hand → sleep). Cleaner than ad-hoc
     lastFleeAttempt cooldowns in reflex.js.

6. runtime/social/conversation.js + cmd:conv-say/conv-recent/conv-list
   - File-JSONL topic channel so two bots from the same repo (different
     usernames, different host dirs under state/) can append turns and
     read peers. Skeleton — multi-agent collaboration on top later.

Differentiator preserved: every Pi-written skill still lands on main via
auto-patch.js (real git branch + smoke gate + cherry-pick). Voyager
keeps skills in a Chroma JSON, Mindcraft keeps them in RAM — pepa keeps
them as versioned source code reviewable in `git log`.

package.json: 0.0.1 → 0.1.0. 174/174 tests pass. README + AGENTS updated.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 15:16:42 +03:00
mayatnikovandClaude Opus 4.7 d960db4819 feat(runtime): persistent memory — world-journal + scenario-memory
Closes a structural gap: the bot now actually REMEMBERS what it
discovered and what it tried. Two stores live under state/<host>/ and
are wired in automatically.

runtime/world-journal.js
- Append-only JSONL of discovered points (chopped, placed, base,
  shelter, farm, dead_end). Indexed by 16-block spatial grid; O(neighbors)
  nearest() lookups; 6 h age prune; 10k line ceiling with trim.
- leanestQuadrant({x,z}) reports the quadrant the bot has the FEWEST
  markers in — used by explore.far to circle rather than retread.
- summary() exposed for the stuck-incident proposal body.

runtime/scenario-memory.js
- Sliding window of (skillId, situationHash, code, ok, detail) tuples.
- situationHash() is a coarse fingerprint (16x8x16 cell + day/night +
  food/hp bucket + inv key set + closest hostile). So "same kind of
  place + same kind of state" matches.
- shouldSkip({skillId, situation}) → true after ≥3 failures within 30
  min UNLESS a more-recent success in the same situation un-locks it.
- recentTailFor() exposed for the stuck-incident body.

Wiring (runtime/bot.js):
- dispatchAction captures situationHash BEFORE the action runs and
  records (skillId, situation, code, ok) after — failures are attributed
  to the dispatch-time state, not the partial-effect state.
- worldDelta fields (choppedAt, minedAt, placedAt, baseAt, shelterAt,
  plantedAt, harvestedAt, tilledAt) auto-flow into the journal.
- no_target + silent_dig_failure also write dead_end markers.

Scheduler / skills now consume memory:
- reflex.js curriculum reflex calls memory.shouldSkip — if the same
  (skill, situation) failed 3+ times recently, auto-converts to a
  wander hint so the bot leaves and tries elsewhere.
- explore.far calls journal.leanestQuadrant when multiple cardinal
  directions are walkable and prefers the less-explored one.
- gather.logs walks to the nearest known "chopped" bucket within 96
  blocks before falling through to findBlock — chunks with confirmed
  trees are more likely to yield another.

stuck-incident body now includes journal byKind + last 12 scenario
entries so Pi can write a structural fix, not just a guard clause.

Architecturally: this is the foundation for "bot rewrites itself".
The proposals Pi now receives carry real signal about what was tried
and what's around, instead of a single snapshot in isolation.

10 new tests (world-journal × 5, scenario-memory × 5). npm test 134/134.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 12:20:03 +03:00
mayatnikovandClaude Opus 4.7 19dc8e12c6 fix(runtime): unstick wander loop + chop radius + explore.far skill
Follow-up to the iteration-1 fixes. Live smoke on play.xmatic.team
revealed the bot was spawning into a tree-less plain (no log within
32 blocks of spawn), looping wander→gather→no_target→wander
forever inside a 16-block box.

- runtime/actions.js: chopNearestTree search radius 32 → 64 (still no
  trees on this spawn, but a normal biome will be served well by it).
  wander now has a blind-walk fallback when pathfinder times out
  (look+forward+jump for 3 s) so the bot at least unsticks from leaves
  or pillars. Pathfinder timeout reduced 30 s → 15 s.
- runtime/skills/explore-far.js: new explore.far skill — walks ~48
  blocks in a quadrant (NE/SE/SW/NW, rotating per call) so successive
  hints actually circle the spawn instead of bouncing in place. Blind
  walk fallback included.
- runtime/reflex.js: when the scheduler is told to wander twice in a
  row by gather.* recover hints, it now dispatches explore.far instead
  so the bot actually leaves the patch it's stuck in. Resets the
  consecutiveWanderHints counter on any success.
- runtime/reflex.js (sleep): no longer dispatches when the bot has
  neither a bed in inventory NOR a known shelter/base location —
  saved one dispatch + 5-min cooldown per restart at night.
- runtime/reflex.js (eat): inventory check + lastEatAt always updated
  fix the eat-spam loop observed live (every tick fired "eat" → "no
  food in inventory" → again).
- runtime/skills/chop-logs.js: recognise "no log within ..." as
  no_target so the recover hint switches the bot to wander/explore.

npm test 124/124.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 11:18:36 +03:00
mayatnikovandClaude Opus 4.7 29542f0559 fix(runtime): unstick scheduler + chop + sleep + bed/shelter/farm skills
Recovers the bot from the live-server symptoms reported 2026-05-26:
1) constant supervisor reconnects, 2) chop "clicks once and stops",
3) sleep does nothing without a bed and so blocks night-skipping for
other players, 4) curriculum reflex always fell through to wander.

Supervisor (#38):
- runtime/watch-filter.js: pure predicate excluding *.test.js + the
  supervisor itself; recursive:true so skills/ + social/ edits also
  restart. Burned a working main once when test files counted toward
  the rollback threshold.
- runtime/supervisor.js: watch-triggered restarts no longer count
  toward the crash-loop rollback path. Watcher is now recursive.

Chop / mine (#39):
- runtime/actions.js + runtime/skills/gather-stone.js: replaced raw
  pathfinder.goto + bot.dig with mineflayer-collectblock's
  bot.collectBlock.collect — handles approach, repositioning, LoS,
  dig and pickup as one primitive. Old version "swung once" because
  GoalGetToBlock often parked the bot in leaves above the log.

Sleep + bed (#40):
- runtime/actions.js: sleepInBed now ALSO places a carried bed on
  solid ground next to the bot and sleeps on it. Critical so the bot
  stops blocking player night-skipping the moment it owns a bed.

Bed pipeline (#41):
- runtime/skills/gather-wool.js: gather.wool skill — mines wool block
  if any nearby, otherwise shears or attacks the nearest sheep.
- runtime/skills/craft.js: craftBedSkill (any colour the bot has ≥3
  wool of, plus 3 planks, plus a table).
- runtime/curriculum.js: new milestone survive.bed sits between
  wood.tools and stone.32 so the bot gets a bed BEFORE everything else.
  Test fixture updated to include a red_bed in post-survive.bed stages.

Village / shelter / wheat (#42, #43):
- runtime/skills/build-shelter.js: village.build-shelter — real 3×3×3
  resumable hut blueprint around the recorded base, places one block
  per loop, idempotent so an interrupted build resumes correctly,
  marks each placed block in the owned-blocks ledger.
- runtime/skills/deposit-surplus.js: village.deposit-surplus opens
  the nearest chest and transfers surplus stacks while keeping a
  reserve of tools/food/bed.
- runtime/skills/farm-wheat.js: farm.wheat does one step per call
  (till adjacent-to-water grass, plant seeds, or harvest ripe wheat).
- runtime/curriculum.js: village.shelter milestone after base-site.

Scheduler glitch (root of "always wander"):
- runtime/bot.js: curriculum + locations are now computed BEFORE
  runTick. Previously they were stamped AFTER, so reflex.js saw
  snapshot.curriculum=undefined every tick and fell through to the
  wander fallback. Verified live: scheduler now dispatches
  gather.logs/gather.stone/craft.* by id via runSkill.

Eat-spam:
- runtime/reflex.js: eatReflex now checks inventory for actual food
  and updates lastEatAt on EVERY dispatch (not only successes), so a
  failed eat respects the 5 s cooldown instead of firing every tick.

npm test 123/123. Validated live on play.xmatic.team (curriculum
dispatched gather.logs via runSkill, recover hint switched to wander
when no log in range).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 11:12:21 +03:00
ea4f16a0da feat(runtime): scheduler-via-runSkill + Pi banter escalation + base-site (follow-ups) (#20)
Three closures of remaining PRD follow-ups, one merge:

1. Reflex scheduler now drives behaviour from the curriculum.
   - reflex.js: replaced ad-hoc techTreeReflex + autonomousReflex with
     curriculumReflex that dispatches the skill suggested by
     snapshot.curriculum.plan via runSkill. Per-skill backoff for
     missing_tool / missing_material / no_target / no_food_source /
     unsupported_version. recover() hint with `{hint:"wander"}` swaps
     the next tick to wander for 60 s.
   - Chain is now: defend > eat > sleep > curriculum > idle.
   - reflex.test.js: 11 new tests covering busy/disconnected,
     defend/eat preemption, dispatch by id, unknown-skill fallback,
     per-skill + wander-hint backoffs, onComplete updating backoff.

2. Pi escalation for ADDRESSED_BANTER with hard rate limit.
   - bot.js: when generateReply returns {escalate:true}, spawn askPi
     with bot state + last 5 lines from that speaker (redacted via
     chatMemory). Reply capped at 200 chars, sent as one chat line.
   - Rate cap: 6 calls/hour, 90 s min gap. Suppressed escalations
     log once and silently drop.

3. Phase 4 substrate.
   - runtime/locations.js: atomic JSON store
     (state/<host>/locations.json) with setLocation / getLocation /
     nearestLocation / removeLocation; 6 tests.
   - runtime/base-site.js: scoreCurrentPosition(bot) + pure scoreSite
     bundle (wood / stone / water / flatness / no-players /
     no-foreign-builds, owned-blocks excluded from claim penalty);
     6 tests.
   - runtime/skills/choose-base.js: village.choose-base skill — scores
     the current spot, writes locations.base if score ≥ 8, otherwise
     returns code:"too_weak" with a wander recover hint.
   - curriculum.js: new final milestone village.base-site fires
     village.choose-base until a base location exists.
   - bot.js: stamps snapshot.locations from listLocations() each tick
     so the curriculum can read it without coupling to disk.

docs/runtime.md updated with three new sections.
npm test now 116/116.

Co-authored-by: Yuriy Mayatnikov <mayatnikov@me.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 10:46:14 +03:00
3310cb320f feat(runtime): survival-bot pivot — MC chat is dialog-only (Phase 0) (#12)
Phase 0 of plans/autonomous-survival-bot-prd.md: change the product
direction from operator-driven remote control to autonomous survival
resident. MC chat is dialog-only for everyone, including
OPERATOR_USERNAMES — commands like come/follow/build/pause/stop are
recorded in the diary but not dispatched. TUI remains the only local
control plane.

Runtime changes:
- Remove operatorGoalReflex from reflex.js (the come-here chat command).
- Replace handleOperatorChat in bot.js with a dialog-only handleChat
  that answers greetings/status questions and records command-like
  verbs (en+ru) without dispatching them.
- Default MC_VERSION to "auto" in runtime/config.js; mineflayer
  receives `false` to trigger version auto-detection.
- Update auto-escalation prompt's reflex chain summary.

Docs:
- AGENTS.md: product pivot notice up top; chat-driven scope-trust is
  flagged as legacy/Pi-only.
- README.md / docs/runtime.md: replace operator-chat command list with
  dialog-only description; update reflex chain summary.
- docs/roadmap.md: Phase 2/3 marked superseded by the PRD where they
  assumed chat-driven control.

Co-authored-by: Yuriy Mayatnikov <mayatnikov@me.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 22:01:26 +03:00
313ef081d1 feat(runtime): village progression — crafting, tech-tree reflex, LLM planner (#11)
Closes the loop "стой и кидай proposals" → "копит ресурсы, строит,
работает к глобальной цели". Three pieces:

1. Crafting primitives (runtime/actions.js).

   craftPlanks (4 per log, any wood type), craftSticks (4 per 2 planks),
   placeCraftingTable (crafts a table from planks if needed + places at
   reference block + reuses an existing table within 4 m), craftWoodenAxe,
   craftWoodenPickaxe, craftWoodenSword. Each uses bot.recipesFor()
   + bot.craft() with a 15s timeout. Returns the same {ok, detail}
   contract as the other actions.

   inv.{getItemCount, getAnyPlanksCount, getAnyLogCount} helpers
   exported so the reflex layer can read inventory cheaply without
   pulling mineflayer state through every reducer.

2. Tech-tree reflex (runtime/reflex.js).

   New techTreeReflex between sleep and autonomous. Inventory-driven
   progression: log+0 planks → planks; planks+0 sticks → sticks;
   planks+sticks+no axe → wooden_axe; +no pickaxe → wooden_pickaxe;
   +no sword → wooden_sword. 5 s cooldown so we don't fire on every
   tick.

   Pure script, no LLM. The progression is exactly what a player
   does in the first 10 min on a new world; making it scripted means
   the bot never burns tokens on it.

3. LLM planner (runtime/planner.js).

   Background timer (every 15 min, with a 30 s warm-up after start).
   Reads goal.md + plan.md + a slim snapshot, prompts Pi to output a
   fresh plan.md to stdout. Stripped of code fences and written
   verbatim to state/<host>/plan.md. Capped at 16 KB.

   The plan is markdown the operator can read or edit by hand. Numbered
   milestones, ✓ prefix for completed ones, kept short. The reflex
   layer doesn't auto-execute LLM text — but the planner sets the
   long-horizon shape that future reflexes (build house, plant farm)
   can read.

   5 min timeout on the pi subprocess. If it crashes or times out, the
   next 15-min tick just retries — no propagation to the reflex loop.

The progression now looks like, roughly:

  chop log (autonomous) →
  craft planks → craft sticks → wooden_axe (tech-tree) →
  chop faster (autonomous, has axe now) →
  wooden_pickaxe + wooden_sword (tech-tree) →
  mine stone … (next PR: stone tools, farm site selection,
  house frame)

Smoke-tested: all three modules import cleanly, exports check out.

Co-authored-by: Yuriy Mayatnikov <mayatnikov@me.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 17:00:07 +03:00
f5361ad9a4 feat(runtime): autonomous reflex — chop wood + wander + proactive movement (#8)
Closes the "bot stands on a tree doing nothing" problem reported live
when the operator launched the TUI after PR #6 landed. The reactive
chain (operator > defend > eat > sleep > idle) was passive by design:
day-time, full HP and food, no hostile within 4 m ⇒ every reflex
returned noop. The bot perched in dark-oak canopy and never moved.

Changes

runtime/actions.js:
  - chopNearestTree: find any *_log within 32 blocks, equip best axe
    (falls back to fists), path to the block, dig. Per-bot 5-min
    blacklist of unreachable log positions so we don't grind on the
    same impossible target.
  - wander: pick a random offset 6-16 blocks away and path there.
  - setMovementsForGather / setMovementsForTravel: every action that
    uses pathfinder now sets its own Movements profile (canDig=true)
    instead of inheriting whatever the previous caller left. The old
    behaviour caused chop to inherit flee's canDig=false and get stuck
    in the canopy.
  - fleeFrom now uses canDig=true too — the user observed the bot
    permanently stuck on a leaf block because escape required digging.

runtime/reflex.js:
  - new autonomousReflex between sleep and idle. Cooldown 10s. Picks
    chop when log count < 16, else wander. When chop reports "no
    reachable log within 32 blocks" we switch to wander for 60s so we
    don't re-fire chop against the same impossible position.
  - defendReflex tightened: only flee when closest is ≤8m (or ≤12m
    on low HP). Avoids the "82 distant hostiles ⇒ constant flee
    loop" pathology observed at this spawn.
  - flee cooldown: same mob name within 60s ⇒ noop, so we yield to
    other reflexes if flee keeps timing out.
  - sleepReflex retry cooldown raised 30s → 5min. Sleeping fails
    permanently if no bed is around; the short retry blocked
    autonomous behaviour every tick.
  - ctx.lastReflex now records {name, label, ts} after each
    dispatched/completed reflex so the TUI can show what the bot
    just decided.

runtime/bot.js:
  - per-tick snapshot adds lastReflex and busy fields for the TUI.
  - maybeReplyToPlayer: light canned greetings (yo/hey/hi/привет)
    to non-operators when they address the bot. 30s cooldown so we
    don't spam.

tui/tui.tsx:
  - status bar shows either "▸ busy: <label>" while an action is
    in flight, or "last reflex: <name> (<label>) Ns ago" when idle.
    Gives an at-a-glance answer to "what is the bot doing right now?"

Smoke-tested live (play.xmatic.team, 2026-05-25T13:30-13:39):
  - bot did dispatch chop tree (oak_log at 617,82,95)
  - real bug surfaced and 3-fail rule filed a proposal automatically
  - after the runtime fix, chop returns "no reachable log" gracefully
  - bot switched to wander on the next tick
  - position moved from (623.31, 85, 95.12) to (623.41, 86.02, 96.7)
    — the first observable movement in this dark-oak spawn

Co-authored-by: Yuriy Mayatnikov <mayatnikov@me.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 16:40:36 +03:00
ee2b4c26eb feat(runtime): real reflex bodies + auto-escalation + operator chat (#5)
runtime/actions.js — Mineflayer wrappers with hard timeouts and structured
{ok, detail} returns:
  - attackNearest: equip best melee, lookAt, single swing per call
  - fleeFrom: lazy-load pathfinder, walk N blocks away (canDig=false to
    avoid burrowing through walls under panic)
  - eatBestFood: scan inventory by FOOD_PRIORITY, equip + consume
  - sleepInBed: find nearest placed bed within 16 blocks, path to it, sleep
  - goTo: pathfinder.goto for operator come/follow

runtime/reflex.js — bodies now dispatch real actions via ctx.dispatch:
  - operator-goal (highest): satisfy come/follow command
  - defend: ≤4m attack, ≤12m + low HP/many hostiles flee
  - eat: food < 16 + 5s cooldown
  - sleep: night + safe + 30s retry cooldown
  - idle: heartbeat every 20th tick
Reflex returns "skipped" when ctx.busy so we don't count busy ticks as
either productive or noop in the escalation counter.

runtime/bot.js:
  - ctx.dispatch fire-and-forget wrapper with busy gate, onComplete hook
  - consecutiveNoops counter; after ESCALATE_AFTER_NOOPS (=20, ~1 min at
    tick=3s), askPi with the current snapshot. 10-min cooldown.
  - operator chat handler: parses `<botname> <verb>` messages from
    OPERATOR_USERNAMES. Verbs: status, pause, resume, stop, come.
  - Death drops any pending operator goal.

Smoke-tested live against play.xmatic.team:25565: bot connected, logged
in via AuthMe, reflex chain dispatched flee/sleep, hard timeout fired
when pathfinder couldn't reach the flee target (expected — no usable
ground path in dark_forest at this spawn).

Co-authored-by: Yuriy Mayatnikov <mayatnikov@me.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 16:11:53 +03:00
1e3b36a9a1 feat(runtime): hybrid script reflex + Ink TUI + Pi-on-demand escalation (#4)
* fix(mindcraft-skills): hard timeout on every skill call

mc_avoid_enemies (and 7 other tools) wrapped only in safeCall without a
withTimeout. When mindcraft's underlying pathfinder/pvp goal couldn't be
satisfied, the call never resolved — the Pi tick loop blocked forever.
Observed live: mc_avoid_enemies pending >10 minutes after one mc_observe.

safeCall now takes timeoutMs (default 30s) and wraps withTimeout itself,
so every tool gets a hard ceiling. Per-tool overrides:
  - goToPosition / goToNearestBlock: 120s / 90s (unchanged from before)
  - defendSelf / avoidEnemies: 45s
  - stay: secs*1000 + 10s
  - craft / consume / pickup / place: 30s
  - equip: 15s
collectBlock still uses its bespoke per-iter 75s loop.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* feat(runtime): script-driven reflex daemon + Ink TUI dashboard

Pure-Pi runtime had three failure modes in practice:
  - slow: 20-60s per decision because LLM was in the hot path
  - expensive: every tick (defend, eat, idle) paid for a reasoning pass
  - invisible: required tmux capture-pane to know what the bot was doing

New runtime/ layer is a long-running Node daemon that owns the MC
connection, ticks a priority-ordered reflex chain (defend > eat > sleep
> idle) with NO LLM in the hot path, and exposes status + commands over
a Unix-socket IPC. tui/ is an Ink dashboard that attaches over IPC and
can detach freely — multiple TUI clients can connect at once.

Pi/Codex are still available, but as on-demand escalation: TUI hotkey
'a' spawns `pi -p "<prompt>"` as a subprocess and streams stdout into
the dashboard. The self-improvement loop (proposals → operator approval
→ Pi-driven patch → hot reload) is documented in docs/runtime.md but
not yet wired.

Reflex bodies are stubs today — they log decisions but don't drive
Mineflayer actions yet. The priority chain, IPC contract, and TUI are
fully working; subsequent commits will fill in defend/eat/sleep bodies
and wire automatic escalation.

Run with `npm run bot` + `npm run tui`. Pi-only fallback stays at
`npm run agent`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Yuriy Mayatnikov <mayatnikov@me.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-25 16:04:48 +03:00