Skip to main content
Every agent run produces a complete audit trail — the final output, every tool call, token usage, and execution logs. Two ways to get notified when a run completes: polling and webhooks.

Polling status

Endpoint
GET /agent/v1/run/:id/status
Lightweight endpoint — returns only the status and timing, no output or steps.
const status = await client.agents.runs.getStatus(run.id)
// { id, status, startedAt, completedAt, durationMs }
Response
{
  "id": "run_abc123",
  "status": "completed",
  "startedAt": "2025-01-15T10:30:05Z",
  "completedAt": "2025-01-15T10:35:22Z",
  "durationMs": 317000
}
FieldDescription
statusqueued, running, completed, failed, or cancelled
durationMsElapsed time. For running tasks, this updates on each poll.
Poll every 2-5 seconds. Status is a lightweight read — no rate limit concerns at this interval.

Getting full results

Endpoint
GET /agent/v1/run/:id/details
Returns everything: the output, every step the agent took, token usage, and execution logs.
const details = await client.agents.runs.getDetails(run.id)

console.log(details.result.output) // Final text output
console.log(details.steps.length) // Number of steps taken
Response
{
  "id": "run_abc123",
  "agentId": "agent_xyz",
  "status": "completed",
  "prompt": "Research employment law...",
  "result": {
    "output": "## Research Report\n\nBased on my analysis of the vault documents...",
    "logs": {
      "opencode": "opencode server listening on http://0.0.0.0:4096...",
      "runner": "[runner] Sending prompt to OpenCode..."
    }
  },
  "usage": {
    "model": "anthropic/claude-sonnet-4.6",
    "inputTokens": 45000,
    "outputTokens": 3200,
    "toolCalls": 12,
    "durationMs": 317000
  },
  "steps": [
    {
      "id": "step_001",
      "type": "tool_call",
      "toolName": "bash",
      "toolInput": { "command": "casedev vault list" },
      "toolOutput": "{ \"vaults\": [...] }",
      "durationMs": 1200,
      "timestamp": "2025-01-15T10:30:10Z"
    },
    {
      "id": "step_002",
      "type": "output",
      "content": "I found 3 vaults. Let me search the most relevant one...",
      "timestamp": "2025-01-15T10:30:12Z"
    }
  ],
  "createdAt": "2025-01-15T10:30:00Z",
  "startedAt": "2025-01-15T10:30:05Z",
  "completedAt": "2025-01-15T10:35:22Z"
}

Response fields

FieldTypeDescription
result.outputstringThe agent’s final text response
result.logsobjectSandbox execution logs (OpenCode server + runner script)
usage.inputTokensnumberTotal input tokens consumed
usage.outputTokensnumberTotal output tokens generated
usage.toolCallsnumberNumber of tool invocations
stepsarrayOrdered list of every action the agent took

Step types

TypeDescription
outputText generated by the agent
thinkingInternal reasoning (if model supports it)
tool_callTool invocation with input
tool_resultTool output/response

Watching runs

Register an HTTPS callback URL to get notified when a run completes. Useful for production workflows where you don’t want to poll.
Endpoint
POST /agent/v1/run/:id/watch
// Register before executing
const run = await client.agents.runs.create({
  agentId: agent.id,
  prompt: 'Analyze the contract...',
})

await client.agents.runs.watch(run.id, {
  callbackUrl: 'https://your-app.com/webhooks/agent-run',
})

await client.agents.runs.exec(run.id)

Callback requirements

RequirementDetails
ProtocolHTTPS only — HTTP is rejected
NetworkMust be publicly reachable — private IPs, localhost, and .local domains are blocked
Multiple watchersYou can register multiple callback URLs for the same run
Retries3 retries with exponential backoff
The callback is best-effort. Always treat /run/:id/details as the source of truth. Use watchers to trigger your workflow, then fetch the full details.

Production polling pattern

For production use, wrap polling in a timeout:
async function waitForRun(
  client: Casedev,
  runId: string,
  timeoutMs = 600_000
): Promise<RunDetails> {
  const deadline = Date.now() + timeoutMs

  while (Date.now() < deadline) {
    const status = await client.agents.runs.getStatus(runId)

    switch (status.status) {
      case 'completed':
        return client.agents.runs.getDetails(runId)
      case 'failed':
        throw new Error(`Run failed after ${status.durationMs}ms`)
      case 'cancelled':
        throw new Error('Run was cancelled')
      default:
        await new Promise((r) => setTimeout(r, 3000))
    }
  }

  throw new Error(`Run timed out after ${timeoutMs}ms`)
}

const details = await waitForRun(client, run.id)
console.log(details.result.output)

Next: Sandbox environment

Learn what tools and capabilities are available inside the sandbox →