Teams
Team management: SQLite persistence, CoordTask DAG dispatch, leader strategy planning, ACP members, Snapshot / Workflow Canvas.
The teams module provides team management for multi-agent collaboration, supporting structured teamwork with lifecycle management, Coord-Task DAG dispatch, plan approval (Team Strata), and task tracking.
The historical standalone modules/enums that earlier docs referenced —
TeamLifecycle,TeamPlan,ApprovalState, plusteams/lifecycle.rs,teams/plans.rs,teams/sessions.rs,teams/artifacts.rs,teams/messages.rs— no longer exist as separate constructs. This page describes the code as it lives insrc/teams/today.
Overview
Teams enables:
- Team creation — Define teams of agents with a designated leader and members; members may be in-process agents or ACP sessions (
TeamMemberKind::Agent | AcpSession) - Strategic coordination ("Team Strata") — The leader plans first; the plan is welded into every member's prompt
- Coord Tasks — Per-member task assignment through the unified
coord_tasksDAG insrc/agents/swarm/tasks/, with statusPending | Blocked | InProgress | WaitingReview | Completed | Failed | Cancelled | Skipped | Paused | Unsatisfiable - Verification loop — Leader accepts or rejects member deliverables via the
task_reviewtool (see Coord Tasks) - Team protocol — Each team can set
Team.protocol(≤32 KiB); the dispatcher injects it into every member's launch context - ACP members —
teams.acp_member.add / list / removewire external CLIs (Claude Code / Codex / Gemini) - Snapshot / Kanban —
teams.snapshot.*records team state;team_workflow_canvastool exposes a drag-and-drop board - JSON-RPC driven — Full
teams.*surface (lifecycle, tasks, messages, snapshots, workflow)
Architecture
src/teams/
├── mod.rs # entry; re-exports core types
├── store.rs # SqliteTeamStore: teams / team_members tables
├── types.rs # Team / TeamMember / TeamMemberKind / TeamStatus / TeamSummary
├── plans.rs # PlanSubmission / PlanManager
├── context.rs # TeamInboxContextProvider
├── events.rs # SqliteEventLogStore / TeamEventLogger
├── artifacts.rs # ArtifactStore + ArtifactType / TaskArtifact / TaskStatus
├── snapshots.rs # SqliteSnapshotStore + capture/restore
├── workflow_canvas.rs # workflow canvas
├── run_mode.rs # team run-mode (usage_mode pin)
├── leader_prompt.rs # leader-prompt construction
├── member_provision.rs # member provisioning
├── notifier.rs # TeamNotifier
├── dispatcher/ # TeamDispatcher (autonomous) + handoff + runner + clarify + acp_bridge + schedule
├── broadcast/ # GroupChatBroadcaster (deterministic fan-out; targets / transcript / member_prompt)
├── messages/ # TeamMessage persistence (store / inbox / router / mentions / aggregator / types)
├── sessions/ # collaborative session (coordinator / store / types)
└── templates/ # builtin team-template loader + materialize + substituteCore types (src/teams/types.rs)
pub struct Team {
pub id: TeamId,
pub name: String,
pub description: String,
pub leader_id: String,
pub status: TeamStatus, // actually only Active | Disbanded
pub created_at: i64,
pub disbanded_at: Option<i64>,
pub protocol: Option<String>, // leader-authored operating protocol (≤32 KiB)
}
pub enum TeamStatus { Active, Disbanded } // snake_case on the wire
pub enum TeamMemberKind { Agent, AcpSession }
pub struct TeamMember {
pub team_id: TeamId,
pub agent_id: String, // Agent: registry id; AcpSession: synthetic `acp:<harness>:<cwd>[:name]`
pub role: String,
pub joined_at: i64,
pub kind: TeamMemberKind,
pub acp_harness_id: Option<String>,
pub acp_cwd: Option<String>,
pub acp_session_name: Option<String>,
}TeamMember.kind discriminates runtime dispatch — in-process agents flow through MemberDispatchTarget::Agent, ACP sessions through MemberDispatchTarget::AcpSession (src/teams/dispatcher/runner.rs).
TeamSummary is the list view (id / name / description / leader / status / member_count / timestamps) and does not include task counts — those live in the coord_tasks DAG (a separate database); use the team_status tool for an accurate task breakdown.
Storage (SqliteTeamStore)
#[async_trait]
pub trait TeamStore: Send + Sync {
async fn create_team(&self, input: NewTeam) -> Result<Team>;
async fn get_team(&self, id: &str) -> Result<Option<Team>>;
async fn get_team_by_name(&self, name: &str) -> Result<Option<Team>>;
async fn list_teams(&self) -> Result<Vec<TeamSummary>>;
async fn disband_team(&self, id: &str) -> Result<()>;
async fn delete_team(&self, id: &str) -> Result<()>; // Disbanded only
async fn add_member(&self, input: NewTeamMember) -> Result<TeamMember>;
async fn get_members(&self, team_id: &str) -> Result<Vec<TeamMember>>;
async fn remove_member(&self, team_id: &str, agent_id: &str) -> Result<()>; // cannot remove leader
async fn get_agent_teams(&self, agent_id: &str) -> Result<Vec<TeamSummary>>;
async fn set_protocol(&self, team_id: &str, protocol: Option<String>) -> Result<()>;
async fn rename_team(&self, id: &str, name: &str) -> Result<()>;
async fn set_name_auto(&self, id: &str, value: bool) -> Result<()>;
async fn take_auto_name_flag(&self, id: &str) -> Result<bool>; // atomic once-only fire
}
pub struct SqliteTeamStore { /* Arc<Mutex<Connection>> */ }MAX_PROTOCOL_LEN = 32 KiB is enforced — the protocol is rendered verbatim into the leader's launch context, so an unbounded value would let whoever can call set_protocol inject arbitrary instructions into the leader context.
Strategic coordination ("Team Strata")
Teams have a leader and members. On a team's first message, the leader plans first — before any member begins work. This planning pass fires once per team (a fire-once strategy keyed by the team, race-safe via atomic put_if_absent), and the resulting plan is welded into every member's prompt so that all members share the same strategic context.
First message to a team
│
▼
Leader plans first ──► Strategy (fire-once, keyed by team; atomic put_if_absent)
│
▼
Plan welded into every member's prompt
│
▼
Members work under assigned Coord Tasksteam_key(team_id) is one of the composite-key namespaces (alongside loop / goal / session); see Loop / Goal / Strategy.
Coord Tasks
coord_tasks is the unified DAG (in src/agents/swarm/tasks/), not a table owned by src/teams/. Members' work is expressed through it:
| Status | Meaning |
|---|---|
Pending | waiting on deps + dispatcher pickup |
Blocked | Derived (never stored): deps unresolved |
InProgress | dispatcher claimed, running |
WaitingReview | run finished, awaiting lead review; downstream stays blocked |
Completed | terminal-success (satisfies deps) |
Failed | terminal-failure (does not satisfy deps; triggers derived Unsatisfiable) |
Cancelled | cancelled |
Skipped | manually marked not-required (still satisfies deps) |
Paused | manually paused; dispatcher does not claim; does not satisfy deps |
Unsatisfiable | Derived: a dependency terminally failed; the task can never run on its own |
BlockedandUnsatisfiableare derived at query time;from_storeddeliberately rejects both.CoordTaskStatus::is_terminal()(stored-terminal) andis_settled()(observer-side) give the predicates used by team / workflow completion checks.
Dependency edges are stored in coord_task.blocked_by. The dispatcher in src/teams/dispatcher/schedule/ (select / settle / reclaim / failure) keeps readiness, reschedule, zombie reclamation, and failure propagation correct.
Verification loop: task_review
// src/builtin_tools/team/task_review.rs
#[derive(Clone)] pub struct TaskReviewTool { /* … */ }
impl AlephTool for TaskReviewTool {
const NAME: &'static str = "task_review";
async fn call(&self, args: TaskReviewArgs) -> Result<TaskReviewOutput> {
// soft leader-only guard (prompt-gating is the primary gate; this is defense-in-depth)
// Approve → CoordTaskStatus::Completed (satisfies deps)
// Reject → CoordTaskStatus::InProgress (owner redoes)
// when task metadata.require_grounding=true and approve has no grounding → bounce
}
}// task_review tool call
{
"task_id": "<coord-task-id>",
"decision": "approve", // or "reject"
"feedback": "…", // written back to owner on reject
"grounding": { // optional; required on approve when require_grounding=true
"kind": "exit_code", // exit_code | numeric | line_count
"source": "cargo test -p alephcore --lib",
"value": "0"
}
}The leader's accept/reject decision goes through
task_review(not a JSON-RPC method). Members work under Coord Tasks created/updated viateams.create_task/teams.update_task.workflow_step_reviewis the equivalent tool for workflow steps (src/builtin_tools/team/workflow_step.rs).
G2 — per-task git-worktree isolation
execute_member_task (src/teams/dispatcher/runner.rs) optionally wraps each member task in a detached-HEAD git worktree, wrapping WorktreeSandbox and pointing the run's workspace_override at the worktree so concurrent workers cannot corrupt each other's index. Outside a git repo the call silently falls back to the pre-G2 behaviour and logs a warning.
Team run-mode pin
26.7.25+: when teams.run runs, the leader stamps usage_mode (chat / work / code) and inherits it to sub-members:
- Sub-agents cannot invoke
session_set_mode— inheritance is one-way teams.runrejects anysession_set_moderequest from a sub-agent- When the leader switches mode via
task_review, the entire team re-aligns
// src/teams/run_mode.rs
if turn.is_subagent() {
ensure!(turn.parent_mode.is_some(), "team run mode pin must be carried");
ensure!(request.action != Some("session_set_mode"),
"subagents inherit parent mode; cannot switch mid-team-run");
}Configuration
[teams]
enabled = true
max_team_size = 10
require_plan_approval = true[team_broadcast] exposes tunable BroadcastConfig parameters (max_chain_depth / max_fanout_width / max_total_activations / transcript_token_budget / member_run_timeout_secs).
JSON-RPC surface
| Group | Methods |
|---|---|
| Lifecycle | teams.create (leader + members + optional auto_name), teams.get, teams.list, teams.rename, teams.disband, teams.delete |
| Work-thread | teams.chat.send, teams.chat.thread, teams.chat.history, teams.chat.cancel |
| Coord Tasks | teams.create_task, teams.update_task, teams.list_tasks, teams.add_task_comment, teams.list_task_comments, teams.list_task_events, teams.list_task_runs |
| Task control | teams.task.pause, teams.task.resume, teams.task.retry, teams.task.skip, teams.task.trace, teams.task.journal.get, teams.task.journal.list |
| Snapshots | teams.snapshot.create, teams.snapshot.get, teams.snapshot.list, teams.snapshot.restore, teams.snapshot.delete |
| Workflow | teams.workflow.export_canvas, teams.workflow.import_canvas, teams.workflow.approve_step, teams.workflow.reject_step, teams.workflow.retry_step |
| ACP members | teams.acp_member.add, teams.acp_member.list, teams.acp_member.remove |
| Misc | teams.list_templates, teams.usage |
The durable per-team group-chat thread is exposed through teams.chat.send / teams.chat.thread / teams.chat.history / teams.chat.cancel; this history powers the attribution-bubble replay in the group chat window. The agents.teams method returns a team list augmented with members_preview + last_message.
Team templates (src/teams/templates/)
Members of built-in team templates can declare their tool surface (sharing agents.tools_schema); no longer hardcoded:
templates/builtin/— built-in templatestemplates/loader.rs— template loadertemplates/materialize.rs— materialize a template intoTeam+ member rowstemplates/substitute.rs—${var}substitutiontemplates/types.rs— template types
loop-auditor is the 26.7.21+ built-in audit / supervision template; it defaults to independent-context evidence.
See Also
- Group Chat — Multi-persona conversations
- A2A — Agent-to-Agent protocol
- Orchestrator — Agent orchestration
- Teams Concept
- Group Chat Concept
- Gateway RPC
teams.*
Group Chat
Team group chat: GroupChatBroadcaster fan-out, three storm-prevention gates (chain depth / fanout width / total activations), team event topics, and three-panel attribution UI.
Workflow
Declarative, reusable workflow templates — the workflow half of the agent-workflow spectrum; compile to a coord_tasks DAG, executed by TeamDispatcher.