> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orcapods.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Runs

> A Run is a single task execution against a session. Events stream in real time over SSE.

## What is a Run?

A **Run** is a single task submitted to an agent. Each run:

1. Targets a named **Profile** (the agent's definition)
2. Carries a **SubTask** (the work to do)
3. Produces a stream of **RunEvents** (progress, tool calls, responses)
4. Is assigned a unique `runId` and a `sessionId`

Multiple runs can share a session, but only **one run** executes per session at a time.

***

## Submitting a Run

```bash theme={null}
POST /api/runs
Content-Type: application/json

{
  "profile": "researcher",
  "title": "Q1 market analysis",
  "prompt": "Summarize the top AI agent frameworks released in Q1 2026.",
  "files": []
}
```

**Response `202 Accepted`:**

```json theme={null}
{
  "runId": "run-f3a9b72c",
  "sessionId": "sess-a1b2c3d4-e5f6g7h8"
}
```

***

## SubTask Schema

```go theme={null}
type SubTask struct {
  ID        string
  ParentID  string
  Profile   string
  SessionID string
  Title     string
  Prompt    string
  Files     []string
}
```

***

## Streaming Events (SSE)

Once you have a `runId`, open an SSE stream:

```bash theme={null}
curl -N https://api.orcapods.ai/api/runs/run-f3a9b72c/stream \
  -H 'Accept: text/event-stream'
```

The conductor streams `RunEvent` objects as Server-Sent Events. Events are replayed for late subscribers (the conductor buffers the run's event log).

***

## RunEvent Types

Every event in the stream is a JSON object with a `type` field:

### `progress`

Lifecycle milestones emitted by the runner and conductor.

```json theme={null}
{
  "type": "progress",
  "message": "Starting run...",
  "ts": "2026-04-25T10:00:00Z"
}
```

### `session_init`

Runtime-native conversation handle emitted once per run when the sidecar reports it.
The conductor preserves it as `runtimeSessionId`; UIs can ignore this event unless they need to correlate an Orca session with a Claude SDK `session_id` or Codex `thread_id`.

```json theme={null}
{
  "type": "session_init",
  "runtimeSessionId": "sdk-session-or-thread-id",
  "ts": "2026-04-25T10:00:01Z"
}
```

### `assistant`

A text response from the LLM.

```json theme={null}
{
  "type": "assistant",
  "message": "Based on my research, the top frameworks are...",
  "ts": "2026-04-25T10:00:02Z"
}
```

### `tool_call`

The agent is invoking a tool.

```json theme={null}
{
  "type": "tool_call",
  "toolCallId": "call_abc123",
  "toolName": "web_search",
  "input": { "query": "AI agent frameworks 2026", "max_results": 5 },
  "ts": "2026-04-25T10:00:03Z"
}
```

### `tool_result`

The result of a tool invocation.

```json theme={null}
{
  "type": "tool_result",
  "toolCallId": "call_abc123",
  "toolName": "web_search",
  "output": { "results": [...] },
  "isError": false,
  "ts": "2026-04-25T10:00:04Z"
}
```

### `usage`

Token usage reported by the LLM provider.

```json theme={null}
{
  "type": "usage",
  "usage": {
    "inputTokens": 1240,
    "outputTokens": 380,
    "cacheReadTokens": 800,
    "cacheCreateTokens": 440
  },
  "ts": "2026-04-25T10:00:10Z"
}
```

### `result`

The final answer produced by the agent. Signals that the run is complete.

```json theme={null}
{
  "type": "result",
  "message": "1. LangGraph 2.0\n2. Orca\n3. AutoGen v3",
  "ts": "2026-04-25T10:00:11Z"
}
```

### `error`

An unrecoverable error during the run.

```json theme={null}
{
  "type": "error",
  "message": "LLM provider returned 429: rate limit exceeded",
  "ts": "2026-04-25T10:00:05Z"
}
```

***

## Complete Event Sequence

A typical run produces events in this order:

```
progress    "Starting run..."
progress    "Session ready"
assistant   (first token stream begins)
tool_call   web_search { query: "..." }
tool_result web_search { results: [...] }
assistant   (continues writing)
tool_call   web_extract { urls: [...] }
tool_result web_extract { content: "..." }
assistant   (final answer)
result      "Final answer: ..."
usage       { inputTokens: 1240, outputTokens: 380 }
```

***

## Consuming Events in TypeScript

```typescript theme={null}
const BASE = "https://api.orcapods.ai";
const apiKey = process.env.ORCA_API_KEY ?? "ao_..."; // Tenant API key (Settings → API Keys)
const authHeaders = {
  Authorization: `Bearer ${apiKey}`,
};

type RunEvent = { type: string; message?: string };

function parseSSEBlock(block: string): RunEvent | undefined {
  const data = block
    .split("\n")
    .filter((line) => line.startsWith("data:"))
    .map((line) => line.slice(5).trimStart())
    .join("\n");
  return data ? JSON.parse(data) : undefined;
}

async function* streamSSE(response: Response): AsyncGenerator<RunEvent> {
  if (!response.ok || !response.body) throw new Error("stream failed");

  const reader = response.body.getReader();
  const decoder = new TextDecoder();
  let buffer = "";

  while (true) {
    const { done, value } = await reader.read();
    if (done) break;
    buffer += decoder.decode(value, { stream: true });

    let boundary;
    while ((boundary = buffer.indexOf("\n\n")) !== -1) {
      const event = parseSSEBlock(buffer.slice(0, boundary));
      buffer = buffer.slice(boundary + 2);
      if (event) yield event;
    }
  }

  buffer += decoder.decode();
  if (buffer.trim()) {
    const event = parseSSEBlock(buffer);
    if (event) yield event;
  }
}

async function runAgent(profileName: string, prompt: string) {
  // 1. Submit run
  const { runId } = await fetch(`${BASE}/api/runs`, {
    method: "POST",
    headers: { "Content-Type": "application/json", ...authHeaders },
    body: JSON.stringify({
      profile: profileName,
      title: "Task",
      prompt,
    }),
  }).then((r) => r.json());

  // 2. Stream events. Use fetch instead of EventSource when you need headers.
  const response = await fetch(`${BASE}/api/runs/${runId}/stream`, {
    headers: { Accept: "text/event-stream", ...authHeaders },
  });

  for await (const event of streamSSE(response)) {
    switch (event.type) {
      case "assistant":
        process.stdout.write(event.message ?? "");
        break;
      case "result":
        return event.message ?? "";
      case "error":
        throw new Error(event.message);
    }
  }

  return "";
}

const result = await runAgent("researcher", "What is LangGraph?");
console.log("\n\nFinal:", result);
```

***

## Run Persistence

Runs are stored in an append-only **JSONL log** when `AGENT_ORC_RUNS_DIR` is set on the conductor:

```bash theme={null}
AGENT_ORC_RUNS_DIR=/data/runs ./conductor
```

Each run gets one `<runId>.jsonl` file containing a header, event lines, and a finish line. Conductor replays these files on restart to reconstruct the in-memory run registry.

Without `AGENT_ORC_RUNS_DIR`, runs are **in-memory only** and lost on conductor restart.

***

## Listing & Inspecting Runs

```bash theme={null}
# List recent runs
curl https://api.orcapods.ai/api/runs

# Get a specific run
curl https://api.orcapods.ai/api/runs/run-f3a9b72c
```

The `GET /api/runs/{id}` response includes the full event log for completed runs.

***

## Run Status

| Status        | Description                                                               |
| ------------- | ------------------------------------------------------------------------- |
| `running`     | Actively executing on a session                                           |
| `ok`          | `result` event received                                                   |
| `error`       | `error` event received                                                    |
| `cancelled`   | Explicitly cancelled                                                      |
| `interrupted` | Orphaned by conductor restart and closed by the boot reconciliation sweep |

## Stopping a Run

Use `DELETE /api/runs/{runId}` for normal, cooperative cancellation. It cancels the run context, aborts the runner stream and sidecar turn, and returns `204 No Content`.

If the run remains stuck, use `POST /api/runs/{runId}/terminate` as a last resort. Termination independently attempts to cancel any in-process run, waits up to two seconds for the session's run claim to drain normally, then steals the claim only if it remains held. It also marks the durable run row as `cancelled`; a late completion cannot overwrite that status. The response reports which steps succeeded:

```json theme={null}
{
  "runId": "run-f3a9b72c",
  "cancelled": true,
  "sessionReleased": true,
  "claimStolen": false,
  "statusWritten": true,
  "sessionId": "sess-a1b2c3d4-e5f6g7h8",
  "warnings": []
}
```

Termination is idempotent and still attempts the durable status and session-release steps when the run is no longer in the current conductor's in-memory registry. `claimStolen: true` identifies a claim that did not drain and had to be forcibly taken. Any `warnings` are stable codes rather than internal error details.
