Skip to content

Squads

A squad is a group of LLM agents that share one workspace directory, a memory bank, and a task list, and that run a planner-produced plan together. The Management API exposes the whole surface: 95 endpoints under /squads, /squad-discussions, /squad-templates, and /squad-registry, including a cross-squad and a per-squad Server-Sent Events stream, plus a per-execution event ledger.

Every endpoint on this page is reachable at the Management API base URL, http://127.0.0.1:8001/api/v1 by default, and appears in Swagger UI at /api/docs under the Squads, Squad Sessions, Squad Templates, and Squad Discussions tags. The aigo squad command group wraps the same endpoints; the flags are in the CLI reference.

How this surface behaves headless

Two things are worth knowing before you build against this surface.

Running a plan works the same headless. POST /squads/{id}/execute on a headless aigo-server validates the request, resolves the planner agent, runs the planner decomposition, stores the plan it produced, and, when autoApprove is set, starts the executor. POST /squads/{id}/executions/{eid}/approve starts the executor for a plan that was waiting. The desktop Tauri commands and these endpoints are thin wrappers over the same two shared services, so a run reaches the same states in the same order on both, and the intervention endpoints below act on a run that is actually moving: pause and resume hold and release it between task dispatches, steer is read by the executor before the next dispatch, and skip and retry address real tasks. Before issue #4954 this endpoint recorded an execution with an empty plan and started nothing, so on a headless server those controls acted on a run that never progressed. One difference remains, and it is a policy one rather than a capability one: a task's tool calls go through the same gates as POST /tools/execute, so a tool that needs the desktop app is refused with the reason the tool catalog gives instead of running.

Squads are available as MCP tools. Issue #4581 moved the squad tools' state access behind squad::runtime_handles, which both the desktop app and aigo-server install, so the MCP endpoint advertises them and POST /tools/execute accepts them in either mode. Thirteen tools are exposed: list_squads, list_squad_templates, create_squad, get_squad, list_squad_tasks, get_squad_execution, list_squad_executions, submit_squad_request, approve_squad_plan, reject_squad_plan, cancel_squad_execution, steer_squad_execution, and send_squad_agent_message. submit_squad_request and approve_squad_plan reach the same shared services the endpoints above do, so a request submitted through a tool is planned and an approved plan runs, on either runtime. Both report what happened rather than leaving the caller to assume it: submit_squad_request returns plannerStarted, and approve_squad_plan returns executorStarted. plannerStarted answers whether a planner actually decomposed the request, not whether the runtime called the submission path: run_planner_decomposition degrades to a one-task-per-agent split when the router is down, the planner agent has no usable model, or the planner reply creates no task, and a submission that landed on that split reports plannerStarted: false with plannerDegradedReason naming the cause (issue #4966). The reason stays on the execution, so get_squad_execution carries it on every later poll as well.

Authentication and scopes

Requests authenticate the same way as the rest of the Management API: an X-API-Key header, an Authorization: Bearer token, or the aigo_session cookie obtained from POST /api/v1/auth/login.

Access keys carry scopes, and the squad surface uses two:

  • agent_read for every GET.
  • agent_write for every POST, PUT, PATCH, and DELETE.

Two mutating routes are deliberately agent_read, because neither changes stored state: POST /squads/workspace/validate (it inspects a candidate directory path) and POST /squad-discussions/{id}/export (it renders a transcript). The authoritative table is ROUTE_MANIFEST in src-tauri/crates/aigo-rest/src/route_scope.rs; the scope column in the tables below is copied from it.

In a managed install, an administrator can hide the Squad page with the /squad page id in features.hiddenPages. That gate covers the /squads, /squad-discussions, /squad-templates, and /squad-registry route prefixes and the matching Tauri commands, so hiding the page also refuses the API, not just the UI. POST /squad-registry/install is in that list because it writes into the squad template store, and because leaving it out would have refused the operation over IPC while allowing it over HTTP.

Resource model

Squad

A squad is the top-level record: a name, a description, an agent lineup, an optional planner agent, a workspace path, and a status. Its id is a UUID v4. Squads are stored as <app_data_dir>/squads/<squad-uuid>.json with an index.json beside them for fast listing, and every write is an atomic write-to-temp-then-rename.

Agent

An agent is one member of the lineup: a name, a role (planner, developer, reviewer, writer, or a custom label), a model, a system prompt, instructions, a tool configuration, and an execution mode (in-process or container). An agent id is a UUID v4 the server generates when the request does not supply one, so an agent created from a template gets its id at creation time. plannerAgentId on the squad names the agent that plans and aggregates; the API refuses to remove that agent while it holds the role.

Workspace

The workspace is a directory on disk that the squad's agents share. POST /squads/{id}/workspace/init creates it with a fixed layout:

{workspace_path}/
|-- .squad.json        # squad metadata and full config, so the directory is portable
|-- plans/             # planner-generated task plans
|-- tasks/             # per-task tracking files
|-- memory/            # per-agent memory bank files
|-- artifacts/         # output files and deliverables
|-- logs/              # execution logs and event ledgers
|-- sessions/          # persisted agent conversation history

Path traversal is rejected, symlinks are not followed when archiving or walking, and cleanup verifies that .squad.json is present before it deletes anything.

Task

A managed task has a title, a description, a priority, a status, an optional assignee, dependencies on other tasks, and a retry budget. Statuses are pending, ready, assigned, in_progress, review, done, failed, and cancelled. Tasks form a dependency graph that GET /squads/{id}/tasks/graph returns and that the planner uses to build execution waves.

Plan and execution

An execution is one run of one request. Submitting a request produces an execution id; the planner turns the request into a plan of tasks grouped into waves; the plan waits for approval unless the request asked for auto-approval; and the executor then runs one wave at a time. An execution carries a status, the current wave, per-task attempt counts, the tasks an operator skipped, and the operator instructions the run has received.

Agent session

Separate from plan execution, each agent can hold a 1:1 chat session. A session has its own conversation history persisted under sessions/, and a squad agent can have several stored sessions with one of them current.

Memory bank

Each agent has a Markdown memory file under memory/, divided into named sections. Writes append to a section by default and replace it when asked. GET /squads/{id}/memory/search searches across the squad's agents, with optional agent and section filters.

Discussion room

A discussion room is a moderated multi-agent conversation attached to a squad: agents take turns speaking under a strategy and a turn budget, users can enqueue messages between turns, and the room can be concluded into a structured summary and handed off to a squad execution.

Template

A squad template is a reusable lineup: agent definitions with roles, models, and prompts, without a workspace. Creating a squad from a template copies the lineup and selects the template's planner-role agent as the planner. Templates can be imported from a file, exported, saved from an existing squad, or installed from a shared registry catalog.

Budget

A budget caps a squad's token and cost consumption, with a warning threshold below the hard limit. The configuration is stored and readable, and GET /squads/{id}/budget/usage reports the active run's consumption against it. The two budget events below are declared but nothing emits them today, so do not build an alert on them; poll the usage endpoint instead.

Endpoints

Paths below omit the /api/v1 prefix. {id} is the squad id except under /squad-discussions, where it is the discussion room id.

Squad lifecycle

Method Path Scope Body or query Returns
GET /squads agent_read - SquadIndexEntry[]
POST /squads agent_write CreateSquadRequest Squad (201)
GET /squads/{id} agent_read - Squad
PUT /squads/{id} agent_write UpdateSquadRequest Squad
DELETE /squads/{id} agent_write ?keepWorkspace= (default true) 204
POST /squads/restore agent_write RestoreSquadRequest RestoreSquadPreview

DELETE /squads/{id} keeps the workspace directory by default; pass ?keepWorkspace=false to remove it as well. POST /squads/restore reads a workspace's .squad.json and returns a preview of the squad it would restore, so a caller can confirm before committing.

Workspace

Method Path Scope Body or query Returns
POST /squads/{id}/workspace/init agent_write InitWorkspaceRequest WorkspaceInfo
GET /squads/{id}/workspace/status agent_read - WorkspaceStatus
GET /squads/{id}/readiness agent_read - SquadReadiness
DELETE /squads/{id}/workspace agent_write ?archive= (default false) CleanupResult
POST /squads/workspace/validate agent_read ValidatePathRequest ValidationResult

GET /squads/{id}/readiness is the pre-flight model check, not a workspace check. available is true when the squad has at least one agent and every agent resolves a model; agents carries the same verdict per agent in squad order, and cause plus message say what is blocking. routerHealthy reports whether the inference router answered its health probe and is advisory: a false there does not lower available. Workspace existence and structure come from GET /squads/{id}/workspace/status instead.

Tasks

Method Path Scope Body or query Returns
GET /squads/tasks/summary agent_read - SquadTaskSummary[]
POST /squads/{id}/tasks agent_write CreateTaskRequest ManagedTask (201)
GET /squads/{id}/tasks agent_read ?status= ManagedTask[]
GET /squads/{id}/tasks/graph agent_read - TaskGraph
GET /squads/{id}/tasks/{task_id} agent_read - ManagedTask
PATCH /squads/{id}/tasks/{task_id} agent_write UpdateTaskRequest ManagedTask
DELETE /squads/{id}/tasks/{task_id} agent_write ?force= (default false) 204
POST /squads/{id}/tasks/{task_id}/retry agent_write RetryTaskRequest (optional) ManagedTask
POST /squads/{id}/tasks/{task_id}/reassign agent_write ReassignTaskRequest (optional) ManagedTask
PATCH /squads/{id}/tasks/{task_id}/status agent_write UpdateTaskStatusRequest ManagedTask

An edit is refused when the task is too far along: a running task accepts only description, priority, and maxRetries, and a finished one accepts nothing. Both refusals answer 409 with SQUAD_TASK_ACTIVE and SQUAD_TASK_TERMINAL respectively. DELETE on a running task needs ?force=true, which cancels it first; tasks that depended on the deleted one lose the dependency rather than becoming unsatisfiable. retry re-queues a failed or cancelled task and grants one extra attempt when force is set and the retry budget is spent.

Memory bank

Method Path Scope Body or query Returns
POST /squads/{id}/memory/init agent_write - 204
GET /squads/{id}/memory/search agent_read ?q=, ?agentFilter=, ?sectionFilter=, ?caseSensitive=, ?limit= MemorySearchResult[]
GET /squads/{id}/memory/{agent_id} agent_read - MemoryContent
POST /squads/{id}/memory/{agent_id} agent_write WriteMemoryRequest 204
GET /squads/{id}/memory/{agent_id}/sections agent_read - string[]

Agents

Method Path Scope Body or query Returns
GET /squads/{id}/agents agent_read - AgentConfig[]
POST /squads/{id}/agents agent_write AddSquadAgentRequest AgentConfig (201)
GET /squads/{id}/agents/{agent_id} agent_read - AgentConfig
PATCH /squads/{id}/agents/{agent_id} agent_write UpdateSquadAgentRequest UpdateSquadAgentResponse
DELETE /squads/{id}/agents/{agent_id} agent_write - 204
POST /squads/{id}/agents/bulk-update-model agent_write BulkUpdateAgentModelRequest BulkUpdateAgentModelResult

Removing an agent is refused with 409 in two cases: SQUAD_AGENT_IS_PLANNER when the agent is the squad's planner (clear plannerAgentId first), and SQUAD_AGENT_BUSY when the agent is mid-flight in a chat session or a running execution. Every add, update, and remove raises squad:agents-changed, so a Squad page left open picks up an edit made from the CLI or the API without a reload.

PATCH replaces whole objects rather than merging them: sending modelPreferences replaces the whole preference record, and sending toolConfig replaces the whole tool configuration. aigo squad agent set reads the agent first and merges for those two fields; a direct API caller has to do the same. settingsOverrides and containerConfig accept an explicit null to clear them, while omitting them leaves them alone.

Agent sessions and 1:1 chat

Method Path Scope Body or query Returns
POST /squads/{id}/agents/{agent_id}/session agent_write - SessionInfo (201)
DELETE /squads/{id}/agents/{agent_id}/session agent_write - 204
GET /squads/{id}/agents/{agent_id}/status agent_read - AgentSessionStatus
POST /squads/{id}/agents/{agent_id}/message agent_write SendMessageRequest SendAgentMessageResponse
PUT /squads/{id}/agents/{agent_id}/response agent_write RecordResponseRequest 200
GET /squads/{id}/agents/{agent_id}/conversation agent_read - PersistedSession \| null
GET /squads/{id}/agents/{agent_id}/sessions agent_read - SessionIndexEntry[]
POST /squads/{id}/agents/{agent_id}/sessions agent_write - new session id (201)
GET /squads/{id}/agents/{agent_id}/sessions/{session_id} agent_read - PersistedSession \| null
DELETE /squads/{id}/agents/{agent_id}/sessions/{session_id} agent_write - 204
GET /squads/{id}/agents/{agent_id}/chat-system-prompt agent_read ?maxTokens= AgentChatSystemPrompt

Execution and its controls

Method Path Scope Body or query Returns
POST /squads/{id}/execute agent_write SubmitExecutionRequest { executionId } (201)
GET /squads/{id}/executions/{eid} agent_read - SquadExecution
POST /squads/{id}/executions/{eid}/approve agent_write ApprovePlanRequest (optional) 200
POST /squads/{id}/executions/{eid}/reject agent_write RejectPlanRequest 200
DELETE /squads/{id}/executions/{eid} agent_write - 200
POST /squads/{id}/executions/{eid}/pause agent_write - SquadExecution
POST /squads/{id}/executions/{eid}/resume agent_write - SquadExecution
POST /squads/{id}/executions/{eid}/steer agent_write SteerExecutionRequest SteerMessage
POST /squads/{id}/executions/{eid}/tasks/{task_id}/skip agent_write - SquadExecution
POST /squads/{id}/executions/{eid}/tasks/{task_id}/retry agent_write - SquadExecution

Every control acts between tasks. A task already running is never interrupted: pausing lets it finish and holds the run before the next dispatch, and skipping applies only to a task that has not started. The refusals are 409 with SQUAD_EXECUTION_NOT_PAUSABLE, SQUAD_EXECUTION_NOT_RUNNING, SQUAD_EXECUTION_NOT_STEERABLE, SQUAD_EXECUTION_TASK_NOT_SKIPPABLE, and SQUAD_EXECUTION_TASK_NOT_RETRIABLE.

approve optionally carries a plan override in planOverride. An override is a replacement, not a patch: a stored task the override does not name is removed, a task carrying an id the stored plan holds keeps that identity and its recorded status, a task carrying an id the stored plan does not hold is added under that id, and one with no id is added under a generated one.

Choosing the id is what lets one override introduce several tasks and order them, because dependsOn is resolved against the ids of the resulting plan rather than against the stored one. A chosen id may hold only ASCII letters, digits, and hyphens, and at most 128 bytes: it travels back as a URL path segment on the task skip and retry endpoints, and when the run starts the squad's board writes the task as <id>.json and <id>.md in the workspace's tasks directory. A few names are therefore refused outright: index, because the board keeps its own index.json in that directory, and the Windows device names (CON, PRN, AUX, NUL, COM1 to COM9, LPT1 to LPT9). No two tasks of the resulting plan may share an id, or differ only by case, because macOS and Windows would make those one file; naming a stored id is not a collision, it is the instruction to keep that task. Assignees must be agents of the squad, every dependsOn entry must name a task the override itself leaves in the plan, and a cycle, including a task naming itself, is rejected before the run starts.

A chosen id must also be free on the squad's board. The board is per squad rather than per run: it lives in the workspace and outlives every execution, so an id an earlier run of the same squad already put there is refused with a message naming the id and the plan holding it, plus that plan's run when the server still has it in memory. Reusing a memorable id such as draft across two runs of one squad is therefore an error you see at approve time, rather than a silent overwrite of the earlier run's recorded status, result and error. An id the approving run's own plan already holds on the board is not a collision: that is the same plan re-syncing, which is what a run recovered after a restart does. A row created directly on the board with POST /squads/{id}/tasks and no planId belongs to no run and cannot be claimed either; delete it first if you want the id.

A steer instruction is standing by default and is appended to every later task turn. The body is { "message": "...", "scope": {...} }, and scope is internally tagged: omit it for the whole run, or send {"type": "task", "taskId": "..."} or {"type": "agent", "agentId": "..."} to narrow it. The request rejects unknown fields, so a bare top-level taskId answers 400.

Read How this surface behaves headless before building an operator flow on these.

Workspace files

Method Path Scope Body or query Returns
GET /squads/{id}/workspace/files agent_read ?path= FileEntry[]
GET /squads/{id}/workspace/files/content agent_read ?path= FileContent
GET /squads/{id}/workspace/search agent_read ?q= WorkspaceSearchResult[]

?path= is workspace-relative. A .. component is rejected, and the resolved path is then canonicalized and required to stay inside the workspace root, so a symlink pointing outside it is refused.

Activity log

Method Path Scope Body or query Returns
GET /squads/{id}/activity-log agent_read ?limit=, ?offset= ActivityLogResponse
GET /squads/{id}/activity-log/load agent_read ?limit=, ?offset= ActivityLogResponse

GET /squads/{id}/activity-log reads the in-memory ring buffer, which is empty after a server restart. GET /squads/{id}/activity-log/load first reloads logs/events.jsonl from the workspace, so it is the one to use for a squad the server has not been running.

History and the event ledger

Method Path Scope Body or query Returns
GET /squads/{id}/history agent_read ?limit=, ?offset= ExecutionRecord[]
GET /squads/{id}/history/{eid} agent_read - ExecutionRecord
GET /squads/{id}/history/{eid}/logs agent_read ?agentId=, ?minLevel=, ?limit= LogEntry[]
GET /squads/{id}/history/{eid}/events agent_read ?eventTypes=, ?afterSeq=, ?limit= ExecutionEventPage
POST /squads/{id}/history/{eid}/report agent_write - Report file path

GET /squads/{id}/history/{eid}/events reads the per-execution typed ledger, logs/{execution-id}.events.jsonl. One line per squad:* event that named the execution, in emission order, each carrying a 1-based seq, so a reader can resume with ?afterSeq=. ?eventTypes= takes a comma-separated list of event names. A run is capped at a maximum number of recorded events and a maximum byte size, whichever comes first; on reaching either cap the writer appends one truncation record naming which cap it hit, so a truncated ledger says so instead of ending mid-run.

The squad-wide logs/events.jsonl behind the activity log is a different file: it holds only the most recent events across all runs and is not keyed by execution.

Analytics, budget, emergency stop, event stream

Method Path Scope Body or query Returns
GET /squads/{id}/analytics agent_read ?period= SquadAnalytics
GET /squads/{id}/budget agent_read - BudgetConfig
PUT /squads/{id}/budget agent_write BudgetConfig 200
GET /squads/{id}/budget/usage agent_read - BudgetUsage
POST /squads/{id}/emergency-stop agent_write - 200
GET /squads/{id}/events agent_read ?types= text/event-stream

GET /squads/{id}/events is a Server-Sent Events stream of the squad's live events. ?types= filters it to a comma-separated list of event names, for example ?types=squad:task-completed,squad:execution-failed. A name outside the squad: family is rejected with 400 naming it, rather than opening a connection that never delivers anything. POST /squads/{id}/emergency-stop cancels every non-terminal execution for the squad and raises squad:emergency-stopped.

Templates

Method Path Scope Body or query Returns
GET /squad-templates agent_read - SquadTemplate[]
GET /squad-templates/{id} agent_read - SquadTemplate
POST /squad-templates/import agent_write ImportTemplateRequest SquadTemplate (201)
GET /squad-templates/{id}/export agent_read - ExportTemplateResponse
DELETE /squad-templates/{id} agent_write - 204
POST /squads/{id}/save-as-template agent_write SaveAsTemplateRequest SquadTemplate (201)
POST /squad-registry/install agent_write InstallSquadTemplateRequest InstallSquadTemplateResult

Discussion rooms

Method Path Scope Body or query Returns
POST /squad-discussions agent_write CreateDiscussionRequest DiscussionRoom (201)
GET /squad-discussions/{id} agent_read - DiscussionRoom
DELETE /squad-discussions/{id} agent_write - 204
GET /squads/{id}/discussions agent_read ?limit=, ?offset= DiscussionSummary[]
GET /squads/{id}/discussions/completed agent_read ?limit=, ?offset= DiscussionSummary[]
POST /squad-discussions/{id}/start agent_write - 200
POST /squad-discussions/{id}/pause agent_write - 200
POST /squad-discussions/{id}/resume agent_write - 200
POST /squad-discussions/{id}/stop agent_write - 200
POST /squad-discussions/{id}/messages agent_write PostDiscussionMessageRequest PostDiscussionMessageResponse
DELETE /squad-discussions/{id}/messages/{message_id} agent_write - { queueLength }
PUT /squad-discussions/{id}/mode agent_write SetDiscussionModeRequest 204
PUT /squad-discussions/{id}/turn-budget agent_write SetTurnBudgetRequest 204
PUT /squad-discussions/{id}/strategy agent_write SetDiscussionStrategyRequest 204
POST /squad-discussions/{id}/conclusion agent_write SynthesizeDiscussionConclusionRequest (optional) DiscussionConclusion
POST /squad-discussions/{id}/handoff agent_write - DiscussionHandoffRequest
POST /squad-discussions/{id}/export agent_read ExportDiscussionTranscriptRequest (optional) ExportDiscussionTranscriptResponse
GET /squad-discussions/{id}/analytics agent_read - DiscussionAnalytics

GET /squads/{id}/discussions returns lightweight summaries without transcripts; fetch the full room with GET /squad-discussions/{id} when a user opens one. Both listings page with ?limit= (default 50, capped at 100) and ?offset=.

Event stream

Method Path Scope Body or query Returns
GET /squads/events agent_read ?types= text/event-stream
GET /squads/ws agent_read ?types=, ?since= WebSocket (101)
GET /squads/{id}/ws agent_read ?types=, ?since= WebSocket (101)

/squads/events and /squads/ws are registered before /squads/{id}, so their last segments are read as literals rather than squad ids. /squads/ws is the WebSocket twin of the cross-squad SSE stream. /squads/{id}/ws is the WebSocket twin of the per-squad SSE stream, and unlike it supports replay. See Events and Event Streams.

Events

Squad events reach a client through the cross-squad SSE or WebSocket stream, the per-squad SSE or WebSocket stream, and, for events that named an execution, the durable ledger at GET /squads/{id}/history/{eid}/events. The same names are emitted as Tauri events in the desktop app, so a payload is identical on both transports. Payload fields are camelCase.

GET /squads/events and GET /squads/ws carry every squad:* name from every squad, including the squad:discussion_* family, and require agent_read, the same scope as GET /squads. Use them when the client does not know the squad ids yet, or watches several at once; use a per-squad stream when it does. None carries anything outside squad:*, and GET /events remains the cross-domain stream requiring admin. ?types= narrows to a comma-separated subset, and a name outside the squad: family is refused with 400 rather than accepted into a connection that silently delivers nothing. Both WebSocket routes refuse it before the socket exists. The cross-squad pair and per-squad WebSocket honor a resume cursor and report a truncated replay with stream:gap and a lagging consumer with stream:lagged; replayed events pass the same domain filter the live stream applies. See Event Streams.

This stream works on the desktop app's own embedded Management API too. The squad emitter is repointed at that server when it starts (issue #4889), so a turn started from the desktop UI is visible on SSE. /schedules/events, /memory/events, and /data/events do the same since issue #5040, which replaced the webview-only emitter each of those subsystems built for itself with one shared sink the embedded server attaches to.

Execution-scoped payloads carry executionId and task-scoped ones carry taskId, so a client can correlate a stream event with a run without tracking state of its own.

Execution lifecycle

Event Payload Emitted when
squad:planning-started PlanningStartedPayload The planner begins analyzing a request.
squad:plan-ready PlanReadyPayload A plan is ready for approval.
squad:execution-started ExecutionStartedPayload Execution starts, after approval or auto-approval.
squad:task-wave-started TaskWaveStartedPayload A new wave of parallel tasks begins.
squad:task-completed TaskCompletedPayload A task finishes, successfully or not.
squad:aggregation-started AggregationStartedPayload The planner starts aggregating results.
squad:execution-completed ExecutionCompletedPayload The run finishes successfully.
squad:execution-failed ExecutionFailedPayload The run fails.

Execution controls

Event Payload Emitted when
squad:execution-paused ExecutionPausedPayload An operator pauses a running execution.
squad:execution-resumed ExecutionResumedPayload An operator resumes a paused execution.
squad:execution-steered ExecutionSteeredPayload An operator instruction is accepted.
squad:task-skipped TaskSkippedPayload An operator skips a task that has not started.

Agent sessions

Event Payload Emitted when
squad:agent-session-started AgentSessionStartedPayload An agent session starts.
squad:agent-state-changed AgentStateChangedPayload An agent's state changes.
squad:agent-stream-chunk AgentStreamChunkPayload A streaming token chunk arrives from an agent.
squad:agent-stream-completed AgentStreamCompletedPayload Streaming from an agent completes.
squad:agent-error AgentErrorPayload An agent hits an error.
squad:agent-tool-call AgentToolCallPayload An agent is about to run a tool, in a 1:1 chat turn or in a plan execution task. Execution-path payloads carry executionId and taskId; chat-turn payloads omit both. On the execution path each string inside toolCall.arguments is bounded (4,000 characters, with a truncation marker) and the serialized whole is capped at 8,000; the tool received the arguments unmodified.
squad:agent-tool-result AgentToolResultPayload A tool an agent called returns. On the execution path toolResult.output is a bounded copy (4,000 characters, with a truncation marker); the agent still received the full output.

Squad configuration

Event Payload Emitted when
squad:agents-changed SquadAgentsChangedPayload An agent is added, updated, or removed. change is added, updated, or removed.

Task lifecycle

Event Payload Emitted when
squad:task-created TaskCreatedPayload A task is created.
squad:task-status-changed TaskStatusChangedPayload A task's status changes.
squad:task-assigned TaskAssignedPayload A task is assigned to an agent.
squad:task-failed TaskFailedPayload A task fails.
squad:task-updated TaskUpdatedPayload A task's editable fields change. Carries the whole task, because one edit can move several fields.
squad:task-deleted TaskDeletedPayload A task is deleted.

Resources and state

Event Payload Emitted when
squad:token-usage-update TokenUsageUpdatePayload Token usage changes (debounced).
squad:execution-token-usage ExecutionTokenUsagePayload After each LLM round trip during plan execution, carrying the run's cumulative usage.
squad:memory-updated MemoryUpdatedPayload An agent's memory is written.
squad:workspace-file-changed WorkspaceFileChangedPayload A workspace file changes (debounced).

Budget and safety

Event Payload Emitted when
squad:budget-warning BudgetWarningPayload Declared for the warning threshold, and not emitted by anything today.
squad:budget-exceeded BudgetExceededPayload Declared for a budget limit being exceeded, and not emitted by anything today.
squad:emergency-stopped EmergencyStoppedPayload The emergency stop is triggered.

Container output

Event Payload Emitted when
squad:container-output ContainerOutputPayload A structured output block is parsed from container stdout.
squad:container-log ContainerLogPayload A non-marker debug line arrives on container stdout.
squad:container-status ContainerStatusPayload A container's execution status changes.

Discussion rooms

Discussion events use underscores after the prefix, unlike the hyphenated names above.

Event Payload Emitted when
squad:discussion_message DiscussionMessagePayload A message is appended to the shared log.
squad:discussion_turn_started DiscussionTurnStartedPayload An agent turn begins.
squad:discussion_turn_ended DiscussionTurnEndedPayload An agent turn finishes, successfully or with an error.
squad:discussion_status_changed DiscussionStatusChangedPayload The orchestrator status transitions.
squad:discussion_queue_changed DiscussionQueueChangedPayload The pending user-message queue changes (push, drain, cancel).
squad:discussion_conclusion_synthesized DiscussionConclusionSynthesizedPayload A conclusion is synthesized.
squad:discussion_turn_delta DiscussionTurnDeltaPayload A streaming content chunk arrives during a turn. A live preview only; the committed squad:discussion_message is the source of truth for the transcript.
squad:discussion_turn_tool_call DiscussionTurnToolCallPayload The current speaker is about to run a tool.
squad:discussion_turn_tool_result DiscussionTurnToolResultPayload A tool the current speaker called returns.

Walkthroughs

The examples use an access key in X-API-Key. Drop the header if the server runs on loopback with Require API key off.

Create a squad from a template

BASE=http://127.0.0.1:8001/api/v1
KEY=<your-access-key>

# Pick a template
curl -s -H "X-API-Key: $KEY" "$BASE/squad-templates" | jq -r '.[] | "\(.id)\t\(.name)"'

# Create the squad from it
SQUAD=$(curl -s -X POST "$BASE/squads" \
  -H "X-API-Key: $KEY" -H 'Content-Type: application/json' \
  -d '{"name":"docs-team","workspacePath":"/tmp/docs-team","templateId":"builtin-fullstack-dev-team"}' \
  | jq -r .id)

# Create the workspace directory
curl -s -X POST "$BASE/squads/$SQUAD/workspace/init" \
  -H "X-API-Key: $KEY" -H 'Content-Type: application/json' \
  -d '{"path":"/tmp/docs-team"}' | jq .

Add an agent

AGENT=$(curl -s -X POST "$BASE/squads/$SQUAD/agents" \
  -H "X-API-Key: $KEY" -H 'Content-Type: application/json' \
  -d '{"name":"editor","role":{"type":"reviewer"},"modelPreferences":{"preferredModelId":"qwen3-8b"}}' \
  | jq -r .id)

curl -s -H "X-API-Key: $KEY" "$BASE/squads/$SQUAD/agents" \
  | jq -r '.[] | "\(.id)\t\(.name)\t\(.role.type)"'

Submit a request and approve the plan

EXEC=$(curl -s -X POST "$BASE/squads/$SQUAD/execute" \
  -H "X-API-Key: $KEY" -H 'Content-Type: application/json' \
  -d '{"request":"Draft the release notes for 1.13"}' \
  | jq -r .executionId)

# Read the plan once the planner has produced it
curl -s -H "X-API-Key: $KEY" "$BASE/squads/$SQUAD/executions/$EXEC" | jq '.plan.tasks'

# Approve it as planned
curl -s -X POST "$BASE/squads/$SQUAD/executions/$EXEC/approve" -H "X-API-Key: $KEY"

# Or approve with a replacement task list
curl -s -X POST "$BASE/squads/$SQUAD/executions/$EXEC/approve" \
  -H "X-API-Key: $KEY" -H 'Content-Type: application/json' \
  -d @plan-override.json

The same call does the same work on a headless aigo-server: the planner runs, the plan is stored, and autoApprove starts the executor. See How this surface behaves headless.

Watch events

# Everything for this squad
curl -N -H "X-API-Key: $KEY" "$BASE/squads/$SQUAD/events"

# Only the two that matter for a progress bar
curl -N -H "X-API-Key: $KEY" \
  "$BASE/squads/$SQUAD/events?types=squad:task-completed,squad:execution-completed"

Steer, then read the report

curl -s -X POST "$BASE/squads/$SQUAD/executions/$EXEC/pause" -H "X-API-Key: $KEY" | jq .status

curl -s -X POST "$BASE/squads/$SQUAD/executions/$EXEC/steer" \
  -H "X-API-Key: $KEY" -H 'Content-Type: application/json' \
  -d '{"message":"Keep the public API unchanged"}' | jq .

curl -s -X POST "$BASE/squads/$SQUAD/executions/$EXEC/resume" -H "X-API-Key: $KEY" | jq .status

# Replay what the run actually did
curl -s -H "X-API-Key: $KEY" \
  "$BASE/squads/$SQUAD/history/$EXEC/events?limit=200" | jq -r '.events[] | "\(.seq)\t\(.event)"'

# Generate the Markdown report. The response is the report's path on the
# server's filesystem, not the report body, so a remote caller gets a path it
# cannot open; read the run through the events endpoint above instead.
curl -s -X POST "$BASE/squads/$SQUAD/history/$EXEC/report" -H "X-API-Key: $KEY" | jq -r .

See also

  • CLI reference for the aigo squad command group.
  • MCP endpoint for exposing Backend.AI GO's tools to external MCP clients.
  • External access for binding the Management API to a non-loopback address safely.