Files
boxmaker/docs/plans/M2b/02-loopd-config.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.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 config reports 9 passed; make gate prints gate: ok.
  • grep -c deny_unknown_fields crates/loopd/src/config.rs prints 10 (the six old structs and the four new ones).

Stop and report if

  • r#loop cannot be used as a field name with serde. (It can; serde strips the r#.)