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>
This commit was merged in pull request #7.
This commit is contained in:
@@ -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/<host>/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 <filename>` — 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:
|
||||
|
||||
+115
-37
@@ -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 "<question>"` 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/<host>/proposals/YYYY-MM-DD-<slug>.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/<host>/proposals/<ts>-<slug>.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/<host>/proposals/approved/
|
||||
8. operator runs: npm run propose:apply <filename>
|
||||
9. script verifies clean working tree, creates feat/proposal-<slug>
|
||||
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/<MC_HOST>_<MC_PORT>/`, gitignored, same
|
||||
as before. The `bot.sock` lives there too.
|
||||
Per-server state stays under `state/<MC_HOST>_<MC_PORT>/`, 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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user