team-mode

August 31, 2026 · View on GitHub

Generated: 2026-05-15

OVERVIEW

Spawns coordinated agent teams with shared mailbox, task list, optional tmux layout, and graceful lifecycle. Modeled after Claude Code Agent Teams. OFF by default. Enable via team_mode.enabled in .omo/omo.jsonc; restart OpenCode after enabling. Harness-neutral registry/mailbox/tasklist/state/worktree/tmux-layout primitives are extracted to packages/team-core/; this directory remains the OpenCode adapter for session spawning, hooks, tools, and config integration.

User docs: docs/guide/team-mode.md.

CONFIG

Full schema: src/config/schema/team-mode.ts.

{
  "team_mode": {
    "enabled": false,                       // gate
    "tmux_visualization": false,            // optional tmux pane layout
    "max_parallel_members": 4,              // 1..8
    "max_members": 8,                       // 1..8 hard cap
    "max_messages_per_run": 10000,          // 1..∞
    "max_wall_clock_minutes": 120,          // 1..∞
    "max_member_turns": 500,                // 1..∞
    "base_dir": null,                       // optional override of ~/.omo/teams or <project>/.omo/teams
    "message_payload_max_bytes": 32768,     // 1024..∞ — per-message payload cap
    "recipient_unread_max_bytes": 262144,   // 1024..∞ — per-recipient inbox cap
    "mailbox_poll_interval_ms": 3000        // 500..∞ — recipient poll cadence
  }
}

12 TEAM_* TOOLS

Registered via src/plugin/tool-registry.ts teamModeToolsRecord only when enabled.

ToolSource FilePurpose
team_createtools/lifecycle.tsSpawn team + member sessions from named or inline TeamSpec
team_deletetools/lifecycle.tsTear down state, mailbox, tasklist, worktrees, optional tmux
team_shutdown_requesttools/lifecycle.tsMember or lead requests its own shutdown
team_approve_shutdowntools/lifecycle.tsLead acks shutdown
team_reject_shutdowntools/lifecycle.tsLead rejects shutdown with reason
team_send_messagetools/messaging.tsSend to member name or * broadcast
team_task_createtools/tasks.tsCreate task on shared list
team_task_listtools/tasks.tsList tasks (filter by status / owner)
team_task_updatetools/tasks.tsClaim / complete / delete (atomic file lock)
team_task_gettools/tasks.tsFetch single task
team_statustools/query.tsFull team run status (members, tasks, mailbox)
team_listtools/query.tsList declared + active teams

ELIGIBLE AGENTS

AGENT_ELIGIBILITY_REGISTRY in types.ts — three verdict tiers, each with its own rejection message:

VerdictAgentsNotes
eligiblesisyphus, atlas, sisyphus-juniorThree only
conditionalhephaestusLacks teammate: "allow" permission by default. Either apply D-36 patch (add teammate: "allow" in tool-config-handler.ts) or use subagent_type: "sisyphus" instead
hard-rejectoracle, librarian, explore, multimodal-looker, metis, momus, prometheusRead-only or plan-mode-only — cannot write to mailbox; use task (delegate-task) instead

Hard-reject agents throw at TeamSpec parse with a specific message ("Agent 'X' is read-only…"). The error message points members at delegate-task as the right escape hatch.

MEMBER KINDS

{
  "members": [
    { "kind": "subagent_type", "name": "scout", "subagent_type": "sisyphus" },
    { "kind": "category", "name": "writer", "category": "writing", "prompt": "Write release notes" }
  ]
}
  • kind: "subagent_type" — direct agent. prompt optional.
  • kind: "category" — routed through sisyphus-junior with the chosen category model. prompt REQUIRED.

MODULE LAYOUT

team-mode/
├── index.ts                    # barrel and adapter exports
├── types.ts                    # OpenCode-facing re-exports/wrappers for team-core schemas plus AGENT_ELIGIBILITY_REGISTRY
├── deps.ts                     # checkTeamModeDependencies (git, tmux availability)
├── member-parser.ts            # member validation against eligibility registry
├── member-guidance.ts          # auto-injected guidance per member kind
├── member-session-resolution.ts
├── member-session-routing.ts
├── resolve-caller-team-lead.ts # determine if a session is acting as lead
├── team-session-registry.ts    # spawn-race-safe sessionID → team/member lookups
├── team-registry/              # adapter shim over team-core team spec loading
│   ├── loader.ts
│   ├── paths.ts                # ensureBaseDirs, resolveBaseDir
│   └── validator.ts
├── team-state-store/           # adapter shim over team-core durable runtime state.json with atomic locks
├── team-runtime/               # create/status/shutdown lifecycle
├── team-mailbox/               # adapter shim over team-core async messaging (send / poll / ack / inbox)
├── team-tasklist/              # adapter shim over team-core CRUD + claiming + dependencies
├── team-worktree/              # adapter shim over team-core git worktree primitives
├── team-layout-tmux/           # adapter shim over team-core optional pane layout
└── tools/                      # 12 team_* tool implementations + tests

STORAGE LAYOUT

~/.omo/teams/{name}/                       # user scope
<project>/.omo/teams/{name}/               # project scope (wins on collision)
  ├── config.json                          # TeamSpec
  ├── state.json                           # runtime: members, sessionIDs, lifecycle
  ├── mailbox/                             # one .jsonl per recipient
  ├── tasklist.jsonl                       # shared task list
  └── worktrees/{member-name}/             # git worktree per member

LIFECYCLE

1. team_create
   → load TeamSpec → validate eligibility → spawn member sessions
   → init mailbox + tasklist + worktrees → optional tmux layout
2. Lead delegates via team_send_message + team_task_create
3. Members claim tasks (team_task_update status="claimed") → execute → report (team_send_message)
4. team_shutdown_request → team_approve_shutdown / team_reject_shutdown
5. team_delete → cleanup state, mailbox, tasklist, worktrees, panes

KEY INVARIANTS

  1. Spawn-race-safe resolution: every team spawn calls registerTeamSession(sessionId, entry) synchronously when sessionID is known; every hook resolving sessionID calls lookupTeamSession BEFORE loadRuntimeState to avoid the spawn-race window.
  2. Deferred ack: messages are fire-and-forget; recipient acks via separate call.
  3. Locked tasks: task claiming uses atomic file locks; concurrent claims resolve safely.
  4. Atomic writes: state changes write to temp file then rename.
  5. Eligible agents only: rejection at parse, never at runtime.
  6. No nested teams: members CANNOT call team_create.

INTEGRATION POINTS

WhereWhat
src/index.ts (entry)checkTeamModeDependencies() + ensureBaseDirs() if team_mode.enabled
src/plugin/tool-registry.ts teamModeToolsRecordRegisters 12 team_* tools
create-transform-hooks.tsConditionally builds teamModeStatusInjector (team-mode-status-injector hook) and teamMailboxInjector (team-mailbox-injector hook) — both Transform tier
create-tool-guard-hooks.tsConditionally builds teamToolGating (team-tool-gating hook) — Tool Guard tier
src/plugin/event.tsRegisters 4 team-session-event handlers from src/hooks/team-session-events/: team-idle-wake-hint, team-lead-orphan-handler, team-member-error-handler, team-member-status-handler
src/cli/doctor/checks/team-mode.tsDoctor check for team-mode prerequisites
skills-loader-core/src/features/builtin-skills/skills/team-mode.tsBuilt-in skill documenting the 12 tools — gated on team_mode.enabled

WHERE TO LOOK

TaskLocation
Add new team tooltools/ + register in src/plugin/tool-registry.ts teamModeToolsRecord
Modify member eligibilitytypes.ts AGENT_ELIGIBILITY_REGISTRY
Change storage formattypes.ts Zod schemas
Add worktree behaviorteam-worktree/manager.ts
Modify tmux layoutteam-layout-tmux/layout.ts
Task lifecycle changesteam-tasklist/
Mailbox protocol changesteam-mailbox/
Recover orphaned runsteam-state-store/resume.ts

ANTI-PATTERNS

  • Never bypass team-session-registry — direct loadRuntimeState lookups will hit the spawn-race window.
  • Never write team state files without the atomic lock from team-state-store/locks.ts.
  • Never substitute task (delegate-task) for team_* tools when the user explicitly asks for team-mode work — they are not equivalent.
  • Never allow members to call team_create (nested teams are forbidden by team-tool-gating hook).