Coordinate teams of specialized AI agents to tackle large-scale software engineering tasks.
Agent swarms let you create a team of autonomous agents that collaborate through structured communication, task management, and role-based authority. Instead of a single agent handling everything, a swarm breaks work across specialized roles -- a coordinator delegates to managers, who assign tasks to engineers, who report results back up the chain.
Key capabilities:
- 24 specialized roles from intern to CTO, each with distinct authority levels
- 6 development phases that gate which tools are available as a project matures
- File-based messaging so agents communicate through inboxes without shared memory
- Task dependency tracking with automatic unblocking when blockers complete
- Auto-assignment that matches idle agents to available tasks by role seniority
- Lifecycle management with graceful shutdown, idle detection, and failure handling
# Via slash command
/team:enable
# Or via environment variable
LEVELCODE_ENABLE_SWARMS=1 levelcode/team:create my-projectThis creates a team in the planning phase with you as the lead. The team config is persisted to ~/.config/levelcode/teams/my-project/config.json.
/team:phase pre-alphaEach phase unlocks more tools. In planning you can only create and manage tasks. From pre-alpha onward agents can communicate. From alpha onward all tools are available including agent spawning.
Once in alpha or later, the coordinator agent can spawn managers and engineers, create tasks, and drive the project forward. Agents communicate through their inboxes and report progress through the task system.
/team:status # Overview with task counts
/team:members # Table of all agents and their current statusAll team commands are prefixed with /team:.
Create a new team and set it as the active team.
/team:create backend-rewrite- Team name must contain only letters, numbers, hyphens, and underscores
- Creates the team directory at
~/.config/levelcode/teams/<name>/ - Creates the tasks directory at
~/.config/levelcode/tasks/<name>/ - Initializes with phase
planningand no members - Automatically enables swarm features
Delete the currently active team and all its data.
/team:delete- Removes the team directory and all task files
- Resets the team store to its initial state
- Cannot be undone
Display the active team's status including phase, member count, and task breakdown.
/team:statusExample output:
Team: backend-rewrite
Phase: alpha
Members: 5
Tasks:
Pending: 12
In Progress: 3
Completed: 8
Blocked: 2
Advance the team to the next development phase.
/team:phase pre-alpha- Only forward single-step transitions are allowed (
planning->pre-alpha, notplanning->alpha) - Fires a
phase_transitionhook event - Valid phases:
planning,pre-alpha,alpha,beta,production,mature
Enable swarm features globally. Persists to ~/.config/levelcode/settings.json.
/team:enableDisable swarm features globally.
/team:disableDisplay a table of all team members with their role, status, and current task.
/team:membersExample output:
Role Status Name Task
-------------------------------------------------------------------------
coordinator active team-lead -
manager active mgr-frontend 42
senior-engineer working eng-api 37
researcher idle research-1 -
Roles are organized into authority levels. Higher-level roles can manage and spawn lower-level roles.
| Level | Role | Description |
|---|---|---|
| 0 | intern |
Entry-level agent for simple, well-defined read-only tasks |
| 0 | apprentice |
Learning-level agent for straightforward tasks with basic analysis |
| 1 | junior-engineer |
Junior engineer handling well-scoped tasks under guidance |
| 2 | mid-level-engineer |
Mid-level engineer building features and fixing bugs independently |
| 2 | tester |
Testing specialist writing and running tests for quality assurance |
| 3 | senior-engineer |
Senior IC handling complex implementations and mentoring |
| 3 | researcher |
Research specialist investigating codebases, APIs, and documentation |
| 3 | scientist |
Research engineer using experimentation and benchmarking for analysis |
| 3 | designer |
UI/UX specialist providing design guidance and specifications |
| 3 | product-lead |
Product specialist handling requirements, prioritization, and scope |
| 3 | reviewer |
Code review specialist |
| 4 | sub-manager |
Team Lead coordinating a small group on a focused workstream |
| 5 | staff-engineer |
Staff Engineer handling complex cross-cutting implementations |
| 5 | manager |
Engineering Manager coordinating engineers and tracking delivery |
| 6 | senior-staff-engineer |
Senior Staff Engineer driving large-scale technical initiatives |
| 7 | principal-engineer |
Principal Engineer defining architecture and solving the hardest problems |
| 8 | distinguished-engineer |
Distinguished Engineer shaping technical strategy across the system |
| 9 | fellow |
Engineering Fellow -- the most senior IC, tackling paradigm-defining problems |
| 10 | director |
Engineering Director overseeing multiple teams and cross-team alignment |
| 11 | vp-engineering |
VP of Engineering managing operations, delivery, and team scaling |
| 12 | coordinator |
Top-level orchestrator that drives multi-agent projects to completion |
| 13 | cto |
Chief Technology Officer responsible for technical strategy and team structure |
Authority determines what a role can do:
- Manage: A role can manage any role with a strictly lower authority level
- Spawn: Each role has an explicit list of roles it can spawn as sub-agents
import { canManage, getRoleLevel, getSpawnableRoles } from '@levelcode/agents/team/role-hierarchy'
canManage('manager', 'senior-engineer') // true (level 5 > level 3)
canManage('senior-engineer', 'manager') // false (level 3 < level 5)
getRoleLevel('coordinator') // 12
getRoleLevel('intern') // 0
getSpawnableRoles('manager') // ['senior-engineer', 'mid-level-engineer', 'sub-manager']
getSpawnableRoles('intern') // [] (cannot spawn team agents)Management roles can spawn specific sub-roles:
| Role | Can Spawn |
|---|---|
cto |
vp-engineering, coordinator, director, fellow, distinguished-engineer, principal-engineer, manager |
vp-engineering |
director, manager, senior-staff-engineer, principal-engineer |
coordinator |
manager, senior-engineer, researcher, designer, product-lead, scientist |
director |
manager, senior-engineer, researcher, product-lead |
manager |
senior-engineer, mid-level-engineer, sub-manager |
sub-manager |
mid-level-engineer, junior-engineer |
senior-engineer |
junior-engineer, intern, apprentice |
staff-engineer |
mid-level-engineer, junior-engineer |
principal-engineer |
senior-engineer, staff-engineer |
distinguished-engineer |
senior-engineer, staff-engineer |
fellow |
senior-engineer, staff-engineer |
Phases represent the maturity of a project and gate which team tools are available.
planning -> pre-alpha -> alpha -> beta -> production -> mature
Only forward single-step transitions are allowed. You cannot skip phases.
| Phase | Description | Available Team Tools |
|---|---|---|
planning |
Define goals, architecture, and task breakdown. No implementation. | task_create, task_update, task_get, task_list |
pre-alpha |
Core scaffolding and foundational work. Communication enabled. | + send_message, team_create |
alpha |
Active feature development. All tools available. | + team_delete, spawn_agents, spawn_agent_inline |
beta |
Stabilization, testing, bug fixes. All tools available. | All team tools |
production |
Release-ready code. All tools available. | All team tools |
mature |
Ongoing maintenance and incremental improvements. | All team tools |
Non-team tools (read_files, write_file, grep, etc.) are never gated by phase.
import {
getPhaseTools,
isToolAllowedInPhase,
getMinimumPhaseForTool,
canTransition,
transitionPhase,
} from '@levelcode/common/utils/dev-phases'
getPhaseTools('planning')
// ['task_create', 'task_update', 'task_get', 'task_list']
isToolAllowedInPhase('send_message', 'planning') // false
isToolAllowedInPhase('send_message', 'pre-alpha') // true
getMinimumPhaseForTool('spawn_agents') // 'alpha'
getMinimumPhaseForTool('read_files') // null (not a team tool)
canTransition('planning', 'pre-alpha') // true
canTransition('planning', 'alpha') // false (must go through pre-alpha)Tasks are the primary unit of work in a swarm. They are persisted as individual JSON files in ~/.config/levelcode/tasks/<team-name>/.
interface TeamTask {
id: string
subject: string // Brief imperative title
description: string // Detailed requirements
status: 'pending' | 'in_progress' | 'completed' | 'blocked'
owner?: string // Agent name that owns this task
blockedBy: string[] // Task IDs that must complete first
blocks: string[] // Task IDs waiting on this one
phase: DevPhase // Phase this task belongs to
activeForm?: string // Present continuous form ("Running tests")
createdAt: number
updatedAt: number
metadata?: Record<string, unknown> // Arbitrary key-value data
}- Create -- A coordinator or manager creates a task with
task_create - Assign -- The task is assigned to an agent (manually or via auto-assign)
- In Progress -- The owning agent marks it
in_progressand begins work - Complete -- The agent marks it
completed, which:- Updates the agent's status to
idle - Sends a
task_completedmessage to the team lead - Checks if any blocked tasks are now unblocked
- Updates the agent's status to
- Blocked -- If a task depends on unfinished work, it stays
blockeduntil allblockedBytasks complete
Tasks can declare dependencies via blockedBy:
Task #3 (blockedBy: [#1, #2])
|
+-- Task #1 (completed)
+-- Task #2 (in_progress) <-- still blocking
When Task #2 completes, the system automatically detects that Task #3 is now unblocked and makes it available for assignment.
When autoAssign is enabled in team settings, idle agents are automatically matched to available tasks:
- Find all pending tasks with no owner and no unresolved blockers
- Find all idle team members
- For each task (in ID order), find the first idle agent whose role seniority meets the task's requirements
- Assign the task and update both the task and agent status
Tasks can specify a seniority requirement via metadata.seniority:
| Seniority Tag | Minimum Role Level |
|---|---|
junior |
1 (junior-engineer+) |
mid |
4 (mid-level-engineer+) |
senior |
6 (senior-engineer+) |
Agents can also self-service claim tasks:
import { claimTask, releaseTask, completeTask } from '@levelcode/agent-runtime/task-assignment'
// Agent claims an available task
const result = claimTask('my-team', 'engineer-1', 'task-42')
// { success: true }
// Agent releases a task back to pending
releaseTask('my-team', 'task-42')
// Agent marks a task as completed
const { unblockedTaskIds } = completeTask('my-team', 'task-42')
// unblockedTaskIds: ['task-43', 'task-44'] -- tasks that were waiting on #42Agents communicate through a file-based inbox system. Each agent has an inbox file at ~/.config/levelcode/teams/<team>/inboxes/<agent-name>.json.
The team protocol supports 9 message types:
| Type | Direction | Purpose |
|---|---|---|
message |
Agent -> Agent | Direct message to a specific teammate |
broadcast |
Agent -> All | Same message delivered to every teammate |
idle_notification |
Agent -> Lead | Agent finished work and is available |
task_completed |
Agent -> Lead | Agent completed a task |
shutdown_request |
Lead -> Agent | Request an agent to shut down |
shutdown_approved |
Agent -> Lead | Agent accepts shutdown |
shutdown_rejected |
Agent -> Lead | Agent declines shutdown with reason |
plan_approval_request |
Agent -> Lead | Agent submits a plan for review |
plan_approval_response |
Lead -> Agent | Approve or reject a submitted plan |
Agents use the send_message tool with a type parameter:
Direct message:
{
"type": "message",
"recipient": "engineer-1",
"content": "Please refactor the auth module to use JWT tokens.",
"summary": "Refactor auth to JWT"
}Broadcast (use sparingly -- sends to every teammate):
{
"type": "broadcast",
"content": "Blocking issue found in CI pipeline. All work paused.",
"summary": "CI pipeline blocked"
}Shutdown request:
{
"type": "shutdown_request",
"recipient": "researcher-1",
"content": "Task complete, wrapping up the session"
}Shutdown response:
{
"type": "shutdown_response",
"request_id": "abc-123",
"approve": true
}Plan approval:
{
"type": "plan_approval_response",
"recipient": "manager-1",
"request_id": "plan-456",
"approve": false,
"content": "Please add error handling for the API calls"
}Agents poll their inbox file on a configurable interval (default 2 seconds):
import { InboxPoller } from '@levelcode/agent-runtime/inbox-poller'
const poller = new InboxPoller({
teamName: 'my-team',
agentName: 'engineer-1',
pollIntervalMs: 2000,
logger,
})
poller.start()
// Between agent turns, drain accumulated messages
const { messages, formattedContent } = poller.drain()
if (formattedContent) {
// Inject into the agent's message history
}
// On shutdown
poller.stop()For one-shot reads (no background polling):
import { drainInbox } from '@levelcode/agent-runtime/inbox-poller'
const { messages, formattedContent } = drainInbox({
teamName: 'my-team',
agentName: 'engineer-1',
logger,
})Swarm settings are stored in ~/.config/levelcode/settings.json:
{
"mode": "DEFAULT",
"adsEnabled": true,
"swarmEnabled": true
}| Variable | Description | Default |
|---|---|---|
LEVELCODE_ENABLE_SWARMS |
Set to 1 or true to enable swarm features |
false |
The environment variable takes precedence over the settings file. If LEVELCODE_ENABLE_SWARMS=1, swarms are enabled regardless of the swarmEnabled setting.
Each team stores its configuration at ~/.config/levelcode/teams/<name>/config.json:
{
"name": "my-project",
"description": "Backend API rewrite",
"createdAt": 1738886400000,
"leadAgentId": "lead-abc123",
"phase": "alpha",
"members": [
{
"agentId": "lead-abc123",
"name": "team-lead",
"role": "coordinator",
"agentType": "coordinator",
"model": "",
"joinedAt": 1738886400000,
"status": "active",
"cwd": "/home/user/project"
}
],
"settings": {
"maxMembers": 20,
"autoAssign": true
}
}| Setting | Type | Default | Description |
|---|---|---|---|
maxMembers |
number | 20 | Maximum number of agents in the team |
autoAssign |
boolean | true | Automatically assign idle agents to available tasks |
~/.config/levelcode/
settings.json # Global settings (swarmEnabled, mode)
teams/
<team-name>/
config.json # Team config (members, phase, settings)
inboxes/
<agent-name>.json # Per-agent message inbox
tasks/
<team-name>/
<task-id>.json # Individual task files
┌──────────────────────────────────────────────────────────────────┐
│ CLI Layer │
│ /team:* commands -> command-registry.ts │
│ Team panel TUI -> team-panel.tsx │
│ Zustand store -> team-store.ts (in-memory state + sync) │
└──────────────────────────────────────────────────────────────────┘
│
┌──────────────────────────────────────────────────────────────────┐
│ Agent Runtime Layer │
│ Tool handlers: team-create.ts, team-delete.ts, send-message.ts│
│ task-create.ts, task-get.ts, task-update.ts, │
│ task-list.ts, task-completed.ts │
│ Lifecycle: team-lifecycle.ts (registry, shutdown, idle) │
│ Context: team-context.ts (discover which team an agent │
│ belongs to) │
│ Messaging: inbox-poller.ts + message-formatter.ts │
│ Assignment: task-assignment.ts (auto-assign, claim, deps) │
│ Prompts: team-prompt.ts (inject team context into system │
│ prompt) │
└──────────────────────────────────────────────────────────────────┘
│
┌──────────────────────────────────────────────────────────────────┐
│ Common Layer │
│ Types: team-config.ts, team-protocol.ts, team-hook-events.ts│
│ Schemas: team-config-schemas.ts (Zod validation) │
│ Utils: team-fs.ts (CRUD for configs, tasks, inboxes) │
│ dev-phases.ts (phase ordering, tool gating) │
│ team-hook-emitter.ts (fire hook events) │
│ team-analytics.ts (track events) │
└──────────────────────────────────────────────────────────────────┘
│
┌──────────────────────────────────────────────────────────────────┐
│ Agent Templates │
│ agents/team/index.ts (registry of all 20 templates) │
│ agents/team/coordinator.ts (top-level orchestrator) │
│ agents/team/manager.ts (work stream coordinator) │
│ agents/team/senior-engineer.ts (complex implementations) │
│ agents/team/role-hierarchy.ts (authority levels, spawn rules) │
│ ... 16 more role templates │
└──────────────────────────────────────────────────────────────────┘
- Agent A calls the
send_messagetool withtype: "message"andrecipient: "agent-b" - The
send-message.tshandler discovers which team Agent A belongs to by scanning team configs - The handler validates the recipient exists in the team's member list
- The message is appended to Agent B's inbox file (
inboxes/agent-b.json) - Agent B's
InboxPollerreads and clears the inbox on its next poll cycle - The messages are formatted and injected into Agent B's next prompt turn
- A coordinator creates tasks with dependencies via
task_create - When auto-assign is enabled, the system periodically:
- Finds pending tasks with no owner and no unresolved blockers
- Finds idle agents (status =
idle, no in-progress tasks) - Matches agents to tasks by role seniority
- When an agent completes a task:
- The task status is set to
completed - The agent status is set to
idle - A
task_completedmessage is sent to the team lead - All tasks listing this task in their
blockedByare checked -- if all their blockers are now complete, they become available
- The task status is set to
The team-lifecycle.ts module maintains an in-memory registry of active agents:
- registerAgent -- Called after an agent is spawned. Stores the agent's
AbortControllerfor graceful shutdown. - markAgentWorking -- Called when an agent picks up a task. Updates both registry and team config.
- markAgentIdle -- Called when an agent has no more work. Fires
teammate_idlehook, sends idle notification to lead. - markAgentBlocked -- Called when an agent is waiting on a dependency.
- markAgentFailed -- Called on unrecoverable error. Aborts the controller, notifies the lead, unregisters.
- approveShutdown -- Aborts the agent, removes it from the team, sends confirmation.
- shutdownAllAgents -- Force-stops all agents in a team (used when deleting a team).
All data read from disk is validated with Zod schemas before use:
teamConfigSchema-- validates team config structureteamTaskSchema-- validates individual task filesteamProtocolMessageSchema-- validates inbox messages (discriminated union ontype)
Corrupted files produce clear error messages identifying the file and validation failure.
Multiple agents may read and write the same files concurrently. The file-lock.ts utility provides advisory locking via withLock() to prevent data corruption:
import { withLock } from '@levelcode/common/utils/file-lock'
await withLock(configPath, () => {
const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
config.members.push(newMember)
fs.writeFileSync(configPath, JSON.stringify(config, null, 2))
})Critical operations in team-fs.ts (saveTeamConfig, addTeamMember, removeTeamMember, createTask, updateTask, sendMessage) all use file locking.
team-config.ts
type TeamRole =
| 'coordinator' | 'cto' | 'vp-engineering' | 'director'
| 'fellow' | 'distinguished-engineer' | 'principal-engineer'
| 'senior-staff-engineer' | 'staff-engineer' | 'manager'
| 'sub-manager' | 'senior-engineer' | 'super-senior'
| 'mid-level-engineer' | 'junior-engineer' | 'researcher'
| 'scientist' | 'designer' | 'product-lead' | 'tester'
| 'reviewer' | 'intern' | 'apprentice'
type DevPhase =
| 'planning' | 'pre-alpha' | 'alpha'
| 'beta' | 'production' | 'mature'
type AgentStatus =
| 'active' | 'idle' | 'working'
| 'blocked' | 'completed' | 'failed'
interface TeamMember {
agentId: string
name: string
role: TeamRole
agentType: string
model: string
joinedAt: number
status: AgentStatus
currentTaskId?: string
cwd: string
}
interface TeamConfig {
name: string
description: string
createdAt: number
leadAgentId: string
phase: DevPhase
members: TeamMember[]
settings: {
maxMembers: number
autoAssign: boolean
}
}
interface TeamTask {
id: string
subject: string
description: string
status: 'pending' | 'in_progress' | 'completed' | 'blocked'
owner?: string
blockedBy: string[]
blocks: string[]
phase: DevPhase
activeForm?: string
createdAt: number
updatedAt: number
metadata?: Record<string, unknown>
}| Function | Description |
|---|---|
createTeam(config) |
Create a new team directory and config file |
loadTeamConfig(teamName) |
Load and validate team config from disk |
saveTeamConfig(teamName, config) |
Write team config to disk (with file locking) |
deleteTeam(teamName) |
Remove team directory and task files |
addTeamMember(teamName, member) |
Add a member to the team config |
removeTeamMember(teamName, agentId) |
Remove a member from the team config |
createTask(teamName, task) |
Write a new task file |
updateTask(teamName, taskId, updates) |
Partially update a task file |
listTasks(teamName) |
List all tasks for a team |
getTask(teamName, taskId) |
Read a single task file |
sendMessage(teamName, to, message) |
Append a message to an agent's inbox |
readInbox(teamName, agentName) |
Read all messages from an agent's inbox |
clearInbox(teamName, agentName) |
Clear an agent's inbox |
| Function | Description |
|---|---|
getPhaseOrder(phase) |
Get numeric index (0-5) for a phase |
canTransition(current, target) |
Check if a phase transition is valid |
transitionPhase(config, target) |
Return a new config with the updated phase |
getPhaseTools(phase) |
Get team tool names available in a phase |
isToolAllowedInPhase(tool, phase) |
Check if a tool is allowed in a phase |
getMinimumPhaseForTool(tool) |
Get the earliest phase a tool is available |
| Function | Description |
|---|---|
getRoleLevel(role) |
Get numeric authority level (0-13) |
canManage(manager, subordinate) |
Check if a role has authority over another |
getSpawnableRoles(role) |
Get the list of roles a given role can spawn |
| Function | Description |
|---|---|
registerAgent(team, id, name, abort) |
Register a spawned agent |
unregisterAgent(team, id) |
Remove an agent from the registry |
getActiveAgents(team) |
List all registered agents for a team |
isAgentActive(team, id) |
Check if an agent is registered and active |
updateAgentStatus(team, id, status) |
Update status in registry and config |
markAgentIdle(params) |
Mark idle, fire hook, notify lead |
markAgentWorking(params) |
Mark working, set current task |
markAgentBlocked(params) |
Mark agent as blocked |
markAgentFailed(params) |
Mark failed, abort, notify lead |
approveShutdown(params) |
Gracefully shut down an agent |
rejectShutdown(params) |
Reject a shutdown request |
shutdownAllAgents(team) |
Force-stop all agents in a team |
| Function | Description |
|---|---|
isTaskBlocked(team, taskId) |
Check if a task has unresolved blockers |
getUnblockedTasks(team) |
Get pending tasks with all blockers resolved |
findAvailableTasks(team) |
Get unblocked tasks with no owner |
findIdleAgents(team) |
Get idle members with no in-progress tasks |
isAgentSuitableForTask(agent, task) |
Check role seniority vs task requirements |
autoAssignTasks(team) |
Match idle agents to available tasks |
claimTask(team, agent, taskId) |
Agent self-service task claiming |
releaseTask(team, taskId) |
Release a task back to pending |
completeTask(team, taskId) |
Complete a task and unblock dependents |
| Event Type | Fields | Fired When |
|---|---|---|
teammate_idle |
agentName, teamName, lastTaskId | Agent finishes work and has nothing to do |
task_completed |
taskId, taskSubject, owner, teamName | A task is marked as completed |
phase_transition |
teamName, fromPhase, toPhase | The team's dev phase changes |
For creating custom standalone agents, see Custom Agents. For general architecture, see Architecture.