[PR #12731] feat: add team tools, HTTP routes, and tool registry integration #14345

Open
opened 2026-02-16 18:19:08 -05:00 by yindo · 0 comments
Owner

Original Pull Request: https://github.com/anomalyco/opencode/pull/12731

State: open
Merged: No


Summary

Adds the tool and API layer for agent teams (Fixes #12711). Depends on #12730 — merge that first.

This is PR 2/3. It exposes the Team namespace from PR 1 as 9 MCP tools the LLM can call, plus HTTP routes for TUI consumption. All tools are conditionally registered behind the OPENCODE_EXPERIMENTAL_AGENT_TEAMS feature flag.

What's included

Business logic in Team namespace (team/index.ts — +333 lines over PR 1)

Core team operations live in the Team namespace, not the tool layer. Tools are thin wrappers that handle authorization checks and format output.

New Team functions:

  • Team.resolveModel() — Model resolution with priority chain: explicit provider/model param → agent config → lead's last model → global default. Validates against configured providers with fuzzy suggestions on miss.
  • Team.spawnMember() — Creates child session, registers member (with orphaned session cleanup on addMember failure), constructs teammate context message, launches fire-and-forget prompt loop. Sync-throw guard on SessionPrompt.loop().
  • Team.approvePlan() — Removes plan-approval deny rules from teammate session, updates plan state, notifies teammate via team message.
  • Team.autoCleanup() — Subscribes to TeamEvent.MemberStatusChanged. When all members reach "shutdown", auto-cleans the team. Safety net that doesn't depend on the LLM remembering to call team_cleanup.
  • Team.onCleanedRestorePermissions() — Subscribes to TeamEvent.Cleaned. Restores lead session permissions when a delegate-mode team is cleaned up. Decouples cleanup from direct Session imports.
  • Private notifyLead() — Sends idle/error notification to lead when a teammate's prompt loop finishes. Uses setMemberStatus with { guard: true } to prevent overwriting "shutdown" (TOCTOU prevention).

Team tools (tool/team.ts — 557 lines)

9 MCP tools, each a thin wrapper over Team namespace methods with authorization checks and formatted output:

Tool Description
team_create Create a new team. Caller becomes lead. Optional delegate mode restricts lead to coordination-only. Optional initial task list. Prevents nested teams.
team_spawn Spawn a teammate via Team.spawnMember(). Configurable agent type, model, initial prompt, auto-claim task, and optional require_plan_approval. Wraps spawnMember in try/catch for proper error reporting.
team_message Send a directed message to a specific teammate or "lead".
team_broadcast Send a message to all active teammates.
team_tasks View, add, complete, or replace the shared task list.
team_claim Atomically claim a pending task. Checks dependency resolution before allowing claim.
team_approve_plan Lead-only. Approve (unlocks write tools) or reject (keep read-only) a teammate's plan via Team.approvePlan().
team_shutdown Request graceful shutdown of a teammate.
team_cleanup Lead-only. Remove team resources. Reports delegate restriction removal.

Security & authorization:

  • Teammates are denied team_create, team_spawn, team_shutdown, team_cleanup, team_approve_plan via session permission rules
  • Reserved name "lead" rejected on spawn
  • Plan approval uses tagged deny patterns (*:plan-approval) distinct from delegate deny patterns (*) so they can be independently managed
  • Delegate mode adds write-tool deny rules (bash, write, edit, multiedit, apply_patch) to the lead session

Teammate system prompt design

Teammates always receive the full provider-specific system prompt for their model (e.g. PROMPT_ANTHROPIC for Claude, PROMPT_BEAST for GPT-4/o1/o3, PROMPT_GEMINI for Gemini, PROMPT_CODEX for GPT-5/Codex). This matches Claude Code's behavior where sub-agents get the same system prompt as standalone sessions.

How it works: Teammate sessions are marked with session.teammate = true. In llm.ts, system prompt construction becomes additive for teammates:

// Teammate (additive):   provider prompt + agent prompt + input.system
// Non-teammate (existing): agent prompt OR provider prompt + input.system

Empty-provider fallback: if SystemPrompt.provider(model) returns empty for a teammate, falls back to SystemPrompt.instructions().

For Codex teammates, options.instructions is always set (API requirement).

The team-specific context (role identity, available tools, communication rules) is injected as a user message, not a system prompt — keeping it separate from the provider behavioral guidance.

Event-based decoupling

Team.cleanup() no longer imports Session directly. Instead it publishes TeamEvent.Cleaned with leadSessionID and delegate fields. The onCleanedRestorePermissions() listener (wired in bootstrap) subscribes to this event and handles the Session.update to remove delegate deny rules. This keeps the team module decoupled from session internals.

HTTP routes (server/routes/team.ts — 130 lines)

Routes use hono-openapi with describeRoute, validator, and resolver middleware for OpenAPI metadata, request validation, and consistent error responses:

  • GET /team — List all teams
  • GET /team/:name — Get team by name
  • GET /team/:name/tasks — Get team's task list
  • GET /team/by-session/:sessionID — Find team context for a session (team info + tasks + role)
  • POST /team/:name/delegate — Toggle delegate mode (validates body with Zod schema)

Bootstrap wiring (project/bootstrap.ts)

  • Team.onCleanedRestorePermissions() — wired before recovery
  • Team.recover() — marks stale teammates as interrupted
  • Team.autoCleanup() — wired after recovery completes

Registry integration (tool/registry.ts)

  • Conditional import + registration of all 9 team tools when Flag.OPENCODE_EXPERIMENTAL_AGENT_TEAMS is enabled

Server integration (server/server.ts)

  • Mounts TeamRoutes() at /team path — single 2-line change

Tests

11 test files, 134 tests covering:

Test file Coverage
team.test.ts Core tool execution, auto-cleanup behavior, constraints
team-spawn.test.ts Spawn validation — agent resolution, model validation, permission rules, plan mode, session.teammate flag
team-e2e.test.ts Full lifecycle — create → spawn → work → message → complete → shutdown → cleanup
team-edge-cases.test.ts Error paths — invalid names, missing teams, double create, nested team prevention, cleanup with active members
team-scenarios.test.ts Real-world patterns — multi-agent code review, parallel implementation, research → implement pipeline
team-plan-approval.test.ts Plan approval flow — pending → approved (unlocks tools), pending → rejected (stays read-only)
team-delegate-cleanup.test.ts Delegate mode — lead permission denial, cleanup restores permissions via event (no sleeps — Bus.publish awaits subscribers)
team-integration.ts Integration patterns — team + task tool interaction, concurrent operations
team-multi-model.ts Multi-model teams — different providers per teammate, model resolution priority chain
team-scenarios-integration.ts Cross-cutting scenarios — error recovery, message ordering, task dependency chains

Files changed

File Change
src/tool/team.ts New — 9 MCP tools (thin wrappers)
src/team/index.ts Modified — +333 lines (resolveModel, spawnMember, approvePlan, notifyLead, autoCleanup, onCleanedRestorePermissions)
src/server/routes/team.ts New — hono-openapi routes with validation
src/project/bootstrap.ts Modified — wire event listeners and auto-cleanup
src/session/llm.ts Modified — additive system prompt for teammates, empty-provider fallback
src/session/index.ts Modified — add teammate flag to Session.Info
src/session/prompt.ts Modified — pass teammate + sessionPermission to LLM
src/tool/registry.ts Modified — conditional registration
src/server/server.ts Modified — mount routes
test/team/*.test.ts New — 11 test files, 134 tests
Total +8,198 / −11

Stack

PR 2/3 — merge in order:

  1. #12730 — Core team primitives
  2. This PR — Tools + routes + prompting + lifecycle
  3. #12732 — TUI integration
**Original Pull Request:** https://github.com/anomalyco/opencode/pull/12731 **State:** open **Merged:** No --- ## Summary Adds the **tool and API layer** for agent teams (Fixes #12711). Depends on #12730 — merge that first. This is PR 2/3. It exposes the Team namespace from PR 1 as 9 MCP tools the LLM can call, plus HTTP routes for TUI consumption. All tools are conditionally registered behind the `OPENCODE_EXPERIMENTAL_AGENT_TEAMS` feature flag. ## What's included ### Business logic in Team namespace (`team/index.ts` — +333 lines over PR 1) Core team operations live in the Team namespace, not the tool layer. Tools are thin wrappers that handle authorization checks and format output. **New Team functions:** - `Team.resolveModel()` — Model resolution with priority chain: explicit `provider/model` param → agent config → lead's last model → global default. Validates against configured providers with fuzzy suggestions on miss. - `Team.spawnMember()` — Creates child session, registers member (with orphaned session cleanup on `addMember` failure), constructs teammate context message, launches fire-and-forget prompt loop. Sync-throw guard on `SessionPrompt.loop()`. - `Team.approvePlan()` — Removes plan-approval deny rules from teammate session, updates plan state, notifies teammate via team message. - `Team.autoCleanup()` — Subscribes to `TeamEvent.MemberStatusChanged`. When all members reach `"shutdown"`, auto-cleans the team. Safety net that doesn't depend on the LLM remembering to call `team_cleanup`. - `Team.onCleanedRestorePermissions()` — Subscribes to `TeamEvent.Cleaned`. Restores lead session permissions when a delegate-mode team is cleaned up. Decouples cleanup from direct Session imports. - Private `notifyLead()` — Sends idle/error notification to lead when a teammate's prompt loop finishes. Uses `setMemberStatus` with `{ guard: true }` to prevent overwriting `"shutdown"` (TOCTOU prevention). ### Team tools (`tool/team.ts` — 557 lines) **9 MCP tools**, each a thin wrapper over Team namespace methods with authorization checks and formatted output: | Tool | Description | |------|-------------| | `team_create` | Create a new team. Caller becomes lead. Optional `delegate` mode restricts lead to coordination-only. Optional initial task list. Prevents nested teams. | | `team_spawn` | Spawn a teammate via `Team.spawnMember()`. Configurable agent type, model, initial prompt, auto-claim task, and optional `require_plan_approval`. Wraps spawnMember in try/catch for proper error reporting. | | `team_message` | Send a directed message to a specific teammate or `"lead"`. | | `team_broadcast` | Send a message to all active teammates. | | `team_tasks` | View, add, complete, or replace the shared task list. | | `team_claim` | Atomically claim a pending task. Checks dependency resolution before allowing claim. | | `team_approve_plan` | Lead-only. Approve (unlocks write tools) or reject (keep read-only) a teammate's plan via `Team.approvePlan()`. | | `team_shutdown` | Request graceful shutdown of a teammate. | | `team_cleanup` | Lead-only. Remove team resources. Reports delegate restriction removal. | **Security & authorization:** - Teammates are denied `team_create`, `team_spawn`, `team_shutdown`, `team_cleanup`, `team_approve_plan` via session permission rules - Reserved name `"lead"` rejected on spawn - Plan approval uses tagged deny patterns (`*:plan-approval`) distinct from delegate deny patterns (`*`) so they can be independently managed - Delegate mode adds write-tool deny rules (`bash`, `write`, `edit`, `multiedit`, `apply_patch`) to the lead session ### Teammate system prompt design Teammates always receive the **full provider-specific system prompt** for their model (e.g. `PROMPT_ANTHROPIC` for Claude, `PROMPT_BEAST` for GPT-4/o1/o3, `PROMPT_GEMINI` for Gemini, `PROMPT_CODEX` for GPT-5/Codex). This matches Claude Code's behavior where sub-agents get the same system prompt as standalone sessions. **How it works:** Teammate sessions are marked with `session.teammate = true`. In `llm.ts`, system prompt construction becomes **additive** for teammates: ``` // Teammate (additive): provider prompt + agent prompt + input.system // Non-teammate (existing): agent prompt OR provider prompt + input.system ``` Empty-provider fallback: if `SystemPrompt.provider(model)` returns empty for a teammate, falls back to `SystemPrompt.instructions()`. For Codex teammates, `options.instructions` is always set (API requirement). The team-specific context (role identity, available tools, communication rules) is injected as a **user message**, not a system prompt — keeping it separate from the provider behavioral guidance. ### Event-based decoupling `Team.cleanup()` no longer imports Session directly. Instead it publishes `TeamEvent.Cleaned` with `leadSessionID` and `delegate` fields. The `onCleanedRestorePermissions()` listener (wired in bootstrap) subscribes to this event and handles the Session.update to remove delegate deny rules. This keeps the team module decoupled from session internals. ### HTTP routes (`server/routes/team.ts` — 130 lines) Routes use `hono-openapi` with `describeRoute`, `validator`, and `resolver` middleware for OpenAPI metadata, request validation, and consistent error responses: - `GET /team` — List all teams - `GET /team/:name` — Get team by name - `GET /team/:name/tasks` — Get team's task list - `GET /team/by-session/:sessionID` — Find team context for a session (team info + tasks + role) - `POST /team/:name/delegate` — Toggle delegate mode (validates body with Zod schema) ### Bootstrap wiring (`project/bootstrap.ts`) - `Team.onCleanedRestorePermissions()` — wired before recovery - `Team.recover()` — marks stale teammates as interrupted - `Team.autoCleanup()` — wired after recovery completes ### Registry integration (`tool/registry.ts`) - Conditional import + registration of all 9 team tools when `Flag.OPENCODE_EXPERIMENTAL_AGENT_TEAMS` is enabled ### Server integration (`server/server.ts`) - Mounts `TeamRoutes()` at `/team` path — single 2-line change ## Tests 11 test files, 134 tests covering: | Test file | Coverage | |-----------|----------| | `team.test.ts` | Core tool execution, auto-cleanup behavior, constraints | | `team-spawn.test.ts` | Spawn validation — agent resolution, model validation, permission rules, plan mode, `session.teammate` flag | | `team-e2e.test.ts` | Full lifecycle — create → spawn → work → message → complete → shutdown → cleanup | | `team-edge-cases.test.ts` | Error paths — invalid names, missing teams, double create, nested team prevention, cleanup with active members | | `team-scenarios.test.ts` | Real-world patterns — multi-agent code review, parallel implementation, research → implement pipeline | | `team-plan-approval.test.ts` | Plan approval flow — pending → approved (unlocks tools), pending → rejected (stays read-only) | | `team-delegate-cleanup.test.ts` | Delegate mode — lead permission denial, cleanup restores permissions via event (no sleeps — Bus.publish awaits subscribers) | | `team-integration.ts` | Integration patterns — team + task tool interaction, concurrent operations | | `team-multi-model.ts` | Multi-model teams — different providers per teammate, model resolution priority chain | | `team-scenarios-integration.ts` | Cross-cutting scenarios — error recovery, message ordering, task dependency chains | ## Files changed | File | Change | |------|--------| | `src/tool/team.ts` | New — 9 MCP tools (thin wrappers) | | `src/team/index.ts` | Modified — +333 lines (resolveModel, spawnMember, approvePlan, notifyLead, autoCleanup, onCleanedRestorePermissions) | | `src/server/routes/team.ts` | New — hono-openapi routes with validation | | `src/project/bootstrap.ts` | Modified — wire event listeners and auto-cleanup | | `src/session/llm.ts` | Modified — additive system prompt for teammates, empty-provider fallback | | `src/session/index.ts` | Modified — add `teammate` flag to Session.Info | | `src/session/prompt.ts` | Modified — pass `teammate` + `sessionPermission` to LLM | | `src/tool/registry.ts` | Modified — conditional registration | | `src/server/server.ts` | Modified — mount routes | | `test/team/*.test.ts` | New — 11 test files, 134 tests | | **Total** | **+8,198 / −11** | ## Stack PR 2/3 — merge in order: 1. #12730 — Core team primitives 2. **This PR** — Tools + routes + prompting + lifecycle 3. #12732 — TUI integration
yindo added the pull-request label 2026-02-16 18:19:08 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#14345