Skip to content

Agent sessions ​

A session is a conversation with an agent: its messages, the files attached to it, what it has spent, and what it is doing right now. The Responses API starts one for you on the first message — the id in resp_{id} is the session id. The sessions API lets you start one without a message, so you can upload files to it first, give it a budget or tag it, and also list a project's sessions and delete one.

This page covers sessions in Studio and over the API. The things that happen inside a session have their own pages: events, approvals and questions, budgets, files and background runs.

Sessions in Studio ​

Open Build → Agents, pick an agent and go to the Sessions tab. The tab badge counts the sessions; an orange badge beside it counts those that are waiting for a person.

Start a session ​

Start session (or Start new session when the list is empty) opens a form:

FieldWhat it does
NameOptional. Left blank, the first message names the session.
Run againstDraft (current config) or one published version (v3 · published). Pin a version to test a frozen snapshot. Not offered for connected agents.
Runtime contextOptional JSON object, applied to every turn of the session; a per-message context still overrides it. metadata fills the prompt's {{variables}}; headers are offered to downstream tools, MCP servers and connected agents, subject to each one's own passthrough policy.

Starting a session runs nothing and costs nothing; it opens the chat.

The session list ​

Every conversation with the agent is listed, whatever started it. The Source column says where it came from:

SourceStarted by
TestA person in Studio. Only these can be continued from the list.
APIA client token calling /agents/responses, /responses or POST /agents/sessions.
A2AAn agent-to-agent client.
ScheduleOne of the agent's schedules.
Evaluation / Red teamAn evaluation or red-team run that targets the agent.
PortalA person using a portal.

Filters: free text (name or session id), last activity (any time, 24 hours, 7 days, 30 days or a custom range), status (Completed, Has errors, Stopped early, Waiting for you), source, and All sessions / Has turns / Never used / Unpriced turns. A row shows a Waiting for you badge while the agent waits for an approval or an answer, and a Budget reached badge while it is paused on its budget.

Clicking a row opens a read-only session detail drawer — the conversation as one ordered stream, each tool call grouped with its result, beside a properties rail. API, A2A and scheduled sessions are real traffic you want to inspect, not continue, so that is the default. A session that is waiting shows its approval and question cards in the drawer, answerable like on the session page. The Continue (play) icon opens the full session page for a Test session.

The Background runs switch above the list shows runs for the agent, with a count of the active ones.

The session page ​

The session page is the chat plus a side panel.

  • Header — the title, a status badge (Idle, Running, Waiting for you, Budget reached), the session id, duration, tokens and cost, and a spend meter (Set budget when there is none). Compare runs the same message against two versions; Continue and Delete are on the right.
  • Transcript — messages, tool calls and the files each message carried. Approval and question cards appear on the assistant message that paused.
  • Composer — disabled with an explanation while a request is open (or a warning that sending cancels it, depending on the agent's setting), and while a session is stopped on its budget. The paperclip appears when the agent has files on and a bucket chosen.
  • Side panel tabs — Session (properties, cost per turn, usage), Events, Tools, Context, Activity (the event log) and Files (only when files are on).

Opening a session that is running follows it live from its last event, so a reload or a second tab picks up where the turn is.

Sessions over the API ​

All calls take the same bearer token as the rest of /api/client/v1 and see only the token's project. {id} below is the session id — the part of resp_{id} after the prefix.

Start a session ​

bash
curl -X POST "$CONSOLE_URL/api/client/v1/agents/sessions" \
  -H "Authorization: Bearer $COGNIPEER_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "agent": "support-bot",
    "title": "Refund #4821",
    "metadata": { "ticket": "4821", "customer": "acme" },
    "budget": { "max_cost_usd": 2, "on_reach": "pause" },
    "runtime_context": { "metadata": { "tier": "gold" } }
  }'
typescript
import { ConsoleClient } from '@cognipeer/console-sdk';

const client = new ConsoleClient({
  apiKey: process.env.COGNIPEER_API_TOKEN!,
  baseURL: process.env.CONSOLE_URL,
});

const session = await client.agents.sessions.create({
  agent: 'support-bot',
  title: 'Refund #4821',
  metadata: { ticket: '4821', customer: 'acme' },
  budget: { max_cost_usd: 2, on_reach: 'pause' },
  runtime_context: { metadata: { tier: 'gold' } },
});
console.log(session.id);

The answer is 201 with the session:

json
{
  "id": "6f1c0a2b9d3e4f5a6b7c8d9e",
  "object": "agent.session",
  "agent": "support-bot",
  "title": "Refund #4821",
  "created_at": "2026-10-11T09:00:00.000Z",
  "updated_at": "2026-10-11T09:00:00.000Z",
  "metadata": { "ticket": "4821", "customer": "acme" },
  "runtime_status": "idle",
  "stop_reason": null,
  "pending_actions": [],
  "last_event_seq": 0,
  "budget": { "max_cost_usd": 2, "on_reach": "pause" },
  "spent_usd": 0
}

Nothing runs and nothing is spent. The session belongs to the user that owns the token and is marked as coming from the API.

FieldWhat it does
agentRequired. The key of an active agent in the token's project. 404 agent_not_found, or 400 agent_not_active.
titleUp to 200 characters. Default New conversation; the first message names the session after what was asked.
metadataUp to 16 string pairs (keys 1–64 characters, values up to 512). Yours to use — returned as metadata, never read by the agent. 400 invalid_metadata otherwise.
budgetA session budget. null for none, absent for the agent's default. The same rules as the first message of a Responses call, including 403 budget_override_forbidden when the agent's own budget binds API clients.
runtime_contextContext entered once for the whole session and merged under every turn's own (headers, metadata, …) — the object a Responses call takes. The HTTP headers of this request are not kept.

Send messages to it ​

Continue the session with session_id on the Responses API (conversation is accepted too, the way OpenAI spells it — a string or { "id": "…" }):

bash
curl -X POST "$CONSOLE_URL/api/client/v1/agents/responses" \
  -H "Authorization: Bearer $COGNIPEER_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "model": "support-bot", "session_id": "6f1c0a2b9d3e4f5a6b7c8d9e", "input": "Where is my refund?" }'
typescript
const reply = await client.agents.responses.create({
  model: 'support-bot',
  session_id: session.id,
  input: 'Where is my refund?',
});
console.log(reply.output[0].content[0].text);

session_id, conversation and previous_response_id all name the same thing. Give one — or several, as long as they agree. Naming two different sessions is 400 (invalid_request when session_id and conversation differ, session_conflict when either differs from previous_response_id). A session of another agent or project is 404 session_not_found.

previous_response_id accepts both resp_{id} and run_{id} — the id of a background run works too.

Files uploaded to the session before the first message go with it. See Session files for the whole create → upload → ask flow.

List sessions ​

bash
curl "$CONSOLE_URL/api/client/v1/agents/sessions?agent=support-bot&status=requires_action&limit=20" \
  -H "Authorization: Bearer $COGNIPEER_API_TOKEN"
typescript
// One page…
const page = await client.agents.sessions.list({ agent: 'support-bot', status: 'requires_action', limit: 20 });
for (const s of page.data) console.log(s.id, s.runtime_status, s.spent_usd);

// …or every session, fetching pages lazily.
for await (const s of client.agents.sessions.list({ agent: 'support-bot' })) {
  console.log(s.id, s.title);
}
Query
agentOnly this agent's sessions.
statusidle, running, requires_action or budget_paused. idle includes a session that has not run yet. 400 invalid_status otherwise.
limit1 to 100, default 20. 400 invalid_limit otherwise.
afterThe next_cursor of the previous page. 400 invalid_cursor if it is not one this API returned.

Newest first:

json
{
  "object": "list",
  "data": [
    {
      "id": "6f1c0a2b9d3e4f5a6b7c8d9e",
      "object": "agent.session",
      "agent": "support-bot",
      "title": "Refund #4821",
      "created_at": "2026-10-11T09:00:00.000Z",
      "updated_at": "2026-10-11T09:14:03.000Z",
      "metadata": { "ticket": "4821" },
      "message_count": 6,
      "runtime_status": "idle",
      "stop_reason": "end_turn",
      "last_event_seq": 41,
      "budget": null,
      "spent_usd": 0.0412
    }
  ],
  "has_more": true,
  "next_cursor": "WzE3NjA…"
}

A row is the session without its transcript (message_count instead) and without pending_actions — fetch one session to see those. A row's spent_usd is the session's stored running total; it is null for a session that last ran before the total was kept (a list does not load transcripts to add the turns up), and the single session has the figure. The cursor marks the last row you read, so a session created while you page never shifts a row into or out of a page you already have.

Get, update, delete ​

CallWhat it does
GET /agents/sessions/{id}The session resource: runtime state, pending requests, budget and spend.
PATCH /agents/sessions/{id}Set, raise, lower or remove the budget; continues a session paused on it.
DELETE /agents/sessions/{id}Delete the session.
bash
curl -X DELETE "$CONSOLE_URL/api/client/v1/agents/sessions/6f1c0a2b9d3e4f5a6b7c8d9e" \
  -H "Authorization: Bearer $COGNIPEER_API_TOKEN"
# => { "id": "6f1c0a2b9d3e4f5a6b7c8d9e", "object": "agent.session", "deleted": true }

Deleting removes the session with its files (object, markdown and record), its event log and any open approvals. It is refused with 409 agent_run_conflict while a run (synchronous or background) is queued or running on it — cancel it or wait for it first. In the SDK, client.agents.sessions.delete(id) and client.agents.runs.cancel(runId).

Errors ​

StatusCodeMeaning
400invalid_requestagent missing, a malformed field, or session_id and conversation name different sessions.
400agent_not_activeThe agent exists but is not active.
400invalid_metadata, invalid_budget, invalid_limit, invalid_status, invalid_cursorThe named field was rejected.
403budget_override_forbiddenThe agent's own budget binds API clients and the request would raise or remove it.
404agent_not_foundNo such agent in the token's project.
404session_not_foundNo such session in the token's project, or it belongs to another agent.
409agent_run_conflictA turn is queued or running on the session.

Troubleshooting ​

A session I created from the API does not show in Studio. Check the project. A token is bound to the project that was active when it was created and sees only that project's sessions; switch to the same project in Studio.

status=idle returns sessions that never ran. That is intended: idle includes a session that has not run yet. Use message_count > 0 on the rows to tell them apart.

The session list shows spent_usd: null. The row comes from a stored running total that older sessions never kept. GET /agents/sessions/{id} adds the turns up and returns the figure.

Who can do this ​

An agent session is a conversation with an agent: its messages, files, runs, events and budget. A session belongs to the person who started it (for API traffic, the owner of the token that created it). This page explains what other people may do with it, how to restrict everybody to their own sessions, and who may choose the Document Store bucket that holds a session's files.

Requires a Console release with the IAM engine

The session actions agent.session_view_others and agent.session_manage_others, and the bucket checks on an agent's Files settings, are decided by the IAM engine. Releases without the engine do not look at who started a session on these paths.

The model ​

The person who started a session needs only the route's own service check (Agents read to look, write to act). Anybody else needs a permission for the sessions of an agent they did not start:

Action (as shown in the IAM center)IdNeeded forLevelDefault
Agents · View others' sessionsagent.session_view_othersReading a session somebody else started: its transcript, events, files, runs, tracing thread; listing itReadEvery holder of Agents Read
Agents · Act on others' sessionsagent.session_manage_othersChanging it: continuing it, raising its budget, uploading or deleting its files, cancelling its runs, deleting itWriteEvery holder of Agents Write

Details that matter:

  • Continuing a session needs both. A new message, a run, or an answer to its question asks agent.session_manage_others and agent.session_view_others: the reply is written from everything the session holds and goes back to the sender, so somebody a rule stops from viewing a session is stopped from talking into it too. The other changes (budget, files, delete) hand nothing of the transcript back and need only agent.session_manage_others.
  • Sessions that no person started (a public A2A call, a schedule, an evaluation, a red-team run) belong to nobody in particular: they always count as someone else's.
  • The default follows the agents service, on every path to a session (see Tracing and Files screens).
  • Answering a question in somebody else's session is not an approval; it follows these two actions. See Who can approve tool calls.

Nothing changes until your workspace narrows these actions with a deny policy. Then a refused caller gets 403 session_forbidden with a decision id, and lists show only their own sessions.

Restrict people to their own sessions ​

Use an ordinary deny policy. In the IAM center (visual editor):

  1. Open Settings → IAM center → Policies and select Create policy.
  2. Name it, for example Contractors only see their own agent sessions.
  3. Set Effect to Deny.
  4. Under Who, choose Specific people and pick the Groups (for example Contractors). A rule about a group also covers the API tokens its members own, so the restriction cannot be walked around with a token.
  5. Under What, open Specific actions and select Agents · View others' sessions and Agents · Act on others' sessions. Both are marked Checked on agent requests, which means the agent routes really ask them.
  6. Leave Where on Anywhere, or limit it to Services → Agents, to some Projects, or to one agent under Specific resources (type Agent).
  7. Select Preview impact, then Create policy.

The same policy as a spec:

json
{
  "name": "Contractors only see their own agent sessions",
  "enabled": true,
  "spec": {
    "effect": "forbid",
    "principals": { "groups": ["<contractors group id>"] },
    "actions": ["agent.session_view_others", "agent.session_manage_others"],
    "resources": { "services": ["agents"] }
  }
}

Which actions to deny:

You wantDeny
Nobody in the group may touch another person's session at allboth actions
They may read others' sessions but not change themagent.session_manage_others only
They may not read others' sessions (this also stops them from continuing them)agent.session_view_others only; add the other to also stop budget, file and delete changes

A deny rule blocks exactly the levels it names, so denying the action does not touch the group's access to their own sessions: the person who started a session is never asked.

What the restricted person sees ​

  • A list shows everybody's sessions or, when the rule stops the caller, only their own. The database applies this before the page is cut, so other people's sessions never push the caller's own off it, and limit and has_more are about what the caller may see. The Sessions tab explains it above the table.
  • Opening somebody else's session by its address shows You don't have access to this session, and an API call answers 403 session_forbidden:
json
{
  "error": {
    "type": "permission_error",
    "message": "This session was started by someone else, and you are not allowed to view it.",
    "code": "session_forbidden",
    "decision_id": "7c0f6b2e-5d1a-4c1e-9a53-0d9b8a1f2e44"
  }
}
  • The sessions of an agent that was deleted (they stay when the agent goes) are shown only to the person who started them, as soon as any rule restricts the caller.
  • Lists that are cut by the tracing and Files stores (tracing threads and sessions, the objects of a bucket) are read on until the caller's page is full. For a restricted caller the tracing lists answer hasMore and nextSkip (where the next page starts) instead of total, which cannot be known without reading everything.
  • A session event stream is checked when it opens and again every minute while it stays open (the person's standing in the project, a revoked token, a rule written since). When the answer is no the stream ends with an error event, and a client that resumes with Last-Event-ID is answered at the door.
  • If the engine cannot decide, or the store cannot say who started a session, the answer is 503 authorization_unavailable with Retry-After. It is never a 403 and never an allow, lists included.

Every path asks the same question ​

The session routes themselves, the files a session holds (also through the Document Store and the client Files API), its events, its runs (polling and cancelling), a tracing thread, the threads of the Assistants API and the continuation of a session by its id in the Responses and chat completions routes, whichever service (agents or models) lets the token in. Rows of the Sessions table carry who started them.

A session started through A2A is still only visible to the caller that started it (the stricter rule that existed before). The Enterprise sandbox routes address a machine by its id and do not ask the session question yet.

Tracing and Files screens ​

The default follows the agents service, on every path to a session. A role that holds Tracing or Files but has no agents level does not reach other people's agent sessions through those screens either: the tracing threads of an agent are left out of its lists and refused with 403 when asked for by id, and so are the session files in the Document Store. Earlier releases did not look at who started a session on those paths at all, so a Tracing-only or Files-only reader loses that reach after the upgrade.

To keep it, give the role Agents Read, or write an allow rule for agent.session_view_others. Nothing in the Tracing and Files screens themselves changes for ids that are not agent sessions.

Where an agent keeps its files ​

Session files are ordinary Document Store files in a bucket of the project. Naming the bucket in an agent's Files settings (attachments.bucketKey) is a Files decision about that bucket:

What the person doesNeeds
Open the bucket list in Configure → FilesRead on Files (files service). Without it the section says You cannot choose a bucket instead of showing an empty list.
See a bucket in the listfile-bucket.get on that bucket (by default, Read on Files)
Choose or change the bucketfile-bucket.update on that bucket (by default, Write on Files). A bucket they may see but not name is shown disabled with the reason (You need write access to this bucket in Files to choose it).
Save the agent with the bucket it already hasNothing from Files

An API token is held to the same rules within its own scope. If the person lacks the permission when they save, the answer is 403 with the code files_write_required:

json
{
  "error": "Choosing where an agent keeps its files needs write access to that bucket in Files. Ask someone with access to the bucket to pick it, or keep the bucket that is already set.",
  "type": "files_write_required",
  "code": "files_write_required",
  "field": "attachments.bucketKey",
  "decisionId": "0b6f2c3e-5d1a-4c1e-9a53-0d9b8a1f2e44"
}

When the engine cannot decide, the answer is 503 authorization_unavailable. A refusal is recorded in the denials feed like any other, and the decision id can be replayed in IAM center → Access check.

Keep one bucket out of agents' settings ​

Write a deny rule on one bucket to keep it out of agents' settings, or out of the picker:

  1. IAM center → Policies → Create policy, Effect: Deny.
  2. Who: the group (or everybody).
  3. What → Specific actions: file-bucket.update (naming it) and/or file-bucket.get (listing it).
  4. Where → Specific resources: type File bucket, then pick the bucket.
json
{
  "name": "Nobody names the finance bucket in agent settings",
  "enabled": true,
  "spec": {
    "effect": "forbid",
    "principals": { "any": true },
    "actions": ["file-bucket.update"],
    "resources": { "resources": [{ "type": "file-bucket", "id": "<bucket id>" }] }
  }
}

Who sees a session's files ​

A session file is the session's. Whoever started the session sees and manages its files; anybody else needs View others' sessions (read: list, open, download) or Act on others' sessions (upload, delete). The same holds when the file is reached through the Document Store or the client Files API: a bucket listing leaves out the files of sessions the caller may not view.

Two things to know when you choose the bucket: portal users get the settings of the agent's published version, not the draft, so a new bucket reaches them with the next publish; and a bucket on a server-disk provider keeps files on one server's disk, which is fine for a single node but not for a cluster that scales out. A bucket an agent keeps session files in cannot be deleted or disabled until you choose another bucket for that agent.

Check it ​

IAM center → Access check: choose the person, an action (Agents · View others' sessions) and the agent. For session actions the form asks Session started by (leave it empty to check as someone else's session). The result names the rule that decided. Over the API, send context.ownerId with the person who started the session:

bash
curl -X POST https://console.example.com/api/iam/simulate \
  -H "Content-Type: application/json" --cookie "token=..." \
  -d '{
    "principal": { "type": "user", "id": "<person id>" },
    "action": "agent.session_view_others",
    "resource": { "kind": "resource", "type": "agent", "id": "<agent id>" },
    "context": { "channel": "dashboard", "ownerId": "<user who started the session>" }
  }'

Troubleshooting ​

A person sees an empty Sessions tab, or fewer sessions than before. A rule restricts them to their own sessions, or the agent they look at was deleted. Open Access check for the person and agent.session_view_others.

A Tracing reader suddenly cannot open agent threads. See Tracing and Files screens: give the role Agents Read, or an allow rule for agent.session_view_others.

A person can read a session but cannot continue it. Continuing needs both agent.session_view_others and agent.session_manage_others. Check that neither is denied.

A call answers 503 authorization_unavailable. The engine or the store could not decide. Clients that retry on 503 (the Console SDK does, and honours Retry-After) simply try again. Check the Overview tab of the IAM center and GET /api/health/ready.

I cannot choose a bucket in an agent's Files settings. You need Read on Files to list buckets and Write (or a rule that allows file-bucket.update) on the bucket to choose it. A bucket that is disabled or whose provider is not active cannot be chosen at all.

What's new ​

  • Sessions belong to the person who started them; other people need agent.session_view_others and agent.session_manage_others, which every holder of Agents Read and Write has by default and a deny rule narrows.
  • The same rule now applies to the agent threads in Tracing and the session files in the Document Store.
  • Choosing the bucket for an agent's session files is a Files decision about that bucket.

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