Add M2b plan: ten tasks, tests, recordings and the first system prompt
The tasks build the agent loop on M2a's client: channel messages and the usage record in proto, four config tables, the tool port and registry with find_tool and call_tool, the baseline and replay, the session store, the turn loop with its limits and the append-only property test, the channel server, loopd serve, bxctl chat, and the device checks including a four-turn conversation with a restart. Checked against a private reference implementation: the gate passes after every task in order, the new suites pass under CPU load, and the reference passes make verify-device on straylight with no cache loss. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
# M2b implementation plan: the agent loop
|
||||
|
||||
> **For the implementing model:** do not work from this file. The owner gives you one task file at
|
||||
> a time (`01-…` to `10-…`). This file is the index for the owner and the reviewer.
|
||||
|
||||
**Goal:** `loopd serve` holds conversations: each turn's request extends the one before, sessions
|
||||
live on disk and survive a restart, tool calls go through a port with limits on every kind of
|
||||
runaway, and `bxctl chat` drives it all over `loop.sock`.
|
||||
|
||||
**Architecture:** On top of M2a's inference client. A session is a directory with a baseline
|
||||
snapshot and an append-only log; the request for every turn is rebuilt from them. The turn loop
|
||||
runs the model, dispatches tool calls through a `ToolPort` trait (a fake in process for now,
|
||||
`brokerd` in M3), and enforces the limits. The channel server runs one turn per connection with the
|
||||
M1 frame protocol. `bxctl chat` is a client of that protocol.
|
||||
|
||||
**Tech stack:** as M2a. No new dependencies. `serde_json` is added to `bxctl`.
|
||||
|
||||
**Spec:** `docs/specs/2026-09-18-m2b-agent-loop.md`. Brief: `docs/design.md`.
|
||||
|
||||
## Global constraints
|
||||
|
||||
- Everything in `AGENTS.md`, including "Lessons from earlier reviews".
|
||||
- No new dependency. Formats we define reject unknown fields (the baseline file, the config
|
||||
tables, the wire messages); the server's JSON does not.
|
||||
- Nothing is written to a session log until a completion is final. Nothing volatile is ever put
|
||||
in a message or the baseline.
|
||||
- Branch `m2b`. One task, one fresh OpenCode session, one commit. Run `cargo fmt --all` before the
|
||||
gate. Review happens once, after task 10.
|
||||
|
||||
## Tasks
|
||||
|
||||
| # | File | Delivers | Tests that define it |
|
||||
|---|---|---|---|
|
||||
| 01 | `01-proto-channel-types.md` | `Usage`, `LogRecord::Usage`, `Turn`, `TurnEvent`, `TurnDone`, six error codes | `proto/tests/turn_wire.rs`, updated `strict.rs`, fixtures |
|
||||
| 02 | `02-loopd-config.md` | `[paths]`, `[channel]`, `[loop]`, `[baseline]`, `channel_socket()` | updated `loopd/tests/config.rs`, `m2b.toml` |
|
||||
| 03 | `03-loopd-tools.md` | `ToolPort`, `Registry`, `dispatch`, `cap_result`, `FakeTools` | `loopd/tests/tools.rs` |
|
||||
| 04 | `04-loopd-baseline.md` | `Baseline` and `messages()` | `loopd/tests/baseline.rs`, updated `support/mod.rs` |
|
||||
| 05 | `05-loopd-session.md` | `Session`: create, open, append, resume | `loopd/tests/session.rs` |
|
||||
| 06 | `06-loopd-turn.md` | `run_turn` with every limit | `loopd/tests/turn.rs`, `limits.rs`, `support/turn.rs`, three recordings |
|
||||
| 07 | `07-loopd-channel.md` | The channel server on `loop.sock` | `loopd/tests/channel.rs` |
|
||||
| 08 | `08-loopd-serve.md` | `loopd serve` | `loopd/tests/serve.rs` |
|
||||
| 09 | `09-bxctl-chat.md` | `bxctl chat` | `bxctl/tests/chat.rs` |
|
||||
| 10 | `10-verify-device.md` | `make verify-device` extended; the first `config/system.md` | `loopd/tests/device.rs` |
|
||||
|
||||
`files/` holds everything the tasks copy in. As in M2a, all of it was checked against a private
|
||||
reference implementation: the gate passes after every task in order, the new suites pass under CPU
|
||||
load, and the reference passes `make verify-device` on straylight, including a four-turn
|
||||
conversation with a `loopd` restart in the middle and no cache loss.
|
||||
|
||||
## For the owner: running a task
|
||||
|
||||
`tools/run-plan.sh docs/plans/M2b`, or one fresh OpenCode session per task with
|
||||
"Read `docs/plans/M2b/01-proto-channel-types.md` and do exactly that task." Task 10 talks to
|
||||
straylight and takes about a minute.
|
||||
|
||||
## For the reviewer: after task 10
|
||||
|
||||
1. `git log --oneline master..m2b`: ten commits with the trailer.
|
||||
2. Copied files unchanged:
|
||||
`for f in $(cd docs/plans/M2b/files && find . -type f); do cmp "docs/plans/M2b/files/$f" "$f"; done`
|
||||
3. `git diff master..m2b --stat -- docs/design.md docs/specs docs/plans AGENTS.md CLAUDE.md deny.toml`
|
||||
is empty.
|
||||
4. `make gate`, `make audit`, `make verify-device`.
|
||||
5. Read every source file against its task and the spec. Probe from outside: a log edited by hand,
|
||||
two channels on one session, a tool result at the cap, a `call_tool` with odd arguments.
|
||||
6. Run `channel`, `serve`, `limits` and `bxctl`'s `chat` repeatedly under CPU load.
|
||||
7. Findings to `docs/implementer-log.md`; lessons to `docs/implementer-lessons.md`.
|
||||
Reference in New Issue
Block a user