main
13
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
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> |
||
|
|
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>
|
||
|
|
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>
|
||
|
|
d2e52a1b79 |
feat(runtime): compatibility hardening (Phase 7) (#18)
Phase 7 of plans/autonomous-survival-bot-prd.md. Five small modules
that close the recurring "shared state" and "version-pinned list"
failure modes the PRD flags in §7 and §5.4.
New:
- runtime/movement-profiles.js: named profiles (GATHER, TRAVEL, FLEE,
BUILD, RETURN_TO_BASE) as pure descriptors via PROFILE_DEFAULTS,
plus applyProfile(profile, bot) that hands a fresh Movements to
pathfinder. Avoids the "flee left canDig=false on the shared
Movements, next chop got stuck in canopy" regression.
- runtime/owned-blocks.js: JSONL ledger of blocks this bot placed/
removed (state/<host>/owned-blocks.jsonl); isOwned({x,y,z}) for
O(1) lookups; ensureDir() makes the parent dir lazily.
- runtime/claim-avoidance.js: classifyArea({blocks, isOwned}) returns
player_build / natural_or_owned / insufficient_data based on
man-made block density vs ownership ratio; shouldAvoid(area) helper.
Designed for gather/place skills to call before touching contested
area.
- runtime/skills/compat.test.js: runs runtime/skills/groups.js against
real minecraft-data registries for 1.18.2, 1.20.4, 1.21.5; spot-
checks that pale_oak_log only appears on 1.21+ etc.
- runtime/compat.test.js: 10 tests covering movement descriptors,
isManMadeBlockName, classifyArea, owned-blocks markPlaced/dedup/
isOwned/markRemoved.
npm test now 79/79.
Co-authored-by: Yuriy Mayatnikov <mayatnikov@me.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
c7eab06f22 |
feat(runtime): stuck-incident detector + skill metrics + edit scope (Phase 6) (#17)
Phase 6 of plans/autonomous-survival-bot-prd.md. Expand the
self-improvement loop so the bot can spot and report no-progress
stagnation, not just exception-class failures.
New:
- runtime/stuck-incident.js: detector fires a structured proposal when
the same noProgressReason persists past 5 min (cooldown 30 min).
Body includes runtimeState, milestone, suggested skill, slim
snapshot, last action result, per-skill success/failure metrics
and a forbidden-paths list. Pure module — caller (bot.js) writes
the proposal.
- runtime/skill-metrics.js: in-memory per-skill ok/fail counters
surfaced on snapshot.skillMetrics for the TUI and the incident
body.
- runtime/stuck-incident.test.js: 6 tests covering null reason,
threshold gating, cooldown, reason change resetting the timer,
body composition and metrics snapshot.
Wiring:
- runtime/state-store.js: writeProposal accepts {editScope: string[]}
and persists it in the frontmatter; readProposalEditScope() reads
it back so future auto-patch.js can refuse cherry-picks that touch
other areas.
- runtime/bot.js: tick() invokes the stuck detector each tick,
records skill ok/fail via skillMetrics, stamps snapshot.skillMetrics
and writes the stuck proposal via writeProposal({editScope}).
dispatchAction now records into skillMetrics for both the
resolved-result and the exception path.
npm test now 46/46.
Co-authored-by: Yuriy Mayatnikov <mayatnikov@me.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
fc62160524 |
feat(runtime): social layer — intent / templates / chat memory (Phase 5) (#16)
Phase 5 of plans/autonomous-survival-bot-prd.md. Make the bot feel
present in chat without ever becoming a command executor.
New: runtime/social/
- intent.js: classifyIntent({text, botName}) returns one of GREETING /
STATUS_QUESTION / ADDRESSED_BANTER / COMMAND_LIKE / UNSAFE_REQUEST /
AMBIENT. Unicode-aware word boundaries so cyrillic + latin both work
("Привет всем" → GREETING, "build me a tower" → AMBIENT unless
addressed).
- reply.js: generateReply({intent, speaker, snapshot, diaryTail}) →
short templated response, or {send: null, escalate: true} for the
caller to decide whether to spend Pi tokens.
- memory.js: createChatMemory() — per-speaker LRU buffer of recent
lines; redacts password / api_key / JWT-shaped tokens at append
time, so the buffer can be safely fed back into any future prompt.
- social.test.js: 12 tests (intent edges, memory eviction, redaction,
reply routing). npm test now 40/40.
state-store.js additions:
- readDiaryTail(n) — reads the last N lines of today's diary; used by
status replies.
- writeEscalation({from, request, whyUnsure, wouldHave}) /
listEscalations() — JSONL log under state/<host>/escalations.jsonl
for UNSAFE_REQUEST classifications and future operator review.
bot.js: handleChat() now routes through social/intent + social/reply
(replacing the Phase-0 inline regexes), records every line into
chatMemory, and writes an escalation when classifyIntent returns
UNSAFE_REQUEST. Command-like notice + dialog-only behaviour from
Phase 0 are preserved.
Co-authored-by: Yuriy Mayatnikov <mayatnikov@me.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
ae7b4d89cb |
feat(runtime): early-game survival curriculum + stone/craft skills (Phase 3) (#15)
Phase 3 of plans/autonomous-survival-bot-prd.md. Gives the bot a
deterministic path from empty inventory through stone-tier tools and
basic storage, without an LLM call per tick.
New:
- runtime/curriculum.js: ordered milestone chooser
(wood.16 → wood.planks-and-sticks → wood.tools → stone.32 →
stone.tools → food.basic → storage.chest → shelter.torch). Each
milestone exposes isDone(inventory, snapshot) and suggest() returning
a { skillId } plan the scheduler can dispatch via runSkill. isDone
uses "stage reached" escapes so progress is monotonic — crafting
planks doesn't bounce the chooser back to "gather 16 logs".
- runtime/skills/gather-stone.js: gather.stone with pickaxe-required
precondition, blacklist on failed paths, registry-aware matching
(stone / cobblestone / deepslate / cobbled_deepslate / andesite /
diorite / granite).
- runtime/skills/craft.js: factory + concrete skills for craft.planks,
craft.sticks, craft.wooden-axe/-pickaxe/-sword, craft.stone-axe/
-pickaxe/-sword, craft.furnace, craft.chest, craft.torch (torch
requires coal or charcoal preflight).
Tests:
- runtime/curriculum.test.js: 14 tests covering chooser ordering,
per-milestone skill suggestion, inventoryFull threshold, monotonic
advancement across stage transitions.
- npm test now runs the full suite: 28/28 passing.
Wiring:
- runtime/bot.js: lastSnapshot.curriculum carries the next milestone
+ suggested skill on every tick; lastSnapshot.currentMilestone
prefers the curriculum title over the planner.md line.
- tui/tui.tsx: milestone line shows the curriculum's suggested skill
and an [inventory full] flag when isInventoryFull fires.
Reflex.js still calls actions.js directly; wiring the scheduler to
runSkill(plan.skillId, …) lands in Phase 4.
Co-authored-by: Yuriy Mayatnikov <mayatnikov@me.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
4b7541435d |
feat(runtime): skill substrate + dynamic groups + reference skills (Phase 2) (#14)
Phase 2 of plans/autonomous-survival-bot-prd.md. Establishes the
composable skill contract from PRD §5.2 and ports three reference
skills so future phases can layer survival behaviour on top instead of
adding more ad-hoc branches to reflex.js.
New: runtime/skills/
- index.js: skill registry + runSkill(id, ctx, args) wrapper. Enforces
preconditions, hard timeout, normalises {ok, code, detail, worldDelta}
on every result, runs validate() and calls recover() on failure.
Stable failure codes live in RUNNER_CODES (unknown_skill,
precondition_failed, timeout, threw, validation_failed, done).
- groups.js: registry-derived item/block sets — logs/planks/sticks/beds
derived by suffix; foods intersects a curated allowlist with the live
bot.registry; axes/pickaxes/swords scoped to whatever the connected
server's item table actually ships. Empty set instead of throwing on
missing registry, so skills can emit code:"unsupported_version".
- chop-logs.js: gather.logs reference skill (wraps chopNearestTree).
- eat.js: survive.eat (wraps eatBestFood, preconditions check carrying
edible food from the registry-derived set).
- wander.js: explore.wander (wraps wander).
- contract.test.js + groups.test.js: 14 tests covering precondition
gating, timeout firing recover(), execute exceptions, validate
flipping ok→false, dynamic group filtering across mock registries.
package.json: `npm test` runs the new contract + groups suites.
docs/runtime.md: documents the skill contract, runner, dynamic groups
and the reference skills.
Reflex.js still calls actions.js directly — wiring the scheduler to
runSkill() lands in later phases when the survival curriculum kicks in.
Co-authored-by: Yuriy Mayatnikov <mayatnikov@me.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
f301529f42 |
feat(runtime): observability + no-progress detector (Phase 1) (#13)
Phase 1 of plans/autonomous-survival-bot-prd.md. The bot must always be able to answer "what am I doing and why am I not doing more?" without parsing the log stream. New modules: - runtime/state.js: pure FSM classifier emitting emergency / working / recovering / planning / social / idle from snapshot + reflex context. - runtime/no-progress.js: sliding-window detector that watches position and inventory; when both are unchanged for 60 s+, emits one stable reason code from REASONS (waiting_for_day, night_hostile_nearby, no_food_source, inventory_full, no_reachable_target, planner_empty, awaiting_action_cooldown). - runtime/viewer.js: optional prismarine-viewer launcher behind VIEWER_PORT. Lazy import so the dep is not required by default. Wiring: - runtime/bot.js: tick() now computes runtimeState + noProgressReason every tick and stamps them on the snapshot along with activeSkill, currentMilestone (read from plan.md, cached 30 s), lastResult, failuresByCode and lastEscalation. - runtime/bot.js: dispatchAction records lastResult and lastFailureAt for the recovering-state classifier. - runtime/planner.js: exports isPlannerBusy(), readNextMilestone() and planExists() so the runtime can show planning state + current milestone without spawning extra Pi calls. - runtime/config.js: adds VIEWER_PORT support. TUI: - tui/tui.tsx: StatusBar gains a state badge, current-skill row, milestone row, no-progress reason warning, last-result line with ok/fail color, failures-by-class summary and last-escalation age. Co-authored-by: Yuriy Mayatnikov <mayatnikov@me.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
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> |
||
|
|
7797dd3d5a |
feat(runtime): fully autonomous self-healing — no operator approval (#10)
Operator feedback: "бот должен быть полностью автономным — сам себя улучшать и чинить, в этом и есть смысл; все что я вижу пока что он стоит на месте и кидает proposals на каждый чих — это кардинально не то что я хочу". Acted on: 1. Trigger filter — proposals only on real bugs. runtime/bot.js classifies failure detail into bug / timeout / feature-gap / other. The 5-in-a-row trigger fires only when the run contains a bug (TypeError / Cannot read / is not defined …) OR is entirely timeouts on the same operation. Feature gaps like "no reachable log within 32 blocks", "no food in inventory", "no bed in range", "no target in reach" are SKIPPED — the reflex layer routes around them (noTreesUntil → wander, etc). The LLM has no business patching code for missing inventory. Threshold raised 3 → 5 in a row. Cooldown unchanged (30 min). 2. Auto-apply, no operator-in-the-loop. New runtime/auto-improve.js polls proposals/ every 2s. When it sees a new .md and 10s have passed since first sighting (debounce), spawns scripts/auto-patch.js detached. New scripts/auto-patch.js: refuses on dirty tree, moves proposal pending → approved/, branches `auto/<slug>` off main, runs `pi -p` with 10-min timeout. If Pi committed AND every changed file is under runtime/ → cherry-picks onto main. Otherwise discards the branch. No push, no PR. Audit trail in state/<host>/proposals/approved/. Rate limit: 15-min cooldown between finished runs + 4/hour hard cap. 3. Auto-rollback on bad patches. runtime/supervisor.js: when MAX_RESTARTS_PER_MINUTE is exceeded AND `git log -1 HEAD` is younger than 15 min AND HEAD touched runtime/, runs `git reset --hard HEAD~1`. Up to MAX_ROLLBACKS=3 lifetime, then exits 1 for manual investigation. Restart counters are reset after a successful rollback so the next attempt isn't immediately killed. 4. current-task.json slim. No longer stores the full perception snapshot (was ~3 KB per write × every action). Position only — sufficient as a resume anchor. Slim snapshot still goes into the proposal markdown for context. docs/runtime.md — rewrote the self-improvement section: full flow diagram, classification rules, all rate-limit knobs, manual escape hatches kept but documented as rarely-needed. Also cleared 5 stale proposals from previous smoke tests so the first production run isn't burning Pi tokens on stale bugs that have since been fixed. Co-authored-by: Yuriy Mayatnikov <mayatnikov@me.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com> |
||
|
|
445524b34d |
docs(runtime): reflect live reflex bodies + operator chat + self-improvement loop (#7)
Updates docs/runtime.md and README.md to match what's actually shipped:
- reflex chain priorities and what each body now dispatches
- operator chat command list (status, come, pause, resume, stop)
- automatic + manual Pi escalation paths and the no-code-change rule
- the full self-improvement loop end-to-end (detector → TUI approval
→ propose:apply → supervisor restart) with the rationale for the
manual propose:apply step
- new state files layout (proposals/, proposals/approved/, etc.)
- supervisor.js + bot:bare script flags
No code changes.
Co-authored-by: Yuriy Mayatnikov <mayatnikov@me.com>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
||
|
|
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> |