The Agentweaver backend is the single source of truth for run lifecycle, streaming, review, and merge. Every client is a thin layer over these endpoints.
- Base path:
/api - Authentication: bearer API key on authenticated API requests
- Event ordering: use
sequence, nottimestamp
Send the API key on API requests unless the endpoint is explicitly public (/, /health, /auth/entra/*, or /api/server/info):
Authorization: Bearer <api-key>Keys map to the user accountable for the runs they submit. You can configure multiple keys through Auth:Keys, or one key through Auth:ApiKey and Auth:User.
{
"Auth": {
"Keys": [
{ "Token": "dev-local-key", "User": "local-developer" }
]
}
}A request without recognized credentials returns 401 Unauthorized. A request for a run the caller
cannot access returns 403 Forbidden (or 404 Not Found on existence-hiding artifact routes).
When no credentials are configured, authenticated API requests are unauthorized.
A run with a persisted project_id inherits authorization from that project.
Viewer can use read, stream, history, graph, metrics, workspace, file, and preview-list endpoints;
Contributor and Owner can also use run mutations such as review, approval, steering, retry,
archive, cancellation, and sandbox preview control. The server resolves the project from the stored
run record, never from caller input. GitHub App capabilities do not grant project access. Runs with
no project_id retain submitting-user ownership. The trusted internal
service identity is denied on ordinary run read and mutation routes; only explicitly opted-in,
run-bound callbacks such as agent-initiated preview creation accept it.
Project-scoped run endpoints use the authorization boundary of the run's persisted project: viewers may inspect a run and contributors or owners may operate it. Older runs without a project remain submitting-user scoped. There is no username-based administrative override.
| Method | Path | Purpose |
|---|---|---|
GET |
/ |
Health banner (Agentweaver API) |
GET |
/api/runs/{id} |
Get current run state |
POST |
/api/runs/{id}/archive |
Archive a run |
DELETE |
/api/runs/{id} |
Cancel (if active) and delete a run record |
POST |
/api/runs/{id}/cancel |
Cancel a run's live work but keep the record |
GET |
/api/runs/{id}/stream |
Stream ordered run events over SSE |
GET |
/api/runs/{id}/events |
Return persisted run events |
POST |
/api/runs/{id}/review |
Record an approve or decline decision |
POST |
/api/runs/{id}/shell-approvals |
Approve a pending destructive shell command |
POST |
/api/runs/{id}/shell-denials |
Deny a pending destructive shell command |
GET |
/api/runs/{id}/history |
Replay persisted session events for terminal runs |
GET |
/api/runs/{id}/graph |
Get the workflow graph descriptor for rendering the run topology |
POST |
/api/runs/{id}/commit |
Commit worktree changes and merge into originating branch |
POST |
/api/runs/{id}/request-changes |
Request a revision cycle: agent rewrites in place |
POST |
/api/runs/{id}/retry |
Retry a failed run as a new linked run |
GET |
/api/runs/{id}/workspace |
List workspace files with change status and line counts |
GET |
/api/runs/{id}/files |
List changed files (flat, with filter) |
GET |
/api/runs/{id}/files/{**path} |
Get diff or content for a specific file |
POST |
/api/runs/{id}/tool-approvals |
Approve a pending tool call |
POST |
/api/runs/{id}/tool-denials |
Deny a pending tool call |
POST |
/api/runs/{id}/questions/{requestId}/answer |
Answer a pending ask_question request |
POST |
/api/runs/{id}/auto-approve |
Toggle the per-run auto-approve-tools option |
POST |
/api/runs/{id}/autopilot |
Toggle the coordinator Autopilot option |
POST |
/api/runs/{runId}/sandbox/port-forward |
Start a sandbox pod port-forward |
GET |
/api/runs/{runId}/sandbox/port-forward |
List sandbox port-forwards for a run |
DELETE |
/api/runs/{runId}/sandbox/port-forward/{sessionId} |
Stop a sandbox port-forward |
Assistant endpoints back the Sessions feature (see The Assistant and Sessions — Getting Started): a chat-driven, top-level (not project-scoped) conversation that calls MCP tools on the caller's behalf. A session is stored as a run record (agent_name: "Operator") so it's deleted via the generic DELETE /api/runs/{id} above — there is no separate Assistant delete endpoint.
| Method | Path | Purpose |
|---|---|---|
POST |
/api/assistant/runs |
Start a new session, optionally running an opening turn |
GET |
/api/assistant/runs |
List the caller's own sessions, newest first |
POST |
/api/assistant/runs/{id}/messages |
Send the next message into an existing session |
| Method | Path | Purpose |
|---|---|---|
POST |
/api/projects |
Create a project (blank or from GitHub) |
GET |
/api/server/info |
Get server metadata, including the optional Repo App install URL when configured |
GET |
/api/projects |
List all projects |
GET |
/api/projects/{id} |
Get a project by id |
PATCH |
/api/projects/{id} |
Rename a project |
PUT |
/api/projects/{id}/provider-settings |
Update provider and model defaults |
DELETE |
/api/projects/{id} |
Delete a project (record only; cancels active runs) |
GET |
/api/projects/{id}/runs |
List runs for a project |
POST |
/api/projects/{id}/runs |
Deprecated direct run submission; returns 410 Gone |
POST |
/api/projects/{id}/orchestrations |
Start a coordinator orchestration |
Run summary objects returned by GET /api/projects/{id}/runs include a result field ("no_changes" or null). When result is "no_changes", the agent found no file changes to commit; the review and merge gates are skipped. Each summary also includes coordinator_status: for a coordinator run (agent_name: "Coordinator", no parent) this is the current work-plan orchestration status (dispatching, awaiting_assembly, assembling, in_review, complete, assembly_blocked, assembly_failed, assembly_declined); it is null for normal runs. A companion coordinator_status_reason (the coordinator run's result, scoped to coordinator rows) carries the human-readable terminal/failure detail so the UI can render "Failed: <reason>". Children are excluded from this list. The UI should render coordinator_status (plus coordinator_status_reason) for coordinator rows so a long-running assembly does not show as a bare in_progress and a terminal failure does not show as an unexplained failed.
The standalone project-scoped workflow-run detail endpoint (GET /api/projects/{id}/runs/{workflowRunId}) has been removed with the retired standalone run pages. Use owner-scoped /api/runs/{id} and child run endpoints for embedded coordinator panels.
Run control state is durable. Shell approvals/denials, tool approval requests, run-scoped/always allow policies, child-to-parent approval inheritance, ask_question answers, auto-approve, and autopilot are replayed from persisted run events. That means approval, answer, and toggle requests may land on any API replica and still be observed by the worker that owns the run.
These endpoints are the pre-project handoff for GitHub-backed project creation. They require an authenticated human Entra subject and use only that caller's current Repo App authorization.
| Method | Path | Purpose |
|---|---|---|
GET |
/api/github/repository-selections |
List up to 200 bounded, metadata-only repositories available to the caller |
POST |
/api/github/repository-selections |
Verify one selected browse result and mint a short-lived opaque selection code |
GET returns:
{
"repositories": [
{
"full_name": "octo/example",
"owner_login": "octo",
"private": true,
"default_branch": "main",
"pushed_at": "2026-08-28T00:00:00+00:00"
}
]
}The list intentionally has no repository permission map, clone URL, installation ID,
credential data, provider error, or assertion that public metadata proves operational access.
POST accepts { "full_name": "octo/example" } only as a user selection instruction. The server
rechecks that name against the caller's bounded Repo App browse result, then returns:
{ "selection_code": "opaque-43-character-base64url-value", "expires_at": "2026-08-28T00:05:00+00:00" }The code is cryptographically random, stored only as a digest, caller-bound, valid for five
minutes, credential-kind-bound, and atomically single-use. Entra codes are bound to the exact Repo
App authorization used to issue them. A code is not a general GitHub credential. Missing,
revoked, malformed, expired, reused, or cross-subject codes fail closed and do not disclose
repository scope. Browser responses contain no GitHub repository IDs, installation or
authorization IDs, tokens, secrets, or permission maps. These endpoints return 409 with one of
human_entra_subject_required, github_binding_unavailable, or
github_capability_unavailable; malformed selection input returns 400.
The GitHub branch of POST /api/projects accepts only repository_selection_code as repository
authority. It atomically consumes the code for the authenticated caller, verifies the active Repo
App authorization is still usable, then resolves clone metadata server-side. It rejects
client-supplied repository URLs, identifiers, owner/name,
installation IDs, tokens, and permission maps. Project Settings uses the same browse +
selection-code flow when attaching an existing repository to a blank-origin project through
POST /api/projects/{id}/github/repository/connection.
Memory is scoped to projects. MemoryContextCompiler serializes selected decisions, memories, and session fields into a single JSON data envelope marked as untrusted; stored text is never emitted as prompt headings or executable instructions. Cross-team memory and active architectural/scope decisions compile only after approval by a project owner or a run-authenticated Coordinator. Export writes to .squad/decisions.md, .squad/agents/{name}/history.md, .agentweaver/context/boundaries.md, and .agentweaver/context/patterns.md.
Memory and decision responses expose sourceKind, sourceIdentity, sourceRunId, and trustState; approved records also expose approvedBy and approvedAt. Existing rows migrate as sourceKind: "legacy" and trustState: "legacy" and therefore do not compile until explicitly approved. This fail-closed migration avoids treating historical rows with unknown provenance as trusted policy.
Agent loopback writes authenticate with the normal internal API key plus a run-scoped capability. Only a SHA-256 digest of the short-lived capability is stored in the shared database, so validation works across API replicas without persisting the bearer token. The API resolves the project and agent from the verified run and rejects a client-supplied agent_name that does not match. Human API callers continue to use project authorization; only project owners and verified Coordinator runs may promote memory, merge/reject inbox entries, or create/update active decisions.
| Method | Path | Purpose |
|---|---|---|
POST |
/api/projects/{id}/decisions/inbox |
Submit a decision or learning to the inbox |
GET |
/api/projects/{id}/decisions/inbox |
List inbox entries (?agent=, ?type=, ?status=) |
POST |
/api/projects/{id}/decisions/inbox/{entryId}/merge |
Merge a pending entry into decisions |
POST |
/api/projects/{id}/decisions/inbox/{entryId}/promote |
Alias for merge/promote |
POST |
/api/projects/{id}/decisions/inbox/{entryId}/reject |
Reject a pending entry |
| Method | Path | Purpose |
|---|---|---|
POST |
/api/projects/{id}/decisions |
Create a decision directly |
GET |
/api/projects/{id}/decisions |
List decisions (?type=, ?agent=) |
GET |
/api/projects/{id}/decisions/{decisionId} |
Get a single decision |
POST |
/api/projects/{id}/decisions/{decisionId}/approve |
Approve a legacy or pending-trust decision for compilation |
PUT |
/api/projects/{id}/decisions/{decisionId} |
Update decision status/content |
| Method | Path | Purpose |
|---|---|---|
POST |
/api/projects/{id}/agents/{name}/memory |
Add a memory entry for an agent |
GET |
/api/projects/{id}/agents/{name}/memory |
List agent memories (?type=, ?importance=) |
GET |
/api/projects/{id}/agents/{name}/memory/{memId} |
Get a single memory entry |
POST |
/api/projects/{id}/agents/{name}/memory/{memId}/promote |
Approve memory for cross-agent compilation |
GET |
/api/projects/{id}/memory |
Cross-agent memory search (?type=, ?tags=) |
| Method | Path | Purpose |
|---|---|---|
POST |
/api/projects/{id}/sessions |
Start a new session (auto-ends existing) |
GET |
/api/projects/{id}/sessions/current |
Get current open session |
PUT |
/api/projects/{id}/sessions/current |
Update focus, summary, or end session |
GET |
/api/projects/{id}/sessions |
List sessions |
PATCH |
/api/projects/{id}/sessions/{sessionId} |
Update a specific session |
| Method | Path | Purpose |
|---|---|---|
POST |
/api/projects/{id}/memory/export |
Export DB memory → .squad/ + .agentweaver/context/ |
POST |
/api/projects/{id}/memory/import |
Import .squad/decisions/inbox/*.md → DB |
| Method | Path | Purpose |
|---|---|---|
POST |
/api/auth/github/repo-app/authorizations |
Begin an Entra-user-bound Repo App authorization; returns an authorization URL and opaque transaction ID |
POST |
/api/auth/github/repo-app/authorizations/handoff |
Begin an MCP-safe Repo App browser handoff; returns only an opaque transaction ID, browser URL, and expiry |
GET |
/auth/github/repo-app/handoff/{transactionId} |
Redeem an MCP browser URL only from the initiating user's authenticated Entra browser session; issues the callback cookie and redirects to GitHub |
GET |
/auth/github/repo-app/callback |
Complete the Repo App browser callback with its one-time callback cookie |
GET |
/api/auth/github/repo-app/authorizations/{transactionId} |
Return only the initiating subject's safe transaction status |
POST |
/api/auth/github/repo-app/authorization/refresh |
Refresh the caller's Repo App authorization without changing its grant identity |
DELETE |
/api/auth/github/repo-app/authorization |
Revoke the caller's Repo App authorization and write a credential tombstone |
Project Copilot App authorization has equivalent project-scoped endpoints at
/api/projects/{id}/github/copilot/authorizations/handoff and
/auth/github/copilot-app/handoff/{transactionId}. Handoff URLs require the same
initiating Entra browser session through callback completion; the transaction ID alone
cannot issue a callback cookie or authorize a GitHub account.
Team and casting endpoints are project-scoped and use the project owner check before exposing or changing rosters, charters, proposals, or sync state. A caller who does not own the project cannot manage that project's team.
| Method | Path | Purpose |
|---|---|---|
GET |
/api/casting/templates |
List available scenario groupings (team templates) |
GET |
/api/projects/{id}/casting/universes |
List allowed universe names |
GET |
/api/catalog/roles |
List all available role definitions |
POST |
/api/projects/{id}/casting/proposals |
Create a casting proposal |
GET |
/api/projects/{id}/casting/proposals |
List active proposals for a project |
GET |
/api/projects/{id}/casting/proposals/{proposalId} |
Get a proposal |
PATCH |
/api/projects/{id}/casting/proposals/{proposalId} |
Amend a proposal |
POST |
/api/projects/{id}/casting/proposals/{proposalId}/confirm |
Confirm a proposal and create the team |
DELETE |
/api/projects/{id}/casting/proposals/{proposalId} |
Reject a proposal |
GET |
/api/projects/{id}/team |
Get team roster and layout metadata |
GET |
/api/projects/{id}/team/members/{name}/charter |
Get a member's charter |
PUT |
/api/projects/{id}/team/members/{name}/charter |
Replace a member's charter |
GET |
/api/projects/{id}/team/members/{name}/history |
Get agent interaction history |
POST |
/api/projects/{id}/team/members |
Add a team member |
DELETE |
/api/projects/{id}/team/members/{name} |
Retire a team member |
PATCH |
/api/projects/{id}/team/members/{name} |
Re-role a team member |
GET |
/api/projects/{projectId}/team/sync |
Get pending .squad/ changes and change set hash |
POST |
/api/projects/{projectId}/team/sync |
Commit pending .squad/ changes |
Team member objects include is_built_in: true for Scribe, Ralph, and Rai (case-insensitive). Built-in agents cannot be removed, re-roled, or directly run. Attempting to start a run with a built-in agent name returns 400 Bad Request.
| Method | Path | Purpose |
|---|---|---|
GET |
/api/blueprints |
List predefined blueprints |
POST |
/api/blueprints/generate |
Generate a blueprint from a description |
POST |
/api/blueprints/suggest |
Analyze a GitHub repository and recommend a catalog blueprint |
POST |
/api/blueprints/validate |
Validate an inline blueprint |
Suggested blueprint analysis accepts { "repository": "owner/repo" }, returns recommended_blueprint, rationale, confidence, signals, and fallback, and gracefully returns fallback: true when GitHub analysis is unavailable. See Repository blueprint suggestions.
Backlog, board, review-policy, and workflow endpoints are project-scoped and require ownership of the containing project. Backlog tasks do not introduce separate cross-user privileges; callers manage only tasks in projects they own.
| Method | Path | Purpose |
|---|---|---|
GET |
/api/projects/{id}/workspace/files |
List project workspace files for decomposition |
POST |
/api/projects/{id}/backlog/decompose |
Decompose workspace files into backlog tasks |
POST |
/api/projects/{projectId}/backlog/tasks |
Create a backlog task |
PATCH |
/api/projects/{projectId}/backlog/tasks/{taskId} |
Edit a backlog task title/description |
DELETE |
/api/projects/{projectId}/backlog/tasks/{taskId} |
Delete a backlog task |
POST |
/api/projects/{projectId}/backlog/tasks/{taskId}/ready |
Move a task to ready |
POST |
/api/projects/{projectId}/backlog/ready-all |
Move all eligible tasks to ready |
POST |
/api/projects/{projectId}/backlog/tasks/{taskId}/backlog |
Move a task back to backlog |
POST |
/api/projects/{projectId}/backlog/tasks/{taskId}/reorder |
Reorder a backlog task |
POST |
/api/projects/{projectId}/backlog/tasks/{taskId}/archive |
Archive a backlog task |
GET |
/api/projects/{projectId}/board |
Get the board state |
GET |
/api/projects/{projectId}/workflow-stages |
Get workflow stages |
GET |
/api/projects/{projectId}/backlog/settings |
Get backlog pickup settings |
PUT |
/api/projects/{projectId}/backlog/settings |
Update backlog pickup settings |
GET |
/api/projects/{projectId}/review-policies |
List review policies |
POST |
/api/projects/{projectId}/review-policies/sync |
Reload review policies from disk |
GET |
/api/projects/{projectId}/workflows |
List workflows, including structured trigger metadata |
GET |
/api/projects/{projectId}/workflows/{workflowId} |
Get one workflow, including all triggers |
GET |
/api/projects/{projectId}/workflows/{workflowId}/trigger |
Get the workflow's trigger configs as structured JSON |
PUT |
/api/projects/{projectId}/workflows/{workflowId}/trigger |
Create/replace one trigger by type |
PATCH |
/api/projects/{projectId}/workflows/{workflowId}/trigger |
Partially update one trigger by type |
DELETE |
/api/projects/{projectId}/workflows/{workflowId}/trigger |
Clear all triggers, or one type with ?type= |
POST |
/api/projects/{projectId}/workflow-events |
Fire a named workflow event manually |
GitHub delivers repository events only to the Repo App's App-level receiver; do not configure per-project webhook URLs or provisioning routes. The API verifies the webhook signature before parsing and routing the delivery.
| Method | Path | Purpose |
|---|---|---|
POST |
/api/github/webhooks/repo-app |
Receive an HMAC-signed Repo App webhook delivery |
Workflow trigger objects use the existing top-level trigger fields (type, interval,
day_of_week, day_of_month, time_of_day, event_name) plus an optional if predicate array
for event triggers. The array is implicitly ANDed; compound logic uses nested or / not wrapper
predicates. The current JSON predicate vocabulary is:
hasLabel: { label }isNotLabeledWith: { label }baseBranch: { branch }reviewState: { state }wherestateisapproved,changes_requested, orcommentedref: { branch, matchMode }wherematchModeisequalsorprefixcategory: { name }commentMatches: { pattern }
Example trigger payload:
{
"type": "event",
"event_name": "github.pull_request.opened",
"if": [
{
"or": [
{ "baseBranch": { "branch": "main" } },
{ "baseBranch": { "branch": "release/v1" } }
]
}
]
}| GET | /api/projects/{projectId}/review-policies/{policyName} | Get a review policy |
| PUT | /api/projects/{projectId}/review-policies/active | Set the active review policy |
| GET | /api/projects/{projectId}/workflows | List workflow definitions |
| POST | /api/projects/{projectId}/workflows/sync | Reload workflow definitions from disk |
| GET | /api/projects/{projectId}/workflows/{workflowId} | Get a workflow definition |
| GET | /api/projects/{projectId}/workflows/{workflowId}/trigger | Get a workflow's structured trigger config |
| PUT | /api/projects/{projectId}/workflows/{workflowId}/trigger | Create or replace one workflow trigger type |
| PATCH | /api/projects/{projectId}/workflows/{workflowId}/trigger | Partially update one workflow trigger type |
| DELETE | /api/projects/{projectId}/workflows/{workflowId}/trigger | Clear all triggers or one trigger type |
| PUT | /api/projects/{projectId}/workflows/default | Set the default workflow |
| PUT | /api/projects/{projectId}/backlog/tasks/{taskId}/workflow-override | Set a task workflow override |
| GET | /api/projects/{projectId}/workflows/{workflowId}/graph | Get a workflow graph |
| GET | /api/projects/{projectId}/workflows/{workflowId}/yaml | Get workflow YAML |
| PUT | /api/projects/{projectId}/workflows/{workflowId} | Replace a workflow definition |
| POST | /api/projects/{projectId}/workflows/generate | Generate a workflow definition |
Workflow list and detail responses expose the complete triggers array. The legacy trigger field
remains as an alias for the first trigger so existing clients continue to deserialize unchanged. The
dedicated trigger CRUD endpoints expose the same shapes without requiring callers to rewrite the
whole workflow YAML.
GET /api/projects/{projectId}/workflows/{workflowId}/trigger
PUT /api/projects/{projectId}/workflows/{workflowId}/trigger
PATCH /api/projects/{projectId}/workflows/{workflowId}/trigger
DELETE /api/projects/{projectId}/workflows/{workflowId}/trigger- Every response returns
{ "trigger": <WorkflowTriggerDto|null>, "triggers": [...] }. PUTaccepts aWorkflowTriggerDtoand upserts that trigger type without removing other types.PATCHpartially updates the trigger named bytype. When the workflow has multiple triggers,typeis required; a single-trigger workflow retains the legacy type-optional behavior.DELETE ?type=scheduleor?type=eventremoves only that type.DELETEwithout atypepreserves its legacy behavior and clears all triggers.
The JSON contract is intentionally close to workflow YAML:
- top-level trigger keys keep their existing snake_case names such as
event_name,day_of_week,day_of_month, andtime_of_day; - nested predicate objects use camelCase keys such as
hasLabel,baseBranch,commentMatches, andmatchMode.
{
"type": "schedule",
"interval": "weekly",
"day_of_week": "monday",
"time_of_day": "09:00"
}Monthly schedules replace day_of_week with day_of_month (1-28). Schedule triggers reject an
if block.
{
"type": "event",
"event_name": "github.pull_request.opened",
"if": [
{
"or": [
{ "baseBranch": { "branch": "main" } },
{ "baseBranch": { "branch": "release/v1" } }
]
},
{
"not": {
"hasLabel": { "label": "blocked" }
}
}
]
}Sibling entries in if are ANDed by default. Compound logic uses nested or and not wrapper
predicates.
Supported predicate payloads:
| Predicate | JSON shape | Supported GitHub event types |
|---|---|---|
hasLabel |
{ "hasLabel": { "label": "bug" } } |
issues, pull_request |
isNotLabeledWith |
{ "isNotLabeledWith": { "label": "blocked" } } |
issues, pull_request |
baseBranch |
{ "baseBranch": { "branch": "main" } } |
pull_request |
reviewState |
{ "reviewState": { "state": "approved" } } |
pull_request_review |
ref |
{ "ref": { "branch": "refs/heads/main", "matchMode": "equals" } } |
push |
category |
{ "category": { "name": "Ideas" } } |
discussion |
commentMatches |
{ "commentMatches": { "pattern": "^/agentweaver:triage$" } } |
issue_comment |
or |
{ "or": [ ...predicates... ] } |
same as its children |
not |
{ "not": { ...predicate... } } |
same as its child |
The curated GitHub event shortlist is issues, issue_comment, pull_request,
pull_request_review, push, release, and discussion. release currently has no
event-specific predicates beyond the event name itself.
commentMatches accepts a fixed saved pattern only. The backend validates it against a safe regex
subset, executes it with the non-backtracking engine plus a hard timeout, and exposes only the
boolean match outcome to the rest of the workflow-firing pipeline.
| Method | Path | Purpose |
|---|---|---|
GET |
/health |
Public liveness probe |
GET |
/api/health |
API liveness probe |
GET |
/api/diagnostics |
Get API diagnostics (SQLite/disk/workflow/heartbeat) |
GET |
/api/diagnostics/cluster |
Get cluster diagnostics (pods, quota, component health, pending runs) |
GET |
/api/diagnostics/heartbeat |
Get diagnostics heartbeat |
GET |
/api/projects/{id}/diagnostics |
Get project diagnostics |
GET |
/api/projects/{id}/workspace/refs |
List workspace refs |
GET |
/api/projects/{id}/workspace |
List project workspace files |
GET |
/api/projects/{id}/workspace/files/{**path} |
Read a project workspace file |
GET |
/api/projects/{id}/dashboard |
Get project dashboard summary plus compatibility throughput / leaderboard fields |
GET |
/api/projects/{id}/metrics |
Get App Insights-backed throughput and leaderboard widgets |
GET |
/api/overview |
Get global overview metrics |
GET |
/api/runs/{id}/token-breakdown |
Get per-agent token and AI-credit data for a run |
GET |
/api/metrics/runs/{runId}/traces |
Get Application Insights agent and LLM spans for a run |
Returns a ClusterDiagnosticsDto with the current state of the Kubernetes cluster as seen by the Agentweaver API. Requires authentication. Returns 404 Not Found when cluster diagnostics are not available (e.g. non-AKS deployment).
Five component health checks run concurrently with a 5-second individual timeout each:
| Check name | What it tests |
|---|---|
postgresql |
Postgres connectivity |
key_vault |
Azure Key Vault CSI delivery of the required mcp-api-key. critical: secret 'mcp-api-key' not found means API authentication and worker loopback calls cannot run. |
agent_pod_quota |
Effective admission headroom in the sandbox namespace, computed from the tighter of the pods and SandboxClaim object quotas. |
warm_pool |
Warm-pool agent-sandbox availability |
kubernetes_api |
Kubernetes API server reachability |
Response 200 OK — a ClusterDiagnosticsDto:
{
"checks": [
{ "name": "postgresql", "status": "pass", "detail": null, "duration_ms": 12 },
{ "name": "agent_pod_quota", "status": "warn", "detail": "4 additional agent pod starts available before quota exhaustion (limited by pods; pods 196/200, sandboxclaims 188/200 used)", "duration_ms": 45 }
],
"active_agent_pods": [
{ "pod_name": "agent-host-abc123", "run_id": "f36800fd-...", "node": "katapool-vm-1", "started_at": "2026-06-27T17:55:00Z" }
],
"orphaned_agent_pods": [],
"pending_capacity_runs": [
{ "coordinator_run_id": "coord-...", "subtask_id": 7, "pending_since": "2026-06-27T17:58:30Z", "retry_count": 3 }
]
}| Field | Type | Notes |
|---|---|---|
checks |
DetailedHealthCheckDto[] |
One entry per check. Its status is healthy, warning, critical, or unknown. |
active_agent_pods |
AgentPodInfoDto[] |
Pods currently running with a matching active run. |
orphaned_agent_pods |
AgentPodInfoDto[] |
Pods running with no matching active run (candidates for next reaper sweep). |
pending_capacity_runs |
PendingCapacityRunDto[] |
Legacy / back-compat. Subtasks recorded in the historical PendingCapacity status; empty for new runs (Kubernetes now owns scheduling, issue #217). |
See Cluster diagnostics reference for the full DTO schema and field descriptions.
The heartbeat tick records returned by GET /api/diagnostics/heartbeat include an automation_name field on each TickRecordDto:
{
"tick_records": [
{
"automation_name": "Coordinator Heartbeat",
"acted_count": 2,
"error_count": 0,
"duration_ms": 340,
"recorded_at": "2026-06-27T18:00:00Z"
}
]
}The Heartbeat page Recent Activity table shows this as the first column (Automation). Possible values are "Coordinator Heartbeat" and "Checkpoint GC".
Returns the plain text banner Agentweaver API.
Returns the current state of a run. Only the submitting user may access their own runs; non-owners receive 403 Forbidden.
Response 200 OK:
{
"run_id": "f36800fd-f2f8-418c-958e-aae3e4921ba6",
"status": "awaiting_review",
"model_source": "github-copilot",
"started_at": "2026-06-07T21:09:45.7526712+00:00",
"ended_at": "2026-06-07T21:09:52.103+00:00",
"step_count": 4,
"tree_hash": "a1b2c3d4e5f6...",
"diff": "diff --git a/a.txt b/a.txt\n..."
}Unknown ids return 404 Not Found. Status values are pending, in_progress, awaiting_review, merging, merged, declined, merge_failed, failed, and completed. completed is reached when the agent turn produced no file changes (no review gate is entered on that path).
For a coordinator run (agent_name: "Coordinator", no parent), the response also carries coordinator_status: the current work-plan orchestration status (dispatching, awaiting_assembly, assembling, in_review, complete, assembly_blocked, assembly_failed, assembly_declined). It is null for normal runs and for coordinator runs that have no work plan yet. Because a coordinator run stays in_progress while it dispatches children and runs collective assembly, coordinator_status is what the UI should render (for example "Awaiting assembly" or "Failed: <result>") instead of the bare status. On a terminal assembly failure the result — also surfaced as coordinator_status_reason on this response (scoped to coordinator runs) — carries the human-readable reason (for example assembly_blocked: <reason>, assembly_merge_failed: <reason>, assembly_error: <message>).
Coordinator run detail also includes coordinator_steerable (boolean). The backend sets it for coordinator runs whose RunStatus is in_progress or awaiting_review, so the UI can keep Message coordinator and steering controls enabled while the collective assembly review gate is parked (apps/Agentweaver.Api/Contracts/Dtos.cs:178, apps/Agentweaver.Api/Endpoints/RunEndpoints.cs:185, apps/Agentweaver.Api/Coordinator/CoordinatorSteeringService.cs:348).
The response also carries auto_approve_tools and autopilot (booleans) reflecting the current per-run option state (launch value plus any live toggle). Both are false unless explicitly enabled. The frontend uses these to render the toggle controls; see POST /api/runs/{id}/auto-approve and POST /api/runs/{id}/autopilot.
Archives a run for the owner. Response 200 OK:
{ "run_id": "f36800fd-...", "archived_at": "2026-06-07T21:20:00+00:00" }Cancels and deletes a run record. For any non-terminal run, the shared cancellation path runs first (EndpointHelpers.CancelRunWorkAsync): the live MAF workflow is abandoned — which also stops any child subtask runs a coordinator is driving — the worktree is torn down best-effort, and the run is forced to a terminal Failed state. The run row is then removed and its in-memory stream entry is dropped. Runs already in a terminal state (Merged, Declined, MergeFailed, Failed, Completed) are deleted directly with no cancellation work.
Response 204 No Content.
Authorization:
- Human platform administrators may delete any run, including one whose persisted project no longer exists. Dedicated internal-service credentials cannot delete runs.
- The submitting user may delete their own personal session created by the Assistant endpoints even if its incidental project no longer exists or the user's project role was revoked. Sessions are recognized only when the first durable event is the server-authored sequence-1
run.startedmarker with the run's matchingrunId,agentName: "Operator", andkind: "operator"values. - Other project-owned runs require current project Contributor access.
Errors: 400 invalid run id; 404 run not found; 403 caller lacks deletion authority; 500 fetch or delete failed.
Cancels a run's live work but keeps the run record so the user can still inspect it. Runs the same shared cancellation path as DELETE — abandon the workflow (stopping coordinator child runs), best-effort worktree cleanup, force to terminal Failed, and complete the event stream — without deleting the row. This is what the Stop action on the Orchestrations list uses.
For a non-terminal run, response 200 OK:
{ "run_id": "f36800fd-...", "status": "failed", "cancelled": true, "already_terminal": false }An already-terminal run has no live work to cancel: the endpoint reports the current state without acting, response 200 OK:
{ "run_id": "f36800fd-...", "status": "completed", "cancelled": false, "already_terminal": true }Errors: 400 invalid run id; 404 run not found; 403 caller is not the run owner; 500 fetch failed.
Streams the run's events over SSE. Requires a valid bearer key and run ownership — a non-owner receives 404 (no existence leak). Each frame carries the per-run sequence as the SSE id and the event payload as data:
id: 3
event: agent.message.delta
data: {"delta":"Hello","messageId":"msg-001"}
id: 4
event: run.completed
data: {"result":"no_changes"}
event: done
data: {}
The stream ends with a synthetic done frame (no id) after the terminal event.
Set Last-Event-ID to the last sequence you received. The server resumes from that point in the in-memory event buffer. Reconnection works while the run's entry is retained in memory (up to 256 completed runs; in-progress entries are evicted after approximately two hours of inactivity). awaiting_review runs and any run actively being merged are exempt from inactivity eviction — entries for those runs stay in memory until a terminal review decision is recorded. After a process restart, stream entries for awaiting_review runs are re-created so the review endpoint can still emit events to reconnected clients; any run interrupted mid-merge is reverted to awaiting_review and also gets a fresh entry.
After a process restart, the in-memory event history is lost. If the run already completed, the endpoint replays the stored final result as a single agent.message event and closes the stream. If the run was still in progress at restart, recovery marks it as failed and the stream returns done immediately with no events.
Response headers:
Content-Type: text/event-streamCache-Control: no-cacheConnection: keep-alive
Records a human review decision. Only the run owner may submit a decision. Non-owners receive 403 Forbidden.
Request:
{ "approved": true }Primary path (normal operation)
In normal operation the API hands the decision to the background MAF workflow and returns immediately:
- Approve —
200 OK,status: "merging". The merge runs asynchronously inside the workflow. Watch the SSE stream forreview.approvedfollowed by eithermerge.completedormerge.failedto learn the outcome. - Decline —
200 OK,status: "declined". The workflow terminates;review.declinedis emitted on the stream.
{ "run_id": "...", "status": "merging", "merge_result": null }
{ "run_id": "...", "status": "declined", "merge_result": null }Idempotent re-POST
If the run has already reached a matching terminal state, the endpoint returns the current state rather than an error:
- Re-approving an already-
mergedrun returns200 OKwithstatus: "merged"and the storedmerge_result. - Re-declining an already-
declinedrun returns200 OKwithstatus: "declined".
Error responses
| Status | Condition |
|---|---|
403 Forbidden |
The caller does not own the run |
404 Not Found |
No run found for the given id |
409 Conflict |
The run is not in awaiting_review status (and the decision does not match an already-terminal state), or the review decision was already consumed by a concurrent POST |
A 409 from a duplicate or concurrent POST has no body. A 409 from a wrong-status run includes an error message:
{ "error": "Run is in status 'in_progress' and cannot be reviewed." }Direct fallback path (post-restart recovery)
After a server restart, if no workflow checkpoint is available to resume, the endpoint executes the merge or decline synchronously and returns the final outcome directly:
-
Merge succeeds —
200 OK, statusmerged. The run's worktree branch is merged into the originating branch.merge_resultismerged:{commit-hash}. If the originating branch is currently checked out and the tree is clean, the branch ref is advanced and the working tree is updated via a hard reset. If it is not checked out, only the branch ref is advanced. On success the worktree is torn down: its physical directory is deleted first, the admin entry is pruned, then the branch is removed. -
Blocked (retriable) —
409 Conflict, statusawaiting_review. No git mutations occurred. The run stays at the review gate and can be approved again once the condition is resolved. Causes include: uncommitted changes to tracked files, staged changes in the index, untracked files that would be overwritten by the merge, a merge or rebase already in progress in the working tree, the repository lock being held by another concurrent request, or a concurrent approve that already won the CAS gate. Body:{ "error": "there are uncommitted changes to tracked files", "status": "awaiting_review" } -
Terminal conflict —
200 OK, statusmerge_failed. The originating branch has diverged with conflicts that require human resolution, or the tree hash stored at review time no longer matches the worktree branch. The originating branch is unchanged and the worktree is preserved.merge_resultisconflict:{reason}. -
Decline —
200 OK, statusdeclined,merge_result: null.
{ "run_id": "...", "status": "merged", "merge_result": "merged:34c09ee..." }
{ "run_id": "...", "status": "merge_failed", "merge_result": "conflict:The originating branch has diverged..." }
{ "run_id": "...", "status": "declined", "merge_result": null }See events.md for the event types emitted on the stream for each outcome.
Approves a pending shell command. Use the commandHash from the shell.approval_required event as command_hash.
Request:
{ "command_hash": "sha256:..." }Response 200 OK { "run_id", "command_hash", "approved": true }.
Denies a pending shell command.
Request:
{ "command_hash": "sha256:..." }Response 200 OK { "run_id", "command_hash", "denied": true }.
Returns persisted run events ordered by sequence. Each item has sequence, type, and payload.
Replays persisted Copilot SDK session events for a terminal run. The session is identified by agentweaver-run-{runId}. Returns a JSON array of run events in stream order. Only available for terminal runs. Returns 404 if the run is not terminal or the session is not found.
Returns the workflow graph descriptor for the run, describing the node/edge topology so a client can render the live workflow without hardcoding it. The descriptor is built from the same code that wires the MAF workflow (no runtime reflection). Owner-scoped Bearer auth. Coordinator runs (parent_run_id == null, driven by the built-in Coordinator agent, with a persisted work plan) return the coordinator variant (see below); child runs (parent_run_id != null) return the child variant; all others return the full variant.
Response 200 OK — a GraphDescriptor:
{
"graph_id": "agentweaver-workflow-full",
"variant": "full",
"start_node_id": "agent",
"nodes": [
{ "id": "agent", "label": "Agent", "role": "agent", "kind": "live", "node_type": "agent", "child_graph_ref": null }
],
"edges": [
{ "from": "agent", "to": "rai", "cardinality": "direct", "loopback": false }
]
}variant:"full"|"child"|"coordinator".nodes[].id: the logical node id (matches the step key inworkflow.stepevents).kind:"live"|"planned".child_graph_ref: optional reference to a nested graph.nodes[].node_type: self-declared category that drives the frontend's rendered shape/size — one of"agent"(an AI agent turn),"action"(a deterministic system op),"gate"(a human-in-the-loop decision/approval),"terminal"(a workflow endpoint/checkpoint), or"subtask"(a coordinator fan-out child reference). Required on every node.edges[].cardinality:"direct"|"fanout"|"fanin".loopback:truewhen the edge targets an ancestor (a revision cycle back-edge).
When the run is a coordinator run, the descriptor is built from its work plan (graph_id = coordinator:{coordinatorRunId}, start_node_id = coordinator) so the same generic renderer can draw the coordinator, its fan-out subtask children, and the PLANNED Phase 3 collective-assembly stage. It is shape-only — runtime status is NOT baked in (project it from the subtask.* / coordinator.topology streams).
- Node
coordinator(node_type: "agent",role: "coordinator",kind: "live"). - One node per subtask, id
plan:subtask-{id}(node_type: "subtask",role: "subtask",kind: "live"). Subtask nodes carry rich display fields as OPTIONAL snake_case properties (omitted when null):agent,model,phase,isolation,child_run_id. Once the subtask's child run is dispatched,child_graph_refisrun:{childRunId}so the client can expand the child's own graph viaGET /api/runs/{childRunId}/graph; it isnulluntil dispatched. - PLANNED collective-assembly nodes (
kind: "planned"):planned:assembly-rai(node_type: "agent",role: "rai"),planned:assembly-review(node_type: "gate",role: "review"),planned:assembly-merge(node_type: "action",role: "merge"),planned:assembly-scribe(node_type: "agent",role: "scribe"). - Edges:
coordinator→ each root subtask; dependency edgesplan:subtask-{dependsOn}→plan:subtask-{dependent}; each terminal (leaf) subtask →planned:assembly-rai; then the assembly chainassembly-rai→assembly-review→assembly-merge→assembly-scribe. Two loopback back-edges (loopback: true) close the cycle:planned:assembly-rai→coordinatorandplanned:assembly-review→coordinator, reflecting that an RAI flag or a human-review request-changes re-dispatches affected subtasks through the coordinator. All forward edges areloopback: false.cardinalityisfanout/faninby forward (non-loopback) degree; loopback edges are alwaysdirectand are excluded from the degree counts so they do not distort fan-out/fan-in.
{
"graph_id": "coordinator:run_abc",
"variant": "coordinator",
"start_node_id": "coordinator",
"nodes": [
{ "id": "coordinator", "label": "Coordinator", "role": "coordinator", "kind": "live", "node_type": "agent", "child_graph_ref": null },
{ "id": "plan:subtask-1", "label": "Build API", "role": "subtask", "kind": "live", "node_type": "subtask", "child_graph_ref": "run:run_child1", "agent": "morpheus", "model": "gpt-5.3-codex", "phase": "execution", "isolation": "worktree", "child_run_id": "run_child1" }
],
"edges": [
{ "from": "coordinator", "to": "plan:subtask-1", "cardinality": "fanout", "loopback": false }
]
}The same descriptor is emitted once at run start as a run.workflow_graph event on the stream (see events.md).
Commits any remaining uncommitted worktree changes and immediately merges the worktree branch into the originating branch. The run must be in awaiting_review. Uses CAS AwaitingReview → Committing → Merging to prevent concurrent commits.
Response:
200 OK{ run_id, status: "merged", merge_result: "merged:{hash}" }on success200 OK{ run_id, status: "merge_failed", merge_result: "conflict:{reason}", conflicting_files: [...] }on conflict409 Conflict{ error, status: "awaiting_review" }on retriable block (dirty working tree, concurrent request, etc.)409 Conflict{ error }if the run is not inawaiting_review
Requests a revision cycle. The agent is given the reviewer's comment and re-runs on the same worktree without creating a new branch. The run returns to in_progress.
Request body:
{ "comment": "string" }Response: 202 Accepted with the updated run.
Retries a failed or merge-failed run as a new linked run. The source run is not mutated; child runs are retried through their coordinator parent.
Response 201 Created:
{ "run_id": "new-run-id", "retried_from": "old-run-id", "status": "in_progress" }When a failed coordinator can resume its existing recovery point, the response is instead
200 OK and keeps the same run id:
{ "run_id": "existing-run-id", "retried_from": null, "status": "in_progress", "resumed": true }On an AgentHost deployment, the retry requires the project's explicitly connected, run-bound
GitHub Copilot capability before pod creation. A missing connection returns 409 Conflict with
the standard model_provider_connection_required action payload.
Returns a tree of all files in the run's worktree (folders + files). Files include path, is_folder, status (added/modified/deleted, null for unchanged), added_lines, removed_lines. Returns 404 for terminal runs whose worktrees have been removed (failed/merged/declined/merge_failed). Returns empty array for pending. Returns 409 while the worktree does not exist for an active run.
Flat list of changed files. Query param filter: all (committed + uncommitted), committed, uncommitted, last-commit. Returns an empty array while asynchronous worktree provisioning is incomplete. Coordinator runs always return an empty array here because they have no per-run worktree; use GET /api/runs/{id}/assembly/files for their collective output.
Returns diff and content for a specific file. Response includes path, status, diff, content, is_binary.
Approves a pending tool call.
Request:
{ "request_id": "string", "scope": "once" | "run" | "always" | "tool" }Scope values: once = this call only; run = all calls to the same tool+url this run; always = all calls this server session; tool = all calls to this tool regardless of url.
For a decision forwarded to a pod-local gate, always is effectively run-scoped and does not survive a pod restart.
Response 200 OK { "run_id", "request_id", "approved": true }. Terminal/replayed and pod-forwarded responses also include resolved: true, expired, and state: "approved" | "denied" | "expired"; pod-forwarded approvals also include applied, which confirms that the owning AgentHost accepted this exact forwarding request. The returned run_id is the run that actually owned the approval, which may differ from {id}.
Owning-run resolution. The approval context lives on the run that raised the tool call. When {id} is a coordinator run (ParentRunId == null and AgentName == "Coordinator"), the API checks its children and then scans persisted coordinator.child_approval_required events for the matching requestId and childRunId. Approving therefore works whether the client posts the coordinator id or the child id.
Pod-per-run fallback. If the API's DurableToolApprovalGate returns Unknown for the resolved child, the API uses IAgentHostOriginResolver and AgentHostApprovalHttpClient to forward the selected scope to the pod's authenticated /tool-approvals route through the a2a-sandbox-pod client. The bearer is re-fetched with PreviewRunnerCredential.SecretKey(runId). The AgentHost publishes its current-pod scope bridge only when it wins and applies that pending approval; the API publishes the durable cross-pod policy only after it receives resolved: true, state: "approved", and applied: true for that exact forward. A duplicate or late terminal response with applied: false, and every failed, denied, or expired forward, leaves no durable policy. A successful terminal forward emits tool.approval_resolved on the child run.
| Status | Approval result |
|---|---|
200 OK |
The request is terminal (approved, denied, or expired) |
404 Not Found |
state: "unknown" after owning-run resolution and any pod forward, or the run does not exist |
409 Conflict |
state: "pending" and the decision should be retried, or the run is not active |
503 Service Unavailable |
state: "agenthost_unreachable" because the AgentHost origin/call/response was unavailable |
Other errors: 400 invalid run id / missing request_id; 403 caller is not the run owner.
Denies a pending tool call.
Request:
{ "request_id": "string" }Response 200 OK { "run_id", "request_id", "denied": true }. Terminal/replayed and pod-forwarded responses also include resolved: true, expired, and state. Denials use the same persisted coordinator-to-child owning-run resolution and authenticated pod fallback as approvals, so the returned run_id may differ from {id}.
Status codes are the same as tool approvals: 200 terminal, 404 state: "unknown", 409 state: "pending", and 503 state: "agenthost_unreachable", plus 400 validation, 403 ownership, and 409 inactive-run errors.
Sources: apps/Agentweaver.Api/Endpoints/RunEndpoints.cs:1559-1705,
apps/Agentweaver.Api/Endpoints/RunEndpoints.cs:2590-2718,
apps/Agentweaver.Api/Endpoints/EndpointHelpers.cs:43-98,
apps/Agentweaver.Api/Sandbox/AgentHostApprovalHttpClient.cs:28-112.
Answers a pending ask_question request, resuming the agent that called the ask_question tool. The requestId is the value carried by the agent.question_asked event. For a coordinator child run, answer against the CHILD run id (carried by coordinator.child_question).
Request:
{ "answer": "string" }Response: 200 OK { "run_id", "request_id", "answered": true }.
Errors: 400 invalid run id / missing answer; 404 run not found; 409 no pending question for this request_id (already answered, timed out, or never asked); 403 caller is not the run owner. The run must be InProgress.
Toggles the per-run auto-approve-tools option. When enabled, an allow-with-approval tool request (e.g. web_fetch) is auto-granted at the human-in-the-loop gate instead of stalling for an operator. Every auto-grant is logged on the timeline as a tool.auto_approved event. This NEVER overrides a policy deny: dangerous tools are rejected upstream by sandbox governance before the gate is reached. Set the flag at coordinator launch with autoApproveTools on POST /api/projects/{id}/orchestrations. It cascades from a coordinator run to its dispatched children. Defaults to OFF.
Request:
{ "enabled": true }Response: 200 OK { "run_id", "auto_approve_tools": true }.
Errors: 400 invalid run id; 404 run not found; 403 caller is not the run owner; 409 run is not active (InProgress).
Toggles the coordinator Autopilot option. When enabled, CLARIFYING QUESTIONS ONLY (the coordinator's own and those bubbled by child workers as coordinator.child_question) are auto-answered by the coordinator model from the outcome spec + subtask context, then resolved on the child's question gate. Each auto-answer is logged as coordinator.autopilot_answered, and the normal agent.question_answered resolution still surfaces on the child stream. Autopilot does NOT auto-grant tool approvals/permissions (that is the separate auto-approve-tools opt-in). Settable at launch (autopilot on POST /api/projects/{id}/orchestrations) and cascades to children. Defaults to OFF. Set at launch in defineOutcome mode, autopilot additionally auto-confirms the Phase-1 outcome spec unattended (confirmedBy = the submitting user) instead of parking at awaiting_confirmation; this live toggle only governs the clarifying-question answering described above.
Request:
{ "enabled": true }Response: 200 OK { "run_id", "autopilot": true }.
Errors: 400 invalid run id; 404 run not found; 403 caller is not the run owner; 409 run is not active (InProgress).
These endpoints back the Sessions UI (see Sessions & the Assistant — User Guide and Assistant runtime — Deep Dive). Every session is stored as a run record with agent_name: "Operator", so GET /api/runs/{id}, GET /api/runs/{id}/stream//events, and DELETE /api/runs/{id} documented above all work against a session's id too. Auth is enforced globally (no unauthenticated request reaches these handlers); a caller may only ever see their own sessions.
Starts a new session. message is optional — if supplied, the opening turn runs immediately and its reply is returned in the same response; if omitted, the run is created empty and the first message is sent via POST /api/assistant/runs/{id}/messages.
Request:
{
"message": "What's blocked on the board right now?",
"project_id": null,
"run_id": null,
"model_id": null
}All fields are optional. project_id associates the session with a project (sessions are otherwise unscoped); run_id lets a caller pin a specific id instead of a server-generated one; model_id overrides the default model.
Response 201 Created:
{
"run_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"status": "in_progress",
"message": "You have 4 items in Ready and 2 In Progress...",
"tools_invoked": ["backlog_list"]
}message and tools_invoked are null when no opening message was supplied. On an AgentHost deployment, project_id is required and its GitHub Copilot App must be explicitly connected before the API creates a pod; a missing project returns 400 (project_context_required) and a missing connection returns 409 Conflict with the standard redacted model_provider_connection_required action payload. Other errors: 429 Too Many Requests with { "error": "operator_run_limit", "limit": 5 } when the caller already has MaxConcurrentRunsPerUser (5) sessions actively in progress — counted from durable run status, so opening, listing, or replying to an existing conversation never consumes a slot and the API's replicas agree on the count; other 4xx from AssistantRunHttpException; a model/provider failure maps to 401 (auth), 429 (rate limited), or 503 (other provider failure).
Lists the caller's own sessions, newest first. Never returns another user's sessions.
Query: ?limit= — optional, defaults to 50.
Response 200 OK:
{
"runs": [
{ "run_id": "3fa85f64-...", "status": "in_progress", "title": "What's blocked on the board right now?", "created_at": "2026-07-16T21:09:45.75Z" }
]
}Sends the next user message into an existing session and runs a turn. If the session isn't cached in the pod that receives this request — because it went idle (30-minute timeout), the request landed on a different replica, or the pod restarted — it's transparently rehydrated from the session's persisted history before the turn runs; see Assistant runtime — Deep Dive for the mechanism. A session already idle-closed is flipped back to in_progress.
Request:
{ "message": "And which of those are mine?" }message is required; a blank/whitespace-only value returns 400 Bad Request (error: "message_required").
Response 200 OK:
{
"run_id": "3fa85f64-...",
"message": "Of those 4 in Ready, 2 are assigned to you...",
"status": "in_progress",
"tools_invoked": ["backlog_list"]
}Errors: 404 unknown session id, or one that's been permanently closed/deleted — AssistantRunHttpException (error: "run_not_found"); 403 the caller doesn't own the session (error: "forbidden"); provider failures map the same way as POST /api/assistant/runs.
These endpoints back the Kubernetes sandbox preview feature by running kubectl port-forward to the sandbox pod for a run. They are owner-scoped like other run endpoints.
Starts a port-forward session from a random local port to the sandbox pod's target port.
Request:
{ "targetPort": 3000 }Response 200 OK:
{
"session_id": "pf-abc123",
"local_port": 54321,
"target_port": 3000,
"pod_name": "agentweaver-agent-host-...",
"started_at": "2026-06-07T21:00:00+00:00"
}targetPort must be between 1 and 65535. Start failures return 409 Conflict with an error message.
Lists active port-forward sessions for the run. Response 200 OK is an array of { session_id, local_port, target_port, pod_name, started_at }.
Stops an active port-forward session. Response 200 OK:
{ "session_id": "pf-abc123", "stopped": true }Returns 404 Not Found when the run or port-forward session does not exist.
These endpoints read and write the per-project sandbox execution policy stored at .agentweaver/settings.yml in the project repository root. Sandbox policies control whether shell execution is enabled, which commands require human approval, and output handling options. See sandbox-setup.md for setup and deep-dive/sandboxed-execution.md for the full design.
Returns the sandbox policy for the given repository path by reading {repository_path}/.agentweaver/settings.yml. If the file does not exist, returns the default policy.
Query parameters:
| Parameter | Required | Description |
|---|---|---|
repository_path |
Yes | Absolute path to the repository |
Response 200 OK:
{
"repository_path": "C:/repos/myproject",
"shell_enabled": true,
"allowed_repository_roots": [],
"destructive_command_patterns": [
"rm -rf", "del /s", "format ", "mkfs", "dd if=",
"git push --force", "git reset --hard"
],
"require_approval_for_all_shell": false,
"redact_pii": true,
"max_output_bytes": 4194304
}Missing or malformed repository_path returns 400 Bad Request.
Creates or replaces the sandbox policy for a repository path by writing {repository_path}/.agentweaver/settings.yml. The entire policy is replaced on each PUT; there is no partial-update merge. After a PUT, the operator should commit the updated file to the project repository to record the change in version history.
Request body (all fields required):
{
"repository_path": "C:/repos/myproject",
"shell_enabled": true,
"allowed_repository_roots": [],
"destructive_command_patterns": ["rm -rf", "del /s"],
"require_approval_for_all_shell": false,
"redact_pii": true,
"max_output_bytes": 4194304
}Response 200 OK returns the stored policy. Validation failures return 400 Bad Request.
| Field | Type | Default | Notes |
|---|---|---|---|
repository_path |
string | — | Required. Lookup key. Must be an absolute path. |
shell_enabled |
bool | true |
When false, run_command is excluded from the model's tool list for this project and denied by the governance gate. |
allowed_repository_roots |
string[] | [] |
Additional paths mounted read-only inside the sandbox. |
destructive_command_patterns |
string[] | see default | Command substrings that trigger a shell.approval_required pause. |
require_approval_for_all_shell |
bool | false |
When true, every shell command requires approval regardless of pattern matching. |
redact_pii |
bool | true |
When true, emails and IP addresses are removed from command output in addition to secrets. |
max_output_bytes |
int | 4194304 |
Output cap in bytes. Exceeded output is truncated and marked output_truncated: true. |
Blueprint endpoints are global and authenticated. A blueprint response includes both the legacy workflow field and the full workflows array. Generated or inline blueprints may include bespoke_roles; each bespoke role id must also appear in roster.
Blueprint shape:
{
"id": "web-app",
"name": "Web App",
"description": "Frontend + API application",
"roster": ["product-manager", "bespoke-domain-expert"],
"workflow": "default",
"workflows": ["default"],
"review_policy": "default",
"sandbox_profile": "default",
"bespoke_roles": [
{
"id": "bespoke-domain-expert",
"title": "Domain Expert",
"charter": "Inline charter text used when no catalog role fits."
}
]
}Lists predefined blueprints.
Response 200 OK:
{ "blueprints": [ { "...": "BlueprintDto" } ] }Generates a single blueprint from a free-text description.
Request:
{
"description": "Build a travel-planning assistant",
"project_id": null,
"target_repository": null
}When project_id is supplied, the caller must own the project and blueprint generation uses that
project's blueprint_generation_model; the generated workflow fallback uses
workflow_generation_model. Null/omitted project settings inherit the global Generation fallback.
Response 200 OK:
{
"blueprint": { "...": "BlueprintDto" },
"generated_workflow_yaml": null
}generated_workflow_yaml is present when no suitable library workflow exists and a custom workflow was generated. Validation failures return 422 Unprocessable Entity with error: "blueprint_generation_failed" and details.
Validates a blueprint shape, workflow/review policy references, sandbox profile, and roster roles. Roster entries must be catalog role ids or ids declared in bespoke_roles.
Request:
{ "blueprint": { "...": "BlueprintDto" } }Response 200 OK:
{ "valid": true, "errors": [] }The full event taxonomy — types, payload fields, and per-event descriptions — is in events.md.
The done frame (no id field) signals the end of the stream.
The run.outcome event is emitted by the agent just before run.completed when the agent supports self-assessment. See events.md for the full payload.
The following event types are added by the sandboxed execution feature. They appear on the existing SSE stream alongside the base event types.
Emitted at run start after the executor selection probe completes. Present on every run.
{
"backend": "processcontainer",
"is_real_isolation": true,
"reason": "processcontainer supported"
}| Field | Type | Notes |
|---|---|---|
backend |
string | One of processcontainer, wsl-lxc, lxc-native-linux, passthrough-deny |
is_real_isolation |
bool | true when the backend provides real process isolation. false for passthrough-deny. Shell execution is denied when false. |
reason |
string | Human-readable reason from the platform probe or selection logic |
Emitted when the selected executor has a known limitation that operators should be aware of.
{
"category": "network-unrestricted",
"message": "Sandbox running with unrestricted network on Windows (allowlist enforcement unavailable). Data exfiltration surface is open.",
"backend": "processcontainer"
}| Field | Type | Notes |
|---|---|---|
category |
string | Currently only network-unrestricted — the Windows AppContainer backend cannot enforce a network allowlist |
message |
string | Human-readable description |
backend |
string | The backend that produced the warning |
Emitted when a run_command invocation matches a destructive command pattern or when require_approval_for_all_shell is true. The run pauses pending human approval.
{
"request_id": "apr-f36800fd",
"command_length": 42,
"command_hash": "sha256:a1b2c3...",
"message": "Command matches destructive pattern 'rm -rf'. Approve to proceed."
}| Field | Type | Notes |
|---|---|---|
request_id |
string | Unique ID for this approval request. Used by the (pending) approval endpoint. |
command_length |
int | Length of the command line in characters |
command_hash |
string | SHA-256 of the command line, prefixed with sha256: |
message |
string | Human-readable reason the approval was triggered |
The approval API endpoint (POST /api/runs/{id}/shell-approvals) records operator approval for a pending shell command. Use the commandHash from the shell.approval_required event as the request body's command_hash. Once approved, the model may retry the command and it will execute immediately.
POST /api/runs/{id}/shell-approvals
Content-Type: application/json
{ "command_hash": "a1b2c3d4e5f6a1b2" }Response 200 OK:
{ "run_id": "f36800fd-...", "command_hash": "a1b2c3d4e5f6a1b2", "approved": true }Returns 400 Bad Request when command_hash is missing or empty.
Emitted for each chunk of stdout or stderr produced by a sandboxed run_command invocation during streaming execution.
{
"stream": "stdout",
"data": "Hello from sandbox\n"
}| Field | Type | Notes |
|---|---|---|
stream |
string | "stdout" or "stderr" |
data |
string | A line or chunk of output from the command. PII and secrets are redacted per the sandbox policy. |
Reports the terminal outcome of a run_command invocation. Planned — not yet emitted separately from tool.result.
{
"exit_code": 0,
"timed_out": false,
"output_truncated": false
}| Field | Type | Notes |
|---|---|---|
exit_code |
int | Process exit code. -1 when the command timed out or was denied. |
timed_out |
bool | true when the command was terminated because it exceeded the configured time limit |
output_truncated |
bool | true when captured output exceeded max_output_bytes and was cut off |
All project endpoints are caller-owned unless explicitly documented as public metadata. Creating a project records the authenticated caller as owner; listing returns only that caller's projects; project-scoped mutation and child-resource endpoints require that same ownership. Non-owned resources return 403 Forbidden or 404 Not Found depending on the endpoint's existence-leak behavior.
Creates a new project. Set origin to "blank" to register a local directory as a project, or "github" to clone a GitHub repository into the working directory first.
Request:
{
"name": "my-project",
"origin": "blank",
"working_directory": "C:/repos/my-project",
"default_provider": "github-copilot",
"default_model_github_copilot": null,
"default_model_microsoft_foundry": null,
"blueprint_id": null,
"blueprint": null,
"generated_workflow_yaml": null
}For a GitHub-origin project, first mint a repository_selection_code through the repository
selection endpoints, then provide that code. The server verifies and consumes it before resolving
the repository and cloning into working_directory; direct repository URLs and identifiers are rejected.
| Field | Type | Required | Notes |
|---|---|---|---|
name |
string | Yes | Display name |
origin |
string | Yes | "blank" or "github" |
working_directory |
string | Yes | Absolute local path for the project |
repository_selection_code |
string | When origin is "github" |
Short-lived opaque selection code from POST /api/github/repository-selections |
default_provider |
string | No | "github-copilot" or "byok". The legacy "microsoft-foundry" value is still accepted on input. Falls back to the runtime default when omitted. |
default_model_github_copilot |
string | No | Model name override for the GitHub Copilot provider |
default_model_microsoft_foundry |
string | No | Model name override for the BYOK provider. The legacy field name remains supported. |
blueprint_id |
string | No | Predefined blueprint id from GET /api/blueprints. Mutually exclusive with blueprint. |
blueprint |
object | No | Inline BlueprintDto, including optional bespoke_roles. Mutually exclusive with blueprint_id. |
generated_workflow_yaml |
string | No | Custom workflow YAML returned by POST /api/blueprints/generate; materialized before applying the blueprint. |
Response 201 Created returns a project object:
{
"project_id": "a1b2c3d4-...",
"name": "my-project",
"origin": "blank",
"source_repository": null,
"working_directory": "C:/repos/my-project",
"default_branch": "main",
"owner": "local-developer",
"default_provider": "github-copilot",
"default_model_github_copilot": null,
"default_model_microsoft_foundry": null,
"blueprint_generation_model": null,
"workflow_generation_model": null,
"outcome_spec_generation_model": null,
"available": true,
"state": "active",
"source_blueprint_id": null,
"source_blueprint_type": null,
"created_at": "2026-06-07T21:00:00+00:00",
"updated_at": "2026-06-07T21:00:00+00:00"
}available is true when the working directory exists on the server filesystem. state is "active" or "deleting".
Validation failures return 400 Bad Request.
Returns all projects owned by the authenticated user. Each entry uses the same shape as the POST /api/projects response.
Returns public server metadata. Response 200 OK:
{ "data_directory": "C:/Users/name/AppData/Local/Agentweaver" }Returns a single project owned by the caller. Returns 404 Not Found when no project exists for the given id or the caller does not own it.
Renames a caller-owned project.
Request:
{ "name": "new-name" }Response 204 No Content on success. 400 when name is missing. 404 when the project does not exist or is not owned by the caller.
Updates the provider and model defaults for a caller-owned project.
Request:
{
"default_provider": "byok",
"default_model_github_copilot": null,
"default_model_microsoft_foundry": "gpt-4o",
"blueprint_generation_model": "gpt-5-mini",
"workflow_generation_model": null,
"outcome_spec_generation_model": "claude-sonnet-4.6"
}Generation model fields are individual nullable project settings. null clears a project override and
falls back to the corresponding global Generation:* setting (Generation:Model, then gpt-5.6-sol).
They do not affect Console or normal project/run agent execution model selection. Response
204 No Content on success.
Deletes a caller-owned project record. Does not touch the working directory or git history. Active runs for the project are cancelled; each cancelled run emits a run.cancelled event on its stream.
Requires the query parameter confirm=true:
DELETE /api/projects/a1b2c3d4-...?confirm=true
Without confirm=true the request returns 400 Bad Request. Response 204 No Content on success.
Lists all runs for a project. Returns a JSON array. Each entry includes agent_name identifying which team member executed the run (null when the run was not started by a cast team member):
[
{
"workflow_run_id": "workflow-...",
"execution_id": "f36800fd-...",
"status": "merged",
"model_id": null,
"task": "add license headers",
"agent_name": "Aria",
"reviewed_by": "local-developer",
"started_at": "2026-06-07T21:09:45+00:00",
"ended_at": "2026-06-07T21:10:12+00:00",
"result": null,
"coordinator_status": null,
"coordinator_status_reason": null,
"archived_at": null
}
]Deprecated direct project-run submission route. It returns 410 Gone; use POST /api/projects/{id}/orchestrations or backlog pickup instead.
The Coordinator agent can either start directly from a goal or draft a confirmable outcome spec for a goal and suspend at a confirmation gate. These endpoints are a thin HTTP layer over CoordinatorRunService; all orchestration lives in the service. A coordinator run is an ordinary run (agent_name: "Coordinator", no parent), so its events stream from GET /api/runs/{id}/stream and it is owner-scoped like any other run.
Starts a coordinator run for the project. The project's working directory, default branch, and authenticated caller are used as the run's repository path, originating branch, and submitting user. A deployment-wide BYOK provider is used when active. Otherwise, the run uses GitHub Copilot.
The project must have at least one dispatchable cast team member before an orchestration can start. The start path calls CoordinatorRosterGuard.EnsureDispatchableTeam before inserting the run (apps/Agentweaver.Api/Coordinator/CoordinatorRunService.cs:111, :125). A dispatchable member is active, has a role, and is not one of the platform-owned Scribe/Ralph/RAI/Build & Test roles (apps/Agentweaver.Api/Coordinator/CoordinatorRosterGuard.cs:54, apps/Agentweaver.Api/Coordinator/CoordinatorOrchestratorExecutor.cs:687, :750).
Request:
{
"goal": "Make the onboarding flow resumable across sessions",
"modelId": null,
"start_mode": "define_outcome"
}| Field | Type | Required | Notes |
|---|---|---|---|
goal |
string | Yes | The user's prompt/outcome for the coordinator. |
modelId |
string | No | Model override. Falls back to the project's GitHub Copilot default, then the role default. |
start_mode |
"direct" or "define_outcome" |
No | Required contract for the Start Task dialog. Omit or use "define_outcome" to preserve the current outcome-spec draft/confirm gate. Use "direct" to start coordinator planning/dispatch from goal without generating or confirming an outcome spec. Direct still enforces child tool approvals, assembly review, and merge gates. |
autoApproveTools |
bool | No | Launch with auto-approve-tools ON for the coordinator and its children. Defaults to false. |
autopilot |
bool | No | Launch with Autopilot ON: auto-answers clarifying questions and, in defineOutcome mode, auto-confirms the Phase-1 outcome spec unattended (confirmedBy = the submitting user) instead of parking at awaiting_confirmation. Does NOT auto-grant tool approvals. Cascades to children. Defaults to false. |
Response 201 Created (with Location: /api/runs/{runId}):
{ "runId": "f36800fd-..." }400 Bad Request when id is not a valid project id or goal is missing.
404 Not Found when the project does not exist.
409 Conflict when the project has no dispatchable team:
{
"error": "no_team",
"message": "This project has no team. Cast a team before starting an orchestration."
}409 Conflict with error: "project_deleting" when the project is being deleted.
409 Conflict with error: "workspace_unavailable" when the working directory is not accessible.
422 Unprocessable Entity when the team roster exists but cannot be read:
{
"error": "invalid_team",
"message": "The project team roster could not be read. Fix the team before starting an orchestration."
}Returns the current persisted outcome spec for a coordinator run. Owner-scoped.
Response 200 OK:
{
"goal": "Make the onboarding flow resumable across sessions",
"desiredOutcome": "Users can leave and resume onboarding without losing progress",
"scope": "Onboarding wizard, session persistence",
"assumptions": "Existing session store can hold partial onboarding state",
"clarifyingQuestions": "Should resumption work across devices?",
"status": "awaiting_confirmation",
"confirmedBy": null
}| Field | Type | Notes |
|---|---|---|
goal |
string | The submitted goal. |
desiredOutcome |
string | The drafted desired outcome. |
scope |
string | Drafted scope. |
assumptions |
string | Drafted assumptions. |
clarifyingQuestions |
string | Omitted when none were drafted. |
status |
string | drafting, awaiting_confirmation, confirmed, or declined. |
confirmedBy |
string | Set once confirmed; omitted otherwise. |
400 Bad Request when id is not a valid run id.
403 Forbidden when the caller does not own the run.
404 Not Found when the run or its outcome spec does not exist.
Confirms the drafted outcome spec, resuming the suspended coordinator run. Owner-scoped. No request body.
Response 200 OK with the current outcome spec (same shape as GET /api/runs/{id}/outcome-spec, or null if not yet readable).
400 Bad Request when id is not a valid run id.
403 Forbidden when the caller does not own the run.
404 Not Found when the run does not exist.
409 Conflict with error: "run_not_active" when no live coordinator run is registered for the id.
409 Conflict with error: "no_pending_gate" when the spec is not currently awaiting confirmation (for example, already confirmed).
Requests a revision of the drafted outcome spec. The coordinator re-drafts using the feedback and re-suspends at the gate. Owner-scoped.
Request:
{ "feedback": "Tighten the scope to a single device for now" }| Field | Type | Required | Notes |
|---|---|---|---|
feedback |
string | Yes | Revision guidance for the coordinator. |
Response 200 OK with the current outcome spec (same shape as GET /api/runs/{id}/outcome-spec, or null if not yet readable).
400 Bad Request when id is not a valid run id or feedback is missing.
403 Forbidden when the caller does not own the run.
404 Not Found when the run does not exist.
409 Conflict with error: "run_not_active" when no live coordinator run is registered for the id.
409 Conflict with error: "no_pending_gate" when the spec is not currently awaiting confirmation.
Confirming the outcome spec advances the coordinator run through Phase 2: confirm -> decompose -> dispatch -> observe -> steer. After confirmation, the coordinator decomposes the spec into a work plan (subtasks plus dependency edges), dispatches the ready subtasks as child runs (independent subtasks in parallel, dependent ones serialized behind their prerequisites), observes each child's read-only timeline, and relays any steering direction to the running subagents. The work plan, child runs, and steering directives are read and driven through the endpoints below; the live graph streams as coordinator.work_plan, coordinator.topology, subtask.*, and coordinator.steering events on the coordinator run's own GET /api/runs/{id}/stream.
Returns the work plan for a coordinator run: the decomposed subtasks and the dependency edges between them. Owner-scoped. Before asynchronous decomposition persists the plan, returns 404 Not Found with error: "work_plan_not_found"; for an existing coordinator run, clients should treat this as a not-ready state and retry on their normal bounded refresh cadence.
Response 200 OK:
{
"workPlanId": "a1b2c3d4-...",
"coordinatorRunId": "f36800fd-...",
"outcomeSpecId": "9e8d7c6b-...",
"status": "dispatching",
"statusReason": null,
"subtasks": [
{
"subtaskId": 5,
"title": "Add session persistence to the onboarding store",
"scope": "Persist partial onboarding state",
"assignedAgent": "morpheus",
"selectedModelId": "gpt-4o",
"phase": "execution",
"isolation": "worktree",
"status": "running",
"childRunId": "7c1f..."
}
],
"dependencies": [
{ "subtaskId": 7, "dependsOnSubtaskId": 5 }
]
}| Field | Type | Notes |
|---|---|---|
workPlanId |
string | Persisted work plan id. |
coordinatorRunId |
string | The coordinator run that owns the plan. |
outcomeSpecId |
string | The confirmed outcome spec the plan was decomposed from. |
status |
string | planned, dispatching, awaiting_assembly, assembling, in_review, complete, or a parked/terminal state assembly_blocked / assembly_failed / assembly_declined. |
statusReason |
string|null | Human-readable failure reason for a terminal plan, taken from the coordinator run's result (for example assembly_blocked: <reason>, assembly_merge_failed: <reason>, assembly_error: <message>). null while the plan is non-terminal. The UI can render "Failed: <statusReason>" without a second round-trip. |
subtasks |
array | Decomposed units of work; each has subtaskId, title, scope, assignedAgent, selectedModelId, phase, isolation, status, and childRunId (null until dispatched). |
dependencies |
array | { subtaskId, dependsOnSubtaskId } edges; a subtask dispatches only once every dependency reaches assemble_ready/completed. |
400 Bad Request when id is not a valid run id.
403 Forbidden when the caller does not own the run.
404 Not Found when the run does not exist or has no work plan yet.
Lists the child runs dispatched by a coordinator run, one row per subtask that has a child run, each paired with its subtask status. Owner-scoped. Empty array when nothing has been dispatched.
Response 200 OK:
[
{
"subtaskId": 5,
"childRunId": "7c1f...",
"subtaskStatus": "running",
"assignedAgent": "morpheus",
"selectedModelId": "gpt-4o",
"childRunStatus": "in_progress",
"worktreeBranch": "coordinator/5-session-persistence",
"treeHash": null,
"stepCount": 12
}
]| Field | Type | Notes |
|---|---|---|
subtaskId |
integer | The subtask this child run executes. |
childRunId |
string | The dispatched child run id. |
subtaskStatus |
string | The subtask's status in the work plan. |
assignedAgent |
string | The roster agent running the subtask. |
selectedModelId |
string | The model selected for the subtask. |
childRunStatus |
string | The child run's own status. |
worktreeBranch |
string | The child run's worktree branch. |
treeHash |
string | The committed worktree tree hash once the child reaches assemble-ready; null before then. |
stepCount |
integer | Steps observed on the child run so far. |
400 Bad Request when id is not a valid run id.
403 Forbidden when the caller does not own the run.
404 Not Found when the run does not exist.
Creates a steering directive that the coordinator relays to one or more running subagents. Owner-scoped.
Request:
{
"kind": "redirect",
"targetChildRunId": "7c1f...",
"instruction": "Use the existing session store instead of adding a new table"
}| Field | Type | Required | Notes |
|---|---|---|---|
kind |
string | Yes | stop, send, redirect, or amend. Pause is not supported in Phase 2. |
targetChildRunId |
string | No | The child run to steer; omit to broadcast to every active child. At the assembly review gate (see below) this instead narrows the implicated-subtask scope. |
instruction |
string | Yes | Direction relayed to the targeted subagent(s). Optional for send. |
Response 201 Created with the created directive:
{
"directiveId": "d4c3b2a1-...",
"kind": "redirect",
"targetChildRunId": "7c1f...",
"status": "queued",
"instruction": "Use the existing session store instead of adding a new table"
}A stop takes effect immediately: it cancels the targeted child run's in-flight turn. A redirect or amend takes effect at the targeted subagent's next turn boundary, without restarting the run — it is queued and applied when the child's current turn completes (or when it next suspends at a gate). The directive's progress is observable as coordinator.steering events (pending -> queued -> relayed -> applied, plus deferred at the review gate — see below) on the coordinator run stream.
Steering at the assembly review gate (#226). When the run is parked at the collective human-review gate (run.status == awaiting_review, coordinator_steerable == true), redirect/amend/send are intercepted and delivered to the parked assembly loop instead of the child-turn queue (previously they returned queued but were silently dropped):
redirect/amend→ delivered as a request-changes review decision through the same mechanism asPOST /assembly/reviewwithrequest_changes: true— the parked loop re-dispatches the implicated subtasks (#223file-scoped implication + transitive dependents) and unconditionally resets the steering budget. With no target files the scope defaults to all contributors; settargetChildRunIdto narrow to that subtask ∪ its co-touching subtasks. Settlesrelayed(ordeferred, below).send→ an advisory note on the coordinator timeline; the gate stays armed with no decision and no budget reset. Settlesapplied.
In all cases the directive reaches a definite terminal status and is never left silently queued. When the review gate is armed on a different API replica, the decision is durably persisted for the owning replica's poller to drain: the directive status is deferred and the endpoint returns 202 Accepted instead of 201 Created (mirroring the /assembly/review deferred response).
400 Bad Request when id is not a valid run id, kind is not one of stop/send/redirect/amend, or instruction is missing (required for redirect/amend).
403 Forbidden when the caller does not own the run.
404 Not Found when the run does not exist.
409 Conflict with error: "run_not_active" when no live coordinator run is registered for the id.
The ONE collective human-review gate for Phase 3 collective assembly (Feature 008). After every child subtask finishes, the coordinator builds a single integration branch (all eligible child branches merged in dependency order off the originating branch), runs a collective RAI pass over the aggregate diff, then suspends here for one human decision over the combined output of all agents. Mirrors POST /api/runs/{id}/review (owner-scoped, at-most-once) but {id} is the coordinator run id, and the decision is delivered to the service-driven gate the collective pipeline is awaiting. Owner-scoped.
In multi-replica deployments, the reviewer may submit this request to any API replica. If the receiving replica does not own the in-memory assembly pipeline but the durable work plan is still in_review at assembly stage review, the decision is stored as a deferred decision for the owner replica to pick up and apply to the armed gate. A duplicate submit while that deferred decision exists returns the same accepted response rather than replacing the original decision.
Request:
{
"approved": false,
"request_changes": true,
"feedback": "The change in src/auth/login.ts breaks logout",
"target_files": ["src/auth/login.ts"]
}| Field | Type | Required | Notes |
|---|---|---|---|
approved |
bool | Yes | true continues to the ONE collective merge → ONE collective scribe → complete. |
request_changes |
bool | No | When true (and approved is false), the coordinator re-dispatches the affected children rather than declining. |
feedback |
string | No | Free-text reviewer feedback, handed to the revising agent(s). It is not parsed for file paths — use target_files for the implicated-file hint. |
target_files |
string[] | No | Explicit list of the repo-relative files your changes should target. Used as the structured implicated-file hint: the coordinator reverse-maps it onto the subtasks that committed those files (AssemblyPlanning.ScopeImplicatedSubtasks), never prose-scraped from feedback. If omitted or unmatched, the re-dispatch falls back to all contributors. |
Decision routing
- Approve (
approved: true) → the pipeline merges the integration branch into the originating branch and runs the collective scribe, emittingcoordinator.assembly_merge_*,coordinator.assembly_scribe_*, thencoordinator.assembly_completed; the work plan reachescomplete. - Request changes (
approved: false,request_changes: true) → the coordinator scopes the re-dispatch to the subtasks that committed one of yourtarget_files(the implicated set) plus their transitive dependents, resets those subtasks topending(leaving the rest intact), returns the plan todispatching, and re-dispatches. Iftarget_filesis omitted or matches no subtask, it falls back to re-dispatching all children and emitscoordinator.assembly_implicated_scope_fallback. Emitscoordinator.assembly_changes_requested. Because a human request-changes is a supervised action, it also unconditionally resets the autonomous steering budget (there is no round-trip cap). - Decline (
approved: false,request_changes: false) → terminalassembly_declined; the coordinator emitscoordinator.assembly_declined(reason,reviewer), the work plan moves toassembly_declined, the run endsdeclined, and the coordinator stream closes.
When the pipeline arms this gate it emits coordinator.assembly_review_requested on the coordinator stream with integrationBranch, treeHash (the assembled integration tree hash), includedSubtaskIds (which subtasks the assembled output covers), raiSafetyFlagged, and hasChanges — the UI subscribes to this to know a collective human review is being requested and to render the assembled output. If the assembly background task hits an unexpected fault it emits coordinator.assembly_failed (reason, phase) and the run ends failed with result: "assembly_error: <message>".
Response 200 OK:
{ "runId": "f36800fd-...", "accepted": true }400 Bad Request when id is not a valid run id.
403 Forbidden when the caller does not own the run, or does not own the pending review request.
404 Not Found when the run does not exist.
409 Conflict with error: "no_assembly_review_pending" when no collective review is currently awaited for the run (the pipeline has not reached the gate yet, or the decision was already consumed and the work plan has left in_review).
Lists files in the coordinator assembly workspace. Owner-scoped. Returns an empty array before assembly creates the integration branch; this is a normal planning/dispatch state.
Returns diff/content metadata for a specific file in the assembly workspace. Owner-scoped.
Returns the assembly workspace tree. Owner-scoped.
Returns raw file content from the assembly workspace. Owner-scoped.
The team casting API manages the full lifecycle of AI-assisted agent team composition: listing available scenario groupings, creating and amending casting proposals, confirming a proposal into a live team, and committing the resulting .squad/ files back to the repository.
The provider for model-assisted casting is always GitHub Copilot. No provider field is accepted on casting requests.
Lists the available team templates (scenario groupings). Each template groups a curated set of agent roles suitable for a particular project type.
Response 200 OK:
[
{
"id": "quick-software-development",
"title": "Quick Software Development",
"description": "Lean team for rapid software delivery.",
"roles": [
{
"id": "software-engineer",
"title": "Software Engineer",
"summary": "Implements features and fixes bugs.",
"default_model": "gpt-4o"
}
]
}
]Lists allowed universe names for a project.
Response 200 OK:
{ "universes": ["star-wars", "marvel"] }Returns all available role definitions from the catalog. Use the id values when creating proposals in manual mode or adding individual members.
Response 200 OK: a JSON array of role objects, each with id, title, summary, and default_model.
Creates a casting proposal. Depending on mode, the server selects roles deterministically from a template, runs a model-assisted analysis, or accepts an explicit role list.
Request:
{
"mode": "scenario",
"template_id": "quick-software-development",
"universe": "star-wars",
"team_size": null,
"model_id": null,
"goal": null,
"role_ids": null
}| Field | Type | Required | Notes |
|---|---|---|---|
mode |
string | Yes | "scenario", "free_text", "analysis", or "manual" |
template_id |
string | When mode is "scenario" |
ID from GET /api/casting/templates |
goal |
string | When mode is "free_text" |
Natural-language description of the team goal |
role_ids |
string[] | When mode is "manual" |
Explicit list of role IDs from GET /api/catalog/roles |
universe |
string | No | Thematic universe name applied to agent personas (e.g. "star-wars") |
team_size |
int | No | Desired number of team members; guides model-assisted modes |
model_id |
string | No | Model override for free_text and analysis modes |
For "free_text" and "analysis" modes the server runs a GitHub Copilot model to propose roles. All modes return the proposal synchronously once ready.
Response 200 OK — a CastProposalDto:
{
"proposal_id": "prop-a1b2c3",
"mode": "scenario",
"universe": "star-wars",
"run_id": null,
"existing_team_present": false,
"warnings": [],
"rationale": "A balanced team for rapid software delivery.",
"members": [
{
"proposed_name": "Han Solo",
"role": {
"id": "software-engineer",
"title": "Software Engineer",
"summary": "Implements features and fixes bugs.",
"default_model": "gpt-4o"
},
"charter_markdown": "# Han Solo\n...",
"is_named": true,
"default_model": "gpt-4o",
"justification": null
}
]
}run_id is populated for free_text and analysis modes. Use GET /api/runs/{id}/stream to follow the model run while the proposal is being generated; the proposal is ready when the run completes. run_id is null for scenario and manual modes, which resolve synchronously.
Error responses:
| Status | Error | Meaning |
|---|---|---|
400 |
— | mode is invalid, or a required mode-specific field is missing |
404 |
— | Project not found |
409 |
project_unavailable |
The project's working directory is not accessible |
409 |
layout_conflict |
Both canonical and legacy .squad/ layouts are present |
Lists active proposals for the project. Response 200 OK is an array of CastProposalDto objects.
Returns the current state of a proposal.
Response 200 OK — a CastProposalDto (same shape as the POST response above).
Returns 404 Not Found when no proposal exists for the given id.
Amends a proposal by replacing its member list and/or universe. Use this to add, remove, or modify proposed members before confirming.
Request:
{
"universe": "marvel",
"members": [
{
"proposed_name": "Tony Stark",
"role": {
"id": "software-engineer",
"title": "Software Engineer",
"summary": "Implements features and fixes bugs.",
"default_model": "gpt-4o"
},
"charter_markdown": "# Tony Stark\n...",
"is_named": true,
"default_model": "gpt-4o",
"justification": null
}
]
}Both members and universe are optional; omit either to leave it unchanged.
Response 200 OK returns the updated CastProposalDto. Returns 404 Not Found when the proposal does not exist.
Confirms a proposal and materialises the team by writing .squad/ files. For projects with an existing team, the intent field controls how the proposed team relates to the existing one.
Request:
{
"intent": "new"
}| Field | Type | Required | Notes |
|---|---|---|---|
intent |
string | Conditionally | Required when an existing team is detected. "new" replaces the team entirely; "augment" adds the proposed roles to the existing team; "recast" rewrites all existing charters using the proposed configuration. Omit when no existing team is present. |
Response 200 OK — a TeamDto (same shape as GET /api/projects/{id}/team):
{
"project_name": "my-project",
"universe": "star-wars",
"layout": "canonical",
"migration_available": false,
"members": [
{
"name": "Han Solo",
"role_title": "Software Engineer",
"charter_path": ".squad/HanSolo/charter.md",
"status": "active",
"default_model": "gpt-4o",
"is_named": true,
"charter_created_at": "2026-06-07T21:00:00+00:00",
"charter_updated_at": "2026-06-07T21:00:00+00:00"
}
]
}Notable error responses:
| Status | Error | Meaning |
|---|---|---|
404 |
— | Proposal or project not found |
409 |
requires_choice |
An existing team was detected and intent was not provided |
409 |
layout_conflict |
Both canonical and legacy .squad/ layouts are present; resolve manually before confirming |
409 |
project_unavailable |
The project's working directory is not accessible |
Rejects a proposal. No .squad/ files are written or modified. Response 204 No Content.
Returns the current team roster and layout metadata.
Response 200 OK:
{
"project_name": "my-project",
"universe": "star-wars",
"layout": "canonical",
"migration_available": false,
"members": [
{
"name": "Han Solo",
"role_title": "Software Engineer",
"charter_path": ".squad/HanSolo/charter.md",
"status": "active",
"default_model": "gpt-4o",
"is_named": true,
"charter_created_at": "2026-06-07T21:00:00+00:00",
"charter_updated_at": "2026-06-07T21:05:00+00:00"
}
]
}| Field | Values | Notes |
|---|---|---|
layout |
"canonical", "legacy", "conflict", "absent" |
.squad/<Name>/ = canonical; .squad/casting/<Name>/ = legacy; both present = conflict |
migration_available |
bool | true when a legacy layout exists and no canonical layout is present |
status |
"active", "retired" |
Member lifecycle state |
Returns 404 Not Found when no team exists for the project.
Returns the charter for a team member as a JSON object.
Response 200 OK:
{
"member_name": "Han Solo",
"content": "# Han Solo\n\nYou are Han Solo, Software Engineer..."
}Returns 404 Not Found when the member does not exist or has no charter file.
Replaces the charter for a team member.
Request:
{
"content": "# Han Solo\n\nUpdated charter content..."
}Response 200 OK returns { "member_name": "...", "content": "..." }. Returns 404 Not Found when the member does not exist.
Adds a new member to the team. Creates the member's .squad/ directory and an initial charter file generated from the specified role.
Request:
{
"role_id": "software-engineer",
"custom_role_title": null,
"model_id": null
}| Field | Type | Required | Notes |
|---|---|---|---|
role_id |
string | Yes | Role ID from GET /api/catalog/roles |
custom_role_title |
string | No | Override the role's default title for this member |
model_id |
string | No | Override the role's default model for this member |
Response 200 OK — a TeamMemberDto (same shape as members in GET /api/projects/{id}/team).
Retires a team member. Their .squad/ directory and charter file are preserved; the member's status is set to "retired".
Response 204 No Content. Returns 404 Not Found when the member does not exist.
Re-roles an existing member, regenerating their charter for the new role.
Request:
{
"new_role_id": "product-manager",
"custom_role_title": null
}| Field | Type | Required | Notes |
|---|---|---|---|
new_role_id |
string | Yes | New role ID from GET /api/catalog/roles |
custom_role_title |
string | No | Override the role's default title |
Response 200 OK — the updated TeamMemberDto. Returns 404 Not Found when the member does not exist.
Returns the pending uncommitted changes in the project's .squad/ directory and a hash of the current change set.
Response 200 OK:
{
"changes": [
{ "path": ".squad/HanSolo/charter.md", "kind": "modified" }
],
"change_set_hash": "sha256:a1b2c3...",
"nothing_to_sync": false
}changes is an empty array and nothing_to_sync is true when there is nothing to commit. change_set_hash must be passed to POST /api/projects/{projectId}/team/sync to prevent stale commits.
Commits the pending .squad/ changes to the project repository.
Request:
{
"expected_change_set_hash": "sha256:a1b2c3...",
"message": "Update Han Solo charter"
}| Field | Type | Required | Notes |
|---|---|---|---|
expected_change_set_hash |
string | Yes | Hash from GET /api/projects/{projectId}/team/sync. The server rejects the commit if the change set has shifted since you fetched it. |
message |
string | No | Commit message. A default message is used when omitted. |
Returns 409 Conflict with error: "sync_state_changed" when the change set hash does not match. Fetch a fresh hash from GET /api/projects/{projectId}/team/sync and retry.
Response 200 OK returns { "commit_id": "..." }.
SQLite tables are created on startup with WAL enabled:
| Table | Purpose |
|---|---|
runs |
Run records with status, timing, submitting user, task, model source, model id, project id, and the final result text |
projects |
Project records with name, origin, working directory, default branch, owner, provider settings, and state |
github_tokens |
Per-user GitHub tokens stored by the OS credential store (not a SQLite table — managed by OsCredentialStoreGitHubTokenStore) |
The run's event stream is held in memory by RunStreamStore and is not persisted to SQLite. After a process restart, the granular event history is unavailable — only the final result text survives. Completed runs are persisted via the Copilot SDK session store (session ID = agentweaver-run-{runId}). The GET /api/runs/{id}/history endpoint replays persisted session events for terminal runs.
| Key | Default | Purpose |
|---|---|---|
Database:Path |
agentweaver.db in the app data directory |
SQLite database file |
Worktrees:BasePath |
worktrees in the app data directory |
Root folder for run worktrees |
Git:Author:Name |
Agentweaver |
Author name for commits and merges |
Git:Author:Email |
agentweaver@localhost |
Author email for commits and merges |
RunBounds:MaxSteps |
50 |
Maximum tool-call steps before run.bounded |
RunBounds:MaxMinutes |
10 |
Maximum wall-clock duration in minutes |
| Key | Default | Purpose |
|---|---|---|
Runs:AllowedRepositoryRoots |
[] (permissive) |
String array of allowed parent directories for repository_path. Symlinks and junctions in the submitted path are resolved and the final location must fall within one of these roots. When empty (the default), any valid local absolute path is accepted. Shared, exposed, or multi-tenant deployments MUST configure this. |
| Key | Default | Purpose |
|---|---|---|
Auth:Keys |
none | Array of { Token, User } API keys |
Auth:ApiKey |
none | Single-key alternative |
Auth:User |
none | User paired with Auth:ApiKey |
| Key | Default | Purpose |
|---|---|---|
Providers:GitHubCopilot:ApiKey |
none | GitHub Copilot provider credential |
Providers:GitHubCopilot:Endpoint |
https://api.githubcopilot.com |
GitHub Copilot base URL |
Providers:GitHubCopilot:Model |
claude-sonnet-4.6 |
GitHub Copilot model name |
Providers:GitHubCopilot:RuntimeCliPath |
"" (empty) |
Optional explicit path to the native Copilot CLI binary; empty means use the SDK's auto-resolved runtime. Env fallbacks (in order): AGENTWEAVER_COPILOT_CLI_PATH, COPILOT_CLI_PATH. Grounded in packages/Agentweaver.AgentRuntime/Providers/GitHubCopilotClientFactory.cs:50. See Configuration. |
Generation:Model |
gpt-5.6-sol |
Global fallback for blueprint, workflow, and coordinator outcome-spec generation. |
Generation:BlueprintModel |
Generation:Model |
Optional global fallback when a project has no blueprint_generation_model. |
Generation:WorkflowModel |
Generation:Model |
Optional global fallback when a project has no workflow_generation_model. |
Generation:OutcomeSpecModel |
Generation:Model |
Optional global fallback when a project has no outcome_spec_generation_model. |