# Runs API

Start, continue, stream, inspect, cancel, and delete durable agent runs.

The Runs API is the application-facing execution API. A run keeps the pinned
agent version, trusted Pi session, and durable workspace across turns.

## Start a run

```http
POST /api/v1/runs
```

Required scope: `run:write`.

```bash
curl "$SALAMBO_BASE_URL/api/v1/runs" \
  -H "Authorization: Bearer $SALAMBO_API_KEY" \
  -H "Idempotency-Key: start-report-001" \
  -H "Content-Type: application/json" \
  -d '{
    "agent": "agt_01J...",
    "input": "Write a short status report.",
    "background": true
  }'
```

Supply exactly one selector:

* `agent` starts a new run;
* `run_id` continues an existing run.

The default delivery waits for the affected turn. Set `background` to return
after durable admission, or set `stream` to receive Server-Sent Events. Do not
set both fields to `true`.

## Continue a run

Use the same endpoint with the existing run ID:

```json
{
  "run_id": "run_01J...",
  "input": "Revise the report for an executive audience.",
  "on_busy": "queue"
}
```

`on_busy` accepts:

| Value    | Behavior                                                    |
| -------- | ----------------------------------------------------------- |
| `queue`  | Admit one bounded follow-up turn after active work.         |
| `steer`  | Add the input to the active turn when steering is possible. |
| `reject` | Return a conflict when the run is busy.                     |

## Send text and files

`input` can be a string or an array of typed parts:

```json
{
  "run_id": "run_01J...",
  "input": [
    { "type": "input_text", "text": "Review this invoice." },
    { "type": "input_file", "file_id": "file_01J..." }
  ]
}
```

Upload larger input files through `POST /api/v1/files`. Small files can be sent
inline with `filename` and base64 `file_data`.

## Read run and turn state

```http
GET /api/v1/runs/{runId}
GET /api/v1/runs/{runId}?turn_id=turn_...
```

Required scope: `run:read`.

Without `turn_id`, the endpoint selects the latest turn. The returned object
contains the run state and the selected turn:

```json
{
  "id": "run_01J...",
  "object": "run",
  "agent_id": "agt_01J...",
  "agent_version": 7,
  "state": "idle",
  "disposition": "created_run",
  "turn": {
    "id": "turn_01J...",
    "object": "turn",
    "status": "completed",
    "input": [],
    "output": [{ "type": "output_text", "text": "..." }],
    "error": null
  }
}
```

## Read retained events

```http
GET /api/v1/runs/{runId}/events
```

The event list contains public Pi events and Salambo platform events in run
sequence order. Use `turn_id` to select one turn, `after` to continue from an
event cursor, and `limit` to set the page size.

The full event stream is encrypted before Salambo writes it to S2. Postgres
stores the bounded run and turn read model. It does not store the full event
payload history.

Consumers must accept unknown event types and additive data fields. Use
`source` and `type` as discriminators.

## Cancel work

```http
POST /api/v1/runs/{runId}/cancel
```

Use an empty body to target current work. Supply `turn_id` to cancel one active
or queued turn. Active cancellation can return `202` while the runtime settles.

## Delete an idle run

```http
DELETE /api/v1/runs/{runId}
```

Deletion fences new work and starts asynchronous cleanup. A busy run must be
cancelled and allowed to settle before deletion.

## Idempotency

All mutating endpoints require an `Idempotency-Key` header. Reuse a key only
when you retry the same operation.
