Skip to content

Agent Sessions ​

A session is a conversation with an agent seen from the runtime side: whether a turn is running, whether it paused for a person, what it has spent, which files it holds and what happened in it. client.agents.sessions starts one without sending a message, lists them, reads their state, changes their budget and deletes them.

New in 3.1.0

create, list and delete are new in @cognipeer/console-sdk 3.1.0, and responses.create({ session_id }) continues a session you started. They need a Console release that ships the sessions API; get, update, actions, events need the agent runtime phase 1 release. See the Agent Sessions guide for how the pieces fit.

When to use ​

  • You need to upload a file before the first message — a session exists before any response does.
  • You keep your own record (ticket, customer, case) and want to tag the session with it (metadata) and find it again.
  • You want to cap spend per conversation.
  • A client was not holding the original request (page reload, another device, a channel adapter) and needs to read the state of a conversation: running, idle, waiting for an answer.
  • You need to list and clean up conversations.

If you only want a one-shot answer, use responses.create() without a session.

Quick start ​

typescript
import { ConsoleClient } from '@cognipeer/console-sdk';

const client = new ConsoleClient({ apiKey: process.env.COGNIPEER_API_KEY! });

// 1. Start an empty session: nothing runs and nothing is spent.
const session = await client.agents.sessions.create({
  agent: 'support-bot',
  title: 'Invoice question',
  metadata: { ticket: 'T-1042' },
  budget: { max_cost_usd: 2 },
});

// 2. Continue it by id.
const reply = await client.agents.responses.create({
  model: 'support-bot',
  session_id: session.id,
  input: 'I have a billing question',
});

// 3. Read its state any time.
const state = await client.agents.sessions.get(session.id);
console.log(state.runtime_status, state.spent_usd);

The session object ​

create() and get() return an AgentSession:

typescript
interface AgentSession {
  id: string;                        // the conversation id — pass it as `session_id`
  object: 'agent.session';
  agent: string;                     // agent key
  title: string | null;              // the first message names a session that has none
  created_at: string | null;         // ISO timestamps
  updated_at: string | null;
  metadata: Record<string, string>;  // the pairs given to create(); never read by the agent
  runtime_status: 'idle' | 'running' | 'requires_action' | 'budget_paused' | (string & {});
  stop_reason: 'end_turn' | 'requires_action' | 'limit' | 'cancelled' | 'error' | 'budget_reached' | (string & {}) | null;
  pending_actions: PendingAction[];  // approvals / questions waiting for an answer
  last_event_seq: number;            // newest event in the log (0 = none); resume streams after it
  budget: { max_cost_usd: number; on_reach: 'pause' | 'stop' } | null;
  spent_usd: number;                 // priced spend so far
}
FieldNotes
idThe conversation id. A response id is resp_<id>, so previous_response_id: 'resp_' + session.id names the same conversation.
runtime_statusidle — no turn running (also a session that has not run yet). running — a turn is in progress. requires_action — paused on an approval or a question. budget_paused — stopped because the session reached its budget with on_reach: 'pause'. The type is an open string: a newer Console may add values.
stop_reasonHow the last turn ended; null before the first one. Open string.
last_event_seqUse it as after for events.stream() to start from "now".
metadata{} for a session that was not created with create({ metadata }).

A list returns AgentSessionSummary rows instead: the same fields without pending_actions, plus message_count.

Create a session ​

typescript
const session = await client.agents.sessions.create({
  agent: 'support-bot',                 // required: the key of an active agent
  title: 'Invoice question',            // optional, at most 200 characters
  metadata: { ticket: 'T-1042' },       // optional: up to 16 string pairs, never read by the agent
  budget: { max_cost_usd: 2 },          // optional spend cap; null = none
});

POST /api/client/v1/agents/sessions → 201.

Parameters ​

NameTypeRequiredDescription
agentstringYesKey of an active agent of the token's project
titlestringNoAt most 200 characters. Default: the first message names the session
metadataRecord<string, string>NoUp to 16 pairs, keys 1–64 characters, values up to 512 characters, strings only. Returned as session.metadata; the agent never reads it
budget{ max_cost_usd, on_reach? } | nullNoSpend cap. null = none (overriding the agent's default); omitted = the agent's default. See Budget
runtime_contextRuntimeContextNoContext entered once for the whole session, merged under every turn's own (downstream headers and metadata)
options.signalAbortSignalNoAborts the request

The session belongs to the token's owner and is marked as coming from the API. The same agent and budget rules as the first turn of a Responses call apply.

Raw HTTP ​

bash
curl -X POST https://console.cognipeer.com/api/client/v1/agents/sessions \
  -H "Authorization: Bearer $COGNIPEER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "agent": "support-bot", "title": "Invoice question", "metadata": { "ticket": "T-1042" }, "budget": { "max_cost_usd": 2 } }'
json
{
  "id": "66f2a1c8b0d4e5a9c3d71e04",
  "object": "agent.session",
  "agent": "support-bot",
  "title": "Invoice question",
  "created_at": "2026-10-12T09:15:00.000Z",
  "updated_at": "2026-10-12T09:15:00.000Z",
  "metadata": { "ticket": "T-1042" },
  "runtime_status": "idle",
  "stop_reason": null,
  "pending_actions": [],
  "last_event_seq": 0,
  "budget": { "max_cost_usd": 2, "on_reach": "pause" },
  "spent_usd": 0
}

(Ids and timestamps in the examples are illustrative.)

Errors ​

StatuserrorCodeWhen
400invalid_requestagent is missing, or title is not text of at most 200 characters
400agent_not_activeThe agent is not active
400invalid_metadataMore than 16 pairs, a key outside 1–64 characters, a value over 512 characters or a non-string value
400invalid_budgetThe budget is malformed
403budget_override_forbiddenThe agent's own session budget binds API callers and this budget would raise or remove it
404agent_not_foundNo agent with that key in the token's project

List sessions ​

typescript
// One page, newest created first.
const page = await client.agents.sessions.list({
  agent: 'support-bot',
  status: 'requires_action',
  limit: 50,
});
for (const s of page.data) console.log(s.id, s.title, s.message_count);
page.has_more;     // another page exists
page.next_cursor;  // pass as `after` to continue by hand
const next = await page.getNextPage(); // null on the last page

// Every session, fetching pages as you go (break to stop).
for await (const s of client.agents.sessions.list({ agent: 'support-bot' })) {
  console.log(s.id, s.runtime_status, s.spent_usd);
}

GET /api/client/v1/agents/sessions

list() returns an AgentSessionListCall: it is both a promise for the first page and an async iterable over every session. await list() resolves to an AgentSessionPage; for await (const s of list()) walks every page lazily, one request per page.

Query ​

QueryTypeDescription
agentstringOnly this agent's sessions (its key)
status'idle' | 'running' | 'requires_action' | 'budget_paused'Only sessions in this runtime state. idle includes a session that has not run yet
limitnumberPage size, 1 to 100 (default 20). Anything else is 400 invalid_limit
afterstringThe next_cursor of the previous page. Not one the Console issued: 400 invalid_cursor

The second argument, { signal }, applies to every page request.

AgentSessionPage ​

MemberDescription
dataAgentSessionSummary[] — the sessions of this page
has_moreAnother page exists after this one
next_cursorPass as after for the next page; null on the last page
hasNextPage()Whether getNextPage() has a page to fetch
getNextPage()The page after this one (same filters), or null on the last page
[Symbol.asyncIterator]()Every session of this page, then of the pages after it
toJSON()The Console's list body (object, data, has_more, next_cursor)

A row is an AgentSessionSummary: an AgentSession without pending_actions, plus message_count. Open one session with get() to see its pending actions.

The cursor marks the last row you read, so sessions created while you page never shift a row into or out of a page you already have. A session list shows what the caller may see: sessions of other people only when the Console's permissions allow it.

spent_usd in a list row

Typed as a number. A list does not load transcripts, so for a session that last ran before the Console kept a running total the Console may report null (unknown, not zero). Open the session with get() for the figure, and guard with s.spent_usd ?? 'n/a' when you display it.

json
{
  "object": "list",
  "data": [
    {
      "id": "66f2a1c8b0d4e5a9c3d71e04",
      "object": "agent.session",
      "agent": "support-bot",
      "title": "Invoice question",
      "created_at": "2026-10-12T09:15:00.000Z",
      "updated_at": "2026-10-12T09:17:42.000Z",
      "metadata": { "ticket": "T-1042" },
      "message_count": 2,
      "runtime_status": "requires_action",
      "stop_reason": "requires_action",
      "last_event_seq": 9,
      "budget": { "max_cost_usd": 2, "on_reach": "pause" },
      "spent_usd": 0.0112
    }
  ],
  "has_more": false,
  "next_cursor": null
}

Get a session ​

typescript
const state = await client.agents.sessions.get(session.id);
state.runtime_status;   // 'requires_action'
state.pending_actions;  // [{ id: 'act_…', kind: 'tool_approval', tool_name: 'sandbox_exec', … }]
state.last_event_seq;   // resume an event stream after this

GET /api/client/v1/agents/sessions/{id}. A session of another project answers 404 session_not_found, even when you know its id.

Budget ​

A session can carry a spend cap in USD. It is set with create({ budget }), with responses.create({ budget }) for the conversation that request creates (ignored on a continued one), or later with update().

typescript
interface AgentSessionBudget {
  max_cost_usd: number;
  on_reach?: 'pause' | 'stop';   // default 'pause'
}
on_reachWhat happens when the session reaches max_cost_usd
'pause' (default)The turn stops and the session goes budget_paused. Raise the budget with update() and it continues.
'stop'The turn stops and the session is left idle.

In both cases a budget.reached event is logged, a webhook event of the same name fires, and a new message to the session is refused with 402 budget_reached (the response carries spent_usd and max_cost_usd) until the budget is raised or removed. A session.usage event with the per-turn cost (turn_cost_usd) and the running spent_usd is logged for a session that has a budget.

Update the budget ​

typescript
// Raise the cap of a budget_paused session; it continues as a background run.
const result = await client.agents.sessions.update(session.id, {
  budget: { max_cost_usd: 10, on_reach: 'pause' },
});
if (result.object === 'agent.run') await client.agents.runs.wait(result.id);

// Remove the cap, without continuing:
await client.agents.sessions.update(session.id, { budget: null, resume: false });

PATCH /api/client/v1/agents/sessions/{id}

NameTypeDescription
budgetAgentSessionBudget | nullRequired (omitting it is 400 invalid_budget). The new budget, or null to remove it. A new budget must exceed what the session already spent
resumebooleanDefault true. When the session is budget_paused, continue it without a new user message, as a background run. false changes the budget only
runtime_contextRuntimeContextContext for the continuation, when it resumes

The return type is a union — tell the two answers apart with result.object:

AnswerWhen
200 AgentSession (object: 'agent.session')The budget changed and nothing was resumed
202 AgentRun (object: 'agent.run')A budget_paused session continues as a queued background run — follow it with runs.wait()

Errors: 400 invalid_budget (malformed, missing, or not above what the session already spent), 400 agent_background_disabled (a resume needs background execution; the Console refuses before changing anything), 403 budget_override_forbidden, 404 session_not_found, 409 agent_run_conflict (a run is already active).

An agent can bind API callers to its own budget

When the agent's administrator configured a session budget on the agent, API callers may lower it but cannot raise or remove it: 403 budget_override_forbidden. This applies to create, update and responses.create({ budget }).

Delete a session ​

typescript
await client.agents.sessions.delete(session.id);
// { id: '…', object: 'agent.session', deleted: true }

DELETE /api/client/v1/agents/sessions/{id}

delete() removes the session with its files (object, converted text and record), its event log and any open approvals or questions. It is refused with 409 agent_run_conflict while a run is queued or running on the session (cancel it with runs.cancel() first), and answers 404 session_not_found for a session of another project. It cannot be undone.

Continue a session ​

responses.create({ session_id }) (alias conversation, a string or { id }) continues a session by id — the way previous_response_id does, for a session that has no response yet. Give one of session_id, conversation and previous_response_id, or several that agree:

SituationResult
session_id / conversation names a different session than previous_response_id400 session_conflict
session_id and conversation name two different sessions400 invalid_request
The session belongs to another agent or project404 session_not_found
budget on a continued sessionIgnored — use update()

file_id input parts work with any of the three. See Agents → Multi-turn conversations.

Limits ​

LimitValue
Titleat most 200 characters
metadataat most 16 pairs; keys 1–64 characters; values up to 512 characters; strings only
Page size1–100, default 20
Files per session, file size, retentionthe agent's Configure → Files setting — see Session files

Errors ​

Errors are CognipeerAPIErrors; branch on errorCode. The full table is on Agent error codes. The ones this page raises:

StatuserrorCodeRaised by
400invalid_request, agent_not_active, invalid_metadata, invalid_budget, invalid_limit, invalid_status, invalid_cursor, agent_background_disabledcreate, list, update
402budget_reachedresponses.create on a session at its budget
403budget_override_forbiddencreate, update
404agent_not_found, session_not_foundcreate, get, update, delete, responses.create
409agent_run_conflictupdate, delete

Troubleshooting ​

404 session_not_found for a session I just created. The token belongs to a different project than the one the session was created in, or the session was created for another agent than the one named in model. Sessions are resolved through the token's tenant and project.

create() says agent_not_active. Only agents with status active take sessions. Set the agent's status to active in the Console (client.agents.get(key).status tells you where it stands).

update() returned a run, not a session. The session was budget_paused and resume defaults to true. Pass resume: false to change the budget without continuing, or await the run with runs.wait().

update() says agent_background_disabled. A resume runs in the background, and the agent has background execution disabled. Use resume: false, then send a message yourself.

A session is stuck in requires_action. It is waiting for an approval or a question: read get(id).pending_actions and answer with actions.resolve().

list({ limit: 0 }) is rejected. limit must be a whole number from 1 to 100.

Studio · Pulse · Console · Agent SDK and more — the Cognipeer documentation hub