Skip to content

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

Closed as not planned

Description

@ugoenyioha

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:

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

Activity

  1. github-actions commented on Feb 8, 2026

    @github-actions
    Contributor

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

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

  2. ugoenyioha commented on Feb 8, 2026

    @ugoenyioha
    Author

    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).

  3. LeoNardo-LB commented on Feb 12, 2026

    @LeoNardo-LB

    Hope to merge after detailed testing

  4. lucamorettibuilds commented on Feb 17, 2026

    @lucamorettibuilds

    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 frontend reviewer has the same database access as the backend reviewer, and the infra explorer can read/write through every MCP server the lead can.

    This matters for two reasons:

    1. 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.

    2. 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 github MCP but not aws MCP, which is a more natural security boundary for real teams.

    A possible extension to the member config:

    members: [{
      name: "security-reviewer",
      sessionID: string,
      agent: "general",
      mcp?: {
        allow: ["github", "sentry"],   // only these MCP servers available
        deny?: ["aws", "database"],    // or exclude specific ones
      },
      // ...existing fields
    }]
    

    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.

  5. Piping commented on Feb 25, 2026

    @Piping

    for tools part, should teammate list available for members in the team?

  6. h-network commented on Mar 3, 2026

    @h-network

    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

  7. kevinkaylie commented on Mar 25, 2026

    @kevinkaylie

    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:

    • DID-based agent identity: Every agent gets a did:agent: address. In your design doc you mention named agents need stable identities — DID gives you exactly that, globally unique and platform-independent.
    • Named messaging: Your proposal for @agent-name routing maps directly onto AgentNexus capability-based discovery. Agents publish what they can do; others find them by capability, not hardcoded address.
    • Federated Relay: Your design mentions needing a message bus for team communication. AgentNexus Relay handles discovery + routing, supports both shared team Relay and private per-team Relay for data isolation.
    • Built on proven crypto: Ed25519 for authentication, X25519 for key exchange, AES-256-GCM for encryption. Handshake is 4 steps and happens once — subsequent messages are direct and fast.
    • MCP integration: 12 standard MCP tools so Claude, GPT, or any LLM can directly interact with the AgentNexus network.

    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.

  8. andregribeiro commented on Apr 13, 2026

    @andregribeiro

    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:

    • Per-member status in sidebar — visibility into which agents are running, idle, or blocked
    • Plan approval workflow — knowing which teammates need approval before they can proceed
    • Session list spinners — at-a-glance busy/idle state

    Would love to see the TUI sidebar piece land even as a standalone incremental improvement ahead of the full Agent Teams feature. Great design.

  9. FurryWolfX commented on Apr 18, 2026

    @FurryWolfX

    How's the progress going? I really need this feature.
    and I hope I can conveniently view these agents in the TUI.

  10. assigned and unassigned on Apr 26, 2026
  11. panwar-stack commented on May 3, 2026

    @panwar-stack

    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

  12. prassanna-ravishankar commented on Jun 21, 2026

    @prassanna-ravishankar

    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:

    • address by stable identity, not display name. spawned same-path agents collide on name, so routing has to canonicalise to an id underneath or you get intermittent misroutes
    • give asks a correlation id and an explicit ack, so a sender can tell delivered vs queued vs blocked. fire-and-forget hides the failure case that actually bites

    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.

  13. ferhimedamine commented on Jun 25, 2026

    @ferhimedamine

    @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.yml and the environment variable docs showing agent isolation and decay config.

  14. em1315 commented on Jul 29, 2026

    @em1315

    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:

    • OpenCode 1.18.9 on Windows
    • two independent NVIDIA DGX Spark nodes
    • Qwen3-Coder-Next-NVFP4 served by vLLM on separate OpenAI-compatible endpoints
    • Mark1 primary engineer on Spark 1
    • Spencer2 read-only reviewer on Spark 2

    Verified today:

    • two opencode run sessions launch concurrently against different configured providers
    • both session IDs are retained
    • the same sessions can be resumed for a bounded implementation/review loop
    • Mark1's report is relayed to Spencer2, which resumes and returns a structured verdict
    • one harmless end-to-end run completed with an ACCEPTED verdict

    This 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 --agent rejects agents declared only as mode: subagent and 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.

  15. Dante-dan commented on Sep 10, 2026

    @Dante-dan
    Contributor

    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:

    • Queue for the next turn.
    • Deliver at the next execution boundary.
    • Request a pause.
    • Request cancellation.

    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.

  16. h-network commented on Oct 23, 2026

    @h-network

    h-network/h-mesh does exactly what you requested

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions