docs(runtime): reflect live reflex bodies + self-improvement loop #7
@@ -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. |
|
| `r` | Force a fresh status snapshot. |
|
||||||
| `c` | Send a chat message into MC. |
|
| `c` | Send a chat message into MC. |
|
||||||
| `a` | Ask Pi (one-shot subprocess). |
|
| `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. |
|
| `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
|
## Authentication
|
||||||
|
|
||||||
Two dimensions:
|
Two dimensions:
|
||||||
|
|||||||
+115
-37
@@ -90,41 +90,71 @@ TUI; the bot is unaffected.
|
|||||||
| `r` | Force-broadcast a status snapshot now. |
|
| `r` | Force-broadcast a status snapshot now. |
|
||||||
| `c` | Enter **chat mode** — type a message, Enter sends it into MC chat. |
|
| `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. |
|
| `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. |
|
| `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
|
## What the reflex loop does today
|
||||||
|
|
||||||
All reflex bodies are currently **stubs** — they log decisions but don't yet
|
The chain (highest priority first), wired and dispatching real
|
||||||
call into Mineflayer actions. The priority chain is wired:
|
Mineflayer actions:
|
||||||
|
|
||||||
1. `defendReflex` — closest hostile within 6 m → log + decision (next:
|
1. **`operatorGoalReflex`** — if `OPERATOR_USERNAMES` issued a `come` /
|
||||||
actually attack / flee).
|
`follow` command, satisfy it (walk to the operator's last known
|
||||||
2. `eatReflex` — food ≤ 16 → log (next: equip food, eat).
|
position, reply in chat on arrival or failure).
|
||||||
3. `sleepReflex` — night + bed in inventory → log (next: `bot.sleep`).
|
2. **`defendReflex`** — closest hostile within 4 m → `attackNearest`
|
||||||
4. `idleReflex` — every 10th tick, log heartbeat (HP / food / pos).
|
(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
|
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
|
## When the bot calls Pi
|
||||||
|
|
||||||
Reflexes that don't handle a situation simply return `noop`. After N
|
Two escalation paths:
|
||||||
consecutive tick cycles with no useful action — or when a reflex explicitly
|
|
||||||
flags "stuck" — the bot will escalate by calling `pi-bridge.js`:
|
|
||||||
|
|
||||||
```js
|
**1. Manual** — operator presses `a` in the TUI, types a question, the
|
||||||
askPi({
|
bot spawns `pi -p "<question>"` and streams its stdout into the Pi
|
||||||
prompt: "I've been at the same position for 5 minutes, last reflex chain
|
panel.
|
||||||
fell through, snapshot attached. What's a reasonable next action?",
|
|
||||||
onChunk, onDone,
|
**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
|
Unrecognized commands get a polite "didn't recognize" reply. Operator
|
||||||
operator-driven via TUI hotkey `a`. Wiring it up as an automatic fallback is
|
identity verification is the server's job (AuthMe on cracked,
|
||||||
the next milestone.
|
online-mode on premium) — the bot trusts the nickname.
|
||||||
|
|
||||||
## IPC protocol
|
## 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
|
(a Telegram bridge, a web UI, a one-shot CLI) by reading
|
||||||
`runtime/ipc-protocol.js`.
|
`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,
|
End-to-end and wired. The flow:
|
||||||
pathfinder returned noPath each time"), the bot will:
|
|
||||||
|
|
||||||
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.
|
1. reflex chain dispatches an action → action returns { ok: false, detail }
|
||||||
3. Wait for operator approval (TUI key `y` on a proposal — not yet built).
|
2. bot.js failure tracker accumulates the failure under its label
|
||||||
4. On approval: spawn Pi headless with the proposal text + repo context, ask
|
3. same label fails 3× in a row → writeProposal() → markdown lands in
|
||||||
it to write a new skill / patch, commit on a feature-branch.
|
state/<host>/proposals/<ts>-<slug>.md
|
||||||
5. Hot-reload the affected module (reflex / actions) without dropping the MC
|
4. next IPC STATUS event includes pendingProposals: N
|
||||||
connection.
|
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"
|
Operator is in the loop at three guardrails: approving the proposal,
|
||||||
loop — the whole point of having Pi as an escalation rather than a runtime.
|
reviewing Pi's diff, deciding to merge.
|
||||||
Not wired yet; tracked under tasks #56–#58 history.
|
|
||||||
|
### 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
|
## File layout
|
||||||
|
|
||||||
```
|
```
|
||||||
runtime/
|
runtime/
|
||||||
|
supervisor.js forks bot.js, watches runtime/*.js, restart-on-change
|
||||||
bot.js entrypoint — owns MC + tick + IPC + reconnect
|
bot.js entrypoint — owns MC + tick + IPC + reconnect
|
||||||
config.js reads .env, exposes frozen config + redacted view
|
config.js reads .env, exposes frozen config + redacted view
|
||||||
log.js ring buffer + stdout + daily file + IPC fan-out
|
log.js ring buffer + stdout + daily file + IPC fan-out
|
||||||
perceive.js snapshot(bot) → JSON
|
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-server.js Unix-socket server
|
||||||
ipc-protocol.js shared contract (event types, command types, framer)
|
ipc-protocol.js shared contract (event types, command types, framer)
|
||||||
pi-bridge.js spawn `pi -p`, stream stdout
|
pi-bridge.js spawn `pi -p`, stream stdout
|
||||||
@@ -192,10 +256,24 @@ runtime/
|
|||||||
tui/
|
tui/
|
||||||
tui.tsx Ink dashboard (React)
|
tui.tsx Ink dashboard (React)
|
||||||
ipc-client.js socket client → EventEmitter
|
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
|
Per-server state stays under `state/<MC_HOST>_<MC_PORT>/`, gitignored:
|
||||||
as before. The `bot.sock` lives there too.
|
|
||||||
|
```
|
||||||
|
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
|
## Pi-only fallback
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user