A3S Docs
A3S Code

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:

ModeAPIUse when
Team workflowOrchestrator.runTeam(goal, ws, slots)Automatically assemble Lead → Worker → Reviewer team via AgentSlot[] array
Direct task boardTeamRunner.create(agent, ws) + addLead/addWorker/addReviewerFine-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:

ConceptResponsibility
AgentTeamTask board + member registry — manages task lifecycle and team composition
TeamRunnerExecution coordinator — drives Lead/Worker/Reviewer loops
TeamRoleRole enum — Lead, Worker, Reviewer
TeamTaskBoardThread-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 reached

TeamRole

RoleRustTypeScript/PythonResponsibility
LeadTeamRole::Lead"lead"Decomposes goal, posts tasks
WorkerTeamRole::Worker"worker"Claims and executes tasks
ReviewerTeamRole::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

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

  2. Worker execution — one Tokio task per Worker. Each loops: claim() → session.send() → board.complete()

  3. Reviewer loop — for each InReview task, the Reviewer's session evaluates the result and responds APPROVED: <reason> or REJECTED: <feedback>

  4. Termination — when all tasks are Done, or after max_rounds Reviewer polling rounds

AgentExecutor Trait

TeamRunner accepts any type implementing AgentExecutor — not just AgentSession. Useful for testing or custom execution strategies:

Point-to-Point Messaging


On this page