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

# sessions

> Run, steer, budget, and read back a task — client.sessions.

A session is one task run by an agent. Twelve methods, one per route; objects parse with the core `SessionSchema`. API detail: [Sessions](/docs/api/sessions), [Events](/docs/api/events).

## create

```ts theme={"system"}
client.sessions.create(body: SessionCreate): Promise<Session>
```

`POST /v1/sessions`. Creates **and** starts, with the initial message, in one call.

<ResponseField name="agent_id" type="string" required>The agent to run.</ResponseField>
<ResponseField name="message" type="string" required>The initial instruction.</ResponseField>
<ResponseField name="agent_version" type="number">Pin an exact version; default is the agent's current one.</ResponseField>
<ResponseField name="window" type="string">Context-window selection.</ResponseField>
<ResponseField name="budget_micro_usd" type="integer">Spend cap for this session, integer micro-USD.</ResponseField>
<ResponseField name="identity" type="string">The persona this task acts as — an `idn_` id or the persona's name. Answered as `identity_id` on the session.</ResponseField>
<ResponseField name="output_schema" type="object">JSON schema the final document must match.</ResponseField>
<ResponseField name="structured_output_required" type="boolean">Refuse a would-be clean ending that produced no document matching `output_schema`.</ResponseField>
<ResponseField name="deployment_id" type="string">Set when a deployment fired this run.</ResponseField>
<ResponseField name="metadata" type="object">Arbitrary key/value pairs.</ResponseField>

```ts theme={"system"}
const session = await client.sessions.create({ agent_id: agent.id, message: "Reconcile August invoices" });
```

## list

```ts theme={"system"}
client.sessions.list(query?: ListQuery & SessionFilter): Promise<Page<Session>>
```

`GET /v1/sessions`. `SessionFilter` adds `agent_id`, `status`, `stop_reason`, `deployment_id`, `parent_session_id` and `handoff_from` to the [page query](/docs/sdk/pagination). `handoff_from` is a session id and lists the top-level sessions that session [handed work to](#handoffs), newest first.

`stop_reason` is how you find the sessions a person has to answer. A session held on an `ask` tool call is `status: "idle"` like every finished one — what separates it is `stop_reason: "awaiting_approval"`, with the blocked call in `pending_actions`:

```ts theme={"system"}
const waiting = await client.sessions.list({ stop_reason: "awaiting_approval" });
for (const session of waiting.data) {
  for (const action of session.pending_actions) {
    // `action.args` is what is being approved.
    await client.sessions.confirmTool(session.id, { tool_call_id: action.tool_call_id, decision: "allow" });
  }
}
```

`stop_reason: "awaiting_answer"` is the same query for the sessions asking you a *question* rather
than for permission — see [`answer`](#answer). `"budget_paused"` is the same question asked about
money, and `"error"` about failure.

## get

```ts theme={"system"}
client.sessions.get(id: string): Promise<Session>
```

`GET /v1/sessions/{id}`.

## send

```ts theme={"system"}
client.sessions.send(id: string, body: { message: string; interrupt?: boolean; queue?: boolean }): Promise<SendAccepted>
```

`POST /v1/sessions/{id}/messages`. Sending to a *running* session is refused with `session_running` unless you say which of two things you mean: `interrupt: true` steers it mid-run; `queue: true` holds the message in the session's inbox and it becomes the input of the turn that follows. On an idle session `queue` is a plain send. The two together are `validation_failed`.

## handoffs

```ts theme={"system"}
client.sessions.handoffs(id: string, body: SessionHandoffRequest): Promise<HandoffBatch>

type SessionHandoffRequest = {
  agents: string[];            // 1–6, each an `agt_` id or an agent name
  message: string;
  handoff_key?: string;        // ≤ 128 chars; a repeat under a running key answers `already_running`
  budget_micro_usd?: number;   // cap under each receiver's own budget
};

type HandoffBatch = {
  object: "handoff_batch";
  handoffs: { object: "handoff"; agent_id: string; session_id: string; status: "queued" | "already_running"; handoff_key: string | null }[];
  refusals: { object: "handoff_refusal"; agent: string; code: string; message: string }[];
};
```

`POST /v1/sessions/{id}/handoffs`. Hands `message` from session `{id}` (the **sender**, a top-level session of yours) to each named agent, starting a top-level session per target marked `metadata.handoff = { from_session, from_agent, key, depth }`. Who may be named is the sender's agent's [`handoffs`](/docs/capabilities/tools#handoffs) policy — the same admission the model's `send_to_agent` goes through. A target that is refused is a **row** in `refusals`, not a thrown error; the batch resolves for the rest.

```ts theme={"system"}
const batch = await client.sessions.handoffs(root.id, {
  agents: ["trend-scout", "copywriter"],
  message: "Pull this week's numbers and draft the recap.",
  handoff_key: `room:${root.id}`,
});
for (const row of batch.handoffs) {
  // a member already on this key did not get a second session — say the words to the one it has
  if (row.status === "already_running") await client.sessions.send(row.session_id, { message: "…", queue: true });
}
for (const refusal of batch.refusals) console.warn(refusal.agent, refusal.code, refusal.message);
```

The work a session handed out is `client.sessions.list({ handoff_from: root.id })`; the work it delegated in-thread is [`threads`](#threads).

## threads

```ts theme={"system"}
client.sessions.threads(id: string): Promise<Page<Session>>
```

`GET /v1/sessions/{id}/threads`. The child sessions a coordinator [delegated](/docs/team/delegation) from `{id}` with `send_to_agent` `wait: true` — `parent_session_id` is `{id}`, and they ran on its budget. A handoff (`wait: false`, or [`handoffs`](#handoffs) above) is *not* here: it is a top-level session of its own, found by `handoff_from`.

## interrupt

```ts theme={"system"}
client.sessions.interrupt(id: string, body?: { message?: string | null }): Promise<Session>
```

`POST /v1/sessions/{id}/interrupt`. Stops the current turn; an optional `message` tells the agent why.

## confirmTool

```ts theme={"system"}
client.sessions.confirmTool(id: string, body: { tool_call_id: string; decision: "allow" | "deny"; reason?: string; scope?: "call" | "session" }): Promise<Session>
```

`POST /v1/sessions/{id}/tool_confirmations`. Answers a session paused on a gated tool call. It
carries a verdict and no payload, so it cannot answer a *question* — that is `answer`, below, and
this route refuses a `kind: "question"` row rather than folding "allow" back at an agent that asked
for a mailbox address.

`scope` says how far the `allow` reaches. Omitted (or `"call"`) it approves this one call.
`"session"` grants the call's **tool** for the rest of that session — every later call to the same
tool name runs with no confirmation — and nothing else: one tool name, one session, gone when the
session ends, never written onto the agent, and never valid on a `deny`. For a call through the
tool invoker (`tools_execute`) the granted tool is the one named in its `tool` argument: the same
target runs with no confirmation, any other target through the invoker is still confirmed.

## answer

```ts theme={"system"}
client.sessions.answer(id: string, body: { tool_call_id: string; answers: Record<string, string | string[]> }): Promise<Session>
```

`POST /v1/sessions/{id}/answers`. Answers a session parked on a question it asked you.

A session with `stop_reason: "awaiting_answer"` carries a `kind: "question"` row in
`pending_actions`, holding the sentence the agent wrote and one to three fields. `answers` is keyed
by those fields' `key`s; a value is a string, or an array of them for a `multiple` choice:

```ts theme={"system"}
const asking = await client.sessions.list({ stop_reason: "awaiting_answer" });
for (const session of asking.data) {
  for (const action of session.pending_actions) {
    if (action.kind !== "question" || !action.question) continue;
    console.log(action.question.prompt);
    await client.sessions.answer(session.id, {
      tool_call_id: action.tool_call_id,
      answers: { mailbox: "hello@acme.com", tone: "warm" },
    });
  }
}
```

Every key the question asked for must be present — except one the question marked `optional`, which
is answered by leaving the key out entirely rather than by sending an empty string — and no other, so
a wake can never find the thing
the agent stopped for still missing. A `choice` answer is **not** limited to that field's `options`:
`other` defaults to `true` precisely so a person can say what the agent did not think of.

## answer

```ts theme={"system"}
client.sessions.answer(id: string, body: { tool_call_id: string; answers: Record<string, string | string[]> }): Promise<Session>
```

`POST /v1/sessions/{id}/answers`. Answers a session parked on a question (`stop_reason: "awaiting_answer"`), keyed by the question's `fields[].key`. A sibling of `confirmTool`, not a variant: a held tool call takes a verb, a question takes a payload.

## cancel

```ts theme={"system"}
client.sessions.cancel(id: string): Promise<Session>
```

`POST /v1/sessions/{id}/cancel`. Terminal — the session cannot be resumed.

## setBudget

```ts theme={"system"}
client.sessions.setBudget(id: string, capMicroUsd: number): Promise<Session>
```

`PATCH /v1/sessions/{id}/budget`, body `{ cap_micro_usd }`. **Raise only** — a cap at or below what is already consumed would be retroactive and is refused. Integer micro-USD.

## events

```ts theme={"system"}
client.sessions.events(id: string, query?: { after_seq?: number; limit?: number }): Promise<Page<Event>>
```

`GET /v1/sessions/{id}/events` — the same log [`client.stream`](/docs/sdk/streaming) serves, read as pages. `after_seq` is **exclusive**, so polling with the last seq you saw never re-reads it.
