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 withresponses.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).attachmentstells you what the agent accepts. - Human in the loop — a turn can come back
requires_action; answer withsessions.actions.resolve(). - Event log —
sessions.events.list()and a self-reconnectingevents.stream(). - Webhooks —
client.webhooksandverifyWebhook(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
| Term | What it is |
|---|---|
| Session | A 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
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
idleand spends nothing until a message arrives. - While
running, a second turn is refused with409 agent_run_conflict. - A turn that ends with
stop_reason: 'budget_reached'andon_reach: 'stop'leaves the sessionidle; with'pause'it becomesbudget_pausedand can be resumed by raising the budget.
Which API for which job
| You want to... | Use | Reference |
|---|---|---|
| Ask once, no state to keep | responses.create() | Agents |
| Keep a conversation, upload before the first message, tag it with your own id, cap its spend | sessions.create() + responses.create({ session_id }) | Sessions |
| Long turns that outlive an HTTP timeout | responses.create({ background: true }) + runs.wait() | Agents → Background runs |
| Hand the agent a document or an image | sessions.files.upload() | Session files |
| Let a person approve risky tool calls or answer questions | sessions.actions.resolve() | Approvals & questions |
| Show progress live, or rebuild a transcript after a reload | sessions.events.stream() / list() | Session events |
| Be told, without polling, that a turn ended or an approval is waiting | client.webhooks + verifyWebhook | Webhooks |
| Branch on a failure | error.errorCode | Agent error codes |
A session end to end
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:
| Step | Where | Why |
|---|---|---|
| The agent is active | Agents → the agent's status | sessions.create() refuses an agent that is not active (400 agent_not_active) |
| Files are on, with a bucket | Agents → the agent → Configure → Files | Off by default. Needs a Document Store bucket whose storage provider is active. Sets limits and retention |
| Approvals & questions are on | Configure → Approvals & questions | The agent pauses only if Ask before tools run or Let the agent ask the user questions is on. Publish again after changing it |
| Background execution | the agent's execution settings in Configure | On 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 settings | An agent can bind API callers to its own budget: they may lower it but not raise or remove it |
| Permissions | Authorization → People / Groups | See the table below |
| Webhooks | Agents → Webhooks or client.webhooks.create() | Needs webhooks.admin |
Who may do what
| Action | Needs |
|---|---|
| Create / read / list / delete sessions | A token of the project (sessions belong to the token's owner; a list shows what the caller may see) |
| Answer a tool approval | The Console's agent.approve permission — the person who started the session always may |
| Answer a question | Follows session ownership |
webhooks.list / get / deliveries / eventTypes | webhooks.read |
webhooks.create / update / delete / test / rotateSecret / redeliver | webhooks.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 feature | Needs a Console with |
|---|---|
approvals, events, actions, webhooks, session budget, requires_action | the agent runtime phase 1 release |
sessions.create / list / delete, session_id, files on buckets, agent.attachments, files.content | the 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 onrequires_action. It used to wait forsucceeded/failed/canceled. A run waiting on a person would never finish, so it now returns too — always checkrun.status.isAgentRunTerminal()is unchanged; use the newisAgentRunSettled()for "terminal orrequires_action".AgentResponse.statuswidened to'completed' | 'failed' | 'incomplete' | 'requires_action'. An exhaustiveswitchneeds the new cases.AgentResponsegainsrequired_action,stop_reason,stop_detailandincomplete_details.agents.chat()(legacy) addsstatusandpendingActionsto its result when the turn did not complete, so a paused agent (emptycontent) is no longer indistinguishable from an empty answer. A completed turn keeps the exact old shape.- Required fields in types.
AgentSessionandSessionFilecarry 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-Agentnow reports the real package version (it shows in the Console's audit log); before, it said1.1.0.errorCodeis read from a top-levelcodetoo, 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 usesnode: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.idwith 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_actionas a normal outcome, not an error: route it to a person, then callresolve(). - Make handlers idempotent. Webhooks are at-least-once;
resolve()answers409 action_not_pendingif 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.
Related
- Reference: Agents · Sessions · Files · Approvals & questions · Events · Webhooks · Error codes
- Examples
- Error Handling · Streaming

