Harness spec 3.2.0: teams — subagents that work together

team.md: a lead adds members (team_add) that work in the background on
the parts they own; members may lead areas of their own. The chat
setting agents {min, max} (defaults 1/4, an instance cap of 32) bounds
the whole tree, task helpers included; a team is offered only where
subagents are enabled and the model serves two requests at once
(capacity.parallel). A manager's work calls — risk write or execute,
bar job_stop and reading bash — answer a fixed refusal while the tool
list stays unchanged. The room: team_send to names or all, delivered
under "[Team message from X to Y:]" at the next step or waking a
finished member; a member's final reply is its report to its manager;
team_read shows the tree and the visible part of the room; team_stop
stops a subtree. Past the model's capacity a member waits for a slot,
the lead first. Size notes in context.note; texts in loop.json team;
prompts blocks/team_lead.md and team_member.md; tools allowed by
default. Conformance area team, written from a reference model.

The CLI embeds the new tool and prompt files; its tools are pending.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
HomerandClaude Opus 5.5 committed 2026-11-09 08:17:48 +00:00
1 parent d09d51cfec
commit 880289970b
14 files changed
+3899 -5

No files matched your search

+13 -1
View File
@@ -153,10 +153,22 @@ test("spec 3.0.0: every tool says where it is offered; the CLI's are the agent's
// plan_write's former name rather than a tool.
expect(TOOL_SPECS.todo).toBeUndefined()
expect(TOOL_SPECS.plan_write!.aliases.cli).toEqual(["todo"])
const shared = ["ask_user", "edit", "glob", "grep", "job_list", "job_output", "job_send", "job_stop", "list", "memory", "plan_submit", "plan_update", "plan_write", "read", "recall", "report", "session_search", "show", "skill_manage", "skill_view", "skills_list", "task", "web_fetch", "web_search", "write"]
const shared = ["ask_user", "edit", "glob", "grep", "job_list", "job_output", "job_send", "job_stop", "list", "memory", "plan_submit", "plan_update", "plan_write", "read", "recall", "report", "session_search", "show", "skill_manage", "skill_view", "skills_list", "task", "team_add", "team_read", "team_send", "team_stop", "web_fetch", "web_search", "write"]
expect(Object.values(TOOL_SPECS).filter((t) => t.scope === "shared").map((t) => t.name).sort()).toEqual(shared.sort())
})
test("spec 3.2.0: the team tools need `team` and answer one_agent; a manager's refused tools are exactly the writers and executors", () => {
const loop = JSON.parse(readFileSync(join(HARNESS, "loop.json"), "utf8")) as { team: { unavailable: { one_agent: string }; work: { write: string[]; execute: string[]; kept: string[] } } }
for (const n of ["team_add", "team_read", "team_send", "team_stop"]) {
const t = TOOL_SPECS[n]!
expect([t.scope, t.sessions, t.requires, t.unavailable]).toEqual(["shared", "both", ["team"], loop.team.unavailable.one_agent])
}
// team.work names every tool by its risk class, and nothing else: a new tool is placed by its class.
const byRisk = (r: string) => Object.values(TOOL_SPECS).filter((t) => t.risk === r).map((t) => t.name).sort()
expect([...loop.team.work.write].sort()).toEqual(byRisk("write"))
expect([...loop.team.work.execute, ...loop.team.work.kept].sort()).toEqual(byRisk("execute"))
})
test("the personality presets are shared prompt texts, one paragraph each", () => {
const index = JSON.parse(readFileSync(join(HARNESS, "prompts", "index.json"), "utf8")) as { prompts: Record<string, { scope: string; llembas_text?: string }> }
for (const n of ["concise", "pragmatic", "optimistic", "funny", "formal", "socratic"]) {