Agent Teams
Multi-agent collaboration via team workflow and direct task board control
Agent Teams
A3S Code provides two team-based multi-agent collaboration modes:
| Mode | API | Use when |
|---|---|---|
| Team workflow | Orchestrator.runTeam(goal, ws, slots) | Automatically assemble Lead → Worker → Reviewer team via AgentSlot[] array |
| Direct task board | TeamRunner.create(agent, ws) + addLead/addWorker/addReviewer | Fine-grained control with per-member model/prompt/workspace overrides |
For single-agent orchestration with real-time monitoring, see Agent Orchestrator.
Architecture
┌─────────────────────────────────────────────────────────┐
│ AgentOrchestrator.run_team() │
│ │
│ ┌─────────────────────────┐ │
│ │ run_team(goal, ws, slots)│ │
│ │ role = Lead/Worker/ │ │
│ │ Reviewer │ │
│ └──────────────┬──────────┘ │
│ │ │
│ AgentTeam + │
│ TeamRunner │
│ ├─ Lead decomposes │
│ ├─ Workers execute │
│ └─ Reviewer approves │
└─────────────────────────────────────────────────────────┘
↑
AgentRegistry
(built-in: general / explore / plan)
(custom: ~/.a3s/agents/*.yaml)Core concepts:
| Concept | Responsibility |
|---|---|
AgentTeam | Task board + member registry — manages task lifecycle and team composition |
TeamRunner | Execution coordinator — drives Lead/Worker/Reviewer loops |
TeamRole | Role enum — Lead, Worker, Reviewer |
TeamTaskBoard | Thread-safe task queue — Arc<TeamTaskBoard> shared across all members |
Team Workflow (run_team)
run_team(goal, workspace, slots) automatically assembles a Lead → Worker → Reviewer team, decomposes the goal into tasks, and coordinates execution.
Prerequisite: Requires Orchestrator.create(agent) with a real Agent attached.
Quick Start
import { Agent, Orchestrator, AgentSlot } from '@a3s-lab/code';
const agent = await Agent.create('agent.hcl');
const orch = Orchestrator.create(agent);
const slots: AgentSlot[] = [
{ agentType: 'general', role: 'lead', prompt: '', permissive: true, maxSteps: 5 },
{ agentType: 'general', role: 'worker', prompt: '', permissive: true, maxSteps: 5 },
{ agentType: 'general', role: 'reviewer', prompt: '', permissive: true, maxSteps: 3 },
];
const result = await orch.runTeam(
'List 3 common JavaScript data structures and briefly describe each',
'.',
slots,
);
console.log(`Done tasks: ${result.doneTasks.length}`);
console.log(`Rejected tasks: ${result.rejectedTasks.length}`);
console.log(`Rounds: ${result.rounds}`);
for (const task of result.doneTasks) {
console.log(` [${task.id}] ${task.result}`);
}from a3s_code import Agent, Orchestrator, AgentSlot
agent = Agent.create("agent.hcl")
orch = Orchestrator.create(agent)
slots = [
AgentSlot(agent_type="general", role="lead", prompt="", permissive=True, max_steps=5),
AgentSlot(agent_type="general", role="worker", prompt="", permissive=True, max_steps=5),
AgentSlot(agent_type="general", role="reviewer", prompt="", permissive=True, max_steps=3),
]
result = orch.run_team(
"List 3 common Python data structures and briefly describe each",
".",
slots,
)
print(f"Done tasks: {len(result.done_tasks)}")
print(f"Rejected tasks: {len(result.rejected_tasks)}")
print(f"Rounds: {result.rounds}")
for task in result.done_tasks:
print(f" [{task.id}] {task.result}")Workflow Internals
1. Lead decomposes
├─ Receives goal, outputs JSON: {"tasks": ["task 1", "task 2", ...]}
└─ Calls board.post() for each task
2. Workers execute (concurrent)
├─ Loop: claim() → session.send(task) → board.complete()
└─ Sleep poll_interval_ms when no tasks available
3. Reviewer approves
├─ For each InReview task, evaluates the result
├─ "APPROVED: <reason>" → board.approve() → task becomes Done
└─ "REJECTED: <feedback>" → board.reject() → task re-queued for retry
4. Termination
└─ All tasks done, or max_rounds reachedTeamRole
| Role | Rust | TypeScript/Python | Responsibility |
|---|---|---|---|
| Lead | TeamRole::Lead | "lead" | Decomposes goal, posts tasks |
| Worker | TeamRole::Worker | "worker" | Claims and executes tasks |
| Reviewer | TeamRole::Reviewer | "reviewer" | Approves or rejects results |
TeamRunResult
Prop
Type
Direct Task Board
For scenarios requiring full control over orchestration logic, use AgentTeam + TeamTaskBoard + TeamRunner directly.
AgentTeam Setup
TeamConfig
All fields are optional and default to the values shown:
Prop
Type
TeamTaskBoard
The board is shared across all members via Arc<TeamTaskBoard>. Every operation is thread-safe:
Task State Machine
post() claim() complete()
Open ──► InProgress ──► InReview
│
┌───────────┴──────────┐
approve() reject()
│ │
Done Rejected ──► (re-claim())TeamTask Fields
Prop
Type
TeamRunner
TeamRunner binds real AgentSession instances (or any AgentExecutor) to team members and runs the full automated workflow.
There are two ways to create and use a TeamRunner:
Option 1: TeamRunner.create() (Recommended)
import { Agent, TeamRunner, TeamMemberOptions } from '@a3s-lab/code';
const agent = await Agent.create('agent.hcl');
const runner = TeamRunner.create(agent, '.');
// Optional: per-member overrides via TeamMemberOptions
const workerOpts: TeamMemberOptions = {
model: 'openai/kimi-k2.5', // Override model
role: 'You are a terse answering machine.', // Custom role prompt
guidelines: 'Reply in one word only.',
workspace: '/isolated/worktree', // Isolated workspace
maxToolRounds: 10,
};
runner.addLead('general');
runner.addWorker('general', workerOpts); // With per-member options
runner.addWorker('general'); // Inherits defaults
runner.addReviewer('general');
const result = await runner.runUntilDone('Your goal here');Option 2: Team + bindSession (Manual)
const team = new Team('my-team');
team.addMember('lead', TeamRole.Lead);
team.addMember('worker-1', TeamRole.Worker);
team.addMember('reviewer', TeamRole.Reviewer);
const runner = new TeamRunner(team);
runner.bindSession('lead', leadSession);
runner.bindSession('worker-1', workerSession);
runner.bindSession('reviewer', reviewerSession);
const result = await runner.runUntilDone('Goal');Option 1: TeamRunner.create() (Recommended)
from a3s_code import Agent, TeamRunner, TeamMemberOptions
agent = Agent.create("agent.hcl")
runner = TeamRunner.create(agent, ".")
worker_opts = TeamMemberOptions(
model="openai/kimi-k2.5",
role="You are a terse answering machine.",
guidelines="Reply in one word only.",
workspace="/isolated/worktree",
max_tool_rounds=10,
)
runner.add_lead("general")
runner.add_worker("general", worker_opts)
runner.add_worker("general") # Inherits defaults
runner.add_reviewer("general")
result = runner.run_until_done("Your goal here")TeamMemberOptions
Per-member override fields (all optional, inherit from agent definition if unset):
Prop
Type
run_until_done internals
-
Lead decomposition — the Lead's session receives a prompt to output JSON tasks. The runner parses the JSON and calls
board.post()for each task. -
Worker execution — one Tokio task per Worker. Each loops:
claim() → session.send() → board.complete() -
Reviewer loop — for each InReview task, the Reviewer's session evaluates the result and responds
APPROVED: <reason>orREJECTED: <feedback> -
Termination — when all tasks are Done, or after
max_roundsReviewer polling rounds
AgentExecutor Trait
TeamRunner accepts any type implementing AgentExecutor — not just AgentSession. Useful for testing or custom execution strategies: