Skip to content

Agent Sessions ​

A session is one conversation with an agent, seen from the runtime side. Since @cognipeer/console-sdk 3.1.0 you can start a session before any message exists, give it files and a spend budget, watch what happens inside it, answer the agent when it stops to ask, and be called back when something changes — all through client.agents.sessions and client.webhooks.

This guide explains how the pieces fit together. Each piece has a reference page; the examples show them end to end.

What's new in 3.1.0 ​

  • Sessions you start yourself — sessions.create(), list(), get(), update() (budget), delete(); continue one with responses.create({ session_id }).
  • Files — upload to a session before or between messages; the files live in a Document Store bucket the agent's administrator chose. agents.get(key).attachments tells you what the agent accepts.
  • Human in the loop — a turn can come back requires_action; answer with sessions.actions.resolve().
  • Event log — sessions.events.list() and a self-reconnecting events.stream().
  • Webhooks — client.webhooks and verifyWebhook (Node) for signed notifications.
  • Error codes — AgentActionErrorCodes, and a fix so webhook error codes (not_found, forbidden, …) are no longer lost.

3.1.0 is additive: nothing in 3.0.0 changes behaviour, except that agents.runs.wait() now also returns when a run is requires_action. See Upgrading from 3.0.0.

Concepts ​

TermWhat it is
SessionA conversation with one agent, with runtime state (idle, running, requires_action, budget_paused), a budget, files and an event log. session.id is the conversation id
Response (resp_<id>)The result of one turn. Its id names the same conversation: resp_<session.id>
Run (run_<id>)A turn executed in the background (background: true). Its id is also accepted as previous_response_id
Action (act_<id>)A request the agent made to a person: a tool approval or a question
Event (seq)One entry of the session's append-only log. session.last_event_seq is the newest
Webhook event (evt_<id>)A notification the Console POSTs to your URL. Its id is the webhook-id header

Session lifecycle ​

text
                       sessions.create()
                              │
                              ▼
                           [ idle ]  ◄──────────────────────────────┐
                              │  responses.create({ session_id })    │
                              ▼                                      │ turn ends
                         [ running ] ───────────────────────────────┤ (end_turn / limit /
                          │       │                                  │  cancelled / error)
        agent asks a person│       │ budget reached                  │
                          ▼       ▼                                  │
              [ requires_action ]  [ budget_paused ]  (on_reach: 'pause')
                          │             │                            │
        actions.resolve() │             │ sessions.update({ budget })│
                          └─────────────┴──► [ running ] ────────────┘
  • A session starts idle and spends nothing until a message arrives.
  • While running, a second turn is refused with 409 agent_run_conflict.
  • A turn that ends with stop_reason: 'budget_reached' and on_reach: 'stop' leaves the session idle; with 'pause' it becomes budget_paused and can be resumed by raising the budget.

Which API for which job ​

You want to...UseReference
Ask once, no state to keepresponses.create()Agents
Keep a conversation, upload before the first message, tag it with your own id, cap its spendsessions.create() + responses.create({ session_id })Sessions
Long turns that outlive an HTTP timeoutresponses.create({ background: true }) + runs.wait()Agents → Background runs
Hand the agent a document or an imagesessions.files.upload()Session files
Let a person approve risky tool calls or answer questionssessions.actions.resolve()Approvals & questions
Show progress live, or rebuild a transcript after a reloadsessions.events.stream() / list()Session events
Be told, without polling, that a turn ended or an approval is waitingclient.webhooks + verifyWebhookWebhooks
Branch on a failureerror.errorCodeAgent error codes

A session end to end ​

typescript
import { readFile } from 'node:fs/promises';
import {
  ConsoleClient,
  type AgentActionsPendingResponse,
  type AgentResponse,
  type PendingAction,
  type ResolveActionBody,
} from '@cognipeer/console-sdk';

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

// What to do with each request the agent makes. Decide this deliberately in a real app.
const answerFor = (action: PendingAction): ResolveActionBody =>
  action.kind === 'tool_approval' ? { decision: 'allow' } : { cancelled: true };

// 1. Ask the agent what it takes.
const agent = await client.agents.get('support-bot');

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

// 3. Upload a file (only if the agent takes files).
if (agent.attachments?.enabled) {
  await client.agents.sessions.files.upload(session.id, await readFile('invoice.pdf'), {
    name: 'invoice.pdf',
    mimeType: 'application/pdf',
  });
}

// 4. Ask. A turn may finish, or pause for a person.
let outcome: AgentResponse | AgentActionsPendingResponse = await client.agents.responses.create({
  model: 'support-bot',
  session_id: session.id,
  input: 'What is the total on this invoice?',
});

// 5. Answer whatever the agent asked. The continuation can pause again, hence the loop.
while (outcome.status === 'requires_action') {
  for (const action of outcome.required_action!.actions) {
    outcome = await client.agents.sessions.actions.resolve(session.id, action.id, answerFor(action));
  }
}

if (outcome.status === 'completed') console.log(outcome.output[0]?.content[0]?.text);

// 6. Clean up when you are done (files, events and open actions go with it).
await client.agents.sessions.delete(session.id);

Setting things up ​

Most of the behaviour here is configured on the agent in the Console, not in the SDK. Work through this checklist once per agent:

StepWhereWhy
The agent is activeAgents → the agent's statussessions.create() refuses an agent that is not active (400 agent_not_active)
Files are on, with a bucketAgents → the agent → Configure → FilesOff by default. Needs a Document Store bucket whose storage provider is active. Sets limits and retention
Approvals & questions are onConfigure → Approvals & questionsThe agent pauses only if Ask before tools run or Let the agent ask the user questions is on. Publish again after changing it
Background executionthe agent's execution settings in ConfigureOn unless the agent turns it off. Needed for background: true, for actions.resolve({ background }) and for resuming a budget_paused session (400 agent_background_disabled otherwise)
Session budget (optional)the agent's settingsAn agent can bind API callers to its own budget: they may lower it but not raise or remove it
PermissionsAuthorization → People / GroupsSee the table below
WebhooksAgents → Webhooks or client.webhooks.create()Needs webhooks.admin

Who may do what ​

ActionNeeds
Create / read / list / delete sessionsA token of the project (sessions belong to the token's owner; a list shows what the caller may see)
Answer a tool approvalThe Console's agent.approve permission — the person who started the session always may
Answer a questionFollows session ownership
webhooks.list / get / deliveries / eventTypeswebhooks.read
webhooks.create / update / delete / test / rotateSecret / redeliverwebhooks.admin (owners and admins by default)

A token acts for its owner, within its own project: it never sees sessions, files or webhooks of another project, even with an id.

Required Console release ​

SDK featureNeeds a Console with
approvals, events, actions, webhooks, session budget, requires_actionthe agent runtime phase 1 release
sessions.create / list / delete, session_id, files on buckets, agent.attachments, files.contentthe release that ships the sessions API and session files on buckets

Against an older Console these calls answer 404 (or the field is absent, as with agent.attachments). files.content() additionally needs the content route.

Upgrading from 3.0.0 ​

3.1.0 is additive. Check these points:

  • agents.runs.wait() returns on requires_action. It used to wait for succeeded / failed / canceled. A run waiting on a person would never finish, so it now returns too — always check run.status. isAgentRunTerminal() is unchanged; use the new isAgentRunSettled() for "terminal or requires_action".
  • AgentResponse.status widened to 'completed' | 'failed' | 'incomplete' | 'requires_action'. An exhaustive switch needs the new cases. AgentResponse gains required_action, stop_reason, stop_detail and incomplete_details.
  • agents.chat() (legacy) adds status and pendingActions to its result when the turn did not complete, so a paused agent (empty content) is no longer indistinguishable from an empty answer. A completed turn keeps the exact old shape.
  • Required fields in types. AgentSession and SessionFile carry fields the Console always sends (title, created_at, updated_at, metadata; bucket_key, path, content_url). Code that builds these objects by hand — test doubles — needs the new fields.
  • User-Agent now reports the real package version (it shows in the Console's audit log); before, it said 1.1.0.
  • errorCode is read from a top-level code too, so webhook errors are no longer lost.
  • Runtime. Node 18+; the main entry is browser-safe. @cognipeer/console-sdk/webhooks (verifyWebhook) is Node-only because it uses node:crypto.

Design tips ​

  • Create the session first when you need files, a budget or your own id in metadata. For a plain one-off question you do not need one.
  • Store session.id with your own record (a ticket, a chat thread). It is the only handle you need.
  • Prefer webhooks to polling to learn that a turn ended; prefer a stream to polling for progress.
  • Treat requires_action as a normal outcome, not an error: route it to a person, then call resolve().
  • Make handlers idempotent. Webhooks are at-least-once; resolve() answers 409 action_not_pending if someone got there first.
  • Cap spend with a budget on any session driven by end users.
  • Clean up with sessions.delete(); files also expire on their own (default 30 days, set per agent).

FAQ ​

Is a session the same as a previous_response_id conversation? Yes — the same conversation. session_id just lets you name it before it has a response. resp_<session.id> is the response id form.

Can I send a message to a session another app created? If it is in your project and agent, and you have access to it, yes: the id is all responses.create({ session_id }) needs. A session of another agent is 404 session_not_found.

Does sessions.create() cost anything? No. Nothing runs and nothing is spent until a message is sent.

What happens to files when I delete a session? They are deleted with it (object, converted text and record), along with the event log and open actions.

Why does sessions.list() not show everything? It shows what the caller may see; sessions of other people appear only when the Console's permissions allow it.

Where do I get the session id of a conversation started elsewhere? sessions.list({ agent }), or from the response id: strip the resp_ prefix.

Can I follow a session from a browser? The main entry is browser-safe, so events.stream() runs in a browser; keep the API token on your server and relay events instead of shipping a token to the client. verifyWebhook is Node-only.

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