Files
boxmaker/docs/plans/M6a/01-loopd-subagent-registry.md
2026-10-03 19:39:35 -07:00

185 lines
7.2 KiB
Markdown

# M6a task 01: `subagent` in the M5c registry, not core
**Branch:** `m6a`. Check it: `git branch --show-current` prints `m6a` and `git status --short`
is empty; otherwise stop. Never switch branch.
**Commit subject:** `loopd: subagent in the M5c registry, not core`
**Log row:** the Task cell is `M6a/01-loopd-subagent-registry`.
**First action.** Run this before any Read of crate source. Do not `ls` `crates/loopd/tests/`.
Do not Read `tests/support/`, `turn.rs`, `session.rs`, `docs/implementer-log.md` (except the
first 12 lines at the log step). The signatures below are complete.
```sh
cp docs/plans/M6a/files/crates/loopd/tests/subagent_registry.rs crates/loopd/tests/
```
## Goal
`subagent` is in `Registry::m5c()`, not core. `find_tool` finds it. A new session's epoch
`tools` array stays `clock`, `recall`, `memory`, `ask_memory`, `find_tool`, `call_tool`
(spec 3.2). `loopd` does not answer the call yet (task 02).
## Every place that changes
| Place | What happens there |
|---|---|
| `crates/loopd/src/subagent.rs` | new: `SUBAGENT`, `schema()` |
| `crates/loopd/src/lib.rs` | `pub mod subagent;` |
| `crates/loopd/src/tools.rs` | `m5c()` pushes the non-core entry |
| `crates/loopd/tests/subagent_registry.rs` | new, given |
| everything else | **no change**: not `dispatch`, not `turn.rs` |
## Files
- Copy, from `docs/plans/M6a/files/` (the same path under it): the new test
`crates/loopd/tests/subagent_registry.rs`.
- Create: `crates/loopd/src/subagent.rs` (the module below).
- Modify: `crates/loopd/src/lib.rs`, `crates/loopd/src/tools.rs`,
`docs/implementer-log.md`.
- Do not touch: any other file.
At the log-and-commit step, read only the first 12 lines of `docs/implementer-log.md`.
## Facts you need
- **Not core.** `core_schemas()` already filters `e.core`. A non-core `Entry` is found by
`find` and by `get`, and is absent from `Baseline.tools`.
- **`m5c()` only.** Do not add the entry to `m2b`, `m3a`, or `m5a`. `loopd serve` uses `m5c`.
- **`push`, not `insert`.** `ask_memory` stays at index 3. `subagent` is last among the
non-core names, after `http_fetch`.
- rustfmt `mod` order: `subagent` after `sse` / before `summarise` as rustfmt decides.
- Same-crate modules may import each other. This task's module imports nothing from
`turn` or `tools`.
- The items the edits use. This table is complete.
| Item | Signature |
|---|---|
| `ToolSchema` | `{ name: String, description: String, parameters: serde_json::Value }` in `crate::llama` |
| `Entry` | `{ schema: ToolSchema, core: bool }` |
| `Registry::m5c` | `m5c() -> Registry` |
| `Registry::get` | `get(&self, name: &str) -> Option<&Entry>` |
| `Registry::find` | `find(&self, query: &str) -> Vec<&ToolSchema>` |
| `Registry::core_schemas` | `core_schemas(&self) -> Vec<ToolSchema>` |
## The edits, exactly
### `crates/loopd/src/subagent.rs`
Write this module. Do not add `parse_task`, `dispatch_args`, or `run` (task 02 / 03).
```rust
//! The `subagent` tool: loopd answers it itself (M6 spec, section 3).
//! It is not core; the parent finds it with `find_tool` and calls it through `call_tool`.
pub const SUBAGENT: &str = "subagent";
pub fn schema() -> crate::llama::ToolSchema {
crate::llama::ToolSchema {
name: SUBAGENT.to_string(),
description: "Run a short task in an isolated child session on the background slot."
.to_string(),
parameters: serde_json::json!({
"type": "object",
"properties": {
"task": {
"type": "string",
"description": "What the child should do."
}
},
"required": ["task"]
}),
}
}
```
### `crates/loopd/src/lib.rs`
**L1.** After `pub mod sse;` add `pub mod subagent;`. rustfmt decides the final order.
### `crates/loopd/src/tools.rs`
**R1.** The lines (exactly once, inside `m5c`)
```rust
entries.insert(
3,
Entry {
schema: crate::memory::ask_memory_schema(),
core: true,
},
);
Registry::new(entries)
```
become
```rust
entries.insert(
3,
Entry {
schema: crate::memory::ask_memory_schema(),
core: true,
},
);
entries.push(Entry {
schema: crate::subagent::schema(),
core: false,
});
Registry::new(entries)
```
## Steps
- [ ] **1. Copy the given test.**
```sh
cp docs/plans/M6a/files/crates/loopd/tests/subagent_registry.rs crates/loopd/tests/
```
- [ ] **2. See it fail.** `cargo build --tests --keep-going -p loopd`, with a shell timeout of
600000 ms: test target `subagent_registry` does not compile (`E0432` unresolved
`loopd::subagent`). Every other target compiles.
- [ ] **3. The edits.** Write `subagent.rs`, L1, R1. `cargo check -p loopd` prints no error
and no warning. `wc -l crates/loopd/src/subagent.rs` and `wc -l crates/loopd/src/tools.rs`
each ≤ 500.
- [ ] **4. The tests.** `cargo test -p loopd --test subagent_registry --test ask_memory_tool
--test tools --test baseline`: every test passes, 2 in `subagent_registry`.
- [ ] **5. Check.** `git diff --stat` lists exactly `crates/loopd/src/subagent.rs`,
`crates/loopd/src/lib.rs`, `crates/loopd/src/tools.rs`. `git status --short` also shows
the new test as untracked.
- [ ] **6. Run the gate.** `cargo fmt --all`, then `make gate`, with a shell timeout of
600000 ms. Expected last line: `gate: ok`. That is done counting: **1577** (1575 before,
plus the 2 of `subagent_registry.rs`). After `gate: ok`, do not add up `test result:`
lines and do not run `cargo test --workspace`.
- [ ] **7. Log and commit.** Add your row to `docs/implementer-log.md`; its Task cell is
`M6a/01-loopd-subagent-registry`. Read only the first 12 lines of that file; insert the
row under the header. Stage with
`git add crates/loopd docs/implementer-log.md Cargo.lock`, then commit as `AGENTS.md`
says. `Cargo.lock` does not change in this task; the `git add` is harmless.
## Done when
- `make gate` prints `gate: ok` with 1577 tests.
- `git diff HEAD~1 --stat` lists only `crates/loopd/src/subagent.rs`,
`crates/loopd/src/lib.rs`, `crates/loopd/src/tools.rs`,
`crates/loopd/tests/subagent_registry.rs` and `docs/implementer-log.md`.
## Stop and report if
- The branch check at the top fails.
- A "before" block is not in its file exactly as shown, or is there more than once.
- Step 2 shows a different result: the tests compile, or a different crate fails.
- `cargo check` in step 3 prints an error or a warning. Quote it, and stop.
- A test fails in step 4. Do not change the test: stop, and give the failing tests' names
and their messages.
- The fix seems to need a change anywhere else. It does not; stop and say why you think so.
- A test outside `subagent_registry.rs` fails in the gate: check once whether it fails
without your change (`git stash -u`, run it, `git stash pop`), then stop and report both
results.
## What the given tests pin
`subagent` is in `m5c()`, `core` is false, `schema()` matches the entry, `find("subagent")`
and `find("isolated")` both hit it, and a baseline assembled from `m5c()` still lists only
the six core names in `tools`.