Skip to content

Colony Monitor REST API

The monitor process exposes an HTTP API for pipeline observability, issue lifecycle management, and Prometheus metrics scraping.

When agents.monitor.auth is configured, all API and SSE endpoints are gated behind a session. Browsers interact through the dashboard’s in-page login form — there is no native browser Basic Auth prompt. Programmatic and CLI clients (e.g. the @runcolony/sdk ColonyClient) authenticate by sending a valid Authorization: Basic header directly.

colony.config.yaml
agents:
monitor:
auth:
username: colony
password_env: COLONY_DASHBOARD_PASSWORD # env var holding the password

Login flow (browser): POST /api/auth/login with { "username", "password" } validates against the configured credential and, on success, sets a signed COLONY_SESSION httpOnly cookie. The cookie is valid for 12 hours. POST /api/auth/logout clears it. GET /api/auth/session returns { "authenticated": boolean } and can be polled without triggering the auth gate.

Session secret: the signing secret is generated fresh on each monitor process start — a monitor restart invalidates all existing session cookies and requires operators to log in again.

Credentials are compared using timing-safe equality. On failure the server returns:

  • Status: 401 Unauthorized
  • Body: JSON ApiError{"error": "...", "code": "AUTH_REQUIRED" | "AUTH_FAILED", "status": 401}

No WWW-Authenticate: Basic challenge is ever sent.

Auth-exempt endpoints (no session or Authorization header required):

  • GET /health
  • GET /prometheus
  • GET /api/openapi.json
  • GET / — SPA shell (login form must be able to render before auth)
  • GET /assets/* — static dashboard assets
  • POST /api/auth/login, POST /api/auth/logout, GET /api/auth/session

If no auth block is configured, all endpoints are open.

Log in with the configured monitor credential. No prior auth required.

Request body{ "username": string, "password": string }

ResponseAuthLoginResponse

On success: 200 { "ok": true } with Set-Cookie: COLONY_SESSION=<token>; HttpOnly; Secure; SameSite=Strict. On failure: 401 { "code": "AUTH_FAILED" }. Malformed JSON: 400 { "code": "INVALID_JSON" }. When no auth block is configured: 200 { "ok": true } (no cookie set, not needed).


Clear the session cookie. No auth required.

ResponseAuthLogoutResponse

200 { "ok": true } with Set-Cookie: COLONY_SESSION=; Max-Age=0 (cookie cleared).


Return whether the current request is authenticated. No auth required.

ResponseAuthSessionResponse

200 { "authenticated": boolean }true when the request carries a valid session cookie or a valid Authorization: Basic header, or when no auth block is configured.


Returns server liveness. No auth required.

ResponseHealthResponse

{ "status": "healthy", "uptime": 3600 }
FieldTypeDescription
statusstringAlways "healthy"
uptimenumberSeconds since the process started

Returns the full pipeline dashboard snapshot. Auth required.

ResponseDashboardResponse

{
"status": "healthy",
"uptime": 3600,
"agents": {
"sprint-master": {
"agentName": "sprint-master",
"reachable": true,
"timestamp": 1711929600000,
"responseTimeMs": 42,
"health": { "status": "healthy" },
"costUsd": 1.23,
"inFlightIssues": ["owner/repo#10"],
"tenantSpend": { "default": 1.23 }
}
},
"alerts": [],
"costTotalUsd": 12.5,
"pipelineSummary": { "analyzing": 1, "ready-for-dev": 3 },
"workerPool": []
}
FieldTypeDescription
status"healthy" | "degraded" | "unhealthy"Overall pipeline health
uptimenumberSeconds since process start
agentsRecord<string, AgentHealthSnapshot>Per-agent health snapshots
alertsAlert[]Active alerts from failure detectors
costTotalUsdnumberTotal cost across all agents
costByWindow?CostByWindowCost broken down by time window
pipelineSummaryRecord<string, number>Issue counts by pipeline state
issueTimelines?Record<number, IssueTimeline>Per-issue timelines
tenants?TenantBudgetDashboard[]Per-tenant budget info
metrics?Record<string, PipelineMetrics>Per-repo pipeline metrics
workerPool?WorkerPoolSnapshot[]Per-repo worker pool status

Returns the timeline for a single issue. Auth required.

Path parameters:

ParamTypeDescription
ownerstringRepository owner
repostringRepository name
issuenumberIssue number

ResponseTimelineResponse

TimelineResponse extends IssueTimeline with two additional fields:

{
"issueNumber": 42,
"entries": [],
"totalCostUsd": 0.85,
"totalDurationMs": 120000,
"currentState": "ready-for-dev",
"stateTransitions": [],
"isPaused": false,
"isBlocked": false
}
FieldTypeDescription
issueNumbernumberThe issue number
entriesTimelineEntry[]Chronological timeline entries
totalCostUsdnumberTotal cost for this issue
totalDurationMsnumberTotal wall-clock duration
currentStatestringCurrent pipeline state
staleSinceMs?numberHow long the issue has been stale
stateTransitionsStateTransition[]State change history
isPausedbooleanWhether the issue is paused
isBlockedbooleanWhether the issue is blocked

Errors:

StatusCodeWhen
400Expected /api/timeline/:owner/:repo/:issueMalformed path
400Invalid issue numberNon-numeric issue param
503No repo context available for owner/repoRepo not configured
500Failed to build timelineInternal error

Lists all tracks for a repository. Auth required.

The repo query parameter is required. Requests without a ? query string do not match this route and return a 404.

Query parameters:

ParamTypeRequiredDescription
repostringYesRepository as owner/repo

ResponseTrackListResponse

{
"tracks": [
{
"name": "bugs",
"label": "bug",
"cooldown_minutes": 60,
"enabled": true,
"active_version": 1
}
]
}

Errors:

StatusCodeWhen
400Missing or invalid repo query parameterMissing or malformed repo

Creates or updates a track. Auth required.

Request bodyTrackCreateRequest

{
"repo": "owner/repo",
"name": "bugs",
"label": "bug",
"cooldown_minutes": 30,
"enabled": true
}
FieldTypeRequiredDefaultDescription
repostringYesRepository as owner/repo
namestringYesTrack name
labelstringNo""GitHub label filter
cooldown_minutesnumberNo60Cooldown between runs
enabledbooleanNotrueWhether the track is active

ResponseTrackCreateResponse

{ "ok": true }

Errors:

StatusCodeWhen
400Invalid JSON bodyUnparseable request body
400Missing required fields: repo, nameMissing repo or name

Updates a track’s proposal slate, cadence, or weight. Only the fields provided in the body are changed; omitted fields retain their current values. Auth required.

Path parameters:

ParamTypeDescription
ownerstringRepository owner
repostringRepository name
namestringTrack name

Request bodyTrackUpdateRequest

{
"proposal": { "slate": [{ "size": "medium", "count": 3 }] },
"cadence": { "kind": "cron", "expr": "0 9 * * MON" },
"weight": 2
}
FieldTypeRequiredDescription
labelstringNoGitHub label filter
cooldown_minutesnumberNoCooldown between runs (≥ 1)
enabledbooleanNoWhether the track is active
proposalProposalSlate or nullNoCustom proposal slate; null clears a previously set slate (uses default)
cadence{ kind, expr? }NoCadence override; kind is "cooldown" or "cron", expr required for cron
weightnumberNoRelative weight for track selection (≥ 0)

ResponseTrackUpdateResponse

{ "ok": true }

Errors:

StatusCodeWhen
400INVALID_JSONUnparseable request body
400INVALID_BODYInvalid slate, cadence, or weight
404TRACK_NOT_FOUNDTrack does not exist

Deletes a track. Auth required.

Path parameters:

ParamTypeDescription
ownerstringRepository owner
repostringRepository name
namestringTrack name

ResponseTrackDeleteResponse

{ "ok": true }

POST /api/tracks/:owner/:repo/:name/enable

Section titled “POST /api/tracks/:owner/:repo/:name/enable”

Enables a track. Auth required.

ResponseTrackToggleResponse

{ "ok": true }

Errors:

StatusCodeWhen
404Track "name" not foundTrack does not exist

POST /api/tracks/:owner/:repo/:name/disable

Section titled “POST /api/tracks/:owner/:repo/:name/disable”

Disables a track. Auth required.

ResponseTrackToggleResponse

{ "ok": true }

Errors:

StatusCodeWhen
404Track "name" not foundTrack does not exist

GET /api/tracks/:owner/:repo/:name/prompts

Section titled “GET /api/tracks/:owner/:repo/:name/prompts”

Lists all prompt versions for a track. Auth required.

ResponseTrackPromptListResponse

{
"versions": [{ "version": 1, "created_at": "2026-01-15T10:00:00Z", "notes": "initial" }]
}

POST /api/tracks/:owner/:repo/:name/prompts

Section titled “POST /api/tracks/:owner/:repo/:name/prompts”

Creates a new prompt version for a track. Auth required.

Request bodyTrackPromptCreateRequest

{
"seed_title": "Fix {{label}} issues",
"seed_body": "Investigate and fix the problem.",
"instructions": "Follow repo conventions.",
"notes": "v2 with better instructions"
}
FieldTypeRequiredDefaultDescription
seed_titlestringNo""Issue title template
seed_bodystringNo""Issue body template
instructionsstringNo""Agent instructions
notesstringNo""Human-readable version notes

ResponseTrackPromptCreateResponse

{ "version": 2 }

Errors:

StatusCodeWhen
400Invalid JSON bodyUnparseable request body
404variesTrack not found

GET /api/tracks/:owner/:repo/:name/prompts/:version

Section titled “GET /api/tracks/:owner/:repo/:name/prompts/:version”

Returns a single prompt version. Auth required.

Path parameters:

ParamTypeDescription
versionnumberPrompt version number

ResponseTrackPromptGetResponse

{
"prompt": {
"version": 1,
"seed_title": "Fix {{label}} issues",
"seed_body": "Investigate and fix the problem.",
"instructions": "Follow repo conventions."
}
}

Errors:

StatusCodeWhen
404Version N not foundVersion does not exist

POST /api/tracks/:owner/:repo/:name/prompts/:version/activate

Section titled “POST /api/tracks/:owner/:repo/:name/prompts/:version/activate”

Sets a prompt version as the active version for a track. Auth required.

ResponseTrackPromptActivateResponse

{ "ok": true }

Errors:

StatusCodeWhen
404variesTrack or version not found

POST /api/tracks/:owner/:repo/:name/cooldown

Section titled “POST /api/tracks/:owner/:repo/:name/cooldown”

Sets the cooldown period for a track. Auth required.

Path parameters:

ParamTypeDescription
ownerstringRepository owner
repostringRepository name
namestringTrack name

Request bodyTrackCooldownRequest

{ "cooldown_minutes": 120 }
FieldTypeRequiredDescription
cooldown_minutesnumberYesMinimum minutes between track runs; must be a positive integer

ResponseTrackCooldownResponse

{ "ok": true }

Errors:

StatusCodeWhen
400INVALID_JSONUnparseable request body
400INVALID_BODYcooldown_minutes is not a positive integer
404TRACK_NOT_FOUNDTrack does not exist
503STORE_UNAVAILABLEPipeline store (Postgres) not connected

All issue action endpoints follow the pattern POST /api/issues/:owner/:repo/:issue/:action. Auth required.

Path parameters:

ParamTypeDescription
ownerstringRepository owner
repostringRepository name
issuenumberIssue number

ResponseIssueActionResponse

resume, unblock, and retry include a state field with the issue’s current pipeline state. pause and cancel do not.


POST /api/issues/:owner/:repo/:issue/pause

Section titled “POST /api/issues/:owner/:repo/:issue/pause”

Pauses an issue. Adds the colony:paused label.

{ "ok": true }

POST /api/issues/:owner/:repo/:issue/resume

Section titled “POST /api/issues/:owner/:repo/:issue/resume”

Resumes a paused issue. Removes colony:paused and re-enqueues the work task.

{ "ok": true, "state": "ready-for-dev" }

POST /api/issues/:owner/:repo/:issue/unblock

Section titled “POST /api/issues/:owner/:repo/:issue/unblock”

Unblocks an issue. Removes colony:blocked and re-enqueues the work task.

{ "ok": true, "state": "analyzing" }

POST /api/issues/:owner/:repo/:issue/retry

Section titled “POST /api/issues/:owner/:repo/:issue/retry”

Re-enqueues the work task for an issue in its current state.

{ "ok": true, "state": "in-review" }

Errors:

StatusCodeWhen
404Issue not found: owner/repo#NIssue not in pipeline store

POST /api/issues/:owner/:repo/:issue/cancel

Section titled “POST /api/issues/:owner/:repo/:issue/cancel”

Cancels an issue by transitioning it to done.

{ "ok": true }

POST /api/issues/:owner/:repo/:issue/transition

Section titled “POST /api/issues/:owner/:repo/:issue/transition”

Forces an issue to a specific pipeline state. The target state must be a valid outgoing edge in the issue’s active workflow snapshot (use GET …/valid-transitions to retrieve the permitted targets). Auth required.

Request bodyTransitionRequest

{ "targetState": "in-review" }
FieldTypeRequiredDescription
targetStatestringYesThe state to transition to; must be a valid edge in the workflow snapshot

ResponseTransitionResponse

{ "ok": true, "from": "analyzing", "to": "ready-for-dev" }
FieldTypeDescription
oktrueAlways true on success
fromstringState the issue transitioned from
tostringState the issue transitioned to

Errors:

StatusCodeWhen
400INVALID_JSONUnparseable request body
400INVALID_STATEtargetState is missing or not a string
400WORKFLOW_UNAVAILABLENo workflow snapshot is registered for this repo
400INVALID_TRANSITIONtargetState is not a valid outgoing edge from the current state; error message includes the valid targets
404ISSUE_NOT_FOUNDIssue not found in the pipeline store

Common issue action errors (repo not found, store unavailable, etc.) also apply — see the table below.


Common issue action errors:

StatusCodeWhen
404Not foundMalformed URL path
404Repo not found: owner/repoRepo not configured
404variesError message containing “not found”
500Internal server errorUnexpected failure
503Pipeline store not availableNo database connection

The six read-only endpoints below expose per-issue analytics and workflow state. They follow the same path prefix as the issue action endpoints: GET /api/issues/:owner/:repo/:issue/:detail. Auth required.

Shared path parameters:

ParamTypeDescription
ownerstringRepository owner
repostringRepository name
issuenumberIssue number

GET /api/issues/:owner/:repo/:issue/findings

Section titled “GET /api/issues/:owner/:repo/:issue/findings”

Returns agent findings recorded against a specific issue, paginated. Auth required.

Query parameters:

ParamTypeRequiredDefaultDescription
limitnumberNo100Page size (1–200; out-of-range values are clamped)
offsetnumberNo0Row offset for pagination

ResponseFindingsResponse (PaginatedResponse<AgentFinding>)

{
"items": [
{
"id": "f1a2b3c4-...",
"agentName": "developer",
"taskId": "t9z8y7x6-...",
"findingType": "semantic_conflict",
"category": "maintainability",
"severity": "medium",
"confidence": 0.85,
"title": "Unused variable in auth handler",
"summary": "Variable `ctx` is declared but never read in `handleAuth`.",
"recommendation": "Remove the unused variable or use it in the handler.",
"evidence": [{ "kind": "file", "path": "src/auth.ts", "lineStart": 42 }],
"blocksProgress": false,
"status": "open",
"issueNumber": 42,
"createdAt": "2026-01-15T10:00:00Z",
"updatedAt": "2026-01-15T10:00:00Z"
}
],
"page": { "total": null, "limit": 100, "offset": 0, "hasMore": false }
}

AgentFinding fields:

FieldTypeDescription
idstringUUID
agentNamestringAgent that emitted the finding
taskIdstring | nullTask that produced the finding
findingTypestringMachine-readable type. Known values: ci_failure, cost_anomaly, deterministic_check_failure, integration_gap, integration_issue, obs, pre_existing_check_failure, protected_path_violation, requirements_gap, review_finding, risk, semantic_conflict. Free-form strings are also accepted.
categorystringBroad category (e.g. "maintainability")
severitystring"info" | "low" | "medium" | "high" | "critical"
confidencenumber | null0–1 confidence score; null when not computed
titlestringShort human-readable title
summarystringDetailed description
recommendationstring | nullSuggested remediation
evidenceAgentFindingEvidence[]Supporting evidence references (see below)
blocksProgressbooleanWhether this finding stalls the pipeline
statusstring"open" | "accepted" | "resolved" | "dismissed" | "superseded"
issueNumbernumber | nullIssue this finding is attached to
createdAtstringISO 8601 timestamp
updatedAtstringISO 8601 timestamp

AgentFindingEvidence fields:

FieldTypeDescription
kindstringEvidence type: "file", "symbol", "issue", "pr", "url", "text", etc.
ref?stringSymbol or entity reference
path?stringFile path (for "file" / "symbol")
lineStart?numberStart line (for "file" / "symbol")
lineEnd?numberEnd line
url?stringURL (for "url")
summary?stringHuman-readable excerpt

PageInfo fields (returned in page):

FieldTypeDescription
totalnumber | nullTotal matching rows; null for this endpoint
limitnumberApplied page size
offsetnumberApplied offset
hasMorebooleanWhether more pages are available

Errors:

StatusCodeWhen
400INVALID_INPUTMalformed path (wrong segment count)
400INVALID_ISSUE_NUMBERNon-numeric issue number in path
500INTERNAL_ERRORFailed to query issue findings
503STORE_UNAVAILABLEPipeline store (Postgres) not connected

GET /api/issues/:owner/:repo/:issue/digest

Section titled “GET /api/issues/:owner/:repo/:issue/digest”

Returns a diagnostic summary for a single issue: current state, block classification, recent failed runs, active tasks, and auto-generated recommendations. Auth required.

ResponseIssueDiagnosticResponse

{
"issue": {
"issueNumber": 42,
"title": "Fix login redirect",
"state": "failure-blocked",
"isBlocked": true,
"isPaused": false,
"blockReason": "build_failure",
"blockedFromState": "ready-for-dev",
"stateEnteredAt": "2026-01-15T09:00:00Z"
},
"blockClassification": "transient",
"autoUnblockCount": 1,
"maxAutoUnblocks": 3,
"recentFailedRuns": [
{
"agentName": "developer",
"errorMessage": "tsc: error TS2304: Cannot find name 'Foo'",
"finishedAt": "2026-01-15T09:05:00Z"
}
],
"activeTasks": [],
"recommendations": ["Retry after resolving the TypeScript compilation error."]
}
FieldTypeDescription
issueobject | nullCurrent issue state; null when not tracked in the pipeline
issue.issueNumbernumberIssue number
issue.titlestringIssue title
issue.statestringCurrent pipeline state
issue.isBlockedbooleanWhether the issue is blocked
issue.isPausedbooleanWhether the issue is paused
issue.blockReasonstring | nullBlock reason code (e.g. "build_failure")
issue.blockedFromStatestring | nullState from which the issue was blocked
issue.stateEnteredAtstring | nullISO 8601 timestamp when the current state was entered
blockClassification"transient" | "permanent" | "quota" | "unknown" | nullBlock type; null when not blocked
autoUnblockCountnumberNumber of automatic unblock attempts so far
maxAutoUnblocksnumberConfigured maximum auto-unblock attempts per issue
recentFailedRunsobject[]Recent agent run failures
recentFailedRuns[].agentNamestringAgent that failed
recentFailedRuns[].errorMessagestring | nullError message from the failed run
recentFailedRuns[].finishedAtstring | nullISO 8601 timestamp when the run finished
activeTasksobject[]Tasks currently pending or claimed for this issue
activeTasks[].taskTypestringTask type (e.g. "develop")
activeTasks[].statusstringTask status ("pending" or "claimed")
activeTasks[].claimedBystring | nullWorker ID that claimed the task, if claimed
recommendationsstring[]Auto-generated operator action suggestions

Errors:

StatusCodeWhen
404NOT_FOUNDMalformed path (regex did not match)
500INTERNAL_ERRORFailed to get issue digest
503STORE_UNAVAILABLEPipeline store (Postgres) not connected

GET /api/issues/:owner/:repo/:issue/status

Section titled “GET /api/issues/:owner/:repo/:issue/status”

Returns the lightweight status snapshot for an issue: current state, when it was entered, and when the last event occurred. Auth required.

ResponseIssueStatusResponse

{
"currentStatus": "Implementing fix",
"state": "ready-for-dev",
"stateEnteredAt": "2026-01-15T08:00:00Z",
"lastEventAt": "2026-01-15T09:30:00Z"
}
FieldTypeDescription
currentStatusstring | nullLatest status message emitted by the agent, if any
statestringCurrent pipeline state
stateEnteredAtstring | nullISO 8601 timestamp when the current state was entered
lastEventAtstring | nullISO 8601 timestamp of the most recent issue event

Errors:

StatusCodeWhen
404NOT_FOUNDMalformed path or issue not in pipeline
500INTERNAL_ERRORFailed to get issue status
503STORE_UNAVAILABLEPipeline store (Postgres) not connected

GET /api/issues/:owner/:repo/:issue/events

Section titled “GET /api/issues/:owner/:repo/:issue/events”

Returns paginated issue events (status updates, state transitions, operator actions) for a single issue. Auth required.

Query parameters:

ParamTypeRequiredDefaultDescription
limitnumberNo50Page size
sincestringNoISO 8601 timestamp; return only events after this
offsetnumberNo0Row offset for pagination

ResponseIssueEventsResponse (PaginatedResponse<IssueEvent>)

{
"items": [
{
"id": 1001,
"issueId": 7,
"actorType": "agent",
"actorId": "worker-1",
"status": "Starting implementation",
"details": null,
"state": "ready-for-dev",
"createdAt": "2026-01-15T08:01:00Z"
}
],
"page": { "total": null, "limit": 50, "offset": 0, "hasMore": false }
}

IssueEvent fields:

FieldTypeDescription
idnumberEvent row ID
issueIdnumberInternal pipeline issue ID
actorType"agent" | "workflow-engine" | "operator" | "system"Who produced this event
actorIdstring | nullWorker ID or operator identifier
statusstringHuman-readable status message
detailsobject | nullStructured metadata attached to the event
statestringPipeline state at the time of the event
createdAtstringISO 8601 timestamp

Errors:

StatusCodeWhen
400INVALID_INPUTlimit or offset is not a valid number
404NOT_FOUNDMalformed path or issue not found in pipeline
500INTERNAL_ERRORFailed to get issue events
503STORE_UNAVAILABLEPipeline store (Postgres) not connected

GET /api/issues/:owner/:repo/:issue/retrospective

Section titled “GET /api/issues/:owner/:repo/:issue/retrospective”

Returns the post-issue retrospective for a completed issue, or null when none has been recorded yet. Auth required.

ResponseIssueRetrospectiveResponse

{
"retrospective": {
"id": "r1a2b3c4-...",
"issueNumber": 42,
"status": "complete",
"outcomeClassification": "success",
"summary": "Implemented login redirect fix with 2 retries.",
"plannedFiles": { "src/auth.ts": "modify" },
"actualFiles": { "src/auth.ts": "modify", "src/auth.test.ts": "modify" },
"failures": null,
"lessons": { "note": "Test coverage was added opportunistically." },
"createdAt": "2026-01-16T10:00:00Z",
"updatedAt": "2026-01-16T10:00:00Z"
}
}

When no retrospective has been recorded, retrospective is null:

{ "retrospective": null }
FieldTypeDescription
retrospectiveobject | nullRetrospective record; null if not yet recorded
retrospective.idstringUUID
retrospective.issueNumbernumberIssue number
retrospective.statusstringProcessing status (e.g. "complete")
retrospective.outcomeClassificationstring | nullOutcome label (e.g. "success", "failure")
retrospective.summarystringHuman-readable summary of the issue outcome
retrospective.plannedFilesobject | nullFiles the agent planned to touch
retrospective.actualFilesobject | nullFiles the agent actually touched
retrospective.failuresobject | nullStructured failure details, if any
retrospective.lessonsobject | nullLessons learned, promoted to repo intelligence
retrospective.createdAtstringISO 8601 timestamp
retrospective.updatedAtstringISO 8601 timestamp

Errors:

StatusCodeWhen
400INVALID_INPUTMalformed path (wrong segment count)
400INVALID_ISSUE_NUMBERNon-numeric issue number in path
500INTERNAL_ERRORFailed to fetch issue retrospective
503STORE_UNAVAILABLEPipeline store (Postgres) not connected

GET /api/issues/:owner/:repo/:issue/valid-transitions

Section titled “GET /api/issues/:owner/:repo/:issue/valid-transitions”

Returns the issue’s current pipeline state and the set of states it can legally transition to, as defined by the active workflow snapshot. Use this to populate a transition picker before calling POST …/transition. Auth required.

ResponseValidTransitionsResponse

{
"currentState": "ready-for-dev",
"validTargets": ["in-review", "failure-blocked"]
}
FieldTypeDescription
currentStatestringCurrent pipeline state of the issue
validTargetsstring[]States the issue can transition to from currentState

Errors:

StatusCodeWhen
404NOT_FOUNDMalformed path (regex did not match)
404ISSUE_NOT_FOUNDIssue not found in the pipeline store
400WORKFLOW_UNAVAILABLENo workflow snapshot is registered for this repo
503STORE_UNAVAILABLEPipeline store (Postgres) not connected

Lists agent findings for a repository. Auth required.

The repo query parameter is required. Requests missing it return 400.

Query parameters:

ParamTypeRequiredDefaultDescription
repostringYesRepository as owner/repo
statusstringNoFilter by status (open, accepted, resolved, dismissed, superseded)
categorystringNoFilter by category
severitystringNoFilter by severity (info, low, medium, high, critical)
limitintegerNo100Page size (max 200)
offsetintegerNo0Page offset

ResponseFindingsResponse (PaginatedResponse<AgentFinding>)

{
"items": [
{
"id": "f_abc123",
"tenantId": 1,
"repoId": 42,
"issueId": 100,
"issueNumber": 17,
"prId": null,
"prNumber": null,
"agentName": "reviewer",
"taskId": "task_xyz",
"findingType": "semantic_conflict",
"category": "injection",
"severity": "high",
"confidence": 0.92,
"title": "SQL injection risk in user query path",
"summary": "Raw string interpolation detected in a database query.",
"recommendation": "Use parameterized queries.",
"evidence": [],
"metadata": {},
"blocksProgress": false,
"status": "open",
"resolvedByIssueId": null,
"resolvedAt": null,
"dismissedReason": null,
"createdAt": "2026-01-15T10:00:00Z",
"updatedAt": "2026-01-15T10:00:00Z"
}
],
"page": { "total": null, "limit": 100, "offset": 0, "hasMore": false }
}
FieldTypeDescription
idstringUUID identifying this finding
tenantIdnumberTenant the finding belongs to
repoIdnumberRepository the finding belongs to
issueIdnumber | nullPipeline issue ID (internal)
issueNumbernumber | nullGitHub issue number
prIdnumber | nullPipeline PR ID (internal)
prNumbernumber | nullGitHub PR number
agentNamestringAgent that emitted this finding
taskIdstring | nullWork task that created this finding
findingTypestringType tag. Known values: ci_failure, cost_anomaly, deterministic_check_failure, integration_gap, integration_issue, obs, pre_existing_check_failure, protected_path_violation, requirements_gap, review_finding, risk, semantic_conflict. Free-form strings are also accepted.
categorystringCategory within the type
severity"info" | "low" | "medium" | "high" | "critical"Severity level
confidencenumber | nullConfidence score (0–1)
titlestringShort finding title
summarystringFull finding description
recommendationstring | nullSuggested remediation
evidenceAgentFindingEvidence[]Supporting evidence items
metadataobjectArbitrary agent-supplied metadata
blocksProgressbooleanWhether this finding blocks issue progress
status"open" | "accepted" | "resolved" | "dismissed" | "superseded"Lifecycle status
resolvedByIssueIdnumber | nullPipeline issue that resolved this finding
resolvedAtstring | nullISO timestamp when resolved
dismissedReasonstring | nullReason for dismissal
createdAtstringISO timestamp of creation
updatedAtstringISO timestamp of last update

Errors:

StatusCodeWhen
400INVALID_INPUTrepo is missing or not in owner/repo format
500INTERNAL_ERRORDatabase query failure
503STORE_UNAVAILABLEPostgres not connected

Debug endpoint: runs the repo-intelligence retrieval for a given repo and agent role, returning the same ranked snippets, candidate files, and token estimate that the agent would receive. Auth required.

Context source: Issue title, body, and labels are loaded from pipeline_issues. Planned files are loaded from the planned_files column (developer role only). Changed files are loaded from pull_requests.changed_file_paths (reviewer role). This means:

  • Results match agent retrieval for all stored fields.
  • For the reviewer role, the changedFiles context is sourced from the stored changed_file_paths column rather than the live worktree diff the reviewer agent computes at review time — these may differ if enrichment hasn’t completed yet or the PR was updated after enrichment.
  • If intelligence.embedding.enabled is configured, embedding-similarity recall (source 4) is included. Check retrievalContext.embeddingEnabled to confirm.

Query parameters:

ParamTypeRequiredDescription
repostringYesRepository as owner/repo
agentstringYesAgent role: analyzer, planner, developer, reviewer, or retrospector
issueintegerNoIssue number — loads title/body/labels/plannedFiles from pipeline_issues
printegerNoPull request number — loads changed_file_paths for the reviewer role
budgetintegerNoToken budget for the rendered markdown (overrides intelligence.retrieval.token_budget)

ResponseIntelligenceRetrieveResponse

{
"snippets": [
{
"kind": "architecture",
"title": "Service layer boundaries",
"summary": "HTTP handlers must not call repository methods directly.",
"scope": [{ "kind": "repo", "ref": "owner/repo" }],
"evidence": [],
"confidence": 0.95,
"reason": "approved invariant",
"source": "intelligence",
"itemId": "i_abc123"
}
],
"omitted": [{ "reason": "low-confidence with no scope", "count": 2 }],
"candidateFiles": [
{ "path": "packages/foo/src/bar.ts", "score": 9, "method": "anchor", "why": "matched code anchor `bar`" }
],
"tokenEstimate": 420,
"truncated": false,
"methodBreakdown": {
"anchorsExtracted": 12,
"anchorHits": 3,
"precedentHits": 1,
"corpusSize": 47,
"entityCount": 1820
},
"retrievalContext": {
"issueFound": true,
"prFound": null,
"titleUsed": "Fix the authentication flow",
"bodyLength": 342,
"labelsUsed": ["bug", "priority:high"],
"plannedFilesCount": null,
"changedFilesCount": null,
"changedFilesSource": "not_applicable",
"embeddingEnabled": true
}
}

The retrievalContext field reports which context was available and forwarded to the retrieval service. Use it to diagnose why results may differ from agent retrieval:

  • issueFound: false — issue not in pipeline_issues; title/body/labels were not available
  • prFound: false — PR not found; changedFiles could not be loaded (reviewer role)
  • changedFilesSource: "unavailable" — PR exists but changed_file_paths column is NULL
  • embeddingEnabled: false — embedding recall is disabled; configure intelligence.embedding.enabled: true and restart the monitor

Errors:

StatusCodeWhen
400INVALID_INPUTrepo or agent missing/invalid
500INTERNAL_ERRORRetrieval failure
503STORE_UNAVAILABLEPostgres not connected

Returns a single intelligence item by ID with its full scope and evidence links. Auth required.

Path parameters:

ParamTypeDescription
idstringIntelligence item UUID

ResponseIntelligenceDetailResponse

{
"item": {
"id": "i_abc123",
"tenantId": 1,
"repoId": 42,
"kind": "architecture",
"title": "Service layer boundaries",
"summary": "HTTP handlers must not call repository methods directly.",
"details": "All database access must go through the service layer.",
"status": "approved",
"confidence": 0.95,
"qualityScore": 0.88,
"source": "reviewer",
"sourceAgent": "reviewer",
"sourceIssueId": null,
"sourcePrId": null,
"sourceFindingId": null,
"sourceRetrospectiveId": null,
"metadata": {},
"tags": ["architecture", "boundaries"],
"firstObservedAt": "2026-01-10T08:00:00Z",
"lastObservedAt": "2026-01-15T10:00:00Z",
"observationCount": 5,
"approvedAt": "2026-01-12T09:00:00Z",
"approvedBy": "operator",
"dismissedAt": null,
"dismissedReason": null,
"supersededBy": null,
"createdAt": "2026-01-10T08:00:00Z",
"updatedAt": "2026-01-15T10:00:00Z",
"scopes": [
{
"id": "s_xyz",
"itemId": "i_abc123",
"scopeKind": "repo",
"scopeRef": "owner/repo",
"metadata": null,
"createdAt": "2026-01-10T08:00:00Z"
}
],
"evidence": [
{
"id": "e_xyz",
"itemId": "i_abc123",
"evidenceType": "pr",
"ref": "42",
"path": null,
"url": "https://github.com/owner/repo/pull/42",
"excerpt": "PR merged enforcing service layer pattern",
"metadata": null,
"createdAt": "2026-01-10T08:00:00Z"
}
]
}
}

The item object is a RepoIntelligenceItem extended with scopes and evidence arrays:

FieldTypeDescription
idstringUUID identifying this intelligence item
tenantIdnumberTenant the item belongs to
repoIdnumberRepository the item belongs to
kindIntelligenceKindItem kind (see values below)
titlestringShort title
summarystringHuman-readable summary
detailsstring | nullExtended detail text
statusIntelligenceStatusLifecycle status (see values below)
confidencenumber | nullConfidence score (0–1)
qualityScorenumber | nullQuality score (0–1)
sourcestringSystem or agent that created this item
sourceAgentstring | nullAgent name if source is an agent
sourceIssueIdnumber | nullPipeline issue that created this item
sourcePrIdnumber | nullPR that created this item
sourceFindingIdstring | nullFinding that created this item
sourceRetrospectiveIdstring | nullRetrospective that created this item
metadataobjectArbitrary metadata
tagsstring[] | nullFree-form tag list
firstObservedAtstringISO timestamp of first observation
lastObservedAtstringISO timestamp of most recent observation
observationCountnumberNumber of times this item has been observed
approvedAtstring | nullISO timestamp of approval
approvedBystring | nullIdentity that approved this item
dismissedAtstring | nullISO timestamp of dismissal
dismissedReasonstring | nullReason for dismissal
supersededBystring | nullID of the item that supersedes this one
createdAtstringISO timestamp of creation
updatedAtstringISO timestamp of last update
scopesIntelligenceScope[]Code-location scope links (detail endpoint only)
evidenceIntelligenceEvidence[]Evidence records backing this item (detail endpoint only)

kind values: architecture, invariant, workflow_playbook, test_strategy, failure_pattern, coupling, operator_preference, design_decision, risk_area, implementation_note.

status values: candidate, observed, proposed, approved, dismissed, superseded.

IntelligenceScope fields:

FieldTypeDescription
idstringUUID
itemIdstringParent intelligence item ID
scopeKindstringScope granularity (repo, package, path, file, symbol, route, test, service, workflow)
scopeRefstringReference string for the scope (e.g. a file path, symbol name)
metadataobject | nullAdditional scope metadata
createdAtstringISO timestamp of creation

IntelligenceEvidence fields:

FieldTypeDescription
idstringUUID
itemIdstringParent intelligence item ID
evidenceTypestringEvidence source type (issue, pr, finding, retrospective, file, commit, ci, test, log, human)
refstring | nullReference ID (issue number, PR number, etc.)
pathstring | nullFile path if applicable
urlstring | nullURL to the evidence source
excerptstring | nullShort excerpt from the evidence
metadataobject | nullAdditional evidence metadata
createdAtstringISO timestamp of creation

Errors:

StatusCodeWhen
400INVALID_INPUTid path param is empty
404NOT_FOUNDIntelligence item not found
500INTERNAL_ERRORDatabase query failure
503STORE_UNAVAILABLEPostgres not connected

Lists v2 intelligence items for a repository. Auth required.

The repo query parameter is required. Requests missing it return 400.

Query parameters:

ParamTypeRequiredDefaultDescription
repostringYesRepository as owner/repo
kindstringNoFilter by IntelligenceKind
statusstringNoFilter by IntelligenceStatus
scope_kindstringNoFilter by scope kind
scope_refstringNoFilter by scope ref
tagstringNoFilter by tag
sourcestringNoFilter by source
min_confidencenumberNoMinimum confidence threshold (0–1)
limitintegerNo200Page size (max 500)
offsetintegerNo0Page offset

ResponseIntelligenceListResponse (PaginatedResponse<RepoIntelligenceItem>)

{
"items": [
{
"id": "i_abc123",
"tenantId": 1,
"repoId": 42,
"kind": "architecture",
"title": "Service layer boundaries",
"summary": "HTTP handlers must not call repository methods directly.",
"details": null,
"status": "approved",
"confidence": 0.95,
"qualityScore": 0.88,
"source": "reviewer",
"sourceAgent": "reviewer",
"sourceIssueId": null,
"sourcePrId": null,
"sourceFindingId": null,
"sourceRetrospectiveId": null,
"metadata": {},
"tags": ["architecture"],
"firstObservedAt": "2026-01-10T08:00:00Z",
"lastObservedAt": "2026-01-15T10:00:00Z",
"observationCount": 5,
"approvedAt": "2026-01-12T09:00:00Z",
"approvedBy": "operator",
"dismissedAt": null,
"dismissedReason": null,
"supersededBy": null,
"createdAt": "2026-01-10T08:00:00Z",
"updatedAt": "2026-01-15T10:00:00Z"
}
],
"page": { "total": null, "limit": 200, "offset": 0, "hasMore": false }
}

Response item fields are the RepoIntelligenceItem shape documented in the GET /api/intelligence/:id section above. List items do not include scopes or evidence arrays — use the detail endpoint to retrieve those.

Errors:

StatusCodeWhen
400INVALID_INPUTrepo is missing or not in owner/repo format
500INTERNAL_ERRORDatabase query failure
503STORE_UNAVAILABLEPostgres not connected

Returns the daily pipeline digest — a snapshot of current pipeline state counts, blocked issue totals, recent error patterns, aggregate cost, and top regressed KPIs from the regression-guard sweep. Auth required.

ResponseDigestResponse

{
"digest": {
"date": "2026-04-22",
"pipelineByState": {
"analyzing:acme/app": 2,
"ready-for-dev:acme/app": 5
},
"blockedCount": 3,
"recentErrors": [
{ "fingerprint": "tsc_error", "message": "Cannot find name 'Foo'", "count": 2 }
],
"dailyCostUsd": 12.5,
"monthlyCostUsd": 245.0,
"topRegressedKpis": [
{
"owner": "acme",
"repo": "app",
"kpi": "autonomy_rate",
"magnitude": "large",
"currentSampleSize": 10,
"baselineSampleSize": 20,
"topBlockReasons": [{ "blockReason": "build_failure", "count": 3 }],
"topFingerprints": [{ "fingerprint": "tsc_error", "count": 2 }]
}
]
}
}

DailyDigest fields:

FieldTypeDescription
digest.datestringISO date string (e.g. "2026-04-22")
digest.pipelineByStateRecord<string, number>Open issue counts keyed by "state:owner/repo"
digest.blockedCountnumberTotal number of blocked issues
digest.recentErrorsobject[]Error clusters sorted by frequency descending
digest.recentErrors[].fingerprintstringNormalized error fingerprint
digest.recentErrors[].messagestringRepresentative error message
digest.recentErrors[].countnumberNumber of occurrences
digest.dailyCostUsdnumber | nullAggregate spend in the last 24 h; null when unavailable
digest.monthlyCostUsdnumber | nullAggregate spend in the last 30 days; null when unavailable
digest.topRegressedKpisobject[]Top regressed KPIs from the regression-guard sweep, sorted by magnitude descending
digest.topRegressedKpis[].ownerstringRepository owner
digest.topRegressedKpis[].repostringRepository name
digest.topRegressedKpis[].kpistringKPI identifier (e.g. "autonomy_rate")
digest.topRegressedKpis[].magnitudestringRegression magnitude (e.g. "large")
digest.topRegressedKpis[].currentSampleSizenumberIssue count in the current measurement window
digest.topRegressedKpis[].baselineSampleSizenumberIssue count in the baseline window
digest.topRegressedKpis[].topBlockReasonsobject[]Top block-reason codes and their occurrence counts
digest.topRegressedKpis[].topFingerprintsobject[]Top error fingerprints and their occurrence counts

Errors:

StatusCodeWhen
500INTERNAL_ERRORFailed to generate digest

Lists VCS projections (queued label/state sync operations). Auth required.

Query parameters:

ParamTypeRequiredDefaultDescription
statusstringNopending + failedFilter by projection status (pending, failed, complete, discarded)

When status is omitted, results are filtered to only pending and failed projections.

ResponseProjectionListResponse

{
"projections": [
{
"id": 1,
"repoOwner": "acme",
"repoName": "app",
"issueNumber": 42,
"action": { "type": "addLabels", "labels": ["colony:analyzing"] },
"status": "pending",
"attempts": 0,
"maxAttempts": 3,
"clientType": "ops",
"retryAfter": null,
"error": null,
"createdAt": "2026-01-15T10:00:00Z",
"updatedAt": "2026-01-15T10:00:00Z"
}
]
}

The action field is a discriminated union keyed by type. See the ProjectionAction type in the SDK for all variants (e.g. addLabels, removeLabels, swapLabels, closeIssue, postComment, mergePR, etc.).

Errors:

StatusCodeWhen
500Internal server errorUnexpected failure
503Pipeline store not availableNo database connection

Resets a projection to pending for re-execution. Auth required.

Path parameters:

ParamTypeDescription
idnumberProjection ID

ResponseProjectionActionResponse

{ "ok": true }

Errors:

StatusCodeWhen
400Invalid requestMalformed URL
500Internal server errorUnexpected failure
503Pipeline store not availableNo database connection

Marks a projection as discarded, preventing further retries. Auth required.

Path parameters:

ParamTypeDescription
idnumberProjection ID

ResponseProjectionActionResponse

{ "ok": true }

Errors: Same as POST /api/projections/:id/retry.


The two routes below are documented in the order the monitor router matches them: GET /api/logs/messages is matched before GET /api/logs (prefix-match priority).


Returns agent transcript messages (Claude Code conversation turns) for an issue invocation. Auth required.

Query parameters:

ParamTypeRequiredDefaultDescription
ownerstringYesRepository owner
repostringYesRepository name
issuenumberYesIssue number
taskIdstringNoFilter to a specific task ID
invocationnumberNoFilter to a specific invocation index within the task (zero-based)
limitnumberNo500Page size (1–2000; out-of-range values are clamped)
offsetnumberNo0Row offset for pagination
includestringNoPass tools to include tool_use and tool_result messages (omitted by default)

ResponseAgentMessagesResponse (PaginatedResponse<AgentMessageEntry>)

{
"items": [
{
"taskId": "t9z8y7x6-...",
"invocationIndex": 0,
"sequence": 1,
"agent": "worker-1",
"role": "assistant",
"subtype": "text",
"toolName": null,
"content": "I'll start by reading the issue description.",
"truncated": false,
"createdAt": "2026-01-15T08:01:00Z"
}
],
"page": { "total": null, "limit": 500, "offset": 0, "hasMore": false }
}

page.total is always null — the store does not compute a total row count for message queries.

AgentMessageEntry fields:

FieldTypeDescription
taskIdstringTask that produced this message
invocationIndexnumberZero-based index of the Claude Code invocation within the task
sequencenumberMessage order within the invocation
agentstringWorker or agent identifier
role"assistant" | "user" | "system"Message role
subtype"text" | "thinking" | "tool_use" | "tool_result" | "system"Message subtype
toolNamestring | nullTool name (for tool_use and tool_result subtypes)
toolInputunknownTool call input (present when subtype is tool_use and include=tools)
toolOutputunknownTool call output (present when subtype is tool_result and include=tools)
contentstring | nullMessage text content
truncatedbooleanWhether the content was truncated due to size limits
createdAtstringISO 8601 timestamp

Errors:

StatusCodeWhen
400INVALID_INPUTMissing or invalid owner, repo, or issue; non-integer invocation
500INTERNAL_ERRORFailed to query agent messages
503STORE_UNAVAILABLEPipeline store (Postgres) not connected

Returns structured Pino log entries from agent log files. Auth required.

Query parameters:

ParamTypeRequiredDefaultDescription
agentstringNoFilter to a specific agent name (matches the log filename without .log)
levelstringNoMinimum log level: debug, info, warn, error, or fatal
issuestringNoFilter to entries whose issue field matches this value
sincestringNoReturn only entries after this time. Accepts an ISO 8601 timestamp or a relative duration (1h, 30m, 2d, 5s)
searchstringNoCase-insensitive substring filter on the msg field
limitnumberNo200Page size (1–1000; values above 1000 are clamped to 1000)
offsetnumberNo0Row offset for pagination

ResponseLogsResponse (PaginatedResponse<LogEntry>)

{
"items": [
{
"timestamp": "2026-01-15T09:30:00Z",
"level": "info",
"msg": "Worker claimed task",
"agent": "worker",
"context": { "taskId": "t9z8y7x6-...", "issue": 42 }
}
],
"page": { "total": 142, "limit": 200, "offset": 0, "hasMore": false }
}

Results are sorted newest-first. page.total reflects the number of matching entries across all log files (all files are read in memory on each request). When the .colony/logs/ directory does not exist or is unreadable the endpoint returns an empty result set rather than an error.

LogEntry fields:

FieldTypeDescription
timestampstringISO 8601 timestamp (derived from the Pino time field)
levelstringLog level name (debug, info, warn, error, fatal)
msgstringLog message
agentstringAgent name (inferred from the log file name)
contextRecord<string, unknown>All non-internal Pino fields (e.g. taskId, issue, repo)

Errors:

StatusCodeWhen
400INVALID_INPUTInvalid level; unparseable since (not ISO 8601 and not a relative duration); non-positive limit; negative offset

Returns the monitor’s current parsed configuration: database connectivity, per-repo settings, self-healing policy, cost limits, alert channels, and live agent health. Auth required.

Unlike cache-backed observability endpoints, this route always reflects the in-memory state and returns 200 even when the pipeline store is not connected — the database.connected field indicates store availability.

ResponseConfigSummaryResponse

{
"configSource": "/etc/colony/colony.config.yaml",
"database": {
"host": "db.internal",
"port": 5432,
"migrationVersion": null,
"poolSize": 10,
"connected": true
},
"repos": [
{
"slug": "owner/repo",
"branch": "main",
"poolSize": 2,
"setupCommand": null
}
],
"selfHealing": {
"autoUnblockTransient": true,
"autoRestart": false,
"restartStrategy": "pid",
"restartCooldownSec": 300,
"maxRestartAttempts": 3,
"maxAutoUnblocksPerIssue": 3
},
"pollIntervals": {
"sprintMaster": 30,
"monitor": 60,
"metricsRefreshMin": 10
},
"costLimits": {
"dailyUsd": null,
"monthlyUsd": null,
"perIssueUsd": 5.0
},
"alertChannels": [{ "type": "slack", "configured": true }],
"agentHealth": [
{
"name": "sprint-master",
"status": "healthy",
"uptime": 7200,
"consecutiveFailures": 0,
"lastError": null,
"configReload": null
}
],
"configReload": null,
"agentConfigReload": []
}
FieldTypeDescription
configSourcestringAbsolute path to the loaded config file, or "default" if no file was provided
database.hoststringDatabase hostname parsed from DATABASE_URL
database.portnumberDatabase port (default 5432)
database.migrationVersionnumber | nullLatest applied migration version (always null in the current implementation)
database.poolSizenumberConfigured max Postgres connections
database.connectedbooleanWhether the pipeline store is currently connected
reposarrayPer-repo configuration summaries
repos[].slugstringRepository as owner/repo
repos[].branchstringBase branch (defaults to "main")
repos[].poolSizenumberWorker pool size for this repo
repos[].setupCommandstring | nullCustom workspace setup command, or null for the default
selfHealing.autoUnblockTransientbooleanWhether transient blocks are automatically retried
selfHealing.autoRestartbooleanWhether crashed agents are automatically restarted
selfHealing.restartStrategystringRestart mechanism ("pid" or "docker")
selfHealing.restartCooldownSecnumberMinimum seconds between restart attempts
selfHealing.maxRestartAttemptsnumberMaximum consecutive restart attempts before giving up
selfHealing.maxAutoUnblocksPerIssuenumberMaximum automatic unblocks per issue before requiring manual intervention
pollIntervals.sprintMasternumberSprint-master poll interval in seconds
pollIntervals.monitornumberMonitor poll interval in seconds
pollIntervals.metricsRefreshMinnumberRegression-guard metrics refresh cadence in minutes
costLimits.dailyUsdnumber | nullDaily spend cap across all agents, or null if unconfigured
costLimits.monthlyUsdnumber | nullMonthly spend cap (always null in the current implementation)
costLimits.perIssueUsdnumber | nullPer-issue cost cap, or null if unconfigured
alertChannels[].typestringChannel type (e.g. "slack", "pagerduty")
alertChannels[].configuredbooleanWhether the required credential or URL is set in the environment
agentHealth[].namestringAgent name
agentHealth[].status"healthy" | "unhealthy" | "stopped"Agent health status
agentHealth[].uptimenumber | nullAgent uptime in seconds, or null if unreachable
agentHealth[].consecutiveFailuresnumberHealth check failures since last recovery
agentHealth[].lastErrorstring | nullMost recent error message from the agent
agentHealth[].configReloadConfigReloadOutcome | nullLast config reload outcome reported by this agent
configReloadConfigReloadOutcome | nullLast config reload outcome for the monitor process; null if no reload has occurred
agentConfigReloadArray<{ agentName: string; outcome: ConfigReloadOutcome }>Per-agent config-reload outcomes read from the durable pipeline store; empty array when no store is connected

Hot-reloads the monitor’s configuration from the config file on disk, applies all reloadable keys immediately, and returns which keys were applied and which require a redeploy. Auth required. No request body.

ResponseConfigReloadResponse

{
"ok": true,
"appliedKeys": ["agents.monitor.poll_interval", "logging.level"],
"deferredKeys": ["claude", "agents.sprint_master"]
}
FieldTypeDescription
oktrueAlways true on success
appliedKeysstring[]Monitor-owned config keys that were applied without a restart
deferredKeysstring[]Parsed keys that require a worker or sprint-master restart to take effect; the monitor ignores them

Errors:

StatusCodeWhen
400CONFIG_LOAD_FAILEDThe config file exists but cannot be parsed (e.g. invalid YAML)
400CONFIG_VALIDATION_FAILEDThe parsed config fails schema validation
409NO_CONFIG_FILEThe monitor is running with the built-in default config — there is no file to reload

All three routes in this section are cache-backed: their responses are computed by the monitor’s background poll loop and served from memory. A 200 response with an empty result array may indicate either an empty pipeline or a poll cycle that has not yet completed.

Returns all currently blocked pipeline issues with error-pattern clustering. Auth required.

ResponseBlockedIssuesResponse

{
"issues": [
{
"owner": "acme",
"repo": "app",
"issueNumber": 42,
"title": "Fix login timeout",
"state": "blocked",
"blockReason": "ci_hard_failure",
"blockedSince": "2026-07-01T09:00:00Z",
"lastError": "E2E tests timed out after 600s",
"errorPattern": "e2e_timeout"
}
],
"patterns": [
{
"pattern": "e2e_timeout",
"count": 1,
"issues": [{ "owner": "acme", "repo": "app", "issueNumber": 42 }]
}
],
"total": 1
}
FieldTypeDescription
issuesarrayAll blocked pipeline issues
issues[].ownerstringRepository owner
issues[].repostringRepository name
issues[].issueNumbernumberGitHub issue number
issues[].titlestringIssue title
issues[].statestringCurrent pipeline state
issues[].blockReasonstring | nullTaxonomy code for the block cause (e.g. "ci_hard_failure", "merge_conflict")
issues[].blockedSincestring | nullISO timestamp when the issue entered the blocked state
issues[].lastErrorstring | nullMost recent error message
issues[].errorPatternstring | nullNormalized fingerprint used for pattern clustering
patternsarrayError patterns with co-occurring issue references
patterns[].patternstringNormalized error fingerprint
patterns[].countnumberNumber of issues sharing this pattern
patterns[].issuesarrayMinimal issue references (owner, repo, issueNumber) for this pattern
totalnumberTotal number of blocked issues

Errors:

StatusCodeWhen
503STORE_UNAVAILABLEPipeline store (Postgres) not connected

Returns the latest regression-guard KPI snapshots for every configured repository, including trend history for sparkline rendering. Auth required.

ResponseRegressionGuardResponse

{
"repos": [
{
"owner": "acme",
"repo": "app",
"kpis": [
{
"kpi": "block_rate",
"currentValue": 0.25,
"baselineValue": 0.1,
"relativeDelta": 1.5,
"currentSampleSize": 20,
"baselineSampleSize": 50,
"regressed": true,
"magnitude": "high",
"direction": "rise",
"history": [{ "computedAt": "2026-06-30T00:00:00Z", "value": 0.22 }]
}
]
}
]
}
FieldTypeDescription
reposarrayPer-repository KPI snapshots
repos[].ownerstringRepository owner
repos[].repostringRepository name
repos[].kpisarrayKPI results for this repo
kpis[].kpistringKPI name (e.g. "block_rate", "cycle_time_p50")
kpis[].currentValuenumber | nullMeasured value for the current window
kpis[].baselineValuenumber | nullBaseline (historical) value for comparison
kpis[].relativeDeltanumber | nullFractional change from baseline ((current − baseline) / baseline)
kpis[].currentSampleSizenumberIssues counted in the current window
kpis[].baselineSampleSizenumberIssues counted in the baseline window
kpis[].regressedbooleanWhether this KPI has crossed the regression threshold
kpis[].magnitudestringHuman-readable severity level (e.g. "low", "medium", "high")
kpis[].direction"rise" | "fall"Whether an increase ("rise") or decrease ("fall") in this KPI signals regression
kpis[].historyarrayRecent data points for sparkline rendering, newest first
history[].computedAtstringISO timestamp of the measurement
history[].valuenumber | nullMeasured KPI value at that point

Errors:

StatusCodeWhen
503STORE_UNAVAILABLEPipeline store (Postgres) not connected

Returns all pending and claimed work tasks from the queue, plus aggregate queue stats. Auth required.

ResponseTaskQueueResponse

{
"tasks": [
{
"id": "task-uuid",
"taskType": "develop",
"issueNumber": 42,
"repoOwner": "acme",
"repoName": "app",
"status": "claimed",
"priority": 0,
"claimedBy": "worker-1",
"createdAt": "2026-07-01T10:00:00Z",
"claimedAt": "2026-07-01T10:01:00Z",
"waitTimeMs": 60000,
"estimatedDurationMs": 900000
}
],
"summary": {
"totalPending": 2,
"totalClaimed": 1,
"avgWaitMs": 45000,
"oldestPendingAgeMs": 120000
}
}
FieldTypeDescription
tasksarrayAll pending and claimed tasks
tasks[].idstringTask UUID
tasks[].taskTypestringTask type (e.g. "develop", "review", "analyze", "merge")
tasks[].issueNumbernumberAssociated GitHub issue number
tasks[].repoOwnerstringRepository owner
tasks[].repoNamestringRepository name
tasks[].status"pending" | "claimed"Task status
tasks[].prioritynumberQueue priority (lower value = higher priority)
tasks[].claimedBystring | nullWorker ID that claimed this task; null if still pending
tasks[].createdAtstringISO timestamp when the task was created
tasks[].claimedAtstring | nullISO timestamp when the task was claimed; null if still pending
tasks[].waitTimeMsnumberMilliseconds the task waited before being claimed (or has been waiting so far)
tasks[].estimatedDurationMsnumber | nullEstimated task duration in milliseconds; null if unknown
summary.totalPendingnumberCount of unclaimed tasks
summary.totalClaimednumberCount of currently claimed tasks
summary.avgWaitMsnumberAverage wait time in milliseconds across all tasks in the response
summary.oldestPendingAgeMsnumber | nullAge in milliseconds of the oldest pending task; null if no pending tasks

Errors:

StatusCodeWhen
503STORE_UNAVAILABLEPipeline store (Postgres) not connected

Dead letter entries are state-transition attempts that failed permanently and were written to the dead_letters table rather than retried. The four routes below let operators inspect, retry, and resolve them. Auth required.

Dispatch order note: POST /api/dead-letters/resolve-all is registered as a literal path in the route table before POST /api/dead-letters/:id/resolve, so the literal segment resolve-all is matched first and is never treated as an :id value. Calling POST /api/dead-letters/resolve-all always hits the bulk resolver, not the per-id handler.


Returns paginated dead letter entries. Auth required.

Query parameters:

ParamTypeRequiredDefaultDescription
limitnumberNo100Page size (1–500; values above 500 are clamped)
offsetnumberNo0Row offset for pagination

ResponseDeadLettersResponse (PaginatedResponse<DeadLetterEntry>)

{
"items": [
{
"id": "d1a2b3c4-...",
"repoOwner": "acme",
"repoName": "app",
"issueNumber": 42,
"fromState": "ready-for-dev",
"targetState": "in-review",
"agentName": "developer",
"error": "atomicTransitionWithSnapshot failed: transition already applied",
"createdAt": "2026-01-15T10:00:00Z"
}
],
"page": { "total": 1, "limit": 100, "offset": 0, "hasMore": false }
}

DeadLetterEntry fields:

FieldTypeDescription
idstringUUID
repoOwnerstringRepository owner
repoNamestringRepository name
issueNumbernumberIssue number
fromStatestring | nullPipeline state from which the transition was attempted
targetStatestringTarget pipeline state of the failed transition
agentNamestring | nullAgent that attempted the transition
errorstringError message from the failed transition attempt
createdAtstringISO 8601 timestamp when the dead letter was recorded

page is a PageInfo object — see the findings endpoint for field definitions.

Errors:

StatusCodeWhen
503STORE_UNAVAILABLEPipeline store (Postgres) not connected

Marks all currently cached dead letter entries as resolved. Auth required.

This is a bulk operation: every entry returned by the in-memory dead-letter cache is resolved via Promise.allSettled, so individual failures do not abort the batch. The resolved count reflects the number of entries that were attempted, not just the ones that succeeded.

No request body is required.

ResponseDeadLetterResolveResponse

{ "ok": true, "resolved": 3 }
FieldTypeDescription
oktrueAlways true on success
resolvednumberNumber of dead letter entries that were resolved in this request

Errors:

StatusCodeWhen
503STORE_UNAVAILABLEPipeline store (Postgres) not connected
500INTERNAL_ERRORUnexpected failure while resolving entries

Re-enqueues a single dead letter entry for another transition attempt. Auth required.

Path parameters:

ParamTypeDescription
idstringDead letter entry UUID

ResponseDeadLetterRetryResponse

{ "ok": true, "newState": "in-review" }
FieldTypeDescription
oktrueAlways true on success
newStatestring (optional)Pipeline state the issue transitioned to after the retry, if known

Errors:

StatusCodeWhen
404DEAD_LETTER_NOT_FOUNDNo dead letter entry with the given id
500INTERNAL_ERRORUnexpected failure during retry
503STORE_UNAVAILABLEPipeline store (Postgres) not connected

Marks a single dead letter entry as resolved without retrying the transition. Auth required.

Path parameters:

ParamTypeDescription
idstringDead letter entry UUID

ResponseDeadLetterResolveResponse

{ "ok": true }
FieldTypeDescription
oktrueAlways true on success

Errors:

StatusCodeWhen
500INTERNAL_ERRORUnexpected failure while resolving the entry
503STORE_UNAVAILABLEPipeline store (Postgres) not connected

Data-consistency findings are materialized by the periodic reconciliation pass and cached in memory. The routes below expose the cache and provide one-click repairs for each finding class. Auth required.


Returns paginated cached data-consistency findings. Auth required.

Query parameters:

ParamTypeRequiredDefaultDescription
limitnumberNo100Page size (1–500; values above 500 are clamped)
offsetnumberNo0Row offset for pagination

ResponseConsistencyResponse (PaginatedResponse<ConsistencyFindingItem>)

{
"items": [
{
"id": "stale_blocked:acme/app#42",
"kind": "stale_blocked",
"repoOwner": "acme",
"repoName": "app",
"issueNumber": 42,
"detail": { "blockedFromState": "ready-for-dev" }
}
],
"page": { "total": 1, "limit": 100, "offset": 0, "hasMore": false }
}

ConsistencyFindingItem fields:

FieldTypeDescription
idstringStable deterministic identifier, e.g. stale_blocked:owner/repo#42
kindConsistencyFindingKindOne of stale_blocked, stale_subtask_edge, orphan_missing_from_pg, si_linkage_gap, label_drift
repoOwnerstringRepository owner
repoNamestringRepository name
issueNumbernumber | nullIssue number (null for findings not tied to a specific pipeline issue)
detailobjectKind-specific detail fields

Errors:

StatusCodeWhen
503STORE_UNAVAILABLEPipeline store (Postgres) not connected

Clears a stale is_blocked flag on a pipeline issue that has no active dependency edges. Idempotent — a second call returns finding: null if the issue is already unblocked. Auth required.

Path parameters:

ParamTypeDescription
idstringFinding ID from the cache

ResponseConsistencyFixResponse

{ "ok": true, "finding": null }
FieldTypeDescription
oktrueAlways true on success
findingConsistencyFindingItem | nullRe-materialized finding after repair, or null if resolved

Errors:

StatusCodeWhen
404CONSISTENCY_FINDING_NOT_FOUNDNo finding with the given id in the cache
422INVALID_ARGUMENTFinding has no issue number
500INTERNAL_ERRORUnexpected failure during repair
503STORE_UNAVAILABLEPipeline store (Postgres) not connected

POST /api/consistency/:id/resolve-subtask-edges

Section titled “POST /api/consistency/:id/resolve-subtask-edges”

Marks all active subtask dependency edges for a completed epic as resolved. Idempotent — a second call returns finding: null if edges are already resolved. Auth required.

Path parameters:

ParamTypeDescription
idstringFinding ID from the cache

ResponseConsistencyFixResponse

{ "ok": true, "finding": null }

Errors:

StatusCodeWhen
404CONSISTENCY_FINDING_NOT_FOUNDNo finding with the given id in the cache
422INVALID_ARGUMENTFinding has no issue number
500INTERNAL_ERRORUnexpected failure during repair
503STORE_UNAVAILABLEPipeline store (Postgres) not connected

Applies the colony:enqueue label to a GitHub issue that is absent from the pipeline database, seeding it into the pipeline via the normal label-intake path. Uses the VCS write service rather than inline label manipulation so the outbox drainer handles the label update. Auth required.

Path parameters:

ParamTypeDescription
idstringFinding ID from the cache

ResponseConsistencyFixResponse

{ "ok": true, "finding": null }

Errors:

StatusCodeWhen
404CONSISTENCY_FINDING_NOT_FOUNDNo finding with the given id in the cache
422INVALID_ARGUMENTFinding has no issue number
500INTERNAL_ERRORUnexpected failure during label apply
503STORE_UNAVAILABLEPipeline store or write service not connected

Marks an SI (self-improvement) issue as completed to close a si_linkage_gap finding where completed_at is NULL despite the pipeline issue being done. Idempotent — a second call returns finding: null if the linkage is already set. Auth required.

Path parameters:

ParamTypeDescription
idstringFinding ID from the cache

ResponseConsistencyFixResponse

{ "ok": true, "finding": null }

Errors:

StatusCodeWhen
404CONSISTENCY_FINDING_NOT_FOUNDNo finding with the given id in the cache
422INVALID_ARGUMENTFinding has no issue number
500INTERNAL_ERRORUnexpected failure during repair
503STORE_UNAVAILABLEPipeline store (Postgres) not connected

Toggles drain mode for a worker. A draining worker finishes its current task but does not claim new work. Auth required.

Path parameters:

ParamTypeDescription
idstringWorker ID (URL-encoded if necessary)

Request bodyWorkerDrainRequest

{ "drain": true }
FieldTypeRequiredDescription
drainbooleanYestrue to drain, false to resume

ResponseWorkerDrainResponse

{ "ok": true, "draining": true }

Errors:

StatusCodeWhen
400Invalid JSON bodyUnparseable request body
400Missing required boolean field: draindrain is not a boolean
500Failed to set worker drain statusDatabase error
503Pipeline store not availableNo database connection

Returns issue counts over time bucketed by pipeline state. Auth required.

Query parameters:

ParamTypeRequiredDefaultDescription
windowstringNo24hLookback window. One of: 1h, 6h, 12h, 24h, 48h, 7d
bucketsintegerNo48Number of time buckets (1–168)
repostringNoFilter to a single repository (owner/repo)
tenantstringNoFilter to a tenant by external ID

ResponseStateTimeseriesResponse

{
"window": "24h",
"buckets": [
{
"ts": "2026-06-30T22:00:00.000Z",
"states": {
"new": 2,
"analyzing": 1,
"ready-for-dev": 5,
"done": 0
}
}
],
"serverTimestamp": "2026-07-01T10:00:00.000Z"
}
FieldTypeDescription
windowstringEcho of the requested window parameter
bucketsStateTimeseriesBucket[]Time-ordered array of per-bucket state counts
buckets[].tsstringISO 8601 timestamp for the start of the bucket
buckets[].statesRecord<string, number>Issue count per pipeline state, zero-filled
serverTimestampstringISO 8601 server time when the query ran

Errors:

StatusCodeWhen
400INVALID_INPUTInvalid window value, buckets out of 1–168 range, or malformed repo
503STORE_UNAVAILABLEPipeline store not connected
500INTERNAL_ERRORUnexpected query failure

Returns cohort funnel metrics showing how many issues reached each pipeline state and the median time spent there. Auth required.

Query parameters:

ParamTypeRequiredDefaultDescription
windowstringNo24hLookback window. One of: 1h, 6h, 12h, 24h, 48h, 7d
repostringNoFilter to a single repository (owner/repo)
tenantstringNoFilter to a tenant by external ID

ResponseCohortFunnelResponse

{
"window": "24h",
"cohortSize": 18,
"byState": [
{ "state": "new", "count": 18, "medianMinutes": 1 },
{ "state": "analyzing", "count": 15, "medianMinutes": 8 },
{ "state": "ready-for-dev", "count": 12, "medianMinutes": 45 },
{ "state": "done", "count": 10, "medianMinutes": 0 }
],
"drops": [{ "from": "analyzing", "to": "blocked", "count": 2 }],
"serverTimestamp": "2026-07-01T10:00:00.000Z"
}
FieldTypeDescription
windowstringEcho of the requested window parameter
cohortSizenumberTotal issues that entered the pipeline in the window
byStateCohortFunnelStateEntry[]Per-state counts; always includes standard spine states
byState[].statestringPipeline state name
byState[].countnumberIssues that reached this state
byState[].medianMinutesnumberMedian time spent in this state (minutes)
dropsCohortFunnelDrop[]Issues that left the funnel at off-spine transitions
drops[].fromstringSource state
drops[].tostringDestination state (e.g. "blocked")
drops[].countnumberNumber of issues that took this transition
serverTimestampstringISO 8601 server time when the query ran

Errors:

StatusCodeWhen
400INVALID_INPUTInvalid window value or malformed repo
503STORE_UNAVAILABLEPipeline store not connected
500INTERNAL_ERRORUnexpected query failure

Returns autonomous merge rate metrics, comparing the current window to a prior window of equal length. Auth required.

Query parameters:

ParamTypeRequiredDefaultDescription
windowstringNo7dLookback window. One of: 1h, 6h, 12h, 24h, 48h, 7d
repostringNoFilter to a single repository (owner/repo)
tenantstringNoFilter to a tenant by external ID

ResponseAutonomyResponse

{
"window": "7d",
"mergedIssues": 40,
"fullyAutonomousIssues": 34,
"ratePct": 85.0,
"byDay": [{ "date": "2026-06-25", "merged": 6, "autonomous": 5 }],
"weekOverWeek": {
"current": { "ratePct": 85.0 },
"previous": { "ratePct": 80.0 },
"deltaPp": 5.0
},
"serverTimestamp": "2026-07-01T10:00:00.000Z"
}
FieldTypeDescription
windowstringEcho of the requested window parameter
mergedIssuesnumberIssues merged in the window
fullyAutonomousIssuesnumberIssues merged without human intervention
ratePctnumberAutonomous merge rate (percentage, one decimal place)
byDayAutonomyDayEntry[]Per-calendar-day breakdown
byDay[].datestringISO date string (e.g. "2026-06-25")
byDay[].mergednumberIssues merged that day
byDay[].autonomousnumberAutonomously merged issues that day
weekOverWeekobjectComparison to the prior window of equal length
weekOverWeek.current.ratePctnumberAutonomous rate for the current window
weekOverWeek.previous.ratePctnumberAutonomous rate for the prior window
weekOverWeek.deltaPpnumberChange in percentage points (current − previous)
serverTimestampstringISO 8601 server time when the query ran

Errors:

StatusCodeWhen
400INVALID_INPUTInvalid window value or malformed repo
503STORE_UNAVAILABLEPipeline store not connected
500INTERNAL_ERRORUnexpected query failure

Returns the count of issues merged in a time window. Auth required.

Query parameters:

ParamTypeRequiredDefaultDescription
sincestringNotodayLookback window. One of: today (since UTC midnight), 1h, 6h, 12h, 24h, 48h, 7d
repostringNoFilter to a single repository (owner/repo)
tenantstringNoFilter to a tenant by external ID

ResponseThroughputResponse

{
"since": "today",
"count": 7,
"windowStart": "2026-07-01T00:00:00.000Z",
"serverTimestamp": "2026-07-01T10:00:00.000Z"
}
FieldTypeDescription
sincestringEcho of the requested since parameter
countnumberNumber of issues merged in the window
windowStartstringISO 8601 start of the counting window (UTC midnight for "today")
serverTimestampstringISO 8601 server time when the query ran

Errors:

StatusCodeWhen
400INVALID_INPUTInvalid since value or malformed repo
503STORE_UNAVAILABLEPipeline store not connected
500INTERNAL_ERRORUnexpected query failure

Returns a paginated list of pipeline events across all agents. Auth required.

Query parameters:

ParamTypeRequiredDefaultDescription
repostringNoFilter to a repository (owner/repo)
issueintegerNoFilter to a specific issue number
typestringNoFilter by event type string
limitintegerNo50Maximum events per page; capped at 200
offsetintegerNo0Pagination offset

Invalid limit and offset values are silently clamped to their defaults rather than returning a 400.

ResponsePipelineEventsResponse

{
"items": [
{
"id": 1001,
"repoOwner": "acme",
"repoName": "app",
"issueNumber": 42,
"eventType": "state_transition",
"agent": "developer",
"payload": { "from": "analyzing", "to": "ready-for-dev" },
"createdAt": "2026-07-01T09:30:00.000Z"
}
],
"page": {
"total": 1234,
"limit": 50,
"offset": 0,
"hasMore": true
}
}
FieldTypeDescription
itemsPipelineEvent[]Page of event records
items[].idnumberEvent row ID
items[].repoOwnerstringRepository owner
items[].repoNamestringRepository name
items[].issueNumbernumber | nullIssue number, or null for repo-level events
items[].eventTypestringEvent type string (e.g. "state_transition")
items[].agentstringAgent that emitted the event
items[].payloadRecord<string, unknown>Event-specific structured data
items[].createdAtstringISO 8601 event timestamp
page.totalnumber | nullTotal matching events across all pages
page.limitnumberEffective page size
page.offsetnumberCurrent offset
page.hasMorebooleanWhether more pages follow

Errors:

StatusCodeWhen
400INVALID_INPUTNon-integer issue value or malformed repo
503STORE_UNAVAILABLEPipeline store not connected
500INTERNAL_ERRORUnexpected query failure

Opens a Server-Sent Events (SSE) stream of real-time pipeline events. Returns text/event-stream (not JSON). Auth required.

Request headers:

HeaderDescription
Last-Event-IDIf provided, the server replays all buffered events with an ID greater than this value (up to 100 events retained)

Responsetext/event-stream

Each event follows the SSE wire format:

id: 42
event: state-transition
data: {"issueNumber":42,"repo":"acme/app","from":"analyzing","to":"ready-for-dev","agent":"worker-1","timestamp":"2026-01-15T08:05:00Z"}

A heartbeat event is sent every 30 seconds to keep the connection alive.

Event types:

Event typePayload fieldsDescription
state-transitionissueNumber, repo, from, to, agent, timestampAn issue transitioned between pipeline states
cost-updateissueNumber, repo, costUsd, agent, timestampCost data changed for an in-flight issue
worker-statusworkerId, repo, status (idle/busy/draining/offline), issueNumber, taskTypeA worker’s status changed
alertseverity, message, detector, resolvedA failure-detector alert was raised or resolved
issue-statusissueNumber, repo, status, actorType, state, timestampA new status message was emitted for an in-flight issue
heartbeattimestamp, uptimePeriodic keepalive (every 30 s)

All data fields are JSON-encoded. See the SSEEvent discriminated union in @runcolony/sdk for the complete typed payload shapes.

Errors:

StatusBodyWhen
503SSE_UNAVAILABLESSE manager is not running

Returns the worker floor health snapshot — one entry per live worker including its current task, utilization, and alert status. Auth required.

The response uses the UnpaginatedResponse<WorkerFloorEntry> envelope and always returns the full current worker set.

ResponseWorkerFloorResponse

{
"items": [
{
"id": "worker-1",
"status": "running",
"currentTask": {
"id": "task-uuid",
"type": "develop",
"issueNumber": 42,
"repo": "owner/repo",
"pipelineState": "ready-for-dev",
"etaMinutes": 12
},
"queueDepth": 3,
"utilization8hPct": 87.5,
"lastHeartbeat": "2026-07-01T12:00:00Z",
"idle": false,
"alert": null
}
],
"snapshot": true
}
FieldTypeDescription
itemsWorkerFloorEntry[]One entry per live worker
snapshottrueLiteral marker indicating the full current set is returned

WorkerFloorEntry fields:

FieldTypeDescription
idstringWorker ID
statusstringWorker process status (e.g. "running", "draining")
currentTaskWorkerCurrentTask | nullTask currently claimed by this worker; null when idle
queueDepthnumberNumber of pending tasks queued for this worker’s repo
utilization8hPctnumberPercentage of the last 8 hours this worker spent on active tasks
lastHeartbeatstring | nullISO timestamp of the worker’s most recent heartbeat
idlebooleanWhether the worker is currently idle (no active task)
alertWorkerFloorAlert | nullEscalation requiring human attention; null if none

WorkerCurrentTask fields:

FieldTypeDescription
idstringTask UUID
typestringTask type (e.g. "develop", "review", "analyze")
issueNumbernumber | nullAssociated issue number; null for repo-scoped tasks such as code-map-scan
repostringRepository as owner/repo
pipelineStatestring | nullCurrent pipeline state of the associated issue
etaMinutesnumber | nullEstimated minutes until task completion; null if unknown

WorkerFloorAlert fields:

FieldTypeDescription
reasonstringDescription of the escalation
sincestring | nullISO timestamp when the alert was raised
humanAssigneestring | nullAssigned human operator, if any

Errors:

StatusCodeWhen
503STORE_UNAVAILABLEPipeline store (Postgres) not connected

Reclaims a stalled task from the named worker — resets the task to pending so another worker can claim it. Auth required. No request body.

The id path parameter is URL-decoded before being matched against the worker registry.

Path parameters:

ParamTypeDescription
idstringWorker ID (URL-encode if it contains special characters)

ResponseWorkerReclaimResponse

When a stalled task was found and reclaimed:

{ "ok": true, "reclaimed": true, "taskId": "task-uuid", "issueNumber": 42 }

When no stalled task was found for this worker:

{ "ok": true, "reclaimed": false }
FieldTypeDescription
oktrueAlways true on success
reclaimedbooleantrue if a stalled task was found and reset to pending
taskIdstring (optional)ID of the reclaimed task; present only when reclaimed is true
issueNumbernumber (optional)Issue number of the reclaimed task; present only when reclaimed is true

Errors:

StatusCodeWhen
500RECLAIM_FAILEDDatabase error during the reclaim operation
503STORE_UNAVAILABLEPipeline store (Postgres) not connected

Returns strategy snapshots for a repository. Defaults to proposed and active snapshots. Auth required.

Query parameters:

ParamTypeRequiredDefaultDescription
statusstringNoproposed + activeFilter by status: proposed, approved, active, superseded

ResponseStrategySnapshotListResponse

{ "items": [{ "version": 1, "status": "proposed", "intent": "..." }], "snapshot": true }

Errors:

StatusCodeWhen
400INVALID_INPUTInvalid status value
503STORE_UNAVAILABLEPipeline store not connected
500INTERNAL_ERRORUnexpected query failure

GET /api/strategy/:owner/:repo/snapshots/:version

Section titled “GET /api/strategy/:owner/:repo/snapshots/:version”

Returns a single strategy snapshot by version number. Auth required.

Path parameters: owner, repo, version (integer)

ResponseStrategySnapshotDetailResponse

{ "snapshot": { "version": 1, "status": "proposed", "intent": "...", "rationale": "..." } }

Errors:

StatusCodeWhen
400INVALID_INPUTNon-integer version
404SNAPSHOT_NOT_FOUNDSnapshot version not found
503STORE_UNAVAILABLEPipeline store not connected
500INTERNAL_ERRORUnexpected query failure

POST /api/strategy/:owner/:repo/snapshots/:version/approve

Section titled “POST /api/strategy/:owner/:repo/snapshots/:version/approve”

Approves a proposed snapshot and applies it to tracks. Any authenticated monitor user may approve. Auth required.

Path parameters: owner, repo, version (integer)

Request body (optional JSON):

FieldTypeRequiredDescription
approverstringNoApprover identity; defaults to operator

ResponseStrategyApproveResponse

{ "ok": true }

Errors:

StatusCodeWhen
400INVALID_INPUTNon-integer version
422STRATEGY_APPLY_FAILEDSnapshot is stale (parent version mismatch) or violates the risk envelope
503STORE_UNAVAILABLEPipeline store not connected
500INTERNAL_ERRORUnexpected failure

POST /api/strategy/:owner/:repo/snapshots/:version/reject

Section titled “POST /api/strategy/:owner/:repo/snapshots/:version/reject”

Rejects a proposed snapshot with an optional recorded reason. The snapshot moves to terminal status rejected. Auth required.

Path parameters: owner, repo, version (integer)

Request body (optional JSON):

FieldTypeRequiredDescription
reasonstringNoHuman-readable reason for rejection

ResponseStrategyRejectResponse

{ "ok": true }

Errors:

StatusCodeWhen
400INVALID_INPUTNon-integer version
503STORE_UNAVAILABLEPipeline store not connected
500INTERNAL_ERRORUnexpected failure

Returns the active strategy charter for a repository. Auth required.

Path parameters: owner, repo

ResponseStrategyCharterResponse

{ "charter": { "version": 1, "goals": "...", "envelope": { "max_auto_size": "medium", ... } } }

Returns { "charter": null } when no active charter exists.

Errors:

StatusCodeWhen
503STORE_UNAVAILABLEPipeline store not connected
500INTERNAL_ERRORUnexpected query failure

Creates or replaces the active strategy charter for a repository. Any authenticated monitor user may call this endpoint. Auth required.

Path parameters: owner, repo

Request body:

FieldTypeRequiredDescription
goalsstringYesHuman-readable strategic goals
envelopeobjectYesRisk envelope constraining auto-apply behaviour
envelope.max_auto_sizestringYesMaximum issue size Strategist may auto-apply: small, medium, large, epic
envelope.preferstring[]YesTrack types the Strategist should favour
envelope.avoidstring[]YesTrack types the Strategist should avoid
envelope.escalate_whenstring[]YesConditions that require human escalation
authorstringNoAuthor identity; defaults to operator

ResponseStrategyCharterUpsertResponse

{ "ok": true, "version": 2 }

Errors:

StatusCodeWhen
400INVALID_JSONMalformed JSON body
400INVALID_BODYMissing goals, missing/invalid envelope, or invalid max_auto_size
503STORE_UNAVAILABLEPipeline store not connected
500INTERNAL_ERRORUnexpected failure

Returns the latest strategize cycle run state for a repository. Auth required.

Path parameters: owner, repo

ResponseStrategyRunStateResponse

{ "run": { "outcome": "failed", "failureReason": "LLM timeout", "costUsd": null, "nextDueAt": "2026-07-08T10:00:00.000Z", "createdAt": "2026-07-01T10:00:00.000Z", "consecutiveFailures": 3 } }

Returns { "run": null } when no strategize cycles have run yet for the repository.

Errors:

StatusCodeWhen
503STORE_UNAVAILABLEPipeline store not connected
500INTERNAL_ERRORUnexpected query failure

Returns Prometheus-format metrics. No auth required.

Response: text/plain; version=0.0.4; charset=utf-8

Metrics include agent health, alert counts, pipeline state gauges, tenant spend, per-repo pipeline metrics, and worker pool statistics.

For the full per-metric catalog (names, types, descriptions), see Prometheus Metrics Catalog.


Most error responses follow the ApiError shape:

interface ApiError {
error: string; // human-readable message
code: string; // machine-readable error code
status: number; // HTTP status code
}

All API error responses return the full ApiError shape — all three fields are always present, except for the empty-body paths listed under Unmatched routes below.

StatusMeaningWhen
200OKSuccessful request
400Bad RequestInvalid input, malformed JSON, missing required fields
401UnauthorizedMissing or invalid session cookie or Basic credentials
404Not FoundResource not found or unmatched route
500Internal Server ErrorUnexpected server-side failure
503Service UnavailablePipeline store (Postgres) not connected

Unmatched API routes return 404 with a structured JSON body: { "error": "No matching route", "code": "NOT_FOUND", "status": 404 }.

One response path intentionally returns an empty body (no JSON):

  • /assets/* 404s — directory traversal violations and missing static files.

Serves the dashboard SPA (index.html). If the dashboard has not been built, returns a placeholder HTML page. No auth required — the SPA shell must load so the in-page login form can render before authentication.

Serves static files from the dashboard build output directory. Includes directory traversal protection. No auth required — assets (JS, CSS) must load for the login form to function.