AI Run Lifecycle
A Run represents a single execution of an AI feature, agent, or workflow. Runs follow a defined state machine and can be monitored in real time via Server-Sent Events (SSE).
Creating a Run
Start a run by calling the feature execution endpoint:
POST /api/v1/ai/features/:featureKey/runs
Authorization: Bearer <accessToken>
Content-Type: application/json
{
"input": {
"prompt": "Analyze my weekly progress"
}
}The response returns the run object with its initial state:
{
"data": {
"id": "run_abc123",
"featureKey": "weekly-analysis",
"status": "queued",
"createdAt": "2026-03-17T10:00:00.000Z"
},
"meta": {
"requestId": "req_xyz789",
"durationMs": 45
}
}Run States
Every run transitions through a defined set of states:
| State | Description |
|---|---|
| queued | The run has been created and is waiting to be picked up for processing. |
| processing | The run is actively executing. Partial results may be streamed via SSE. |
| completed | The run finished successfully. The full result is available in the run object. |
| failed | The run encountered an error. The error details are available in the run object. |
| cancelled | The run was cancelled by the user before completion. |
Real-Time Streaming (SSE)
Monitor a run's progress in real time by subscribing to its Server-Sent Events stream:
GET /api/v1/ai/runs/:runId/stream
Authorization: Bearer <accessToken>
Accept: text/event-streamThe server sends typed semantic events as the run progresses:
event: run.status.changed
data: {"eventId":1,"type":"run.status.changed","runId":"run_abc123","payload":{"status":"running"}}
event: message.delta
data: {"eventId":2,"type":"message.delta","runId":"run_abc123","payload":{"delta":"Based on your activity this week..."}}
event: message.delta
data: {"eventId":3,"type":"message.delta","runId":"run_abc123","payload":{"delta":" you completed 85% of your chains."}}
event: message.completed
data: {"eventId":4,"type":"message.completed","runId":"run_abc123","payload":{"content":"Based on your activity this week... you completed 85% of your chains."}}Event Types
| Event | Description |
|---|---|
run.status.changed | The run transitioned to a new state. |
message.delta | A partial result from the AI model — always clean user-facing text, never raw reasoning. |
message.completed | The final assistant message is available. |
cot.step | A reasoning step (observation, thought, action, or reflection) emitted by Chao before the reply, when CoT tracing is active. |
tool.* | Chao tool-card, progress, approval, and result events. |
run.error / run.failed | An error occurred during execution. |
run.settlement.completed | The run is fully settled and terminal. |
Client Implementation
Most HTTP clients and frameworks support SSE natively:
const eventSource = new EventSource(
'https://api.chainabit.com/api/v1/ai/runs/run_abc123/stream',
{
headers: {
'Authorization': 'Bearer <accessToken>'
}
}
);
eventSource.addEventListener('message.delta', (event) => {
const { payload } = JSON.parse(event.data);
process.stdout.write(payload.delta);
});
eventSource.addEventListener('run.settlement.completed', () => eventSource.close());
eventSource.addEventListener('run.error', () => eventSource.close());
eventSource.addEventListener('run.failed', () => eventSource.close());Agent-style Runs
Chao runs use a multi-step agent loop. A single user message can drive several iterations of planning, tool calls, and answer composition before producing the final assistant response. Multiple tool.started / tool.completed events may fire for one user message, followed by a single message.completed once the answer is verified.
If your client wants a structured, typed view of the agent's progress, subscribe to the agent.* event family alongside the existing message.* and tool.* events.
| Event | Payload | When |
|---|---|---|
agent.phase.changed | { phase, iteration } | Each high-level phase transition (planning, tool selection, observation, verification, finalization). |
agent.verify.started | { iteration } | The agent is checking its draft answer before sending it. |
agent.verify.passed | { iteration, attempts } | The verifier accepted the answer. |
agent.verify.failed | { iteration, attempt, refinementReason } | The verifier rejected the draft and the agent will refine. |
agent.budget.exhausted | { reason } | The agent reached its reasoning-step limit. The final message is a partial result. |
phase values your client may want to render: plan_or_reason, tool_request, execute_tool, observe_tool_result, verify, message_draft, needs_approval, ask_clarification, final. Internal phases (load_context, decide_next_action, update_working_state, persist_trace) are emitted but should not produce visible UI.
The platform guarantees message.completed always carries a non-empty content field, even if the agent budget was exhausted — in that case agent.budget.exhausted fires before message.completed and the content explains the partial result.
Clients that ignore agent.* events continue to work exactly as before.
Cancelling a Run
Cancel a run that is in queued or processing state:
POST /api/v1/ai/runs/:runId/cancel
Authorization: Bearer <accessToken>Response:
{
"data": {
"cancelled": true
}
}Cancellation is ownership-enforced: you can only cancel runs that belong to your account. Providing a runId that does not exist or belongs to another account returns { "cancelled": false } — the platform does not distinguish between these cases to avoid information disclosure.
Cancellation is best-effort. Tokens already streamed are not retracted and may still be billed.
Retrying a Run
Retry a failed run to create a new execution attempt:
POST /api/v1/ai/runs/:runId/retry
Authorization: Bearer <accessToken>The retry creates a new run that re-enters the queued state. The original failed run is preserved for audit purposes.
{
"data": {
"id": "run_ghi789",
"featureKey": "weekly-analysis",
"status": "queued",
"retriedFromRunId": "run_abc123",
"createdAt": "2026-03-17T10:05:00.000Z"
},
"meta": {
"requestId": "req_jkl012",
"durationMs": 38
}
}Retrying a run that is not in failed state returns a 400 error.
Provider Resilience (Paid Plans)
On paid plans, the platform automatically routes your run to a secondary AI provider when the primary provider becomes temporarily unavailable or rate-limited. This happens transparently — you will not see a failed run; the response arrives from an alternate model.
When a model switch occurs, the SSE stream emits a run.model.switched event:
{
"event": "run.model.switched",
"data": {
"runId": "run_abc123",
"fromProvider": "google",
"toProvider": "openai",
"reason": "unavailable"
}
}Credits are charged for whichever model ultimately handled the request. The credit cost is calculated based on the actual model used, not the originally selected one.
Free-plan runs use a single provider and do not benefit from automatic provider switching.
Saved Provider Preference
Save a default provider in your account preferences so you don't need to specify it on every message:
PATCH /api/v1/preferences
Authorization: Bearer <accessToken>
Content-Type: application/json
{ "preferredAiProvider": "openai" }Valid values: google, openai, anthropic, mistral. Your plan must include access to the chosen provider. If your subscription changes or expires, the saved preference is silently ignored and requests fall back to the plan default (google) — no errors occur.
Per-message provider always takes precedence over the saved preference.
Credit Consumption
Each run consumes credits from your wallet. Credits are deducted when the run enters the processing state. If a run fails due to a platform error (not a user error), credits may be refunded automatically. Check the wallet documentation for details on credit balance and usage.