M4: Multiagent #5

Open
darman wants to merge 6 commits from feature/m4 into feature/m3
Owner
  • agent registry + bundled markdown agents
  • permission intersection + JobBoard (persistence, injection)
  • task tool, subagent spawner, board wiring — aliases/reuse, depth limits
  • subagent context-file reporting to the job board
  • jobs pane, subtask drill-in, reminders
  • dedicated opencode (Zen) provider
- agent registry + bundled markdown agents - permission intersection + JobBoard (persistence, injection) - task tool, subagent spawner, board wiring — aliases/reuse, depth limits - subagent context-file reporting to the job board - jobs pane, subtask drill-in, reminders - dedicated opencode (Zen) provider
darman added this to the M4 — Multiagent milestone 2026-07-10 17:11:31 +02:00
darman added this to the AI Harness project 2026-07-10 17:11:31 +02:00
darman added 6 commits 2026-07-10 17:11:32 +02:00
Adds harness-core::agent, an agent registry loading bundled markdown agent definitions (designer, explorer, fixer, librarian, oracle, orchestrator) with override support.
Adds harness-core::engine::jobs, the JobBoard tracking foreground/background subagent jobs with persistence, plus permission-rule intersection so a spawned subagent's effective permissions are the parent's rules narrowed by its own.
Adds the task tool and subagent spawner in harness-tools, and wires spawning/aliasing/reuse and depth limits through harness-app and the job board, so an orchestrator session can launch and track child sessions.
Lets subagents report context files back to the job board (surfaced from the read tool) so the parent session's next-turn context includes what a background child has been looking at.
Adds the jobs pane to the TUI with a new snapshot test, subtask drill-in navigation, and periodic reminders injected into the engine loop for long-running background jobs.
Adds a dedicated provider entry for opencode's Zen proxy on top of the existing OpenAI-compatible codec, with its own config wiring in harness-app and harness-core::config::load.
darman requested review from claude-reviewer 2026-07-11 14:42:09 +02:00
Collaborator

Review: M4 — Multiagent

This is the most intricate milestone and it's impressively coherent: permission intersection (evaluate_intersected, deny > ask > allow) is correct and well-tested, the depth limit is enforced and covered, foreground/background token scoping is thought through (background childs the parent run token so the job outlives the tool call), reuse genuinely continues the same child session, and the job board persists + reloads. One real bug and a couple of smaller notes.

Job-board alias collisions after trimming (medium)

JobBoard::next_alias (engine/jobs.rs:1756) derives the numeric suffix from the current count of that agent's jobs:

let n = self.jobs.read().unwrap().values().filter(|j| j.agent == agent).count() + 1;
format!("{prefix}-{n}")

But trim_reusable (jobs.rs:1980) deletes completed jobs once max_reusable_per_agent is exceeded. So the count goes stale and a later launch reuses a live alias:

  • explorer launches → exp-1, exp-2, exp-3 (max_reusable = 2)
  • all complete; trim drops the LRU exp-1; board now holds {exp-2, exp-3}
  • next explorer launch: count == 2 → alias exp-3collides with the existing exp-3

Since jobs are keyed by task_id (not alias), both survive, and resolve_reusable/touch/the prompt board now match an arbitrary one of the two — the orchestrator can reuse or reference the wrong session. Use a monotonic per-agent counter (e.g. max(existing suffix for agent) + 1, or a persisted counter) instead of the live filtered count.

register_launch failure silently untracks the job (low)

In spawn (harness-app/src/lib.rs:565) the launch registration is fire-and-forget (let _ = board.register_launch(...)), and the alias is then re-derived via board.snapshot().find(...) rather than using the value register_launch already returned. If the store write fails, the child still runs but never appears on the board, and the later board.finish(&task_id, ...) no-ops (the record doesn't exist) — the subtask vanishes from tracking with no signal. Consider using the returned alias directly and at least logging a registration failure.

Design note: depth_limit > 1 doesn't enable nested delegation by default (low)

depth_guard_rules (lib.rs:249) injects a blanket task deny for every session at depth > 0. So regardless of orchestration.depth_limit (default 3), a subagent can never delegate further unless an agent/config rule re-grants task. That's a reasonable conservative default, but it makes the depth counter and the depth_limit knob mostly moot for delegation — worth a doc note so depth_limit: 3 isn't read as "3 levels of auto-delegation".

Nice touches: filtered_tools exact-name-over-wildcard precedence, {{SUBAGENTS}} routing regeneration so disabled agents drop out of prompts, MIN_REPORTED_LINES + max-lines dedupe on context files.

— automated review (Claude)

## Review: M4 — Multiagent This is the most intricate milestone and it's impressively coherent: permission intersection (`evaluate_intersected`, `deny > ask > allow`) is correct and well-tested, the depth limit is enforced and covered, foreground/background token scoping is thought through (background childs the parent *run* token so the job outlives the tool call), reuse genuinely continues the same child session, and the job board persists + reloads. One real bug and a couple of smaller notes. ### Job-board alias collisions after trimming (medium) `JobBoard::next_alias` (`engine/jobs.rs:1756`) derives the numeric suffix from the *current* count of that agent's jobs: ```rust let n = self.jobs.read().unwrap().values().filter(|j| j.agent == agent).count() + 1; format!("{prefix}-{n}") ``` But `trim_reusable` (`jobs.rs:1980`) deletes completed jobs once `max_reusable_per_agent` is exceeded. So the count goes stale and a later launch reuses a live alias: - explorer launches → `exp-1`, `exp-2`, `exp-3` (max_reusable = 2) - all complete; trim drops the LRU `exp-1`; board now holds `{exp-2, exp-3}` - next explorer launch: `count == 2` → alias `exp-3` — **collides with the existing `exp-3`** Since jobs are keyed by `task_id` (not alias), both survive, and `resolve_reusable`/`touch`/the prompt board now match an arbitrary one of the two — the orchestrator can reuse or reference the wrong session. Use a monotonic per-agent counter (e.g. `max(existing suffix for agent) + 1`, or a persisted counter) instead of the live filtered count. ### `register_launch` failure silently untracks the job (low) In `spawn` (`harness-app/src/lib.rs:565`) the launch registration is fire-and-forget (`let _ = board.register_launch(...)`), and the alias is then re-derived via `board.snapshot().find(...)` rather than using the value `register_launch` already returned. If the store write fails, the child still runs but never appears on the board, and the later `board.finish(&task_id, ...)` no-ops (the record doesn't exist) — the subtask vanishes from tracking with no signal. Consider using the returned alias directly and at least logging a registration failure. ### Design note: `depth_limit > 1` doesn't enable nested delegation by default (low) `depth_guard_rules` (`lib.rs:249`) injects a blanket `task` **deny** for every session at `depth > 0`. So regardless of `orchestration.depth_limit` (default 3), a subagent can never delegate further unless an agent/config rule re-grants `task`. That's a reasonable conservative default, but it makes the depth counter and the `depth_limit` knob mostly moot for delegation — worth a doc note so `depth_limit: 3` isn't read as "3 levels of auto-delegation". Nice touches: `filtered_tools` exact-name-over-wildcard precedence, `{{SUBAGENTS}}` routing regeneration so disabled agents drop out of prompts, `MIN_REPORTED_LINES` + max-lines dedupe on context files. — automated review (Claude)
You are not authorized to merge this pull request.
This pull request can be merged automatically.
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin feature/m4:feature/m4
git checkout feature/m4
Sign in to join this conversation.
No Reviewers
No labels
2 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: darman/ai-harness#5