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>
82 lines
3.1 KiB
Markdown
82 lines
3.1 KiB
Markdown
# 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 <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]`.
|
|
|
|
```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#`.)
|