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,81 @@
|
||||
# M2b task 04: the baseline
|
||||
|
||||
**Branch:** `m2b` (run `git switch m2b`; `git status --short` must be empty, otherwise stop)
|
||||
**Commit subject:** `Add the baseline and the log replay function`
|
||||
|
||||
## Goal
|
||||
|
||||
The baseline is the fixed prefix of every request in an epoch: the system prompt, `memory/core.md`
|
||||
if it exists, and the core tool schemas. This task assembles it, writes and reads it as JSON, hashes
|
||||
it, and turns a session log back into messages.
|
||||
|
||||
## Context
|
||||
|
||||
From the brief's inference contract: "The request for turn N+1 is a strict extension of the request
|
||||
for turn N. Nothing volatile goes anywhere but the newest message." The baseline is snapshotted per
|
||||
session so that an edit to `system.md` changes nothing for a running session. The M0 measurements
|
||||
show why: any change to an earlier byte re-reads the whole prompt.
|
||||
|
||||
## Files
|
||||
|
||||
- Copy: `crates/loopd/tests/baseline.rs`, `crates/loopd/tests/support/mod.rs` (replaces the old
|
||||
one; it gains a `Home` helper and a scripted tool port)
|
||||
- Create: `crates/loopd/src/baseline.rs`
|
||||
- Modify: `crates/loopd/src/lib.rs`, `docs/implementer-log.md`
|
||||
|
||||
## Interfaces
|
||||
|
||||
```rust
|
||||
// our format: deny_unknown_fields; derives Debug, Clone, PartialEq, Serialize, Deserialize
|
||||
pub struct Baseline { pub system: String, pub tools: Vec<ToolSchema> }
|
||||
|
||||
pub enum BaselineError { Read(PathBuf, std::io::Error), Parse(PathBuf, serde_json::Error), Hash } // Display names the file; std::error::Error
|
||||
|
||||
impl Baseline {
|
||||
pub fn assemble(cfg: &Config, registry: &Registry) -> Result<Baseline, BaselineError>;
|
||||
pub fn to_json(&self) -> Result<String, serde_json::Error>;
|
||||
pub fn from_json(text: &str) -> Result<Baseline, serde_json::Error>;
|
||||
pub fn load(path: &Path) -> Result<Baseline, BaselineError>;
|
||||
pub fn hash(&self) -> Result<Hash32, BaselineError>; // proto::sha256 of to_json()
|
||||
}
|
||||
|
||||
/// The message array for a request: the system message, then the log replayed.
|
||||
pub fn messages(baseline: &Baseline, records: &[LogRecord]) -> Vec<ChatMessage>;
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
1. `assemble`: read `cfg.baseline.system`, trim trailing whitespace. If
|
||||
`<cfg.paths.home>/memory/core.md` is a file whose trimmed content is not empty, append a blank
|
||||
line and its trimmed content. `tools` is `registry.core_schemas()`.
|
||||
2. `messages`: `User` gives `ChatMessage::User`; `Assistant` gives `ChatMessage::Assistant` with
|
||||
its three fields unchanged; `ToolResult` gives `ChatMessage::Tool { tool_call_id, content }`;
|
||||
`SessionStart`, `Usage`, `CacheLoss` and `EpochEnd` give nothing. Write the match with every
|
||||
variant named, so that a new variant is a compile error and not a silent skip.
|
||||
|
||||
## Steps
|
||||
|
||||
- [ ] **1. Copy.**
|
||||
|
||||
```sh
|
||||
git switch m2b
|
||||
cp docs/plans/M2b/files/crates/loopd/tests/baseline.rs crates/loopd/tests/
|
||||
cp docs/plans/M2b/files/crates/loopd/tests/support/mod.rs crates/loopd/tests/support/
|
||||
```
|
||||
|
||||
- [ ] **2. See the test fail.** `cargo test -p loopd --test baseline`. Expected: it does not compile.
|
||||
- [ ] **3. Write `baseline.rs`** and add `pub mod baseline;` to `lib.rs`. Run `cargo fmt --all`.
|
||||
- [ ] **4. See the tests pass.** `cargo test -p loopd`. Expected: `baseline` 6 passed, and every
|
||||
earlier file still passes with the new `support/mod.rs`.
|
||||
- [ ] **5. Run the gate.** `make gate`. Expected last line: `gate: ok`.
|
||||
- [ ] **6. Log and commit.** `git add crates/loopd docs/implementer-log.md && git commit`
|
||||
|
||||
## Done when
|
||||
|
||||
- `cargo test -p loopd --test baseline` reports 6 passed; `make gate` prints `gate: ok`.
|
||||
- `cmp crates/loopd/tests/support/mod.rs docs/plans/M2b/files/crates/loopd/tests/support/mod.rs`
|
||||
prints nothing.
|
||||
|
||||
## Stop and report if
|
||||
|
||||
- `replay_of_a_prefix_is_a_prefix` fails: `messages` is reordering or rewriting something.
|
||||
Reference in New Issue
Block a user