Skip to content

feat: add agent team core — Team namespace, messaging, recovery, and events#12730

Open
ugoenyioha wants to merge 4 commits intoanomalyco:devfrom
ugoenyioha:feature/team-core
Open

feat: add agent team core — Team namespace, messaging, recovery, and events#12730
ugoenyioha wants to merge 4 commits intoanomalyco:devfrom
ugoenyioha:feature/team-core

Conversation

@ugoenyioha
Copy link

@ugoenyioha ugoenyioha commented Feb 8, 2026

Summary

Adds the foundational layer for agent teams (Fixes #12711), gated behind OPENCODE_EXPERIMENTAL_AGENT_TEAMS=1.

This is PR 1/3 in the agent teams stack. It introduces the core Team namespace with full CRUD, persistence, recovery, and inter-agent messaging — everything downstream PRs (#12731 tools, #12732 TUI) build on top of.

What's included

Team namespace (team/index.ts — 392 lines)

  • CRUD: create, get, list, addMember, removeMember, cleanup
  • Storage-backed persistence: Teams stored via the Storage namespace with project-scoped keys (["team", projectId, name] and ["team_tasks", projectId, name]). No raw Bun.file/Bun.write or filesystem paths — Storage handles atomicity and locking internally.
  • Runtime validation: create and get wrapped with fn() for Zod input validation
  • Atomic mutations: All state changes (addMember, setMemberStatus, setDelegate, setMemberPlanApproval, removeMember) use Storage.update() — read-modify-write under lock
  • addMember validations: Case-insensitive name duplicate check, sessionID duplicate check, reserved name "lead" rejection — all inside Storage.update callback for atomicity
  • setMemberStatus guard: Optional { guard: true } parameter prevents overwriting "shutdown" status (TOCTOU prevention for teammate completion callbacks)
  • Single-team-per-session constraint: A session cannot lead multiple teams, and teammates cannot create nested teams
  • Member status tracking: active, idle, shutdown, interrupted states with atomic updates
  • Delegate mode: setDelegate() toggles lead's write permissions — when enabled, the lead is restricted to coordination-only tools
  • Plan approval state: setMemberPlanApproval() manages nonependingapproved/rejected flow per member
  • Event-based cleanup: cleanup() publishes TeamEvent.Cleaned with leadSessionID and delegate fields so listeners can handle side-effects (e.g. restoring lead session permissions) — no direct Session import in the cleanup path

Task system (TeamTasks in team/index.ts)

  • Shared task list: list, add, update, complete, claim — all use Storage.update for concurrency safety
  • Atomic task claiming: claim() checks status, assignee, and dependency resolution inside a Storage.update callback
  • Dependency resolution: Same depends_on logic as the per-session Todo system — auto-blocks tasks with unresolved deps, auto-unblocks when deps complete
  • Event publishing: TaskUpdated and TaskClaimed events propagate to TUI in real-time

Messaging (team/messaging.ts — 134 lines)

  • Directed messaging: send() routes messages between teammates and the lead by injecting synthetic user messages into the recipient's session
  • Broadcast: broadcast() sends to all active members (skips shutdown and interrupted), includes the lead if sender isn't the lead
  • Auto-wake: When a message is injected into an idle session, autoWake() starts a new prompt loop so the LLM picks up and processes the message immediately
  • Session injection: Messages arrive as [Team message from <name>]: <text> user messages with synthetic: true

Events (team/events.ts — 130 lines)

  • Zod-validated bus events: team.created, team.member.spawned, team.member.status, team.message, team.broadcast, team.task.updated, team.task.claimed, team.shutdown.request, team.plan.approval, team.cleaned
  • Full type safety: TeamInfoSchema, TeamMemberSchema, TeamTaskSchema with Zod, re-exported for downstream use
  • Expanded Cleaned event: TeamEvent.Cleaned includes leadSessionID and delegate fields for event-based permission restoration
  • Member schema: name, sessionID, agent, status, prompt, model (providerID/modelID format), planApproval

Bootstrap hook (project/bootstrap.ts)

  • Fire-and-forget Team.recover() call during instance startup

Recovery (Team.recover())

  • Scans all teams for members with status: "active" (stale from a crash)
  • Marks them as interrupted
  • Injects a synthetic notification into the lead session: "The following teammates were interrupted and need to be resumed"
  • Lead's LLM sees the notification on next prompt and can re-spawn or message them

Feature flag (flag/flag.ts)

  • OPENCODE_EXPERIMENTAL_AGENT_TEAMS — dynamic getter from environment variable, gates all team functionality

Tests

4 test files, 26 tests:

  • team-autowake.test.ts — Auto-wake behavior when messages arrive for idle sessions
  • team-persistence.test.ts — Storage persistence, config read/write, task serialization (verified via API, not raw file paths)
  • team-recovery.test.ts — Recovery logic for interrupted teammates after server restart (with Storage cleanup)
  • team-recovery-e2e.test.ts — End-to-end recovery scenarios with session injection

Files changed

File Change Lines
src/team/index.ts New +392
src/team/messaging.ts New +134
src/team/events.ts New +130
src/flag/flag.ts Modified +12
src/project/bootstrap.ts Modified +11
test/team/team-autowake.test.ts New +639
test/team/team-persistence.test.ts New +198
test/team/team-recovery.test.ts New +324
test/team/team-recovery-e2e.test.ts New +321
Total +2,161

Stack

PR 1/3 — merge in order:

  1. This PR — Core team primitives
  2. feat: add team tools, HTTP routes, and tool registry integration #12731 — Tools + routes
  3. feat: add team TUI integration — sidebar, header, sync, and team dialog #12732 — TUI integration

@github-actions
Copy link
Contributor

github-actions bot commented Feb 8, 2026

Thanks for your contribution!

This PR doesn't have a linked issue. All PRs must reference an existing issue.

Please:

  1. Open an issue describing the bug/feature (if one doesn't exist)
  2. Add Fixes #<number> or Closes #<number> to this PR description

See CONTRIBUTING.md for details.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 8, 2026

The following comment was made by an LLM, it may be inaccurate:

No duplicate PRs found

- Reorder in autoWake: add .then() handler to transition shutdown_requested → shutdown
- Remove ready from shutdown_requested transitions (prevent overwrite race)
- cancelMember: accept shutdown_requested in addition to busy
- Wrap autoWake in try/catch (prevent unhandled rejections from fire-and-forget calls)
@ugoenyioha ugoenyioha marked this pull request as ready for review February 10, 2026 23:06
- Add inbox.ts (JSONL inbox for O(1) writes)
- Add core-only tests (autowake, cancel, persistence, recovery)
- Update core files to latest dev versions (events, index, messaging)
- Adapt to upstream Session API (no teammate field, no LoopResult)
- Includes shutdown race condition fix
- Move tool-dependent tests to team-tools PR
@iamhenry
Copy link

im hoping the opencode team is considering something like this. thx for taking the first step into it

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

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

3 participants

Comments