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
workingreturnsEXECUTION_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
| State | Description |
|---|---|
idle | Created, waiting for a user.message event to trigger the run |
running | Actively processing, streaming agent output |
completed | Successfully finished, artifacts saved |
failed | Error occurred during execution |
cancelled | Cancelled before completion |
Starting an Execution
Skills are not executed over MCP — there is no
execute-skilltool. 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
| Type | Description | Key Payload Fields |
|---|---|---|
session.status_running | Execution started running | — |
session.status_idle | Execution ended | stop_reason: { type } |
agent.message | Text output from the agent | content: [{ type, text }] |
agent.tool_use | Tool call in progress | name, tool, label |
agent.thinking | Extended thinking block (internal) | — |
agent.info | Informational status | varies |
execution.result | Final result summary | summary, cost, tokenUsage |
span.model_request_end | Token usage per model call | usage: { input_tokens, output_tokens } |
Stop reason types (inside session.status_idle):
| Type | Description |
|---|---|
end_turn | Agent finished normally |
interrupt | User cancelled |
limit_reached | Execution credit limit hit |
error | Execution 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
| Limit | Value |
|---|---|
| Concurrent executions | 1 per workspace |
| Maximum duration | 65 minutes |
| Rate limit | 100 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):
| Component | Cost |
|---|---|
| Input tokens | 1 Ds per 1,000 tokens |
| Output tokens | 5 Ds per 1,000 tokens |
| Minimum | 10 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:
| Error | Cause | Solution |
|---|---|---|
INSUFFICIENT_CREDITS | Not enough Ds | Purchase credit pack |
EXECUTION_RUNNING | Another execution active | Wait or cancel first |
SKILL_NOT_FOUND | Skill not installed | Install the skill first |
RATE_LIMITED | Too many requests | Wait and retry |
TIMEOUT | Execution too long | Simplify input or split task |
Best Practices
- Subscribe before sending input — open the SSE stream (step 2) before posting
user.message(step 3) to avoid missing early events - Use SSE instead of polling — polling the status endpoint is fine for simple checks, but SSE is more efficient for real-time output
- Handle
session.status_idle— always closeEventSourceon this event - Reconnect with
sinceSeq— on reconnect, pass the last seen sequence number to skip already-rendered events - Keep inputs focused — smaller, specific inputs execute faster and cost less