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>
3.1 KiB
M2b task 02: the M2b config tables
Branch: m2b (run git switch m2b; git status --short must be empty, otherwise stop)
Commit subject: Add the paths, channel, loop and baseline config tables
Goal
Four small config tables that the rest of M2b reads. All are optional, with defaults, and reject unknown keys like the existing ones.
[paths]
home = "/var/lib/boxmaker" # default: $BOXMAKER_HOME if set, else this
[channel]
socket = "" # default empty, meaning <home>/run/loop/loop.sock
[loop]
tool_iterations = 8 # completions with tool calls allowed in one turn
repeat_detection = true # stop the same call made twice in a turn
tool_result_cap = 16384 # bytes of a tool result that are kept
[baseline]
system = "system.md" # the system prompt; relative to the config file's directory
Files
- Copy (replacing the old one):
crates/loopd/tests/config.rs; new:crates/loopd/tests/fixtures/config/m2b.toml - Modify:
crates/loopd/src/config.rs,docs/implementer-log.md
Interfaces
Added to Config (all pub, all #[serde(default)]): paths: Paths, channel: Channel,
r#loop: Loop, baseline: Baseline. The field is spelled r#loop because loop is a keyword;
in TOML it is [loop].
pub struct Paths { pub home: PathBuf } // Default: $BOXMAKER_HOME or /var/lib/boxmaker
pub struct Channel { pub socket: PathBuf } // Default: empty
pub struct Loop { pub tool_iterations: u32, pub repeat_detection: bool, pub tool_result_cap: usize }
pub struct Baseline { pub system: PathBuf } // Default: "system.md"
impl Config {
/// The channel socket, with the default filled in.
pub fn channel_socket(&self) -> PathBuf;
}
Derives: Debug, Clone, PartialEq, Eq, Deserialize and #[serde(deny_unknown_fields, default)]
on all four, each with a hand-written Default (or derived, for Channel).
Rule: Config::load makes a relative baseline.system absolute by joining it to the config
file's directory. Config::parse leaves it as it is, because it has no file to be relative to.
Steps
- 1. Copy.
git switch m2b
cp docs/plans/M2b/files/crates/loopd/tests/config.rs crates/loopd/tests/
cp docs/plans/M2b/files/crates/loopd/tests/fixtures/config/m2b.toml crates/loopd/tests/fixtures/config/
- 2. See the tests fail.
cargo test -p loopd --test config. Expected: it does not compile. - 3. Make the changes. Run
cargo fmt --all. - 4. See the tests pass.
cargo test -p loopd --test config. Expected:9 passed. - 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 configreports 9 passed;make gateprintsgate: ok.grep -c deny_unknown_fields crates/loopd/src/config.rsprints 10 (the six old structs and the four new ones).
Stop and report if
r#loopcannot be used as a field name with serde. (It can;serdestrips ther#.)