Skip to content

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:

http
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:

json
{
  "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:

StateDescription
queuedThe run has been created and is waiting to be picked up for processing.
processingThe run is actively executing. Partial results may be streamed via SSE.
completedThe run finished successfully. The full result is available in the run object.
failedThe run encountered an error. The error details are available in the run object.
cancelledThe 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:

http
GET /api/v1/ai/runs/:runId/stream
Authorization: Bearer <accessToken>
Accept: text/event-stream

The 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

EventDescription
run.status.changedThe run transitioned to a new state.
message.deltaA partial result from the AI model — always clean user-facing text, never raw reasoning.
message.completedThe final assistant message is available.
cot.stepA 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.failedAn error occurred during execution.
run.settlement.completedThe run is fully settled and terminal.

Client Implementation

Most HTTP clients and frameworks support SSE natively:

javascript
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.

EventPayloadWhen
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:

http
POST /api/v1/ai/runs/:runId/cancel
Authorization: Bearer <accessToken>

Response:

json
{
  "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:

http
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.

json
{
  "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:

json
{
  "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:

http
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.

Built with purpose.