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

# Session operations

> Send input, retrieve, list, interrupt, archive, and delete sessions.

Once a session exists, use these operations to drive, read, control, or remove it. See [Sessions](/docs/concepts/sessions) for creating one and its lifecycle.

## Retrieve a session

<CodeGroup>
  ```bash CLI theme={"system"}
  vetta session get $SID
  ```

  ```typescript TypeScript theme={"system"}
  const s = await vetta.sessions.get(sessionId);
  console.log(s.status, s.consumed_micro_usd, s.stop_reason); // cost in integer micro-USD
  ```
</CodeGroup>

## List sessions

Results are paginated. Filter by agent or by `deployment_id` (each scheduled fire is a session tagged with its [deployment](/docs/capabilities/deployments)), and sort by creation time. Each response includes `has_more` and a `next_cursor`; pass `next_cursor` back as `after` to fetch the next page. The cursor is `null` at the end.

<CodeGroup>
  ```bash CLI theme={"system"}
  vetta session list --agent Refunder --limit 20
  vetta session list --deployment-id dep_9f... --limit 20     # fires from one deployment
  ```

  ```typescript TypeScript theme={"system"}
  for await (const s of vetta.sessions.list({ agentId: agent.id, limit: 20 })) {
    console.log(s.id, s.status);
  }

  // Or page manually with cursors:
  const page = await vetta.sessions.list({ deploymentId: "dep_9f...", limit: 20 });
  console.log(page.data.length, page.has_more, page.next_cursor);
  ```
</CodeGroup>

## Sending input

You drive a session by sending it input. Every input is recorded on the [event stream](/docs/concepts/events-and-streaming), and if the session is `idle` it wakes into `running`. There are four input types:

| Input                 | What it does                                                                                                                                                                                  |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **User message**      | Ordinary text (or structured content) for the agent to act on.                                                                                                                                |
| **Interrupt**         | Stops the current turn at the next commit boundary.                                                                                                                                           |
| **Tool confirmation** | Allows or denies a tool call the agent is waiting on (see [policies](/docs/concepts/policies)). A deny can carry a reason.                                                                         |
| **Answer**            | Supplies what the agent asked for when it parked on a question — a payload and no verb, where a confirmation is a verb and no payload. See the [awaiting-answer loop](#awaiting-answer-loop). |

<CodeGroup>
  ```bash CLI theme={"system"}
  vetta session send      --session $SID --message "Refund order #4821"
  vetta session interrupt --session $SID
  vetta session confirm --session $SID --tool-call $CALL_ID --allow
  vetta session confirm --session $SID --tool-call $CALL_ID --deny --reason "wrong account"
  vetta session answer  $SID
  ```

  ```typescript TypeScript theme={"system"}
  await vetta.sessions.send(sessionId, { message: "Refund order #4821" });
  await vetta.sessions.interrupt(sessionId);
  await vetta.sessions.confirmTool(sessionId, { tool_call_id: callId, decision: "allow" });
  await vetta.sessions.confirmTool(sessionId, { tool_call_id: callId, decision: "deny", reason: "wrong account" });
  await vetta.sessions.answer(sessionId, { tool_call_id: askId, answers: { mailbox: "hello@acme.com" } });
  ```
</CodeGroup>

Stream the session to watch it react to input — reconnect with `fromSeq` to resume from the last `seq` you handled without gaps or duplicates. See [Events & streaming](/docs/concepts/events-and-streaming).

### Interrupt and redirect in one call

Inputs can be **batched** so they land atomically. The common case is steering a running agent: send an interrupt together with a new message. The agent wraps up the current turn cleanly at the next commit boundary — the interrupted turn ends with `stop_reason: "end_turn"` — and then picks up the redirect.

<CodeGroup>
  ```bash CLI theme={"system"}
  vetta session send --session $SID --interrupt --message "Stop — issue a store credit instead."
  ```

  ```typescript TypeScript theme={"system"}
  await vetta.sessions.send(sessionId, {
    interrupt: true,
    message: "Stop — the customer wants a store credit instead.",
  });
  ```
</CodeGroup>

## Awaiting-approval loop

When the agent calls a tool governed by an [`ask` policy](/docs/concepts/policies), the runtime does not run the tool. Instead the session goes `idle` with `stop_reason: "awaiting_approval"` and exposes the waiting call(s) on `pending_actions[]`. You resolve each one with an allow/deny confirmation; once the last pending action is answered, the session resumes automatically.

```json theme={"system"}
{
  "id": "ses_4a...",
  "status": "idle",
  "stop_reason": "awaiting_approval",
  "pending_actions": [
    {
      "kind": "tool",
      "tool_call_id": "call_8Q...",
      "name": "refund",
      "args": { "order_id": "4821", "amount_micro_usd": 42000000 }
    }
  ]
}
```

<CodeGroup>
  ```bash CLI theme={"system"}
  vetta session get $SID              # stop_reason=awaiting_approval, lists pending_actions
  vetta session confirm --session $SID --tool-call call_8Q... --allow
  vetta session confirm --session $SID --tool-call call_8Q... --deny --reason "not this account"
  ```

  ```typescript TypeScript theme={"system"}
  const s = await vetta.sessions.get(sessionId);
  for (const a of s.pending_actions) {
    if (a.kind !== "tool") continue;       // a question is answered, not approved — see below
    await vetta.sessions.confirmTool(sessionId, {
      tool_call_id: a.tool_call_id,
      decision: a.name === "refund" ? "allow" : "deny",
      reason: "out of policy",
    });
  }
  ```
</CodeGroup>

<Warning>
  **Branch on `kind`, not on `name`, and not on `stop_reason` alone.** `pending_actions[]` holds both
  kinds of park, and a session that stopped on a mix of the two reports `awaiting_approval` while
  still carrying question rows — `awaiting_answer` means *every* parked row is a question. A loop that
  sends every entry to `tool_confirmations` gets a `validation_failed` on each question, and one that
  reads only the stop reason silently never asks them.
</Warning>

A held tool consumes no budget while it waits — a session can sit on a confirmation for hours at storage cost only. See [Policies](/docs/concepts/policies) for how a tool resolves to `ask`, and [Events & streaming](/docs/concepts/events-and-streaming) for the `tool.confirm` event shape.

## Awaiting-answer loop

The mirror of the loop above, for the park the agent starts itself. When an agent calls
[`ask_operator`](/docs/capabilities/tools) the turn stops the same way — `idle`, rows on
`pending_actions[]`, no budget burning — but the row is tagged `kind: "question"` and carries a
`question` object: the sentence the agent wrote, and one to three fields to fill in.

```json theme={"system"}
{
  "id": "ses_4a...",
  "status": "idle",
  "stop_reason": "awaiting_answer",
  "pending_actions": [
    {
      "kind": "question",
      "tool_call_id": "call_9F...",
      "name": "ask_operator",
      "question": {
        "prompt": "Which mailbox should the crew send from?",
        "fields": [
          { "key": "mailbox", "label": "Mailbox", "type": "text", "placeholder": "hello@acme.com" },
          { "key": "tone", "label": "Tone", "type": "choice", "options": ["formal", "warm"], "other": true }
        ]
      }
    }
  ]
}
```

<CodeGroup>
  ```bash CLI theme={"system"}
  vetta session inbox                 # every session waiting on a person, both kinds
  vetta session answer $SID           # prints the question, asks for each field in turn
  ```

  ```typescript TypeScript theme={"system"}
  const s = await vetta.sessions.get(sessionId);
  for (const a of s.pending_actions) {
    if (a.kind !== "question") continue;
    await vetta.sessions.answer(sessionId, {
      tool_call_id: a.tool_call_id,
      answers: { mailbox: "hello@acme.com", tone: "warm" },
    });
  }
  ```
</CodeGroup>

Three rules a surface that renders a question has to keep:

* **Render from `question`, never from `args`.** They hold the same content — `args` is what the model called with and what replays byte-identically — but `question` is the shape a renderer reads, and it is the one that will keep its meaning if the tool's arguments ever change.
* **Every field must be answered**, with a non-empty value: an agent that could proceed without one of them would not have asked it. A partial answer is `validation_failed` (400) naming `answers.<key>`. The escape a person needs is the free-text arm, which is on by default.
* **A `choice` answer is the option's words, never an index.** The model reads them back as prose. Send `string[]` for a `multiple: true` field and a plain `string` for everything else; the route enforces the arity.

The answer folds into the next turn as the result of the call that asked, carrying the question with it — so the model sees what it asked and what came back, not a bare set of words against an id it no longer remembers.

<Note>
  Not every agent can ask. `ask_operator` is offered only on a harness that can hold a call open for a person — see [Harness capabilities](/docs/concepts/harness-capabilities) — and an agent whose toolset denies it cannot park this way at all.
</Note>

### Approver and timeouts

Each resolved confirmation records **who answered it** — the approving principal (a user or API key id) is written to the event and audit trail alongside the decision and any deny reason. This is what lets you prove, after the fact, that a payout or destructive action was signed off by a person rather than silently auto-run.

An `ask` tool does not wait forever. The governing [policy](/docs/concepts/policies#responding-to-ask) sets an `ask_timeout_seconds` and an `on_timeout` disposition; if no one answers in the window, the runtime applies it:

| `on_timeout` | Behavior when the window elapses                                                                                                             |
| ------------ | -------------------------------------------------------------------------------------------------------------------------------------------- |
| `deny`       | The call is rejected as if you denied it; the deny message is fed back to the agent. This is the safe default.                               |
| `allow`      | The call is auto-approved and runs.                                                                                                          |
| `escalate`   | The session stays `idle` with `stop_reason: "awaiting_approval"` and a notification is re-sent, so approval is deferred rather than decided. |

## Interrupting

A `running` session must be interrupted before you can update its agent, archive it, or delete it. An interrupt is delivered as an event and stops the current turn at the next commit boundary — you lose at most one turn, never the run. On its own, an interrupt lands the session at `idle` with `stop_reason: "interrupted"`.

<CodeGroup>
  ```bash CLI theme={"system"}
  vetta session interrupt --session $SID
  ```

  ```typescript TypeScript theme={"system"}
  await vetta.sessions.interrupt(sessionId);
  ```
</CodeGroup>

## Archiving

Cancelling stops a session and releases its sandbox while preserving its record and its events.

```bash CLI theme={"system"}
vetta session cancel --session $SID
```

<Warning>
  **A session cannot be archived or deleted.** There is no `vetta session archive` and no
  `vetta session delete`, and no `DELETE /v1/sessions/{id}` route behind them — `cancel` is the only
  terminal operation. A session's record and event history are permanent; the sandbox and the files
  scoped to it are released on cancel.

  Files the session **produced** go with the sandbox. Files promoted with `publish_file` or uploaded
  to the [Files API](/docs/capabilities/files) are organization-scoped and survive. Skills, deployments,
  webhooks, identities and vaults are independent resources and are unaffected. Publish anything you
  need to keep before cancelling.
</Warning>

<Card title="Next: events & streaming" icon="wave-square" href="/docs/concepts/events-and-streaming">
  The resumable event model behind every session.
</Card>
