Repository navigation
Decision-model supervision: monitors on subagents, stops that reach the swarm, enforced gates, and 10 more gaps - #477
Merged
Merged
Conversation
…models in Crew - A billed answer that cannot be read keeps its usage: decision rows carry the tokens and one token_events row is written per decision (item 9). - A late answer after a timeout, or one whose caller went away, is settled exactly once in the background; the process waits for in-flight ones at exit. - Threshold::Below fires below a level (item 4). - check_model_use / check_settings refuse at save time a monitor whose model or tier cannot run for the chat (item 6). - Crew chats may use decision models that run on this computer; hosted ones are still refused, with a sentence that says why (item 5). - Adapters keep the usage of answers they cannot parse; the catalog says when Ollama is not answering; an installer pulls a local decision model.
…ir latch - Stop monitors stop again on every turn while their answer holds; rearmBelow / rearmAbove give the old stop-once behaviour (item D). - Settings are re-read at every check point, so a change applies mid-turn (item 1); the stop note is also given to the agent (item 2). - Unreadable settings, an empty or unchanged window, the steer grace and a tiny budget either check or say so; onError pause stops the turn (item 3). - The latch is stored on the session and survives a restart (item 7). - Below monitors run through the reply loop (item 4); a monitor stop also stops the chat's subagents with a bounded wait (item B).
…gents - A gated phase cannot be skipped, dropped from a re-declared plan, or have its gate loosened or replaced by the agent; holds and depends_on are enforced at start and spawn; the person passes, skips or retries a gate (item C). - workspace_monitor lets the agent put monitors on its subagents, with the first-crossing disclosure for a private chat writing into a public child; the agent cannot remove a monitor the person set (item A). - workspace_close stops a child through the same step as Stop (item B).
…onitors - One function stops every subagent a chat started; the person's Stop, a monitor stop and workspace_close use it, while Stop-and-Send leaves children running (item B). Each stopped child records why. - Monitors can be set at spawn, come from a plan's phase, or be inherited from a parent monitor that also watches subagents; a child's monitor stop reaches the parent's result; children use the parent's decision service (item A). - A sub-workflow's monitors supervise its child (item 8). - The CLI waits for late decision answers before exiting (item 9).
… a gated plan - decision_settings and orchestration_plan blocks in workflow YAML, validated with clear sentences (item 8). - Every door that starts a workflow (biorouter run, POST /agent/start, schedules, slash commands, sub-workflows) seeds supervision once, refuses before creating a session when it cannot run, and --explain prints it. - Creating a workflow from a chat captures its supervision.
… routes - Saves answer 422 when a monitor is valid but cannot run for the chat, 400 when malformed; 403 stays for reach and proof refusals (item 6). - Install routes for a local decision model (item 5) and person routes to pass, skip, retry or set a phase gate (item C), all behind session_reach. - OpenAPI spec and TypeScript client regenerated.
…ates, decision pull - biorouter session monitors: --below, --rearm-above, --on-error pause, --subagents, owner display, checked saves (items 4, 6, A, D). - biorouter session gates set/pass/skip/retry/list (item C). - biorouter models decision pull installs a local decision model (item 5). - Workspace parity rows and TUI subagent lines show gates and monitor stops.
…e gates - Monitors dialog: thresholds from 0.01, Fires when Below, pause the turn when a check fails, owner badges, also watch subagents, 422 sentences shown (items 4, 6, A); an install panel for a local decision model (item 5). - Phase gate dialog, needs-you chip and held lines in the summary rail (item C). - The workflow warning shows the supervision a workflow will set, and the workflow editor keeps its blocks (item 8).
Describes what the code now does: stop monitors that stop again and their persisted latch, mid-turn changes, the stop note to the agent, the paths that no longer skip silently, fire-below thresholds and onError pause, local decision model install and Crew use, checked saves, workflow supervision blocks, billing of unreadable and late answers, agent monitors on subagents, stops that reach the swarm, and gates that bind the plan.
- Monitor re-arm uses the same epsilon as firing, so a value on the threshold neither fires nor re-arms twice. - A subagent stopped during a later turn, or between finishing and publishing its result, records why it was stopped. - The Crew refusal no longer says no local model is installed when the configured hosted provider is what excludes it. - Misplaced supervision keys are refused at every door that takes a workflow as JSON (deeplinks, the workflow routes), not only when read from a file. - The install test's idle bound no longer depends on wall-clock headroom. - Desktop: the gate row state agrees with the summary rail (person's pass or skip, needs you), and a monitor's re-arm level can be edited or cleared so a CLI-made monitor is not stuck with a now-invalid re-arm after the threshold moves.
Review round 2: - A plain Stop on /agent/cancel stops and names only the subagents the caller could reach on their own; a child raised to private under a public parent keeps running for a secret-only caller and its id is not returned. - An answer that arrives on time is stored and billed in a task the caller's cancellation cannot drop, so a Stop or a gone HTTP client mid-write no longer loses its row or its token_events row. - Workflow capture keeps multi-line supervision fields intact; a chat seeded by one workflow is not reported as seeded by another. - Desktop: editing a phase gate keeps its decision-model adapter; a passing "below" check is not labelled "Below threshold"; decision settings refresh when an agent or another door changes them. Follow-ups: - Turns that end on their action limit, tool-call limit, stall stop or budget stop name the turn_end monitors they did not check (item 3). - With no daemon, `biorouter session monitors add` checks a save against the chat's bound model, so a private-bound chat refuses a hosted monitor before its first turn (item 6). - Workspace tool descriptions trimmed: the branch's growth of the workspace tool schemas fell from about 3,500 to about 1,400 tokens, which also brings the ollama_diagnostics prompt-size test back under its bound. - clippy too_many_lines: helpers extracted in agent.rs, orchestration_plan.rs, workspace/turn.rs and subagent_tool.rs (no behaviour change). - Docs: the forced-exit notices and the remaining abort gap.
…nce per turn - The daemon's save check (decision-settings, decision-chat and phase-gate PUTs) asks the chat's agent for its bound tier and, when the daemon holds no agent for the chat or the agent has no model yet, reads the provider named on the session row. A chat bound to a private model before its first turn now refuses a hosted monitor or gate right after a restart too (item 6; found by the live re-test). - A turn names each unchecked turn_end monitor at most once, so a budget stop after a wound-down wrap-up no longer repeats the notice (item 3).
Broccolito
marked this pull request as ready for review
November 7, 2026 13:29
…iff) CI on Linux and Windows aborted biorouter-server's lib and biorouterd test targets with a tokio worker stack overflow in a_steer_in_the_turn_prologue_is_accepted_and_the_agent_stays_pinned: the branch's monitor awaits had pushed the turn's stack need from about 1.9 MiB (origin/main) to just under the 2 MiB worker limit. In a debug build `Box::pin(self.helper(..)).await` still builds the helper's future in the caller's frame, so `boxed_off_frame(|| self.helper(..))` now builds it in its own frame. Used for the monitor points, forced-exit notices and supervision step the branch added, a few existing reply-loop awaits, the reply prologue, and the workspace turn runner (run_turn, run_turn_body, open_reply_stream, the child-turn bookkeeping). No behaviour change; steer arms are untouched. Measured on macOS debug: the test's minimum worker stack fell from 2026-2041 KiB to 1023-1030 KiB; Agent::reply's future from 23,576 to 1,248 bytes and the spawned turn from 158,248 to 3,304. A new test pins the reply, run_turn and spawned futures at 8 KiB or less.
…on.rs The privacy_capability census (compatible_is_the_only_function_that_compares_ two_affiliations) failed in CI: the Crew rule for decision models compared ModelAffiliation::Local by hand in decision/service.rs, and its test gate did the same in routes/decisions_tests.rs. The comparison now lives beside `compatible` as `privacy::affiliation::runs_on_this_computer`, with a unit test, and both sites call it. No behaviour change.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this does
After #473 (decision models) and #474 (five fixes) merged, a comparison with the prototype in #469 found 13 gaps in how decision models supervise a chat and its subagents. This PR fixes all 13. #469 stays open as a reference and is not touched.
workspace_monitorlets the agent add, list and remove monitors on its subagents. Monitors can also be set at spawn, come from a plan's phase, or be inherited from a person's monitor that "also watches subagents". A child's monitor stop reaches the parent's result. The agent cannot change or remove a monitor the person set. A private chat writing a monitor into a public child goes through the first-crossing disclosure.workspace_closeuse it; every stopped child records why. Stop-and-Send leaves children running.depends_onare enforced at start and spawn. The person passes, skips or retries a gate (biorouter session gates, the phase gate dialog, HTTP routes with proof).rearmBelow/rearmAbovegive the old stop-once behaviour.<decision-monitor-stop>note naming the monitor and its reason.onError: pausestops the turn.--below,--rearm-above,--on-error pause.biorouter models decision pull, an install panel in the Monitors dialog,/decisions/models/installroutes), with honest sentences when none is installed or Ollama is not answering. Crew chats may use decision models that run on this computer; hosted ones are still refused.decision_monitor_state.v1) and survives a daemon restart; editing the question resets it.decision_settings(monitors, a default decision model) and anorchestration_planwith gates. Every door that starts a workflow (run,/agent/start, schedules, slash commands, sub-workflows) seeds them once, refuses before creating a session when they cannot run, and--explainprints them; creating a workflow from a chat captures them.token_eventsrow). A late answer after a timeout, or one whose caller went away, is settled once in the background and counted; the CLI and daemon wait for in-flight answers at exit.Defaults that need the owner's eye
Status codes: 422 means "valid but cannot run for this chat now"; 403 stays for reach, proof and subagent refusals only, because the CLI reads an empty 403 as a missing user-action key.
Needs human review (CLAUDE.md: security-sensitive and async code)
decision/service.rs,crew/mod.rs).decision/late.rs,decision/service.rs): background settlement, exit waits.agents/agent.rs,subagent_handle.rs,routes/reply.rscancel).workspace_monitor(workspace_inspector.rs).Live verification
Every tester ran against its own sandbox daemon (
~/biorouter-runs/decision-gaps-live-20261107-0252/<tester>, evidence underevidence/). 62 of 62 legs passed; none failed.joey-1, which is a public model. ... Nothing was started. session list --subagents shows no child under that parent a…person-guardon this subagent was s…bogus, expected one of ...). At least one of them is set to pause when it cannot check, so the turn stops here. Save the chat's monitors again to replace them, then send a ..." then…bogus...), so none are checked this turn." Turn ran on. (ii) "This chat's monitors could not be read again (unknown fieldbogus...); the ones read earlier are still checked." followe…joey-1is a public model a private chat cannot use...'. Never asked for a key. list unchanged ('no monitors'). no-s…joey-1, which is a public model...'; nothing stored (sqlite count 0). no-such-model locally: exit 1 with pull advice. Gap, not c…monitorsis not a top-level workflow field: monitors go underdecision_settings.monitors.' treshold gives a parse error naming orch…joey-1is a public model a private chat cannot use. Choose a decision model that runs on this computer.' Exit 1. Session count was 11783 before…biorouter run --workflow <abs path>; nothing was sent.' Finish reason stop, 0 tokens, 0 stored messa…checkerdeclares an …msis anoulquestion, but the answer ismangled'. The row is outcome error, 117/1, matching the proxy usage. token_events gained exactly one decision: row (count 3 to 4…msis anoulquestion, but the answer ismangled'. The decisions row is error 117/0, and the proxy usage was 117/0. token_events has one decision row (ollama_decision, tev1:0.8b, 117…Completeness check (an independent critic read the results and evidence):
sleep 300as background shell jobs left orphaned OS processes, seen by swarmB and desktop.Re-test after the review fixes
The legs the later fixes touched were run again on the final binaries (
~/biorouter-runs/decision-gaps-live-20261107-retest/), all passing:biorouter session cancelstopped 3 children and said so; on a keyless daemon a public parent's Stop stopped and listed its children; a private parent's Stop with only the secret was refused (403) and stopped nothing; the same with a private caller stopped all three.BIOROUTER_MAX_TURNS=3) printed the limit message, then "Monitor 'End check' was not checked at the end of this turn: the turn ended because it reached its action limit.", with no decision row; the tiny-budget leg names the monitor (a duplicate notice it showed was fixed in3bb9956e9).joey-1monitor to a chat bound toversa_azurebefore its first turn exits 1 with the private-model sentence and stores nothing; the same on acodexchat succeeds. The daemon path got the same fallback for a chat it does not hold (3bb9956e9, covered by a route test).token_eventsrow); late, abandoned and malformed answers still are.Known limitations
turn_endmonitors without a notice; the action-limit, tool-call-limit, stall and budget exits now say so. Documented indocs/agent-loop/decision-models.md.workspace_monitoris offered even when delegation is off (about 600 tokens of tool schema per chat). The branch's total growth of the workspace tool schemas is about 1,400 tokens after a trim; hidingworkspace_monitorwithout delegation would be a small follow-up.How it was built and checked
21 work packages, each implemented on Opus and reviewed by an independent agent, with fix-ups for every finding; then a whole-branch adversarial review (10 lenses, 3-vote verification, 2 rounds) and parallel live testers, each with its own sandbox daemon and port. Chat models in the live tests were
versa_azure(Private) and thecodexcoding-agent provider (Public); decision models werebaseten_decision/joey-1and local Ollamatev1:0.8b/clef-flash:latest.🤖 Generated with Claude Code