Repository navigation
Description
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
- Non-invasive — builds on existing sessions, agents, and tools. No changes to the core session or task architecture.
- Flat, not hierarchical — one lead, N teammates. No nested teams, no tree traversal.
- Name-addressed messaging — teammates communicate by name, not by session ID or parent-child relationship. The lead doesn't route messages.
- Multi-model — each teammate can use a different provider/model (e.g., Gemini for research, Claude for implementation).
- Opt-in — behind
OPENCODE_EXPERIMENTAL_AGENT_TEAMSflag. 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
- Lead calls
team_spawnwith agent type, name, model, and initial prompt - A child session is created with permission rules denying lead-only tools
- The teammate's prompt loop starts in the background (non-blocking)
- When the teammate's loop completes or errors, an idle notification is sent to the lead via
team_message - 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
tasktool 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:
Precious metals forecast — 3 teammates with task dependencies and claim operations:
Implementation Status
Working implementation submitted as a 3-PR stack:
- PR feat: add agent team core — Team namespace, messaging, recovery, and events #12730 — Core team primitives (Team namespace, messaging, events, recovery)
- PR feat: add team tools, HTTP routes, and tool registry integration #12731 — 9 MCP tools, HTTP routes, tool registry integration
- PR feat: add team TUI integration — sidebar, header, sync, and team dialog #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
- Should there be a max team size limit?
- Should teammate prompt loops auto-restart on server recovery, or is lead-driven re-spawn sufficient?
Related
- [FEATURE]: Add Agent Teams Equivalent or Better #12661 — "Add Agent Teams Equivalent or Better"
- [FEATURE]: Multi-Agent Coding #9649 — Multi-Agent Coding request
- feat: parallel subtask #6478 — Parallel subtask execution
- PR feat(task): Add subagent-to-subagent delegation with budgets, persistent sessions, and hierarchical session navigation #7756 — Subagent-to-subagent delegation (tree-based approach)


Activity
github-actions commentedon Feb 8, 2026
This issue might be a duplicate of existing issues. Please check:
Feel free to ignore if none of these address your specific case.
ugoenyioha commentedon 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:
Team.recover()scans all teams for members withstatus: "active"and marks them as"interrupted"team_messageorteam_broadcastto resume them, which triggers auto-wake and restarts their prompt loopsThis 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).
LeoNardo-LB commentedon Feb 12, 2026
Hope to merge after detailed testing
lucamorettibuilds commentedon Feb 17, 2026
Great design — the flat team topology with named messaging and plan approval is a clean model. The non-invasive approach (adjunct to existing sessions rather than rewriting them) is the right call for getting this merged without risk.
One area I think deserves attention: per-teammate MCP/credential scoping.
In the current design, all teammates inherit the same session environment, which means they share access to every configured MCP server and its credentials. For the security audit example in the proposal, that means the
frontendreviewer has the same database access as thebackendreviewer, and theinfraexplorer can read/write through every MCP server the lead can.This matters for two reasons:
Blast radius — if a teammate hallucinates a destructive tool call (or a malicious prompt injection occurs through a teammate's context), the damage surface is every MCP server rather than just the ones relevant to that teammate's task.
Least privilege for plan approval — the plan approval gate is great for controlling write tools, but it's binary (read-only → full access). With MCP scoping, you could approve a teammate to use
githubMCP but notawsMCP, which is a more natural security boundary for real teams.A possible extension to the member config:
This would complement the tool isolation you already have for task subagents (no team tools) — extending that principle to MCP access per teammate.
I've been working on something related in the MCP credential management space (janee) — specifically agent-scoped credential vaults where each agent identity gets isolated secret access. The pattern there is that credentials are bound to agent identity at the vault level, so even if two agents share the same MCP server, they see different credential scopes. Happy to share more details if this direction is interesting for the design.
Piping commentedon Feb 25, 2026
for tools part, should teammate list available for members in the team?
h-network commentedon Mar 3, 2026
Hi,
I decided that i will try to implement my design within opencode and try to get it in.
I've been using this for a while and works perfectly.
My design and how i
kevinkaylie commentedon Mar 25, 2026
This is an excellent design proposal — flat teams with named messaging hit the nail on the head for complex multi-agent workflows.
I want to draw your attention to AgentNexus (https://github.com/kevinkaylie/AgentNexus), an open-source protocol specifically built for this problem. It might save you from designing the communication layer from scratch:
The nice thing is this could complement your existing ask tool rather than replace it — subagents within a task could use the local Relay, while cross-team communication uses the federated network.
Would love to hear your thoughts — happy to dig into the technical design if useful.
andregribeiro commentedon Apr 13, 2026
Coming from #22315 — we were looking specifically for a sidebar panel showing all active sessions and which ones are waiting for approval/input. The TUI integration section of this proposal covers that perfectly.
Particularly interested in:
Would love to see the TUI sidebar piece land even as a standalone incremental improvement ahead of the full Agent Teams feature. Great design.
FurryWolfX commentedon Apr 18, 2026
How's the progress going? I really need this feature.
and I hope I can conveniently view these agents in the TUI.
panwar-stack commentedon May 3, 2026
Proposal for for adding native support for Agent teams docs: add agent teams proposal
Reference implementation: add agent team orchestration with DB-backed team service, team tools, and HTTP API
panwar-stack commentedon May 3, 2026
Example of prompt, conversation and how complex games can be built using agent teams
https://github.com/panwar-stack/shake-rattle-and-bang/tree/main/agent-teams-examples esp this one https://github.com/panwar-stack/shake-rattle-and-bang/tree/main/agent-teams-examples/html5-game
prassanna-ravishankar commentedon Jun 21, 2026
the named-messaging-between-flat-teams shape in this design is the right call, and a couple of things from having shipped roughly this externally that might be worth baking in:
i built repowire as the cross-runtime version of this (ask/ack/notify/broadcast over a local daemon, peer_id identity). not opencode-native, but if its useful as a reference for the messaging/identity layer the design is open. the TUI-integration piece @FurryWolfX wants is the part an external mesh cant give you, so native is genuinely better there.
ferhimedamine commentedon Jun 25, 2026
@prassanna-ravishankar's point about stable identity over display name extends further — agent identity doubles as the scoping mechanism for shared memory. When teams of agents need persistent knowledge (not just transient messages), the routing layer and the storage layer have to agree on who owns what.
The approach I landed on after iterating through several multi-agent architectures: each agent writes to its own namespace (scoped by a stable
agent_id), and cross-agent recall is always explicit — Agent B pulls from Agent A's namespace only when it genuinely needs that context, not by default broadcast. This prevents knowledge contamination while keeping team-level sharing available. Importance-weighted decay handles staleness without manual TTLs.If you're looking at reference implementations for the persistence layer, here's the setup I run for a 9-agent system:
docker-compose.ymland the environment variable docs showing agent isolation and decay config.em1315 commentedon Jul 29, 2026
We have a reproducible dual-local-vLLM use case and published an early Windows coordinator prototype here:
https://github.com/em1315/opencode/tree/spark-agent-teams
Environment:
Mark1primary engineer on Spark 1Spencer2read-only reviewer on Spark 2Verified today:
opencode runsessions launch concurrently against different configured providersThis is currently an external coordinator using the supported CLI/session-resume interface, not a native Agent Teams port. It does not copy the older session integration code. The goal is to use the prototype to validate lifecycle, permissions, messaging, recovery, and UX requirements before adapting them to the current V2/Effect session architecture.
One useful finding:
opencode run --agentrejects agents declared only asmode: subagentand falls back to the default primary agent. Concurrent external sessions therefore need a primary-mode reviewer profile (with explicit deny permissions), or native team spawning must support selecting a subagent profile without fallback.We'll keep test evidence and implementation notes on the branch and would welcome maintainer guidance on the preferred V2 extension point for native background teammates and named messaging.
Dante-dan commentedon Sep 10, 2026
I think synthetic user messages are a reasonable way to deliver teammate communication into the model’s context. Building on the earlier suggestion about correlation IDs and acknowledgments, I would add a runtime-visible envelope for HITL routing, execution status, message priority, and interruption.
This also connects with the asynchronous HITL discussion in #6330: what happens when a teammate—or a task subagent working underneath it—needs human input?
HITL should retain its identity across delegation boundaries.
A request could carry its originating agent, session, task, request ID, and who is allowed to answer. The lead receives the request and can either resolve a question within its authority or escalate it to the user. A request that specifically requires human authorization must remain human-only.
The answer should resolve the original request and resume the dependent work. Independent teammates could continue running. Task subagents would not need access to the full team toolset; a scoped HITL request could propagate through their supervising teammate.
The lead also needs supervision signals, not just conversation messages.
A silent teammate may be waiting for a model response, running a long tool, waiting for human input, blocked on another task, or genuinely unresponsive. Those situations require different interventions.
The runtime should expose the current execution phase, active tool or request, elapsed time, last observed activity, and blocking reason. A heartbeat can indicate runtime liveness, but does not establish task progress; missing text output should not automatically imply a hang.
I would split responsibility here: the runtime performs monitoring and emits status or timeout events, while the lead decides whether to wait, request an update, redirect work, or stop a teammate. Supervision should remain observable even while the lead itself is waiting on a model call.
Priority and interruption should be separate concepts.
A high-priority message does not always need to abort current work. Delivery policies could distinguish:
For example, a HITL request may need prompt user attention without interrupting unrelated work. A changed implementation constraint may need to reach a teammate before its next tool call. A user cancellation should reach the runtime without waiting for the model to read another synthetic message.
Interruption also needs an explicit lifecycle: requested, acknowledged, stopping, and stopped—or unable to stop / outcome unknown. Sending a cancellation request is not proof that execution stopped, and stopping an agent does not undo an external side effect already committed.
MCP’s request-linked cancellation and progress reporting provide useful reference points, although team supervision would need additional semantics.
These additions could preserve synthetic messages as the model-facing delivery mechanism while giving the runtime enough structure to route human responses, expose blocked states, and apply authorized control actions reliably.
I’d be happy to take on part of the development work. If this direction fits the project, I’d welcome guidance on a bounded first contribution—such as the message envelope and lifecycle events, HITL request routing, or execution-status and interruption support—and the appropriate integration point in the current session architecture.
h-network commentedon Oct 23, 2026
h-network/h-mesh does exactly what you requested