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

# Sessions

> A session represents a single execution of an agent.

export const SessionLifecycle = () => {
  const stroke = {
    fill: "none",
    stroke: "currentColor",
    strokeWidth: 1.75,
    strokeLinecap: "round",
    strokeLinejoin: "round"
  };
  const S = c => ({
    className: c,
    ...stroke
  });
  const icons = {
    queued: c => <svg viewBox="0 0 24 24" {...S(c)}><path d="M8 6h13M8 12h13M8 18h13M3 6h.01M3 12h.01M3 18h.01" /></svg>,
    pending: c => <svg viewBox="0 0 24 24" {...S(c)}><circle cx="12" cy="12" r="10" /><path d="M12 6v6l4 2" /></svg>,
    running: c => <svg viewBox="0 0 24 24" {...S(c)}><path d="M21 12a9 9 0 1 1-6.219-8.56" /></svg>,
    paused: c => <svg viewBox="0 0 24 24" {...S(c)}><rect x="14" y="3" width="5" height="18" rx="1" /><rect x="5" y="3" width="5" height="18" rx="1" /></svg>,
    idle: c => <svg viewBox="0 0 24 24" {...S(c)}><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" /></svg>,
    awaiting_tool_results: c => <svg viewBox="0 0 24 24" {...S(c)}><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.106-3.105c.32-.322.863-.22.983.218a6 6 0 0 1-8.259 7.057l-7.91 7.91a1 1 0 0 1-2.999-3l7.91-7.91a6 6 0 0 1 7.057-8.259c.438.12.54.662.219.984z" /></svg>,
    completed: c => <svg viewBox="0 0 24 24" {...S(c)}><circle cx="12" cy="12" r="10" /><path d="m9 12 2 2 4-4" /></svg>,
    failed: c => <svg viewBox="0 0 24 24" {...S(c)}><circle cx="12" cy="12" r="10" /><path d="m15 9-6 6M9 9l6 6" /></svg>,
    timed_out: c => <svg viewBox="0 0 24 24" {...S(c)}><path d="M10 2h4M12 14l3-3" /><circle cx="12" cy="14" r="8" /></svg>,
    interrupted: c => <svg viewBox="0 0 24 24" {...S(c)}><circle cx="12" cy="12" r="10" /><path d="M4.929 4.929 19.07 19.071" /></svg>
  };
  const State = ({name, sub}) => <div className="flex items-start gap-3">
      <span className="mt-0.5 flex shrink-0 text-zinc-400 dark:text-zinc-500">{icons[name]("h-4 w-4")}</span>
      <div>
        <div className="font-mono text-[13px] font-medium leading-5 text-zinc-900 dark:text-zinc-100">{name}</div>
        <div className="text-sm leading-5 text-zinc-500 dark:text-zinc-400">{sub}</div>
      </div>
    </div>;
  const Phase = ({label, children}) => <div className="flex flex-1 flex-col self-center rounded-xl border border-zinc-200 bg-white p-5 dark:border-zinc-800 dark:bg-zinc-950">
      <div className="mb-4 text-xs font-semibold uppercase tracking-wide text-zinc-500 dark:text-zinc-400">{label}</div>
      {children}
    </div>;
  const Arrow = ({top}) => <div className="flex w-[88px] shrink-0 flex-col items-stretch justify-center gap-1 px-3 text-center text-xs leading-4 text-zinc-500 dark:text-zinc-400">
      <span className="whitespace-nowrap">{top}</span>
      <div className="flex items-center text-zinc-400 dark:text-zinc-600">
        <span className="h-px flex-1 bg-current" />
        <svg className="-ml-px h-3 w-2 shrink-0" viewBox="0 0 8 12" fill="none" stroke="currentColor" strokeWidth="1.5" strokeLinecap="round" strokeLinejoin="round"><path d="M1 1.5 6 6l-5 4.5" /></svg>
      </div>
    </div>;
  return <div className="not-prose my-8 overflow-x-auto">
      <div className="flex min-w-[640px] items-stretch">
        <Phase label="Starting">
          <div className="flex flex-col gap-2.5">
            <State name="queued" sub="waiting for a slot" />
            <State name="pending" sub="launching" />
          </div>
        </Phase>

        <Arrow top="launch" />

        <Phase label="Active">
          <State name="running" sub="working" />
          <div className="my-3 h-px bg-zinc-200 dark:bg-zinc-800" />
          <div className="flex flex-col gap-2.5">
            <State name="paused" sub="you paused it" />
            <State name="idle" sub="waiting for your message" />
            <State name="awaiting_tool_results" sub="waiting for your tool output" />
          </div>
        </Phase>

        <Arrow top="ends" />

        <Phase label="Ended">
          <div className="flex flex-col gap-2.5">
            <State name="completed" sub="finished the task" />
            <State name="failed" sub="stopped by an error" />
            <State name="timed_out" sub="hit a time or step limit" />
            <State name="interrupted" sub="you cancelled it" />
          </div>
        </Phase>
      </div>
    </div>;
};

<SessionLifecycle />

A session moves through a fixed [lifecycle](#lifecycle). You can steer it while it runs and read the result when it finishes. Every follow-up call (sending a message, pausing, cancelling) is addressed to the session's `id`. The optional [`max_steps` and `max_time_s`](/agents-api/sessions/create) caps bound how long it runs before the agent is asked for a final answer.

<CodeGroup>
  ```bash CLI theme={"system"}
  hai run "Top 3 stories on Hacker News?" \
    --agent h/web-surfer-flash
  ```

  ```bash cURL theme={"system"}
  SESSION=$(curl -s -X POST https://agp.eu.hcompany.ai/api/v2/sessions \
    -H "Authorization: Bearer $HAI_API_KEY" -H "Content-Type: application/json" \
    -d '{"agent": "h/web-surfer-flash", "messages": [{"type": "user_message", "message": "Top 3 stories on Hacker News?"}]}' | jq -r .id)
  echo "$SESSION"
  ```

  ```python Python theme={"system"}
  from hai_agents import Client

  client = Client()

  session = client.sessions.create_session(
      agent="h/web-surfer-flash",
      messages=[{"type": "user_message", "message": "Top 3 stories on Hacker News?"}],
  )
  print(session.id)
  ```

  ```typescript TypeScript theme={"system"}
  import { HaiAgentsClient } from "hai-agents";

  const client = new HaiAgentsClient();

  const session = await client.sessions.createSession({
    body: {
      agent: "h/web-surfer-flash",
      messages: [{ type: "user_message", message: "Top 3 stories on Hacker News?" }],
    },
  });
  console.log(session.id);
  ```
</CodeGroup>

Creating a session returns its `id`, which you poll for progress and the answer as in the [Quickstart](/agents-api/quickstart). For a one-shot run, the helper below blocks until the agent finishes and hands back the result.

<CodeGroup>
  ```python Python theme={"system"}
  result = client.run_session(
      agent="h/web-surfer-flash",
      messages="Top 3 stories on Hacker News?",
  )
  print(result.status, result.answer)
  ```

  ```typescript TypeScript theme={"system"}
  const result = await client.runSession({
    agent: "h/web-surfer-flash",
    messages: "Top 3 stories on Hacker News?",
  });
  console.log(result.status, result.answer);
  ```
</CodeGroup>

Pick the call that matches how much control you need:

| You want to…                                        | SDK call                                                                   | You get back                                                           |
| --------------------------------------------------- | -------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| Run and read the answer in one shot                 | `client.run_session(...)` / `runSession`                                   | Blocks, then returns the final result                                  |
| Watch or steer while it runs                        | `client.start_session(...)` / `startSession`                               | A handle bound to the `id`; read and steer, then `wait_for_completion` |
| Drive the loop yourself (raw HTTP, other languages) | `POST /sessions`, then long-poll [`changes`](/agents-api/sessions/changes) | The session `id`; you poll                                             |

### Reading the answer

The simplest is the session snapshot. [`GET /sessions/{id}`](/agents-api/sessions/retrieve) returns `latest_answer`, the agent's most recent final answer, or `null` until it first answers. It needs no cursor and never goes stale, so once a run has settled it is the easiest way to read the answer.

While a run is active, [`GET /sessions/{id}/changes`](/agents-api/sessions/changes) carries the same `answer` alongside the live event feed. The SDK helpers ([`run_session` / `wait_for_session`](/agents-api/sessions/changes#long-polling-pattern)) run the loop for you; if you poll by hand:

* `changes` returns only what is new since your cursor, so the answer rides the page that delivers the final events.
* Keep polling until the session reaches a [terminal state](#lifecycle) and you have drained the remaining events.
* `204 No Content` means no new events yet, not no answer.

Don't poll [`status`](/agents-api/sessions/status) for the answer: it never carries one.

## Session object

| Field                                       | Description                                                                                                                                                                                                                                               |
| ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id`                                        | The session's UUID, the handle for every follow-up call.                                                                                                                                                                                                  |
| `request`                                   | Echoes what you submitted, with `agent` resolved to its full spec even if you passed a catalog id.                                                                                                                                                        |
| `status`                                    | Carries the live `status`, step count, per-model token usage (`usage_per_model`), any `error` and its `error_code`, the agent's self-assessed `outcome`, and `subagent_session_ids`. See [Session status](/agents-api/sessions/status) for the breakdown. |
| `agent_view_url`                            | Link to the session's [Agent View](/agents-api/observe-and-steer) page for live viewing and replay.                                                                                                                                                       |
| `latest_answer`                             | The agent's most recent final answer, mirrored from [`changes`](/agents-api/sessions/changes); `null` until it first answers.                                                                                                                             |
| `created_at` / `started_at` / `finished_at` | Track the run's timeline. The latter two are `null` until they happen.                                                                                                                                                                                    |

## Lifecycle

Every session moves through the same state machine, whichever agent runs it:

| Status                  | Meaning                                                                                                                                                     | Terminal |
| ----------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | -------- |
| `queued`                | Session accepted above your [concurrency limit](/agents-api/plans-and-limits#concurrent-sessions); it starts automatically, oldest first, as slots free up. | No       |
| `pending`               | Session created, agent is launching.                                                                                                                        | No       |
| `running`               | Agent is actively working on the task.                                                                                                                      | No       |
| `paused`                | Manually paused via the API. State is preserved.                                                                                                            | No       |
| `idle`                  | Interactive agent finished a task and is waiting for your next message.                                                                                     | No       |
| `awaiting_tool_results` | Agent is blocked on [custom tool](/agents-api/custom-tools) calls your code must answer.                                                                    | No       |
| `completed`             | Agent finished the task successfully.                                                                                                                       | Yes      |
| `timed_out`             | Agent exceeded the maximum allowed time.                                                                                                                    | Yes      |
| `interrupted`           | Session was canceled via `DELETE`.                                                                                                                          | Yes      |
| `failed`                | An unrecoverable error occurred.                                                                                                                            | Yes      |

## Overrides

Reuse a catalog agent but adjust it for a single run with `overrides`, a map on the [create-session](/agents-api/sessions/create) body. Rather than defining a new agent, you point at fields of the resolved request. Each key is a dotted path, and its value replaces whatever that path resolves to, applied after `agent` is expanded from its catalog id.

* Dots walk into objects. `agent.instructions` sets behavior, `agent.model` swaps the serving model, and `agent.answer_format` pins a [structured answer](#structured-output).
* A `[field=value]` selector picks a list member. `agent.environments[kind=web]` selects the web environment, so `agent.environments[kind=web].start_url` sets just its start page and `agent.environments[kind=web].mode` switches how it reads the page.
* Values are type-checked. Each value must match the type of the field its path targets. An unknown path or a wrong type is rejected with `422` at creation, before the agent runs.

For example, send a catalog web-surfer to a chosen page instead of its default start URL:

<CodeGroup>
  ```bash CLI theme={"system"}
  hai run "Summarize the top discussion right now" \
    --agent h/web-surfer-flash \
    --override 'agent.environments[kind=web].start_url=https://news.ycombinator.com'
  ```

  ```bash cURL theme={"system"}
  curl -X POST https://agp.eu.hcompany.ai/api/v2/sessions \
    -H "Authorization: Bearer $HAI_API_KEY" -H "Content-Type: application/json" \
    -d '{
      "agent": "h/web-surfer-flash",
      "messages": [{"type": "user_message", "message": "Summarize the top discussion right now"}],
      "overrides": {"agent.environments[kind=web].start_url": "https://news.ycombinator.com"}
    }'
  ```

  ```python Python theme={"system"}
  session = client.sessions.create_session(
      agent="h/web-surfer-flash",
      messages="Summarize the top discussion right now",
      overrides={"agent.environments[kind=web].start_url": "https://news.ycombinator.com"},
  )
  ```

  ```typescript TypeScript theme={"system"}
  const session = await client.sessions.createSession({
    body: {
      agent: "h/web-surfer-flash",
      messages: "Summarize the top discussion right now",
      overrides: { "agent.environments[kind=web].start_url": "https://news.ycombinator.com" },
    },
  });
  ```
</CodeGroup>

## Structured output

By default the agent's answer is free-form text. Set an `answer_format` (a JSON Schema) on the agent, or pass a Pydantic / Zod schema to the SDKs, and the final answer comes back as typed, validated data instead. See [Structured output](/agents-api/structured-output).

## Listing and filtering

[`GET /api/v2/sessions`](/agents-api/sessions/list) pages through your sessions, newest first, with filters you can combine:

| Filter                               | Type                | Description                                                               |
| ------------------------------------ | ------------------- | ------------------------------------------------------------------------- |
| `status`                             | string (repeatable) | Filter by session status (e.g. `?status=running&status=queued`).          |
| `agent`                              | string (repeatable) | Filter by agent identifier (e.g. `h/web-surfer-flash`).                   |
| `group_id`                           | string              | Filter by group: useful for multi-session workflows.                      |
| `parent_session_id`                  | string              | Find [child sessions](/agents-api/multi-agent) of a parent.               |
| `schedule_id`                        | string              | Sessions created by a [schedule](/agents-api/schedules/overview)'s fires. |
| `search`                             | string              | Case-insensitive match on the first message or answer.                    |
| `created_before` / `created_after`   | string              | Bound by creation time (ISO 8601).                                        |
| `finished_before` / `finished_after` | string              | Bound by finish time (ISO 8601).                                          |
| `owner`                              | string              | Access scope. Default: `me-in-organization`.                              |

<CodeGroup>
  ```bash cURL theme={"system"}
  curl "https://agp.eu.hcompany.ai/api/v2/sessions?status=running&agent=web-price-finder" \
    -H "Authorization: Bearer $HAI_API_KEY"
  ```

  ```python Python theme={"system"}
  page = client.sessions.list_sessions(status=["running"], agent=["web-price-finder"])
  for summary in page.items:
      print(summary.id, summary.status)
  ```

  ```typescript TypeScript theme={"system"}
  const page = await client.sessions.listSessions({
    status: ["running"],
    agent: ["web-price-finder"],
  });
  for (const summary of page.items) {
    console.log(summary.id, summary.status);
  }
  ```
</CodeGroup>

Like every list endpoint, it returns a page envelope: `items` holds the resources, `page` echoes the page number you asked for, and `total` counts all matches. Responses don't echo `size` back, so track it yourself. There are more pages while `page * size < total`. This endpoint caps `size` at `100` and sorts by `-created_at` (newest first) unless you pass `sort=created_at`. See [List sessions](/agents-api/sessions/list) for the full parameter reference.

## Endpoints

| Method   | Path                                                  | Description                                              |
| -------- | ----------------------------------------------------- | -------------------------------------------------------- |
| `POST`   | `/api/v2/sessions`                                    | [Create a session](/agents-api/sessions/create)          |
| `GET`    | `/api/v2/sessions`                                    | [List sessions](/agents-api/sessions/list)               |
| `GET`    | `/api/v2/sessions/{id}`                               | [Retrieve a session](/agents-api/sessions/retrieve)      |
| `GET`    | `/api/v2/sessions/{id}/status`                        | [Get session status](/agents-api/sessions/status)        |
| `DELETE` | `/api/v2/sessions/{id}`                               | [Cancel a session](/agents-api/sessions/cancel)          |
| `POST`   | `/api/v2/sessions/{id}/messages`                      | [Send a message](/agents-api/sessions/send-messages)     |
| `POST`   | `/api/v2/sessions/{id}/tool_results`                  | [Send tool results](/agents-api/sessions/tool-results)   |
| `POST`   | `/api/v2/sessions/{id}/pause`                         | [Pause a session](/agents-api/sessions/pause)            |
| `POST`   | `/api/v2/sessions/{id}/resume`                        | [Resume a session](/agents-api/sessions/resume)          |
| `POST`   | `/api/v2/sessions/{id}/force_answer`                  | [Force final answer](/agents-api/sessions/force-answer)  |
| `GET`    | `/api/v2/sessions/{id}/changes`                       | [Long-poll for changes](/agents-api/sessions/changes)    |
| `GET`    | `/api/v2/sessions/{id}/events`                        | [List events](/agents-api/sessions/events)               |
| `GET`    | `/api/v2/sessions/quota`                              | [Get quota](/agents-api/sessions/quota)                  |
| `POST`   | `/api/v2/sessions/{id}/feedback`                      | [Session feedback](/agents-api/sessions/feedback)        |
| `PUT`    | `/api/v2/sessions/{id}/events/{event_index}/feedback` | [Event feedback](/agents-api/sessions/event-feedback)    |
| `POST`   | `/api/v2/sessions/{id}/share`                         | [Share a session](/agents-api/sessions/share)            |
| `DELETE` | `/api/v2/sessions/{id}/share`                         | [Unshare a session](/agents-api/sessions/unshare)        |
| `GET`    | `/api/v2/sessions/{id}/resources/{bucket}/{key}`      | [Get a session resource](/agents-api/sessions/resources) |
