← Architecture · Back to README · Configuration →
Base URL: http://localhost:3009
All endpoints return JSON. Request bodies use application/json.
GET /health
Response: 200 OK
{
"status": "ok",
"uptime": 123
}GET /agent/readiness
Checks whether agent authentication is configured via ANTHROPIC_API_KEY and/or Claude profile auth (~/.claude).
Response: 200 OK
{
"ready": true,
"hasApiKey": false,
"hasClaudeAuth": true,
"authSource": "claude_profile",
"detectedPath": "/Users/you/.claude/auth.json",
"message": "Agent authentication is configured.",
"checkedAt": "2026-03-28T17:10:00.000Z"
}authSource values: api_key, claude_profile, both, none.
GET /settings
Returns frontend-visible defaults and runtime readiness metadata.
Response: 200 OK
{
"useSubagents": false,
"maxReviewIterations": 3,
"autoReviewStrategy": "full_re_review",
"runtimeReadiness": {
"availableRuntimeCount": 3,
"runtimeProfileCount": 6,
"enabledRuntimeProfileCount": 5
},
"runtimeDefaults": {
"modules": [],
"openAiBaseUrlConfigured": false,
"codexCliPathConfigured": true,
"app": {
"defaultTaskRuntimeProfileId": "uuid-or-null",
"defaultPlanRuntimeProfileId": null,
"defaultReviewRuntimeProfileId": null,
"defaultChatRuntimeProfileId": "uuid-or-null",
"resolvedDefaultTaskRuntimeProfileId": "uuid-or-null",
"resolvedDefaultPlanRuntimeProfileId": "uuid-or-null",
"resolvedDefaultReviewRuntimeProfileId": "uuid-or-null",
"resolvedDefaultChatRuntimeProfileId": "uuid-or-null"
}
}
}autoReviewStrategy is the resolved global auto-review mode (full_re_review or closure_first).
GET /settings/runtime-defaults
PUT /settings/runtime-defaults
Reads or updates the app-wide runtime defaults used after project defaults and before environment fallback.
PUT body:
{
"defaultTaskRuntimeProfileId": "uuid-or-null",
"defaultPlanRuntimeProfileId": "uuid-or-null",
"defaultReviewRuntimeProfileId": "uuid-or-null",
"defaultChatRuntimeProfileId": "uuid-or-null"
}Rules:
- values must be
nullor enabled global runtime profiles plan/reviewfall back to the app task default when unset- invalid scope combinations fail with
400andfieldErrors
GET /projects
Response: 200 OK
[
{
"id": "uuid",
"name": "My Project",
"rootPath": "/path/to/project",
"plannerMaxBudgetUsd": 10,
"planCheckerMaxBudgetUsd": 2,
"implementerMaxBudgetUsd": 15,
"reviewSidecarMaxBudgetUsd": 2,
"createdAt": "2026-01-01T00:00:00.000Z",
"updatedAt": "2026-01-01T00:00:00.000Z"
}
]POST /projects
Body:
| Field | Type | Required | Description |
|---|---|---|---|
name |
string | yes | Project name (1-200 chars) |
rootPath |
string | yes | Absolute path to project root |
plannerMaxBudgetUsd |
number | no | Budget for planner agent. If omitted, unlimited |
planCheckerMaxBudgetUsd |
number | no | Budget for plan-checker agent. If omitted, unlimited |
implementerMaxBudgetUsd |
number | no | Budget for implementer agent. If omitted, unlimited |
reviewSidecarMaxBudgetUsd |
number | no | Per-sidecar budget for review/security sidecars. If omitted, unlimited |
defaultTaskRuntimeProfileId |
string|null | no | Project-level task/implementation runtime default |
defaultPlanRuntimeProfileId |
string|null | no | Project-level planning runtime default |
defaultReviewRuntimeProfileId |
string|null | no | Project-level review runtime default |
defaultChatRuntimeProfileId |
string|null | no | Project-level chat runtime default |
Response: 201 Created — the created project object.
PUT /projects/:id
Body: Same as Create Project.
Response: 200 OK — the updated project object.
Parallel auto-queue with git.create_branches=true requires
AIF_TASK_WORKTREES_ENABLED=true. With the default false, the API rejects
that combination and the coordinator keeps branch-isolated projects serial.
GET /projects/:id/roadmap/status
Checks whether .ai-factory/ROADMAP.md exists in the project root directory.
Response: 200 OK
{
"exists": true
}Errors:
404— Project not found
POST /projects/:id/roadmap/import
Reads .ai-factory/ROADMAP.md from the project root, uses Agent SDK to convert milestones into structured tasks, and creates them as backlog items with deduplication.
Body:
| Field | Type | Required | Description |
|---|---|---|---|
roadmapAlias |
string | yes | Alias for grouping imported tasks (e.g., v1.0, sprint-1) |
Response: 201 Created
{
"roadmapAlias": "v1.0",
"created": 5,
"skipped": 2,
"taskIds": ["uuid-1", "uuid-2", "..."],
"byPhase": {
"1": { "created": 3, "skipped": 1 },
"2": { "created": 2, "skipped": 1 }
}
}Deduplication: Tasks are deduped by projectId + normalizedTitle + roadmapAlias. Re-running import with the same alias skips already-existing tasks.
Tag enrichment: Each created task automatically receives tags: roadmap, rm:<alias>, phase:<number>, phase:<name>, seq:<nn>.
Errors:
404— Project not found orROADMAP.mdmissing500— Agent SDK unavailable or response parse failure
WebSocket events: task:created for each new task, agent:wake after batch completion.
Timeout: This endpoint may take 30-120 seconds due to Agent SDK processing.
DELETE /projects/:id
Response: 200 OK
{ "success": true }GET /projects/:id/auto-queue-mode
Returns the current auto-queue state for the project. When enabled, the
coordinator advances the next backlog task (by position) into planning
whenever the project has no active/locked task.
Response: 200 OK
{ "enabled": true }PATCH /projects/:id/auto-queue-mode
Body: { "enabled": boolean }
Broadcasts project:auto_queue_mode_changed over WebSocket so connected
clients can update their board indicator.
Response: 200 OK
{ "enabled": true }Parallel auto-queue with git.create_branches=true requires
AIF_TASK_WORKTREES_ENABLED=true. Queued full-mode tasks then receive isolated
git worktrees when planning starts.
GET /projects/:id/warmup
Returns the feature flag state, warmup support metadata for the project's effective planner, implementer, and review runtimes, and active ready warmup sessions if they exist. The response never includes raw seed session ids.
Response: 200 OK
{
"enabled": true,
"support": {
"supported": true,
"skipReason": null,
"workflowKind": "planner",
"profileMode": "plan",
"runtimeId": "claude",
"providerId": "anthropic",
"runtimeProfileId": "profile-1",
"transport": "sdk",
"model": "claude-sonnet-4",
"selectionSource": "project_default"
},
"targets": [
{
"supported": true,
"skipReason": null,
"workflowKind": "planner",
"profileMode": "plan",
"runtimeId": "claude",
"providerId": "anthropic",
"runtimeProfileId": "profile-1",
"transport": "sdk",
"model": "claude-sonnet-4",
"selectionSource": "project_default"
}
],
"warmup": {
"id": "warmup-1",
"projectId": "project-1",
"runtimeProfileId": "profile-1",
"runtimeId": "claude",
"providerId": "anthropic",
"transport": "sdk",
"model": "claude-sonnet-4",
"status": "ready",
"ttlSeconds": 3600,
"expiresAt": "2026-04-30T12:00:00.000Z",
"remainingSeconds": 2400,
"summary": "Warmup summary",
"errorMessage": null,
"createdAt": "2026-04-30T11:00:00.000Z",
"updatedAt": "2026-04-30T11:00:10.000Z"
},
"warmups": [
{
"id": "warmup-1",
"projectId": "project-1",
"runtimeProfileId": "profile-1",
"runtimeId": "claude",
"providerId": "anthropic",
"transport": "sdk",
"model": "claude-sonnet-4",
"status": "ready",
"ttlSeconds": 3600,
"expiresAt": "2026-04-30T12:00:00.000Z",
"remainingSeconds": 2400,
"summary": "Warmup summary",
"errorMessage": null,
"createdAt": "2026-04-30T11:00:00.000Z",
"updatedAt": "2026-04-30T11:00:10.000Z"
}
]
}POST /projects/:id/warmup
Creates reusable seed sessions for each distinct effective warmup-capable
planner, implementer, and review runtime. Requires AIF_WARMUP_ENABLED=true
and a runtime transport that advertises session fork support. TTL is bounded to
60–86400 seconds.
Body:
{ "ttlSeconds": 3600 }Response: 201 Created
Returns the same shape as GET /projects/:id/warmup.
If at least one target warmup is created successfully and a later target fails,
the endpoint returns 207 Multi-Status with the active successful or previously
ready warmups instead of treating the whole request as a cold failure:
{
"error": "Runtime failed while creating warmup",
"code": "partial_warmup_failed",
"failedTarget": "implementer",
"partial": true,
"warmup": {
"id": "warmup-failed",
"status": "failed"
},
"warmups": [
{
"id": "warmup-ready",
"status": "ready"
}
],
"support": {
"supported": true,
"workflowKind": "planner"
},
"targets": []
}Clients should treat warmups in a partial response as usable for the listed
targets and retry only the failed/missing target instead of assuming all warmups
were discarded.
Errors:
400— invalid TTL.403— warmup feature flag is disabled.404— project not found.409— none of the effective warmup target runtimes support session fork.502— runtime execution failed or did not return a seed session id.207— partial success; at least one target warmup remains active while another target failed.
WebSocket event: project:warmup_updated with { projectId, status }.
DELETE /projects/:id/warmup
Clears active warmup rows for the project's current effective warmup target runtimes.
Response: 200 OK
{ "success": true, "cleared": 1 }WebSocket event: project:warmup_updated when at least one row is cleared.
GET /projects/:id/mcp
Reads .mcp.json from the project root and returns its MCP servers map.
Response: 200 OK
{
"mcpServers": {
"example": {
"command": "node",
"args": ["./server.js"]
}
}
}If .mcp.json does not exist (or cannot be parsed), returns:
{ "mcpServers": {} }POST /projects/:id/broadcast
Used by API/agent services to trigger project-scoped WebSocket broadcasts without polling.
Security contract:
- Intended for trusted internal callers (API/agent/mcp services).
- If
INTERNAL_BROADCAST_TOKENis configured, callers must provide the same token viaAuthorization: Bearer <token>orX-Internal-Broadcast-Token. - If no token is configured, only
NODE_ENV=developmentenables the fallback path, and it only accepts loopback caller headers (127.0.0.1,::1,localhost). - Unauthorized callers receive
401. - Relation validation is enforced before broadcasting:
project:auto_queue_advancedreturns400whentaskIddoes not belong to the target project.project:runtime_limit_updatedreturns400whenruntimeProfileIdis omitted.project:runtime_limit_updatedreturns400whenruntimeProfileIddoes not belong to the target project and is not global.
Body:
| Field | Type | Required | Description |
|---|---|---|---|
type |
string | yes | One of project:auto_queue_mode_changed, project:auto_queue_advanced, project:runtime_limit_updated |
taskId |
string | no | Optional related task id for runtime-limit updates |
runtimeProfileId |
string|null | conditional | Required for project:runtime_limit_updated; runtime profile whose persisted limit snapshot changed |
Response: 200 OK
{ "success": true }Runtime profiles carry non-secret transport/model config plus the latest persisted runtime-limit snapshot used by API, agent, and UI surfaces.
For local Codex runtimes (runtimeId=codex with sdk/cli transport), /runtime-profiles and /runtime-profiles/effective/* now read limit overlays from the SQLite Codex index (codex_limit_heads) maintained by the background API indexer. Request handlers do not perform direct ~/.codex/sessions scans.
GET /runtime-profiles?projectId=<uuid>&includeGlobal=true&enabledOnly=false
Response: 200 OK — array of runtime profile objects.
Notable runtime profile fields in list/detail/effective responses:
| Field | Type | Description |
|---|---|---|
runtimeLimitSnapshot |
object|null | Latest normalized provider/runtime limit state persisted for this profile |
runtimeLimitUpdatedAt |
string|null | ISO timestamp when the profile snapshot was last written or cleared |
lastUsage |
object|null | Last recorded per-run usage totals for this profile (input/output/total) |
lastUsageAt |
string|null | ISO timestamp of the latest recorded usage event for this profile |
GET /runtime-profiles/effective/task/:taskId
GET /runtime-profiles/effective/chat/:projectId
Both responses include the resolved profile object (or null) plus source metadata. When a profile is present, its payload includes runtimeLimitSnapshot and runtimeLimitUpdatedAt.
If no indexed Codex head is available for the resolved account/project scope, the response falls back to the persisted profile snapshot.
The normalized runtimeLimitSnapshot object is shared across runtime-profile, task, and chat payloads:
| Field | Type | Description |
|---|---|---|
source |
string | Limit source: provider_api, sdk_event, api_headers, or turn_usage |
status |
string | ok, warning, blocked, or unknown |
precision |
string | exact for hard quota data, heuristic for provider qualitative state |
checkedAt |
string | ISO timestamp when the snapshot was observed |
providerId |
string | Provider namespace (for example anthropic, openai) |
runtimeId |
string|null | Runtime adapter id |
profileId |
string|null | Runtime profile id when known |
primaryScope |
string|null | Main quota window scope (requests, tokens, time, etc.) |
resetAt |
string|null | Provider reset timestamp when available |
retryAfterSeconds |
number|null | Retry hint when only backoff seconds are known |
warningThreshold |
number|null | Exact threshold percentage when the provider reports it |
windows |
array | Per-window quota details (remaining, percentRemaining, resetAt, ...) |
providerMeta |
object|null | Sanitized provider-specific qualitative metadata kept for diagnostics/UI |
providerMeta is client-visible and always normalized before leaving the server. It may include safe identifiers such as limitId, providerLabel, quotaSource, accountLabel, accountFingerprint, planType, modelUsageSummary, or toolUsageSummary, but raw provider responses, headers, bodies, traces, and token-like fields are stripped or redacted.
GET /tasks?projectId=<uuid>
| Param | Type | Required | Description |
|---|---|---|---|
projectId |
query string | no | Filter by project. Omit to list all tasks |
Response: 200 OK — array of task objects sorted by status order, then position.
POST /tasks
Body:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
projectId |
string | yes | Project UUID | |
title |
string | yes | Task title (1-500 chars) | |
description |
string | no | "" |
Task description |
attachments |
array | no | [] |
File attachments (max 10) |
priority |
integer | no | 0 |
Priority level (0-5) |
autoMode |
boolean | no | true |
Auto-advance through agent pipeline, including automatic post-review rework loop when fixes are detected |
isFix |
boolean | no | false |
Marks the task as fix-flow task (uses FIX plan conventions) |
skipReview |
boolean | no | false |
Skip the review stage — task moves directly from implementing to done |
paused |
boolean | no | false |
Pause agent processing — coordinator skips this task until resumed |
useSubagents |
boolean | no | false |
Run via custom subagents (plan-coordinator, implement-coordinator, sidecars). false uses aif-* skills directly |
runtimeProfileId |
string | null | no | null |
Task-specific runtime override. When absent, resolution falls back to project default, then app default, then environment fallback |
roadmapAlias |
string | no | null |
Roadmap alias for grouping (e.g., v1.0) |
tags |
string[] | no | [] |
Tags for filtering/categorization (max 50, each max 100 chars) |
scheduledAt |
string | null | no | null |
ISO-8601 UTC timestamp. If set, the coordinator fires the task into planning at that time. Must be in the future; null clears it. Accepted on both create and update. |
Attachment object:
| Field | Type | Description |
|---|---|---|
name |
string | File name (1-500 chars) |
mimeType |
string | MIME type (max 200 chars) |
size |
integer | File size in bytes (max 10MB) |
content |
string|null | Base64 content (max 2MB encoded) |
Response: 201 Created — the created task object.
WebSocket event: task:created
GET /tasks/:id
Response: 200 OK — full task object.
Notable task fields in the response:
| Field | Type | Description |
|---|---|---|
manualReviewRequired |
boolean | true when auto-review stopped and explicit human review is required while the task remains in done |
autoReviewState |
object|null | Latest persisted blocking-findings snapshot used by the auto-review loop (strategy, iteration, findings[]) |
runtimeLimitSnapshot |
object|null | Persisted runtime-limit snapshot copied onto the task when quota gating or quota failure blocks execution |
runtimeLimitUpdatedAt |
string|null | ISO timestamp for the last task-level runtime-limit snapshot write |
GET /tasks/:id/attachments/:filename
Downloads a file-backed attachment from the task. The :filename must match the attachment name in the task's attachments array.
Response: 200 OK — binary file with Content-Disposition: attachment.
Errors:
404— task not found, attachment not found, or file missing from disk.
GET /tasks/:id/comments/:commentId/attachments/:filename
Downloads a file-backed attachment from a task comment. The :filename must match the attachment name in the comment's attachments array.
Response: 200 OK — binary file with Content-Disposition: attachment.
Errors:
404— task, comment, or attachment not found, or file missing from disk.
PUT /tasks/:id
Body: All fields optional:
| Field | Type | Description |
|---|---|---|
title |
string | Task title |
description |
string | Task description |
attachments |
array | File attachments |
priority |
integer | Priority (0-5) |
autoMode |
boolean | Auto-advance mode (includes automatic post-review rework loop when enabled) |
paused |
boolean | Pause/resume agent processing for this task |
runtimeProfileId |
string|null | Task-specific runtime override |
isFix |
boolean | Marks task as fix-flow |
plan |
string|null | Generated plan (markdown) |
implementationLog |
string|null | Implementation output |
reviewComments |
string|null | Review feedback |
manualReviewRequired |
boolean | Explicit human-review handoff flag for done tasks |
agentActivityLog |
string|null | Agent activity timeline |
blockedReason |
string|null | Why the task is blocked |
blockedFromStatus |
string|null | Status before being blocked |
retryAfter |
string|null | ISO timestamp for retry |
roadmapAlias |
string|null | Roadmap alias for grouping |
tags |
string[] | Tags for filtering |
retryCount |
integer | Number of retries |
lastHeartbeatAt |
string|null | Last heartbeat timestamp from coordinator/subagent activity |
Response: 200 OK — the updated task object.
WebSocket event: task:updated
DELETE /tasks/:id
Response: 200 OK
{ "success": true }WebSocket event: task:deleted
POST /tasks/:id/events
Transitions a task through the state machine.
Body:
| Field | Type | Description |
|---|---|---|
event |
string | One of the valid task events |
Valid events by current status:
| Current Status | Valid Events |
|---|---|
backlog |
start_ai, accept_existing_plan |
plan_ready |
start_implementation, request_replanning, fast_fix |
blocked_external |
retry_from_blocked |
done |
approve_done, request_changes |
Additional constraints:
start_implementationrequiresautoMode=false(manual gate). ForautoMode=true, implementation is picked automatically by the coordinator.accept_existing_planreads the plan file from disk, saves it to the database, and transitions directly toplan_ready— skipping the planning stage entirely. Returns404if the plan file does not exist on disk.fast_fixrequiresautoMode=falseand at least one human comment on the task.request_changestransitionsdone -> implementing, setsreworkRequested=true, and resets watchdog retry state (retryCount=0).- With
autoMode=true, coordinator can trigger this samerequest_changes-style rework loop automatically after review if blocking findings are extracted fromreviewComments. - If auto-review stops converging, the coordinator leaves the task in
done, setsmanualReviewRequired=true, and waits for a humanapprove_doneorrequest_changesaction.
Response: 200 OK — the updated task object.
Error: 409 Conflict if the event is not valid for the current status.
WebSocket event: task:moved
PATCH /tasks/:id/position
Body:
| Field | Type | Description |
|---|---|---|
position |
number | New position value for sorting |
Response: 200 OK — the updated task object.
WebSocket event: task:updated
POST /tasks/:id/broadcast
Used by the agent process to trigger WebSocket broadcasts after updating a task.
Security contract:
- Intended for trusted internal callers (agent/API services only).
- Uses the same internal auth rules as
POST /projects/:id/broadcast. - Unauthorized callers receive
401.
Body:
| Field | Type | Default | Description |
|---|---|---|---|
type |
string | task:updated |
Event type: task:updated, task:moved, task:activity, or task:scheduled_fired |
Response: 200 OK
{ "success": true }GET /tasks/:id/comments
Response: 200 OK — array of comment objects sorted by createdAt ascending.
[
{
"id": "uuid",
"taskId": "uuid",
"author": "human",
"message": "Comment text",
"attachments": [],
"createdAt": "2026-01-01T00:00:00.000Z"
}
]POST /tasks/:id/comments
Body:
| Field | Type | Required | Description |
|---|---|---|---|
message |
string | yes | Comment text (1-20,000 chars) |
attachments |
array | no | File attachments (max 10) |
Response: 201 Created — the created comment object.
GET /runtime-profiles
Query params:
| Param | Type | Required | Description |
|---|---|---|---|
projectId |
string | no | Project id for project-scoped queries and mixed listings |
includeGlobal |
boolean | no | Include global profiles alongside project profiles |
enabledOnly |
boolean | no | Return only enabled profiles |
scope |
global|project|visible |
no | global: only global profiles, project: only same-project profiles, visible: project profiles plus visible globals |
scope=project requires projectId. scope=global returns only reusable profiles (projectId = null).
scope=visible is the default when omitted.
For local Codex profiles, this endpoint overlays the response from indexed Codex limit heads in SQLite instead of scanning ~/.codex/sessions during request handling.
GET /runtime-profiles/effective/task/:taskId
GET /runtime-profiles/effective/chat/:projectId
These endpoints return the effective runtime profile plus the resolution source. The runtime chain is:
- task override
- project default
- app default
- environment fallback
Planning and review follow the same pattern but use their dedicated defaults before inheriting from the task default at the same scope.
Wraps codex login --device-auth running inside the agent container so the
host browser can complete the device-code flow. All /auth/codex/login/*
mutating endpoints are gated behind AIF_ENABLE_CODEX_LOGIN_PROXY=true. When
the flag is false only /auth/codex/capabilities is mounted; the others
return 404. See Providers
for the full design.
GET /auth/codex/capabilities
Response: 200 OK
{ "loginProxyEnabled": true }POST /auth/codex/login/start
Spawns codex login --device-auth in the agent container and parses the
verification URL plus one-time code.
Response: 200 OK
{
"sessionId": "9f3c1a8e-...",
"verificationUrl": "https://auth.openai.com/codex/device",
"userCode": "ABCD-12345",
"startedAt": "2026-04-27T16:30:00.000Z"
}409 Conflict when a session is already active — the body still carries
{sessionId, verificationUrl, userCode} so the client can adopt it.
500 on spawn failure or device-auth parse timeout.
502 when the API cannot reach the broker over the docker network.
GET /auth/codex/login/status
Response: 200 OK
{
"active": true,
"sessionId": "9f3c1a8e-...",
"verificationUrl": "https://auth.openai.com/codex/device",
"userCode": "ABCD-12345",
"startedAt": "2026-04-27T16:30:00.000Z"
}When no session is active the response carries the terminal result of the last run so the client can distinguish success from failure:
{
"active": false,
"lastResult": {
"ok": true,
"sessionId": "9f3c1a8e-...",
"reason": "success",
"exitCode": 0,
"signal": null,
"finishedAt": "2026-04-27T16:32:14.000Z"
}
}reason is one of success, exit_nonzero, signal, timeout,
parse_timeout, cancel, spawn_failed. The UI must gate the success
transition on lastResult.ok === true. If no session has ever run the field
is omitted.
| Reason | Meaning |
|---|---|
success |
Codex CLI exited with code 0 and no signal — ~/.codex/auth.json was written. |
exit_nonzero |
Codex CLI exited with a non-zero status code (network/TLS failure, server reject). |
signal |
Codex CLI was killed by a signal (e.g. SIGKILL) before completing login. |
timeout |
The 5-minute wizard session expired before the user completed the browser flow. |
parse_timeout |
The CLI did not print a verification URL + code within 15 seconds of spawn. |
cancel |
The user pressed Cancel; broker SIGTERMed the child. |
spawn_failed |
The codex binary could not be spawned (missing/unexecutable, ENOENT/EACCES). |
POST /auth/codex/login/cancel
SIGTERMs the active child process. Records a terminal result with
reason: "cancel", ok: false.
Response: 200 OK
{ "ok": true, "cancelled": true, "sessionId": "9f3c1a8e-..." }{ ok: true, cancelled: false } when there was no active session.
Interactive AI chat powered by the runtime adapter system. Messages are sent via REST, responses stream back through WebSocket as tokens. The runtime used depends on the effective chat runtime for the project: project chat default, then app chat default, then environment fallback.
POST /chat
Body:
| Field | Type | Required | Default | Description |
|---|---|---|---|---|
projectId |
string | yes | Project UUID — sets the agent's working directory | |
message |
string | yes | User message (1-50,000 chars) | |
clientId |
string | yes | WebSocket client ID for streaming tokens back | |
conversationId |
string | no | auto-generated | Pass the previous conversationId to continue a multi-turn conversation |
explore |
boolean | no | false |
When true, the message is prefixed with /aif-explore for codebase exploration mode |
taskId |
string | no | Task UUID — injects the task's full context (status, plan, implementation log, review comments, and redacted activity log) into the chat session for task-aware discussion |
Response: 200 OK
{
"conversationId": "uuid",
"sessionId": "uuid-or-null",
"assistantMessage": null,
"attachments": [],
"runtimeLimitSnapshot": null
}Errors:
404— Project not found429— Runtime usage limit reached (code: "CHAT_USAGE_LIMIT", response may includeruntimeLimitSnapshot)500— Chat request failed (code: "CHAT_REQUEST_FAILED")
On error, a chat:error event is sent via WebSocket before the HTTP response. Both HTTP and WebSocket chat payloads normalize runtimeLimitSnapshot before emission, so client-visible snapshots follow the same sanitized contract as runtime-profile and task payloads.
Timeout: Requests may take up to 120 seconds due to agent processing.
Chat responses stream via WebSocket events to the clientId specified in the request:
| Event | Payload | Description |
|---|---|---|
chat:token |
{ conversationId, token } |
Incremental text token from the agent |
chat:done |
{ conversationId, usage?, projectId?, taskId?, runtimeProfileId?, runtimeLimitSnapshot? } |
Stream completed |
chat:error |
{ conversationId, message, code, projectId?, taskId?, runtimeProfileId?, runtimeLimitSnapshot? } |
Error occurred during streaming |
To continue a conversation, pass the conversationId returned from the first message in subsequent requests. The server tracks runtime session IDs internally and uses resume to maintain context (for runtimes that support it).
Calling clearMessages on the client (or omitting conversationId) starts a fresh conversation.
GET /chat/sessions?projectId=<uuid>
POST /chat/sessions
PUT /chat/sessions/:id
DELETE /chat/sessions/:id
Chat sessions persist the runtime profile chosen when the session starts. This keeps older conversations tied to the runtime they were created with even if the project's current default changes later.
For local Codex runtimes, session discovery uses the indexed codex_sessions read-model. Session detail/message reads resolve sessionId -> filePath from the same index before compatibility fallback to runtime-adapter lookups.
POST and PUT accept runtimeProfileId as an optional field. The value must be either a global profile or one owned by the same project.
The agent runs with permissionMode: "bypassPermissions" by default (when AGENT_BYPASS_PERMISSIONS=true in environment) — all file edits and shell commands are auto-approved, matching the behavior of task-processing subagents.
When AGENT_BYPASS_PERMISSIONS=false, the agent runs with permissionMode: "acceptEdits" — file reads and edits are auto-approved, but dangerous shell commands require confirmation.
The chat agent has access to: Read, Glob, Grep, Bash, Edit, Write, Skill. Max turns per request: 20. The agent is scoped to the project's root path and instructed not to access files outside it.
Allowed skills: aif-docs, aif-ci, aif-explore, aif-reference, aif-evolve, aif-build-automation, aif-dockerize, aif-grounded, aif, aif-rules. Other skills are blocked by the system prompt.
When taskId is provided, the chat session's system prompt includes the full task context:
- Task title, status, and description
- Plan (if available)
- Implementation log (if available)
- Review comments (if available)
- Agent activity log (if available)
This allows the agent to discuss implementation details, summarize what was done, help debug review feedback, or create follow-up tasks — all without re-reading from storage.
The chat agent can emit structured actions embedded in responses. The client parses these and presents confirmation cards for user approval.
CREATE_TASK — when the user asks to create a task from the conversation, the agent outputs:
<!--ACTION:CREATE_TASK-->
{"title": "Task title", "description": "Detailed description"}
<!--/ACTION-->The client extracts the JSON, renders a preview card with the task title and description, and shows a "Create Task" button. On confirmation, the task is created via POST /tasks in the current project.
The chat streams additional feedback beyond text tokens:
- Tool use summaries — after a tool executes, its human-readable summary is streamed as a blockquote
- Permission denials — if a tool is blocked by the permission mode, a
**Permission denied**message is streamed with the tool name - Agent errors — max turns, budget limits, and execution errors are surfaced as
**Error:**messages instead of silent failures
When explore: true, the user message is wrapped as /aif-explore <message>, invoking the codebase exploration skill. This is toggled via the "Explore" checkbox in the UI.
Connect to ws://localhost:3009/ws for real-time updates.
All events are JSON with this structure:
{
"type": "event-type",
"payload": {}
}| Event | Payload | Triggered By |
|---|---|---|
project:created |
Full project object | POST /projects |
task:created |
Full task object | POST /tasks, POST /projects/:id/roadmap/import |
task:updated |
Full task object | PUT /tasks/:id, PATCH /tasks/:id/position, POST /tasks/:id/events (fast_fix) |
task:moved |
Full task object | POST /tasks/:id/events |
task:deleted |
{ id: string } |
DELETE /tasks/:id |
sync:task_created |
Full task object | MCP handoff_create_task |
sync:task_updated |
Full task object | MCP handoff_update_task, handoff_push_plan |
sync:status_changed |
Full task object | MCP handoff_sync_status |
sync:plan_pushed |
Full task object | MCP handoff_push_plan |
chat:token |
{ conversationId, token } |
POST /chat — streaming response tokens |
chat:done |
{ conversationId, usage?, projectId?, taskId?, runtimeProfileId?, runtimeLimitSnapshot? } |
POST /chat — stream completed |
chat:error |
{ conversationId, message, code, projectId?, taskId?, runtimeProfileId?, runtimeLimitSnapshot? } |
POST /chat — error during streaming |
task:scheduled_fired |
Full task object | Coordinator fires a backlog task whose scheduledAt is due |
project:auto_queue_mode_changed |
Full project object | PATCH /projects/:id/auto-queue-mode |
project:auto_queue_advanced |
{ id: string } (task id) |
Coordinator auto-advances the next backlog task in an auto-queue project |
project:runtime_limit_updated |
{ projectId, runtimeProfileId, taskId? } |
Persisted runtime-profile limit state or last usage changed |
project:warmup_updated |
{ projectId, status } |
Warmup create/delete/failure changed project warmup state |
The WebSocket endpoint is a broadcast channel with no topic subscriptions; connected clients receive all events. Keep this endpoint behind trusted network boundaries and do not include raw provider diagnostics or secrets in event payloads.
Runtime-limit invalidation is project-scoped:
project:runtime_limit_updatedpayload is{ projectId, runtimeProfileId, taskId? }, andruntimeProfileIdis required at emission time.- API/agent callers emit this via
POST /projects/:id/broadcastafter runtime snapshot/usage updates. project:warmup_updatedpayload is{ projectId, status }, wherestatusisready,failed,partial,cleared, orexpired.
The Handoff MCP server (packages/mcp) provides bidirectional sync between AIF tooling and Handoff via the Model Context Protocol. When MCP tools modify tasks, they broadcast sync:* events over the WebSocket system so the Kanban UI reflects changes in real time.
The web settings route POST /settings/mcp/install installs the MCP server into supported runtimes. When MCP_PORT is a valid integer port in the server environment, it writes a streamable HTTP entry pointing to http://localhost:<MCP_PORT>/mcp; otherwise it writes the local stdio launcher entry. The response includes per-runtime success/error entries, so partial install failures are surfaced without hiding runtimes that succeeded.
See MCP Sync Server for full documentation.
- Architecture — system overview and data flow
- Configuration — server port and environment settings
- MCP Sync Server — MCP tools and sync protocol