[DESIGN]: Agent Teams — flat teams with named messaging, multi-model support, and TUI integration #8842

Open
opened 2026-02-16 18:10:59 -05:00 by yindo · 3 comments
Owner

Originally created by @ugoenyioha on GitHub (Feb 8, 2026).

Originally assigned to: @thdxr on GitHub.

Problem

OpenCode's task tool spawns subagents that run sequentially, return a result, and terminate. There's no way for multiple agents to work in parallel, communicate with each other, or coordinate on shared tasks. Users working on complex problems (multi-file refactors, research + implementation, code review across concerns) are limited to one agent at a time.

Claude Code shipped agent teams (Feb 5, 2026). Several community requests exist: #12661, #9649, #6478. PR #7756 proposes subagent-to-subagent delegation with a tree hierarchy. This proposal takes a different approach: flat teams with named messaging, built as an adjunct to the existing session/task system rather than a redesign of it.

Design Goals

  1. Non-invasive — builds on existing sessions, agents, and tools. No changes to the core session or task architecture.
  2. Flat, not hierarchical — one lead, N teammates. No nested teams, no tree traversal.
  3. Name-addressed messaging — teammates communicate by name, not by session ID or parent-child relationship. The lead doesn't route messages.
  4. Multi-model — each teammate can use a different provider/model (e.g., Gemini for research, Claude for implementation).
  5. Opt-in — behind OPENCODE_EXPERIMENTAL_AGENT_TEAMS flag. Default behavior unchanged.

Architecture

Data Model

A team is a named group of sessions:

Team {
  name: string              // e.g. "auth-review"
  leadSessionID: string     // the session that created the team
  delegate?: boolean        // lead restricted to coordination-only tools
  members: [{
    name: string            // unique within team, e.g. "security-reviewer"
    sessionID: string       // child session
    agent: string           // agent type (e.g. "general", "explore")
    status: "active" | "idle" | "shutdown" | "interrupted"
    model?: string          // "providerID/modelID"
    planApproval?: "none" | "pending" | "approved" | "rejected"
  }]
}

Team state is persisted to disk as JSON files under .opencode/teams/{teamName}/ (config.json and tasks.json). All reads go directly to disk — no in-memory cache. Task state (shared task list) uses file-based storage with file locking for concurrency. No database schema changes.

Tools

Nine tools, split by role:

Lead-only:

Tool Purpose
team_create Create a team, become the lead
team_spawn Spawn a teammate (creates child session, starts prompt loop)
team_approve_plan Approve/reject a teammate's plan (unlocks write tools)
team_shutdown Request a teammate to shut down
team_cleanup Remove team resources

Available to all team members (lead + teammates):

Tool Purpose
team_message Send a message to a specific teammate or "lead" by name
team_broadcast Send a message to all teammates
team_tasks View/add/complete tasks on a shared task list
team_claim Claim a pending task (file-locked to prevent races)

Tool isolation: Task subagents spawned by teammates get NO team tools. Subagents are private utilities — the teammate relays relevant findings. This prevents uncoordinated noise in team communication.

Communication

Messages are injected as synthetic user messages into the recipient's session, which triggers auto-wake if the recipient is idle. No file-based mailboxes, no polling.

TeamMessaging.send({ teamName, from: "security-reviewer", to: "lead", text: "Found 3 SQL injection vulnerabilities" })
// → injects a user message into the lead's session
// → if lead is idle, auto-wake kicks off the prompt loop

Messages between teammates are persisted through OpenCode's existing session storage (messages and parts written as JSON files to ~/.local/share/opencode/storage/).

Teammate Lifecycle

  1. Lead calls team_spawn with agent type, name, model, and initial prompt
  2. A child session is created with permission rules denying lead-only tools
  3. The teammate's prompt loop starts in the background (non-blocking)
  4. When the teammate's loop completes or errors, an idle notification is sent to the lead via team_message
  5. Lead can send follow-up work via team_message, which auto-wakes the teammate

Recovery

On server restart, Team.recover() scans all teams for members with status: "active" (stale from a crash), marks them as interrupted, and injects a synthetic notification into the lead session. The lead's LLM sees the notification on next prompt and can re-spawn or message them. Team structure, member state, and conversation history all survive a restart — the remaining gap is that teammate prompt loops are not auto-restarted.

Optional Features

Delegate mode: Lead is restricted to coordination-only tools (no write/edit/bash). Forces clean separation between orchestration and execution.

Plan approval: Teammates start in read-only mode. They research, formulate a plan, send it to the lead. Lead approves (unlocks write tools) or rejects (teammate revises). Prevents wasted work on wrong approaches.

Shared task list: Teams can maintain a task list with priorities, dependencies, and assignees. Tasks auto-unblock when dependencies complete. Any member can view, add, claim, or complete tasks.

TUI Integration

  • Header: Team badge showing team name and member count/status summary
  • Sidebar: Team section with per-member status, per-member todo progress, current tool activity (live spinner), and shared task list
  • Prompt: Aggregated busy indicator ("3 teammates working") when lead is idle but teammates are active
  • Session list: Spinner on busy teammate sessions
  • Team dialog: Keyboard-driven team creation interface

What This Does NOT Change

  • The existing task tool and subagent system are untouched
  • Session creation, storage, and lifecycle are unchanged
  • The prompt loop, processor, and message system are unchanged
  • No database schema changes
  • No new dependencies

Example Usage

User: Review this codebase for security issues across frontend, backend, and infrastructure

Lead (Claude Opus):
  → team_create("security-audit")
  → team_spawn("frontend", agent="general", model="anthropic/claude-sonnet-4-20250514", prompt="Review React components for XSS...")
  → team_spawn("backend", agent="general", model="google/gemini-2.5-pro", prompt="Review API endpoints for injection...")  
  → team_spawn("infra", agent="explore", prompt="Check Docker, CI, and dependency configs...")
  
  [3 teammates work in parallel, each searching and analyzing independently]
  
  frontend → team_message(to="lead", "Found 3 XSS vulnerabilities in...")
  backend → team_message(to="frontend", "The sanitization you flagged is also missing in the API layer")
  infra → team_tasks(action="complete", task_id="deps")
  
  Lead synthesizes findings into a final report

Screenshots

Super Bowl prediction — 3 teammates researching in parallel with live sidebar activity:

Super Bowl prediction — 3 teammates researching in parallel with sidebar showing live task activity and busy indicator

Precious metals forecast — 3 teammates with task dependencies and claim operations:

Precious metals forecast — 3 teammates with task dependencies and claim operations

Implementation Status

Working implementation submitted as a 3-PR stack:

  • PR #12730 — Core team primitives (Team namespace, messaging, events, recovery)
  • PR #12731 — 9 MCP tools, HTTP routes, tool registry integration
  • PR #12732 — TUI integration (sidebar, header, sync, team dialog, prompt)

By the numbers:

  • ~1,500 lines of core implementation (team state, messaging, events, 9 tools)
  • ~1,100 lines of TUI integration (sidebar panels, header badges, sync store, team dialog, prompt bar)
  • ~8,500 lines of tests across 14 test files (unit, integration, e2e, edge cases, persistence, recovery, plan approval, spawn permissions, auto-wake, delegate cleanup, scaling scenarios)
  • Multi-model tested (Claude + Gemini + OpenAI teammates in one team)

Open Questions

  1. Should there be a max team size limit?
  2. Should teammate prompt loops auto-restart on server recovery, or is lead-driven re-spawn sufficient?

Related

  • #12661 — "Add Agent Teams Equivalent or Better"
  • #9649 — Multi-Agent Coding request
  • #6478 — Parallel subtask execution
  • PR #7756 — Subagent-to-subagent delegation (tree-based approach)
Originally created by @ugoenyioha on GitHub (Feb 8, 2026). Originally assigned to: @thdxr on GitHub. ## Problem OpenCode's `task` tool spawns subagents that run sequentially, return a result, and terminate. There's no way for multiple agents to work in parallel, communicate with each other, or coordinate on shared tasks. Users working on complex problems (multi-file refactors, research + implementation, code review across concerns) are limited to one agent at a time. Claude Code shipped agent teams (Feb 5, 2026). Several community requests exist: #12661, #9649, #6478. PR #7756 proposes subagent-to-subagent delegation with a tree hierarchy. This proposal takes a different approach: flat teams with named messaging, built as an adjunct to the existing session/task system rather than a redesign of it. ## Design Goals 1. **Non-invasive** — builds on existing sessions, agents, and tools. No changes to the core session or task architecture. 2. **Flat, not hierarchical** — one lead, N teammates. No nested teams, no tree traversal. 3. **Name-addressed messaging** — teammates communicate by name, not by session ID or parent-child relationship. The lead doesn't route messages. 4. **Multi-model** — each teammate can use a different provider/model (e.g., Gemini for research, Claude for implementation). 5. **Opt-in** — behind `OPENCODE_EXPERIMENTAL_AGENT_TEAMS` flag. Default behavior unchanged. ## Architecture ### Data Model A team is a named group of sessions: ``` Team { name: string // e.g. "auth-review" leadSessionID: string // the session that created the team delegate?: boolean // lead restricted to coordination-only tools members: [{ name: string // unique within team, e.g. "security-reviewer" sessionID: string // child session agent: string // agent type (e.g. "general", "explore") status: "active" | "idle" | "shutdown" | "interrupted" model?: string // "providerID/modelID" planApproval?: "none" | "pending" | "approved" | "rejected" }] } ``` Team state is persisted to disk as JSON files under `.opencode/teams/{teamName}/` (config.json and tasks.json). All reads go directly to disk — no in-memory cache. Task state (shared task list) uses file-based storage with file locking for concurrency. No database schema changes. ### Tools Nine tools, split by role: **Lead-only:** | Tool | Purpose | |------|---------| | `team_create` | Create a team, become the lead | | `team_spawn` | Spawn a teammate (creates child session, starts prompt loop) | | `team_approve_plan` | Approve/reject a teammate's plan (unlocks write tools) | | `team_shutdown` | Request a teammate to shut down | | `team_cleanup` | Remove team resources | **Available to all team members (lead + teammates):** | Tool | Purpose | |------|---------| | `team_message` | Send a message to a specific teammate or "lead" by name | | `team_broadcast` | Send a message to all teammates | | `team_tasks` | View/add/complete tasks on a shared task list | | `team_claim` | Claim a pending task (file-locked to prevent races) | **Tool isolation:** Task subagents spawned by teammates get NO team tools. Subagents are private utilities — the teammate relays relevant findings. This prevents uncoordinated noise in team communication. ### Communication Messages are injected as synthetic user messages into the recipient's session, which triggers auto-wake if the recipient is idle. No file-based mailboxes, no polling. ``` TeamMessaging.send({ teamName, from: "security-reviewer", to: "lead", text: "Found 3 SQL injection vulnerabilities" }) // → injects a user message into the lead's session // → if lead is idle, auto-wake kicks off the prompt loop ``` Messages between teammates are persisted through OpenCode's existing session storage (messages and parts written as JSON files to `~/.local/share/opencode/storage/`). ### Teammate Lifecycle 1. Lead calls `team_spawn` with agent type, name, model, and initial prompt 2. A child session is created with permission rules denying lead-only tools 3. The teammate's prompt loop starts in the background (non-blocking) 4. When the teammate's loop completes or errors, an idle notification is sent to the lead via `team_message` 5. Lead can send follow-up work via `team_message`, which auto-wakes the teammate ### Recovery On server restart, `Team.recover()` scans all teams for members with `status: "active"` (stale from a crash), marks them as `interrupted`, and injects a synthetic notification into the lead session. The lead's LLM sees the notification on next prompt and can re-spawn or message them. Team structure, member state, and conversation history all survive a restart — the remaining gap is that teammate prompt loops are not auto-restarted. ### Optional Features **Delegate mode:** Lead is restricted to coordination-only tools (no write/edit/bash). Forces clean separation between orchestration and execution. **Plan approval:** Teammates start in read-only mode. They research, formulate a plan, send it to the lead. Lead approves (unlocks write tools) or rejects (teammate revises). Prevents wasted work on wrong approaches. **Shared task list:** Teams can maintain a task list with priorities, dependencies, and assignees. Tasks auto-unblock when dependencies complete. Any member can view, add, claim, or complete tasks. ### TUI Integration - **Header:** Team badge showing team name and member count/status summary - **Sidebar:** Team section with per-member status, per-member todo progress, current tool activity (live spinner), and shared task list - **Prompt:** Aggregated busy indicator ("3 teammates working") when lead is idle but teammates are active - **Session list:** Spinner on busy teammate sessions - **Team dialog:** Keyboard-driven team creation interface ### What This Does NOT Change - The existing `task` tool and subagent system are untouched - Session creation, storage, and lifecycle are unchanged - The prompt loop, processor, and message system are unchanged - No database schema changes - No new dependencies ## Example Usage ``` User: Review this codebase for security issues across frontend, backend, and infrastructure Lead (Claude Opus): → team_create("security-audit") → team_spawn("frontend", agent="general", model="anthropic/claude-sonnet-4-20250514", prompt="Review React components for XSS...") → team_spawn("backend", agent="general", model="google/gemini-2.5-pro", prompt="Review API endpoints for injection...") → team_spawn("infra", agent="explore", prompt="Check Docker, CI, and dependency configs...") [3 teammates work in parallel, each searching and analyzing independently] frontend → team_message(to="lead", "Found 3 XSS vulnerabilities in...") backend → team_message(to="frontend", "The sanitization you flagged is also missing in the API layer") infra → team_tasks(action="complete", task_id="deps") Lead synthesizes findings into a final report ``` ## Screenshots **Super Bowl prediction — 3 teammates researching in parallel with live sidebar activity:** ![Super Bowl prediction — 3 teammates researching in parallel with sidebar showing live task activity and busy indicator](https://raw.githubusercontent.com/ugoenyioha/opencode/assets/agent-teams-screenshots/.github/assets/agent-teams-superbowl.png) **Precious metals forecast — 3 teammates with task dependencies and claim operations:** ![Precious metals forecast — 3 teammates with task dependencies and claim operations](https://raw.githubusercontent.com/ugoenyioha/opencode/assets/agent-teams-screenshots/.github/assets/agent-teams-metals.png) ## Implementation Status Working implementation submitted as a 3-PR stack: - **PR #12730** — Core team primitives (Team namespace, messaging, events, recovery) - **PR #12731** — 9 MCP tools, HTTP routes, tool registry integration - **PR #12732** — TUI integration (sidebar, header, sync, team dialog, prompt) By the numbers: - ~1,500 lines of core implementation (team state, messaging, events, 9 tools) - ~1,100 lines of TUI integration (sidebar panels, header badges, sync store, team dialog, prompt bar) - ~8,500 lines of tests across 14 test files (unit, integration, e2e, edge cases, persistence, recovery, plan approval, spawn permissions, auto-wake, delegate cleanup, scaling scenarios) - Multi-model tested (Claude + Gemini + OpenAI teammates in one team) ## Open Questions 1. Should there be a max team size limit? 2. Should teammate prompt loops auto-restart on server recovery, or is lead-driven re-spawn sufficient? ## Related - #12661 — "Add Agent Teams Equivalent or Better" - #9649 — Multi-Agent Coding request - #6478 — Parallel subtask execution - PR #7756 — Subagent-to-subagent delegation (tree-based approach)
Author
Owner

@github-actions[bot] commented on GitHub (Feb 8, 2026):

This issue might be a duplicate of existing issues. Please check:

  • #12661: Add Agent Teams Equivalent or Better
  • #9649: Multi-Agent Coding
  • #6478: parallel subtask execution

Feel free to ignore if none of these address your specific case.

@github-actions[bot] commented on GitHub (Feb 8, 2026): This issue might be a duplicate of existing issues. Please check: - #12661: Add Agent Teams Equivalent or Better - #9649: Multi-Agent Coding - #6478: parallel subtask execution Feel free to ignore if none of these address your specific case.
Author
Owner

@ugoenyioha commented on GitHub (Feb 8, 2026):

Update: Server restart recovery is now fully implemented.

The gap mentioned in the previous update (teammate prompt loops not auto-restarting after server restart) has been resolved:

  • On startup, Team.recover() scans all teams for members with status: "active" and marks them as "interrupted"
  • A synthetic notification is injected into the lead session listing which teammates were interrupted
  • The lead then uses team_message or team_broadcast to resume them, which triggers auto-wake and restarts their prompt loops
  • No special wake-words needed — the LLM handles it naturally through the existing messaging system

This means team state now fully survives server restarts: structure, member state, conversation history, shared tasks, and teammate resumption — all without manual intervention beyond the lead's natural response to the interruption notification.

Open question 1 ("Should team state persist across server restarts?") is now answered: yes, it does, end-to-end.

Verified with unit tests (7 cases) and e2e tests (2 cases covering full recovery → team_message → auto-wake → LLM call → teammate resumes).

@ugoenyioha commented on GitHub (Feb 8, 2026): **Update: Server restart recovery is now fully implemented.** The gap mentioned in the previous update (teammate prompt loops not auto-restarting after server restart) has been resolved: - On startup, `Team.recover()` scans all teams for members with `status: "active"` and marks them as `"interrupted"` - A synthetic notification is injected into the lead session listing which teammates were interrupted - The lead then uses `team_message` or `team_broadcast` to resume them, which triggers auto-wake and restarts their prompt loops - No special wake-words needed — the LLM handles it naturally through the existing messaging system This means team state now fully survives server restarts: structure, member state, conversation history, shared tasks, **and** teammate resumption — all without manual intervention beyond the lead's natural response to the interruption notification. Open question 1 ("Should team state persist across server restarts?") is now answered: yes, it does, end-to-end. Verified with unit tests (7 cases) and e2e tests (2 cases covering full recovery → team_message → auto-wake → LLM call → teammate resumes).
Author
Owner

@LeoNardo-LB commented on GitHub (Feb 12, 2026):

Hope to merge after detailed testing

@LeoNardo-LB commented on GitHub (Feb 12, 2026): Hope to merge after detailed testing
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: anomalyco/opencode#8842