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

# Runner Wire API

> Internal HTTP API exposed by each Runner for conductor routing, session ownership, tool invocation, and MCP access.

<Note>
  The Runner wire API is internal. Applications should normally use the [Conductor API](/api-reference/conductor).
</Note>

## Base URL

```bash theme={null}
http://runner:7070
```

State-changing endpoints that do not return data use `204 No Content`. Errors use:

```json theme={null}
{ "error": "message" }
```

***

## Runner Info

### Get Runner Info

<ParamField path="GET /runner/info" />

Returns the runner identity used by the conductor pool.

```bash theme={null}
curl http://runner:7070/runner/info
```

```json theme={null}
{
  "id": "runner-a1b2c3d4",
  "hash": "a1b2c3d4",
  "version": "dev",
  "startedAt": "2026-04-25T10:00:00Z",
  "capabilities": ["claude", "codex", "general"],
  "toolkitNames": ["echo", "list_dir", "math_add", "time_now"],
  "substrates": ["daytona", "e2b"],
  "defaultSubstrate": "daytona",
  "substratesKnown": true,
  "mcpBaseUrl": "http://runner-1:7070"
}
```

`hash` is `SHA256(RUNNER_BASE_URL)[:8]`. Session IDs embed this hash so any conductor can route a session back to its owning runner.

`substrates` is the sorted set of worker substrates this runner can launch;
`defaultSubstrate` is the one used when a sandbox-worker profile does not make
an explicit choice. Read the list together with `substratesKnown`: `true` with
an empty list means this runner definitively launches no workers, while an
omitted or false value means an older runner did not report the capability.

***

## Runtime Introspection

### List Runtimes

<ParamField path="GET /runner/runtimes" />

Returns the runtime slots served by this runner, including how each slot is
wired and its current process-wide session count:

```json theme={null}
{
  "runtimes": [
    {
      "id": "claude",
      "name": "claude",
      "mode": "sidecar",
      "url": "http://sidecar-claude:7071",
      "activeSessions": 3
    },
    {
      "id": "general",
      "name": "general",
      "mode": "stub",
      "url": "",
      "activeSessions": 0
    }
  ]
}
```

`mode` is `sidecar` when the runner is bound to an agent-worker base URL,
`stub` when it uses the in-process adapter, or `unknown` when the runtime
cannot expose its slot configuration. `url` is the configured sidecar base
URL and is empty for stub slots; it may front multiple worker replicas and is
not an instance identity. `activeSessions` counts live sessions resolved to
that runtime across all tenants on the runner. Runtimes that cannot provide a
cross-tenant session snapshot report `0`.

### Get Runner Topology

<ParamField path="GET /runner/topology" />

Returns an on-demand snapshot of the runner process, session counts by runtime,
and the sidecar instances observed behind each configured runtime slot:

```bash theme={null}
curl http://runner:7070/runner/topology
```

```json theme={null}
{
  "process": {
    "allocBytes": 18432000,
    "sysBytes": 25600000,
    "heapInuseBytes": 12000000,
    "numGoroutine": 42,
    "numCPU": 8,
    "uptimeSeconds": 86400
  },
  "sessionsByRuntime": { "claude": 3, "codex": 0, "general": 1 },
  "sidecars": [
    {
      "runtime": "claude",
      "mode": "sidecar",
      "endpoint": "http://sidecar-claude:7071",
      "state": "live",
      "activeSessions": 3,
      "observedInstances": [
        {
          "instanceId": "worker-a1b2c3d4",
          "firstSeenAt": "2026-04-25T10:00:00Z",
          "lastSeenAt": "2026-04-25T10:05:00Z",
          "runsServed": 12,
          "consecutiveFailures": 0,
          "lastProbedAt": "2026-04-25T10:04:55Z",
          "inflight": 1,
          "uptimeSeconds": 3600,
          "pid": 42,
          "nodeVersion": "v24.9.0",
          "memory": {
            "rssBytes": 80000000,
            "heapUsedBytes": 30000000,
            "heapTotalBytes": 50000000,
            "externalBytes": 2000000
          },
          "cpu": { "userMicros": 1200000, "systemMicros": 300000 }
        }
      ]
    }
  ]
}
```

`sessionsByRuntime` and each slot's `activeSessions` are process-wide counts
across tenants. Slot states are `stub`, `live`, `cold`, `degraded`, or
`unreachable`: `cold` means a configured slot has no recent successful
observation, while `unreachable` means its latest probe or transport attempt
failed without a recent success. The runner observes instance IDs passively
from run responses and probes a quiet sidecar endpoint's `/health` endpoint on
demand for process stats. An `observedInstances` list is therefore a lower
bound on replicas behind the configured endpoint, not an exact fleet count;
load-balancer connection pinning can leave running replicas unseen. Unprobed
instance stats are `null`. When multiple runtime slots share one endpoint, as
in a `MODE=all` poly sidecar, those slots report the same observed instances
and health state and the endpoint is probed only once. Do not sum
`observedInstances` across such slots: that would count the same worker
processes multiple times.

***

## Profiles

### List Profiles

<ParamField path="GET /runner/profiles" />

```json theme={null}
{ "profiles": [{ "name": "general", "runtime": "general" }] }
```

### Create Profile

<ParamField path="POST /runner/profiles" />

Conductors call this to seed or broadcast profile definitions.

```json theme={null}
{
  "profile": {
    "name": "researcher",
    "runtime": "general",
    "model": "anthropic:claude-sonnet-4-6",
    "tools": ["@default", "web_search", "web_extract"]
  }
}
```

**Response:** `204 No Content`

### Delete Profile

<ParamField path="DELETE /runner/profiles/{name}" />

***

## Pools

### List Pools

<ParamField path="GET /runner/pools" />

```json theme={null}
{ "pools": [{ "name": "research-team", "members": [{ "profile": "researcher", "role": "lead" }] }] }
```

### Create Pool

<ParamField path="POST /runner/pools" />

```json theme={null}
{
  "pool": {
    "name": "research-team",
    "members": [{ "profile": "researcher", "role": "lead" }]
  }
}
```

### Delete Pool

<ParamField path="DELETE /runner/pools/{name}" />

***

## Sessions

### List Sessions

<ParamField path="GET /runner/sessions" />

```json theme={null}
{
  "sessions": [
    {
      "id": "sess-a1b2c3d4-e5f6a7b8",
      "profile": "researcher",
      "runtime": "general",
      "status": 0,
      "createdAt": "2026-04-25T10:00:00Z",
      "lastUsedAt": "2026-04-25T10:00:00Z"
    }
  ]
}
```

Runner session status is the raw enum from `types.SessionStatus`: `0=idle`, `1=running`, `2=errored`, `3=shutdown`.

### Create Session

<ParamField path="POST /runner/sessions" />

```json theme={null}
{ "profileName": "researcher" }
```

**Response `201 Created`:**

```json theme={null}
{ "sessionId": "sess-a1b2c3d4-e5f6a7b8" }
```

### Get Session

<ParamField path="GET /runner/sessions/{sessionId}" />

```json theme={null}
{ "session": { "id": "sess-a1b2c3d4-e5f6a7b8", "profile": "researcher", "runtime": "general" } }
```

### Session Health

<ParamField path="GET /runner/sessions/{sessionId}/health" />

```json theme={null}
{ "healthy": true }
```

When unhealthy, the response is still `200 OK` with `healthy: false` and an `error` string.

### List VFS Leases

<ParamField path="GET /runner/vfs/leases" />

Returns the active per-session VirtualFS leases on this runner. The response is always an array; runners without a VFS manager return `[]`.

```json theme={null}
[
  {
    "sessionId": "sess-a1b2c3d4-e5f6a7b8",
    "allowedMounts": ["/agents", "/pools"],
    "allocatedAt": "2026-04-25T10:00:00Z"
  }
]
```

An empty `allowedMounts` array means the VirtualFS session is unrestricted.

### Get Session VFS Lease

<ParamField path="GET /runner/sessions/{sessionId}/vfs" />

Returns the VirtualFS lease for one session.

```json theme={null}
{
  "sessionId": "sess-a1b2c3d4-e5f6a7b8",
  "allowedMounts": ["/agents"],
  "allocatedAt": "2026-04-25T10:00:00Z"
}
```

Returns `404` with `{ "error": "no vfs session" }` when the session has no active VFS lease.

### List Sandbox Worker Leases

<ParamField path="GET /runner/worker/leases" />

Returns the per-session sandbox-worker leases owned by this runner. The
conductor-facing alias is `GET /api/worker/leases`; in conductor mode it fans
out across runners and may return partial results if a runner does not answer
within the bounded lookup window. Deployments with no worker substrate, and
deployments serving only static profiles, return `[]`.

Rows are sorted by `sessionId` on both planes. The broker's lease map is
keyed by session and iterated in map order, so the handler imposes the sort
rather than trusting what the runtime returned; paging an unordered set would
drop and repeat rows between requests.

**Tenant scoping.** The result is scoped to the tenant on the request context,
which the conductor verifies and forwards as `X-Tenant-ID` for
`runnerTenantMiddleware` to restamp. A request with no tenant on context is the
internal/admin fan-out and still sees every lease. `GET /api/sessions/{id}/worker`
scopes the same way and answers `404` for another tenant's session, matching the
`404` a static-profile session already returns so it reveals nothing about
whether the id exists.

**Pagination.** The `/api/*` alias honors `?limit=` and `?offset=` and sets
`X-Total-Count` to the caller's full (tenant-scoped) lease count. The
`/runner/*` path is deliberately **not** paginated: the conductor's fan-out
reads it whole before merging, and a default window there would silently
truncate a busy runner and make the merged fleet total wrong.

```json theme={null}
[
  {
    "sessionId": "sess-a1b2c3d4-e5f6a7b8",
    "substrate": "daytona",
    "workerId": "sandbox-7f90c2",
    "runtime": "general",
    "state": "running",
    "startedAt": "2026-08-14T10:00:00Z"
  }
]
```

`state` is one of `starting`, `running`, `paused`, `gone`, or `unknown`, read
live from the substrate so it reflects a sandbox stopped or deleted outside
Orca. `paused` is the normal resting state of an idle session and resumes with
its conversation intact; `gone` means the next run launches a replacement
worker without it. `unknown` means the substrate could not be reached, which is
not the same as gone.

It describes the lease, not worker-process health. A substrate can keep a
sandbox available after the worker exits so its diagnostic log can be read.

### Get Session Sandbox Worker

<ParamField path="GET /runner/sessions/{sessionId}/worker" />

Returns one sandbox-worker lease using the same schema. The conductor-facing
alias is `GET /api/sessions/{sessionId}/worker`. A static-profile session, an
unknown session, or a sandbox-worker session before its first run returns
`404` with `{ "error": "no sandbox worker" }`. An upstream runner failure
returns `502`.

### Delete Session

<ParamField path="DELETE /runner/sessions/{sessionId}" />

Shuts down the session and its scoped MCP endpoint.

### Hydrate Session

<ParamField path="POST /runner/sessions/{sessionId}/hydrate" />

Recreates a session under a caller-supplied id after a runner restart. The conductor uses this to restore the runner record and, when present, push an opaque sidecar state bundle back into the runtime.

```json theme={null}
{
  "profileName": "researcher",
  "runtimeSessionId": "sdk-session-or-thread-id",
  "stateContentType": "application/gzip",
  "stateB64": "H4sIAAAAA..."
}
```

`stateB64` is optional and carries the exported state bundle as base64. The endpoint is idempotent: if the session already exists on that runner, the hydrate request is a no-op.

**Response:** `204 No Content`

Returns `400` when the request body fails to decode or `stateB64` is not valid base64. Returns `404` when `profileName` does not match a profile known to this runner, or when `sessionId` is already in use by a session that belongs to a different tenant.

### Export Session State

<ParamField path="GET /runner/sessions/{sessionId}/state" />

Exports the sidecar's opaque state bundle for the session. The response body is the raw bundle, with the envelope carried in headers:

| Header                 | Description                                                                                |
| ---------------------- | ------------------------------------------------------------------------------------------ |
| `Content-Type`         | Bundle media type, such as `application/gzip`                                              |
| `X-Runtime-Session-Id` | Runtime-native conversation handle, such as a Claude SDK `session_id` or Codex `thread_id` |

Returns `404` when the session is unknown or the sidecar has no exportable state. Export failures such as sidecar 5xx responses, transport errors, or bundles over the runner size cap return `500` so clients do not mistake them for a missing bundle.

***

## Run Execution

### Execute Run

<ParamField path="POST /runner/run" />

The conductor calls this action endpoint on the runner that owns the session. The response is an NDJSON stream of run events.

```json theme={null}
{
  "subTask": {
    "id": "run-f3a9b72c",
    "parentId": "run-parent",
    "profile": "researcher",
    "sessionId": "sess-a1b2c3d4-e5f6a7b8",
    "title": "Research task",
    "prompt": "What is the capital of France?",
    "files": []
  }
}
```

**Response:** `application/x-ndjson`

```json theme={null}
{"type":"progress","message":"Starting run..."}
{"type":"session_init","runtimeSessionId":"sdk-session-or-thread-id"}
{"type":"assistant","message":"The capital of France is Paris."}
{"type":"result","message":"Paris"}
{"type":"usage","usage":{"inputTokens":42,"outputTokens":8}}
```

Event types are `progress`, `session_init`, `assistant`, `tool_call`, `tool_result`, `usage`, `result`, and `error`.

***

## Toolkit

### List Global Toolkit

<ParamField path="GET /runner/toolkit/specs" />

Returns every platform tool registered in the runner process.

```json theme={null}
{
  "specs": [
    {
      "name": "time_now",
      "description": "Current wall-clock time in an optional IANA timezone (default UTC).",
      "inputSchema": { "type": "object" },
      "scope": "platform",
      "capability": "introspection"
    }
  ]
}
```

### List Session Toolkit

<ParamField path="GET /runner/sessions/{sessionId}/toolkit/specs" />

Returns the profile-scoped toolkit for one session. Unknown sessions return `404`.

### Invoke Session Tool

<ParamField path="POST /runner/sessions/{sessionId}/toolkit/invoke" />

Executes a tool through the session-scoped registry. Out-of-scope tools return `403` even if the runner hosts that tool globally.

```json theme={null}
{
  "toolName": "math_add",
  "input": { "a": 2, "b": 3 }
}
```

**Response `200 OK`:**

```json theme={null}
{
  "result": {
    "ok": true,
    "value": { "sum": 5 }
  }
}
```

Tool-level failures also return `200 OK` with `result.ok: false`; non-200 responses are wire-level errors such as bad JSON, missing session, or an out-of-scope tool.

***

## Sandbox Worker Callback API

Profiles with `workerMode: "sandbox"` use an outbound-only worker. Every request below requires `Authorization: Bearer <ORCA_WORKER_TOKEN>`. The runner verifies that the token's tenant and session claims match the requested session.

| Method and path                             | Behavior                                                                                                                                                                                                                                   |
| ------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `GET /worker/sessions/{sessionId}/next-run` | Long-polls for a protocol v1 `run`, `export_state`, or `shutdown` command. Returns `204` after an idle poll timeout.                                                                                                                       |
| `POST /worker/sessions/{sessionId}/events`  | Uploads NDJSON run events. `X-Orca-Run-Id` must match the active command; stale or cancelled uploads return `409`. One line may be at most 4 MiB, the complete upload at most 256 MiB, and one upload may remain open for at most 4 hours. |
| `POST /worker/sessions/{sessionId}/state`   | Uploads the latest opaque state bundle. `Content-Type` and `X-Runtime-Session-Id` carry its envelope.                                                                                                                                      |

Missing, invalid, expired, or session-mismatched tokens return `401`. The JSON error keeps the `invalid or missing worker token` prefix and identifies whether the request had no token, the service has no usable key, the token expired or is malformed, its session does not match, or the signer and verifier disagree. Unknown sessions return `404`, and runners without the dynamic-worker runtime surface return `503`. An event upload over 256 MiB returns `413`; one that exceeds 4 hours returns `408`. Either condition ends the event stream with an error so the run fails instead of appearing to finish with truncated output. State uploads use the runner's existing state-bundle size cap and return `413` when exceeded.

<Warning>
  `ORCA_WORKER_TOKEN` and the `orca_worker_token` query parameter embedded in a sandbox worker's session MCP URL are bearer credentials. Do not write them to logs, traces, or error messages. The worker token has a 24-hour lifetime; the MCP query token is minted per run and normally lasts 2 hours when the run has no deadline. A run deadline replaces that default with the remaining time plus 5 minutes of grace, subject to a 5-minute floor and the runner's 24-hour signing ceiling.
</Warning>

***

## Sandbox Event Invalidation (Internal)

<ParamField path="POST /runner/sandbox-events/invalidate" />

The conductor uses this runner-internal route after decoding a provider event. It accepts `{ "provider": "daytona", "sandboxId": "..." }`, marks matching worker-lease status caches stale, and returns `{ "invalidated": 1 }`. A zero count is normal when this runner does not own the provider-scoped sandbox ID. Keep this route on the trusted runner network; unlike the public provider ingress, it accepts an already-decoded event.

***

## Session MCP Endpoint

<ParamField path="POST /runner/sessions/{sessionId}/mcp" />

<ParamField path="GET /runner/sessions/{sessionId}/mcp" />

<ParamField path="DELETE /runner/sessions/{sessionId}/mcp" />

The runner exposes a streamable-HTTP MCP server per session. Sidecars use the URL advertised in the run envelope, and external MCP clients can connect directly when they can reach the runner. Sandbox-worker sessions require their session-scoped worker token; static-sidecar sessions retain the existing unauthenticated runner-network behavior. When an MCP base URL is configured for a sandbox session, the runner refuses to dispatch the run if its signing key is unavailable, token minting fails, or the minter returns an empty token. The run error names the signing configuration or mint failure instead of advertising a bare capability URL that would fail later with `401`.

```bash theme={null}
curl -X POST http://runner:7070/runner/sessions/sess-a1b2c3d4-e5f6a7b8/mcp \
  -H 'Content-Type: application/json' \
  -d '{"jsonrpc":"2.0","method":"tools/list","id":1}'
```

The MCP surface maps to the same session-scoped toolkit as `/toolkit/specs` and `/toolkit/invoke`.

***

## Health & Metrics

### Health Check

<ParamField path="GET /healthz" />

Returns text/plain `ok`.

### Prometheus Metrics

<ParamField path="GET /metrics" />

Returns Prometheus text format metrics from the runner process.
