From 15d6e4ffe2425d264d72b0ce6346d5b4e728c360 Mon Sep 17 00:00:00 2001 From: Yuriy Mayatnikov Date: Mon, 25 May 2026 16:22:31 +0300 Subject: [PATCH] docs(runtime): reflect live reflex bodies + operator chat + self-improvement loop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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: Claude Opus 4.7 (1M context) --- README.md | 15 +++++ docs/runtime.md | 152 ++++++++++++++++++++++++++++++++++++------------ 2 files changed, 130 insertions(+), 37 deletions(-) diff --git a/README.md b/README.md index 7da7c6e..f623207 100644 --- a/README.md +++ b/README.md @@ -96,8 +96,23 @@ The TUI auto-reconnects to the bot if you restart it. Press `q` to leave the TUI | `r` | Force a fresh status snapshot. | | `c` | Send a chat message into MC. | | `a` | Ask Pi (one-shot subprocess). | +| `y` | Open the latest pending proposal (badge appears in status bar). In the panel: `y` approve, `n`/Esc close. | | `q` | Quit the TUI — bot keeps running. | +### Self-improvement loop (short version) + +The bot **proposes its own patches** when something repeatedly fails: + +1. Reflex action fails 3× with the same label → markdown proposal lands under `state//proposals/`. +2. Status bar shows `[proposals N, press y]`. +3. Press `y` to read, `y` to approve. The proposal moves to `proposals/approved/`. +4. Run `npm run propose:apply ` — spawns Pi headless on a fresh feature branch with the proposal + repo conventions. Pi writes a patch and commits. +5. Review the diff, smoke-test locally (`npm run bot`), push + open a PR by hand. + +Supervisor (`npm run bot`) watches `runtime/*.js` and hot-restarts the child on file change, so during step 4 you can iterate quickly. + +See [`docs/runtime.md`](./docs/runtime.md) for the full lifecycle and the operator-chat command list (`pepa_bot status`, `come`, `pause`, etc.). + ## Authentication Two dimensions: diff --git a/docs/runtime.md b/docs/runtime.md index ff928e8..68402c9 100644 --- a/docs/runtime.md +++ b/docs/runtime.md @@ -90,41 +90,71 @@ TUI; the bot is unaffected. | `r` | Force-broadcast a status snapshot now. | | `c` | Enter **chat mode** — type a message, Enter sends it into MC chat. | | `a` | Enter **ask-Pi mode** — type a prompt, Enter spawns `pi -p` and streams output into the Pi panel. | +| `y` | Open the latest pending proposal. In the proposal panel: `y` approves, `n`/Esc closes. | | `q` | Quit TUI only. Bot keeps running. | -`Enter` submits, blank submit cancels. +`Enter` submits, blank submit cancels. The status bar shows `[proposals N, press y]` when there's something pending. ## What the reflex loop does today -All reflex bodies are currently **stubs** — they log decisions but don't yet -call into Mineflayer actions. The priority chain is wired: +The chain (highest priority first), wired and dispatching real +Mineflayer actions: -1. `defendReflex` — closest hostile within 6 m → log + decision (next: - actually attack / flee). -2. `eatReflex` — food ≤ 16 → log (next: equip food, eat). -3. `sleepReflex` — night + bed in inventory → log (next: `bot.sleep`). -4. `idleReflex` — every 10th tick, log heartbeat (HP / food / pos). +1. **`operatorGoalReflex`** — if `OPERATOR_USERNAMES` issued a `come` / + `follow` command, satisfy it (walk to the operator's last known + position, reply in chat on arrival or failure). +2. **`defendReflex`** — closest hostile within 4 m → `attackNearest` + (equips best melee). Within 12 m + low HP or ≥3 hostiles → `fleeFrom` + along the away-vector. +3. **`eatReflex`** — food < 16 → `eatBestFood` (picks from + FOOD_PRIORITY list, equip + consume). 5 s cooldown. +4. **`sleepReflex`** — night + no hostile within 8 m → `sleepInBed` + (finds nearest placed bed within 16 blocks, paths there, sleeps). + 30 s cooldown on failures. +5. **`idleReflex`** — every 20th tick, log heartbeat (HP / food / pos). Adding a new reflex = a function `(ctx) => { action, ... }` in -`runtime/reflex.js`, inserted at the right priority. Pure script, no LLM. +`runtime/reflex.js`, inserted at the right priority. Actions live in +`runtime/actions.js`. Both files trigger a supervisor hot-restart when +saved (see "Self-improvement" below). ## When the bot calls Pi -Reflexes that don't handle a situation simply return `noop`. After N -consecutive tick cycles with no useful action — or when a reflex explicitly -flags "stuck" — the bot will escalate by calling `pi-bridge.js`: +Two escalation paths: -```js -askPi({ - prompt: "I've been at the same position for 5 minutes, last reflex chain - fell through, snapshot attached. What's a reasonable next action?", - onChunk, onDone, -}); +**1. Manual** — operator presses `a` in the TUI, types a question, the +bot spawns `pi -p ""` and streams its stdout into the Pi +panel. + +**2. Automatic** — every tick where the entire reflex chain returns +`noop` (no operator goal, no hostiles in reach, food fine, day or no +bed, etc.) increments a counter. When the counter hits +`ESCALATE_AFTER_NOOPS = 20` (≈1 min at `tick=3s`), the bot fires +`askPi` with the current snapshot and a fixed system prompt telling Pi +to suggest one next action. 10 min cooldown so a permanently-idle bot +doesn't run the LLM dry. + +The auto-escalation prompt explicitly bans code-change proposals — Pi +should only suggest what to do *with the existing tools*. If a deeper +problem is happening, the failure-tracker (see Self-improvement) will +file a proposal instead. + +## Operator chat commands + +Players listed in `OPERATOR_USERNAMES` can address the bot in MC chat +by prefixing the message with the bot's name: + +``` +pepa_bot status → bot replies with HP / food / pos / hostiles / busy +pepa_bot come → bot pathfinds to the operator's current position +pepa_bot pause → reflex loop stops +pepa_bot resume → reflex loop resumes +pepa_bot stop → graceful disconnect + process exit ``` -This is **not wired into the reflex loop yet** — the escalation is currently -operator-driven via TUI hotkey `a`. Wiring it up as an automatic fallback is -the next milestone. +Unrecognized commands get a polite "didn't recognize" reply. Operator +identity verification is the server's job (AuthMe on cracked, +online-mode on premium) — the bot trusts the nickname. ## IPC protocol @@ -159,32 +189,66 @@ The protocol is intentionally tiny — anyone can write a second client (a Telegram bridge, a web UI, a one-shot CLI) by reading `runtime/ipc-protocol.js`. -## Self-improvement loop (planned) +## Self-improvement loop -When a reflex repeatedly fails (e.g. "tried to navigate to base 3 times, -pathfinder returned noPath each time"), the bot will: +End-to-end and wired. The flow: -1. Write `state//proposals/YYYY-MM-DD-.md` describing the gap. -2. Mark a flag in the next `status` event so the TUI surfaces it. -3. Wait for operator approval (TUI key `y` on a proposal — not yet built). -4. On approval: spawn Pi headless with the proposal text + repo context, ask - it to write a new skill / patch, commit on a feature-branch. -5. Hot-reload the affected module (reflex / actions) without dropping the MC - connection. +``` +1. reflex chain dispatches an action → action returns { ok: false, detail } +2. bot.js failure tracker accumulates the failure under its label +3. same label fails 3× in a row → writeProposal() → markdown lands in + state//proposals/-.md +4. next IPC STATUS event includes pendingProposals: N +5. TUI shows [proposals N, press y] badge +6. operator presses y, reads the proposal, presses y again to approve +7. proposal moves to state//proposals/approved/ +8. operator runs: npm run propose:apply +9. script verifies clean working tree, creates feat/proposal- + branch, spawns `pi -p` with the proposal + repo-conventions prompt +10. Pi commits a patch on that branch (no push, no merge) +11. operator reviews diff, runs `npm run bot` to smoke-test +12. operator pushes the branch and opens a PR by hand +13. supervisor on the running bot picks up runtime/*.js changes and + hot-restarts the child the moment they hit disk +``` -This is the "bot writes its own code, asks permission, restarts itself" -loop — the whole point of having Pi as an escalation rather than a runtime. -Not wired yet; tracked under tasks #56–#58 history. +Operator is in the loop at three guardrails: approving the proposal, +reviewing Pi's diff, deciding to merge. + +### Triggers (today) + +Only one detector is wired: "same labelled action fails 3 times in a +row" — for example, three back-to-back `flee from zombie` failures. +30 min cooldown so the same proposal doesn't multiply when the bot +keeps trying. + +More triggers worth adding (each as a small follow-up): +- "Pi auto-escalation fired but the snapshot didn't change in the next + N ticks" → bot is fundamentally stuck, propose a code change. +- "death count >K in M minutes at similar coords" → safety regression. +- "operator typed the same chat command twice and the bot couldn't act" + → missing operator verb. + +### Why a manual `propose:apply` step + +Approval inside the TUI is cheap — one keypress. Spawning Pi to write a +patch is not (subscription tokens, multiple minutes). Splitting "I want +this addressed" (TUI) from "now actually run the patcher" (CLI) means +you can approve five proposals over a session and dispatch them in a +batch when convenient. ## File layout ``` runtime/ + supervisor.js forks bot.js, watches runtime/*.js, restart-on-change bot.js entrypoint — owns MC + tick + IPC + reconnect config.js reads .env, exposes frozen config + redacted view log.js ring buffer + stdout + daily file + IPC fan-out perceive.js snapshot(bot) → JSON - reflex.js priority chain (defend / eat / sleep / idle, stubs) + reflex.js priority chain (operator > defend > eat > sleep > idle) + actions.js attackNearest / fleeFrom / eatBestFood / sleepInBed / goTo + state-store.js current-task / diary / proposals on disk ipc-server.js Unix-socket server ipc-protocol.js shared contract (event types, command types, framer) pi-bridge.js spawn `pi -p`, stream stdout @@ -192,10 +256,24 @@ runtime/ tui/ tui.tsx Ink dashboard (React) ipc-client.js socket client → EventEmitter + +scripts/ + propose-apply.js approved-proposal → feat-branch + `pi -p` patcher ``` -Per-server state stays under `state/_/`, gitignored, same -as before. The `bot.sock` lives there too. +Per-server state stays under `state/_/`, gitignored: + +``` +state/play.xmatic.team_25565/ + bot.sock Unix-domain socket (perms 0600, ephemeral) + joined-before.flag AuthMe /register vs /login marker + current-task.json resume anchor — what the bot was doing + goal.md long-term ambition (operator-seeded) + diary/YYYY-MM-DD.md daily journal (one line per milestone) + proposals/ pending self-improvement proposals + proposals/approved/ approved, waiting on propose:apply + logs/YYYY-MM-DD.log full runtime log mirror +``` ## Pi-only fallback