Files
boxmaker/docs/plans/M2b/04-loopd-baseline.md
kyleandClaude Fable 5.1 e156975649 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>
2026-09-18 17:19:02 -07:00

3.6 KiB

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

// 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.
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.