# 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. ```toml [paths] home = "/var/lib/boxmaker" # default: $BOXMAKER_HOME if set, else this [channel] socket = "" # default empty, meaning /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]`. ```rust 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.** ```sh 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#`.)