loopd: consult in the M5c registry, not core

Co-Authored-By: ornith-1.5-35b-a3b <agent@wntrmute.dev>
This commit is contained in:
2026-10-03 21:43:39 -07:00
co-authored by ornith-1.5-35b-a3b
parent 50e9cc2037
commit 658bc7d1e8
5 changed files with 85 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
//! The `consult` tool: `brokerd` decides it and POSTs an allowlisted body (M6 spec, section 4).
//! It is not core; the parent finds it with `find_tool` and calls it through `call_tool`.
pub const CONSULT: &str = "consult";
pub fn schema() -> crate::llama::ToolSchema {
crate::llama::ToolSchema {
name: CONSULT.to_string(),
description: "Ask a remote model a question. The session history is not sent. Needs a grant from the owner.".to_string(),
parameters: serde_json::json!({
"type": "object",
"properties": {
"question": {
"type": "string",
"description": "The question to send. Nothing else of the session is included."
}
},
"required": ["question"]
}),
}
}
+1
View File
@@ -5,6 +5,7 @@ pub mod broker_port;
pub mod channel;
pub mod compact;
pub mod config;
pub mod consult;
pub mod cron;
pub mod http;
pub mod jobs;
+4
View File
@@ -118,6 +118,10 @@ impl Registry {
schema: crate::subagent::schema(),
core: false,
});
entries.push(Entry {
schema: crate::consult::schema(),
core: false,
});
Registry::new(entries)
}
+58
View File
@@ -0,0 +1,58 @@
//! `consult` in the M5c registry, not core. Do not edit.
mod support;
use loopd::baseline::Baseline;
use loopd::consult::{self, CONSULT};
use loopd::tools::Registry;
use std::path::Path;
use support::Home;
fn core_names(registry: &Registry) -> Vec<String> {
registry
.core_schemas()
.into_iter()
.map(|s| s.name)
.collect()
}
#[test]
fn consult_is_in_m5c_and_is_not_core() {
let r = Registry::m5c();
let entry = r.get(CONSULT).expect("consult");
assert!(!entry.core);
assert_eq!(entry.schema, consult::schema());
assert_eq!(
core_names(&r),
[
"clock",
"recall",
"memory",
"ask_memory",
"find_tool",
"call_tool"
]
);
assert!(r.find("consult").iter().any(|s| s.name == CONSULT));
assert!(r.find("remote").iter().any(|s| s.name == CONSULT));
assert!(r.get("subagent").is_some());
}
#[test]
fn a_baseline_from_m5c_does_not_put_consult_in_the_tools_array() {
let home = Home::new();
let cfg = home.config(Path::new("/tmp/unused.sock"));
let baseline = Baseline::assemble(&cfg, &Registry::m5c()).unwrap();
let names: Vec<&str> = baseline.tools.iter().map(|t| t.name.as_str()).collect();
assert_eq!(
names,
[
"clock",
"recall",
"memory",
"ask_memory",
"find_tool",
"call_tool"
]
);
}
+1
View File
@@ -6,6 +6,7 @@ reviewer adds findings under "Reviews" once per milestone.
| Task | Date | Status | Gate runs | First gate | Deviations | Notes | Model |
|---|---|---|---|---|---|---|---|
| M6b/01-loopd-consult-registry | 2026-10-03 | done | 1 | pass | none | Copied the given test `crates/loopd/tests/consult_registry.rs` from `docs/plans/M6b/files/`. Created `crates/loopd/src/consult.rs` with `CONSULT` const and `schema()` returning the `consult` `ToolSchema`. Added `pub mod consult;` to lib.rs after `pub mod config;`. In tools.rs `m5c()` pushed the non-core `consult` entry after the `subagent` entry (`Registry::new(entries)` unchanged). `cargo check -p loopd` clean, no warning; `wc -l` prints 21 for consult.rs, 481 for tools.rs, both under 500. All suites pass: `consult_registry` 2, `subagent_registry` 2, `ask_memory_tool`, `tools` 11, `baseline` 6. `make gate` printed `gate: ok` with 1592 tests (1590 before, plus 2). No new dependency, so `Cargo.lock` unchanged. | ornith-1.5-35b-a3b |
| M6a/05-brokerd-state-inherit | 2026-10-03 | done | 1 | pass | none | Copied the given test `crates/brokerd/tests/state_subagent.rs` from `docs/plans/M6a/files/`. Step 2 (`cargo test -p brokerd --test state_subagent`) failed as expected: `a_new_subagent_copies_the_parents_untrusted_flag` (child was default private/trusted) and `a_nested_subagent_copies_through_a_missing_middle_child` (leaf was Private not Secret) failed, `a_plain_session_still_creates_nothing_on_read` passed. Applied I1 in state.rs `read`: replaced the `NotFound` early return with `return inherit_or_default(self, session)`. Applied I2 after `impl StateStore` (after `persist`'s and the `impl`'s closing braces, before `/// One line on disk`): added `inherit_or_default` which calls `parent_of_sub`, returns default when `None`, else `store.read(&parent)?` then `store.raise(session, SessionState::default(), Label{class: inherited.taint, untrusted: inherited.untrusted})`, and `parent_of_sub` which strips `sub-`, `rsplit_once('-')`, requires non-empty ASCII-digit suffix and builds `SessionId::new(parent)`. Did not touch `raise` or `persist`. `cargo check -p brokerd` clean, no warning; `wc -l` prints 176 for state.rs, under 500. All suites pass: `state_subagent` 3, `state` 9. `make gate` printed `gate: ok` with 1590 tests (1587 before, plus 3). No new dependency, so `Cargo.lock` unchanged. | ornith-1.5-35b-a3b |
| M6a/04-loopd-scheduler-skip | 2026-10-03 | done | 1 | pass | none | Copied the given test `crates/loopd/tests/scheduler_subagent.rs` from `docs/plans/M6a/files/`. Step 2 (`cargo test -p loopd --test scheduler_subagent`) failed as expected: both tests failed, `work` not empty (a `Heartbeat` and a `Job { name: "mail", session: "job-mail-20261003t0700" }`) and `requests_to(CHAT)` not empty while the child was held. Applied H1 in channel.rs: added `holds_prefix` after `hold`'s closing `}` inside `impl Context`, locking `busy` like `hold` and returning whether any id starts with the prefix. Applied K2 in scheduler.rs `heartbeat`: after the `if !due { return; }` block and before `SessionId::new("heartbeat")`, `if ctx.holds_prefix("sub-") { return; }`. Applied K1 in scheduler.rs `tick`: immediately after `self.heartbeat(...)` and before `jobs_dir`, `if ctx.holds_prefix("sub-") { return work; }` so leftover-close on the first tick is already done. `cargo check -p loopd` clean, no warning; `wc -l` prints 307 channel.rs, 310 scheduler.rs, both under 500. All suites pass: `scheduler_subagent` 2, `scheduler_heartbeat` 6, `scheduler_cron` 6, `scheduler_heartbeat_failed` 1. `make gate` printed `gate: ok` with 1587 tests (1585 before, plus 2). No new dependency, so `Cargo.lock` unchanged. | ornith-1.5-35b-a3b |
| M6a/03-loopd-subagent-spawn | 2026-10-03 | done | 1 | pass | none | Copied the given test `crates/loopd/tests/subagent_spawn.rs` from `docs/plans/M6a/files/`. Step 2 (`cargo build --tests --keep-going -p loopd`) failed as expected: E0560 `Runtime` has no field `ctx`. Added `pub ctx: Option<&'a crate::channel::Context>` to `Runtime` in turn.rs R1, filled it `Some(&*ctx)` in channel.rs C1 (live turn), `Some(ctx)` in the cron and heartbeat literals in scheduler.rs S1/S2 (both already held an immutable borrow of `ctx`), and `None` in the seven test literals: support/turn.rs, compact_turn.rs (the helper and the inline one), session_slot.rs, end_to_end.rs, turn_broker.rs, tool_summary_call.rs. Replaced the stub `run` in subagent.rs with the full spawn: build `sub-<parent>-<n>` from the already-consumed `call_id.0`, overflow returns `denial_message(InvalidArguments, Some(...))` naming subagent-id without holding, then `rt.ctx` `None` and `hold Busy` and block/baseline/create errors each return `failed`, a `Create` `Exists` falls back to `open`, the child runs one `run_turn` plus `finish_turn` on `slots.background`, memory is updated, `untrusted` is read before the hold drops, and the result is `{id} completed\n{summary}` or `{id} failed`, all `Public`. Added `RUNBOOK` and the use lines the body needs. Appended `## subagent-id` to docs/runbook.md via the task's `{ echo; cat ...; }` (grep -c prints 1). `cargo check -p loopd` clean, no warning; `wc -l` prints 185 subagent.rs, 335 turn.rs, 301 channel.rs, 304 scheduler.rs, all under 500. All suites pass: `subagent_spawn` 5, `subagent_dispatch` 3, `subagent_registry` 2, `session_slot` 4, `tool_summary_call` 6, `compact_turn`, `channel`. `make gate` printed `gate: ok` with 1585 tests (1580 before, plus 5). No new dependency, so `Cargo.lock` unchanged. | ornith-1.5-35b-a3b |