Executions

An execution is a single run of a skill with specific input. Executions are processed asynchronously and stream results in real-time via Server-Sent Events backed by a durable, replayable event stream.

One-At-A-Time Rule: Each user can only have one active execution at a time. Attempting to start a second execution while one is working returns EXECUTION_RUNNING. For multi-step pipelines or automatic queueing, use Workflows (requires Based Mode).

Execution Lifecycle

┌────────┐    user.message event    ┌─────────┐    agent finishes    ┌───────────┐
│  idle  │ ─────────────────────>  │ working │ ──────────────────>  │ completed │
└────────┘                         └─────────┘                       └───────────┘

                                   user cancel / error

                                   ┌────────┐
                                   │ failed │  (stop_reason: error or interrupt)
                                   └────────┘

States

StateDescription
idleCreated, waiting for a user.message event to trigger the run
runningActively processing, streaming agent output
completedSuccessfully finished, artifacts saved
failedError occurred during execution
cancelledCancelled before completion

Starting an Execution

Skills are not executed over MCP — there is no execute-skill tool. Start runs through the REST API below. Over MCP you discover, install, and monitor skills (get-execution-status, list-executions, cancel-execution).

Via REST API — one shot

The simplest path runs a skill in a single request:

curl -X POST https://clanker.net/api/v1/skills/readme-generator/run \
  -H "Content-Type: application/json" \
  -H "x-api-key: ck_live_xxxxx" \
  -d '{ "input": "Create a README for my TypeScript CLI tool" }'

Response:

{
  "executionId": "exec_abc123",
  "skillName": "README Generator",
  "status": "started",
  "message": "Execution started. Connect to SSE stream for real-time updates.",
  "streamTokenUrl": "/api/v1/executions/exec_abc123/stream-token"
}

Via REST API (Sessions API — three steps)

The Sessions API pattern (compatible with managed-agent providers) lets you subscribe to the stream before the run begins.

Step 1 — Create the execution (both skill_slug and a non-empty input are required):

curl -X POST https://clanker.net/api/v1/executions \
  -H "Content-Type: application/json" \
  -H "x-api-key: ck_live_xxxxx" \
  -d '{ "skill_slug": "readme-generator", "input": "Create a README for my TypeScript CLI tool" }'

Response: { "id": "exec_abc123", "skill_slug": "readme-generator", "skill_name": "README Generator", "status": "idle", "streamTokenUrl": "/api/v1/executions/exec_abc123/stream-token" }

Step 2 — Mint a signed stream URL, then connect to it (before sending input):

GET /api/v1/executions/exec_abc123/stream-token
→ { "sseUrl": "https://sandbox.clanker.net/executions/exec_abc123/events?ts=…&sig=…" }

Connect an EventSource to that sseUrl. It is short-lived and ownership-checked, so re-mint it on every reconnect. The stream is served by the sandbox worker, not by this API.

Step 3 — Send user.message to start the run:

curl -X POST https://clanker.net/api/v1/executions/exec_abc123/events \
  -H "Content-Type: application/json" \
  -H "x-api-key: ck_live_xxxxx" \
  -d '{
    "events": [
      {
        "type": "user.message",
        "content": [{ "type": "text", "text": "Create a README for my TypeScript CLI tool" }]
      }
    ]
  }'

See Executions API for the full endpoint reference and session-level stream details.

Streaming Results

Executions stream results via Server-Sent Events on a durable, replayable endpoint served by the sandbox worker. Mint its signed URL first — this API exposes no GET stream of its own:

GET /api/v1/executions/:executionId/stream-token
→ { "sseUrl": "…/executions/:executionId/events?ts=…&sig=…" }

POST /api/v1/executions/:executionId/events is a different thing entirely: it SENDS user.message / user.interrupt INTO a run. It accepts no GET.

The stream replays all history from the beginning (gap-free), then delivers live events. It closes automatically when session.status_idle arrives.

Event Types

TypeDescriptionKey Payload Fields
session.status_runningExecution started running
session.status_idleExecution endedstop_reason: { type }
agent.messageText output from the agentcontent: [{ type, text }]
agent.tool_useTool call in progressname, tool, label
agent.thinkingExtended thinking block (internal)
agent.infoInformational statusvaries
execution.resultFinal result summarysummary, cost, tokenUsage
span.model_request_endToken usage per model callusage: { input_tokens, output_tokens }

Stop reason types (inside session.status_idle):

TypeDescription
end_turnAgent finished normally
interruptUser cancelled
limit_reachedExecution credit limit hit
errorExecution failed

SSE Client Example

// 1. Mint the signed stream URL (authenticated, ownership-checked).
const { sseUrl } = await fetch(
  `https://clanker.net/api/v1/executions/${executionId}/stream-token`,
  { headers: { 'x-api-key': 'ck_live_xxxxx' } }
).then((r) => r.json());

// 2. Connect to it. The signature is IN the URL, so no auth header is needed
//    (and EventSource cannot send one anyway).
const eventSource = new EventSource(sseUrl);

eventSource.onmessage = (event) => {
  const data = JSON.parse(event.data);

  if (data.type === 'agent.message') {
    for (const block of data.content) {
      if (block.type === 'text') process.stdout.write(block.text);
    }
  }

  if (data.type === 'agent.tool_use') {
    console.log('Using tool:', data.label);
  }

  if (data.type === 'session.status_idle') {
    console.log('Done. Stop reason:', data.stop_reason?.type);
    eventSource.close();
  }
};

Checking Execution Status

Via MCP

{
  "tool": "get-execution-status",
  "arguments": {}
}

Response (working):

{
  "success": true,
  "data": {
    "executionId": "exec_abc123",
    "status": "working",
    "skillSlug": "readme-generator",
    "startedAt": "2024-01-15T10:30:00Z"
  }
}

Response (idle):

{
  "success": true,
  "data": {
    "status": "idle",
    "message": "No active executions"
  }
}

Via REST API

GET /api/v1/executions/:executionId/status

Execution Limits

LimitValue
Concurrent executions1 per workspace
Maximum duration65 minutes
Rate limit100 requests/minute

One execution runs per workspace at a time. A user with multiple workspaces can run executions in different workspaces concurrently.

Execution Cost

Each execution consumes Dollarinos (Ds):

ComponentCost
Input tokens1 Ds per 1,000 tokens
Output tokens5 Ds per 1,000 tokens
Minimum10 Ds per execution

The final cost appears in the execution.result event:

{
  "summary": "Generated README.md with 8 sections",
  "cost": 45,
  "tokenUsage": { "input": 1500, "output": 8000 }
}

List Executions

Via MCP

{
  "tool": "list-executions",
  "arguments": { "limit": 10 }
}

Via REST API

GET /api/v1/executions

Rerunning Executions

Any completed execution can be rerun with new instructions:

  • Rerun creates a new output (new artifact)
  • Original outputs remain unchanged

Parent-Child Execution Linking

┌──────────────────┐         ┌─────────────────┐
│ Parent Execution │ ──────> │ Child Execution  │
│ (original)       │         │ (rerun)          │
└──────────────────┘         └─────────────────┘
  • Each execution can have at most one child
  • If you rerun a child, it replaces the previous rerun
  • Both parent and child are visible in execution detail view

Error Handling

Common execution errors:

ErrorCauseSolution
INSUFFICIENT_CREDITSNot enough DsPurchase credit pack
EXECUTION_RUNNINGAnother execution activeWait or cancel first
SKILL_NOT_FOUNDSkill not installedInstall the skill first
RATE_LIMITEDToo many requestsWait and retry
TIMEOUTExecution too longSimplify input or split task

Best Practices

  1. Subscribe before sending input — open the SSE stream (step 2) before posting user.message (step 3) to avoid missing early events
  2. Use SSE instead of polling — polling the status endpoint is fine for simple checks, but SSE is more efficient for real-time output
  3. Handle session.status_idle — always close EventSource on this event
  4. Reconnect with sinceSeq — on reconnect, pass the last seen sequence number to skip already-rendered events
  5. Keep inputs focused — smaller, specific inputs execute faster and cost less