Sessions & events
Run an agent by creating a session, sending messages, and streaming the agent's events over SSE.
A session runs an agent; events are the messages on its timeline. This page covers driving a turn end to end.
Create a session
Open an agent and start a session — the Console opens a transcript view where you chat with the agent and watch its events render live.
POST /v1/sessions. The agent field is either a bare id (pins to the latest
version) or { "id": "...", "version": N } to pin a specific version.
curl https://agents.clusterbase.dev/v1/sessions \
-H "Authorization: Bearer $CLUSTER_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "agent": "agt_3f9c2a" }'{
"id": "ses_8b1d4e",
"agent_id": "agt_3f9c2a",
"agent_version": 1,
"status": { "status": "idle" },
"usage": { "input_tokens": 0, "output_tokens": 0 },
"created_at": "2026-06-16T12:00:00Z"
}To give the session VM-backed tools (shell, file, patch), bind it to an
environment with environment_id. To attach MCP
credentials, pass vault_ids — see Vaults & MCP.
Send a message
User events are submitted as a batch under events, so you can send a
message (and, say, a follow-up interrupt) atomically. A turn begins when you
append a user.message.
curl https://agents.clusterbase.dev/v1/sessions/ses_8b1d4e/events \
-H "Authorization: Bearer $CLUSTER_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"events": [
{ "type": "user.message", "content": [{ "type": "text", "text": "Summarize the React 19 release notes." }] }
]
}'Stream the response
GET /v1/sessions/{id}/events/stream is a Server-Sent Events stream of events as
they occur. A typical turn:
data: {"type":"session.status_running","id":"evt_01","processed_at":"..."}
data: {"type":"agent.thinking","id":"evt_02","content":[{"type":"text","text":"..."}],...}
data: {"type":"agent.tool_use","id":"evt_03","tool":"web_search","input":{"query":"react 19"},...}
data: {"type":"agent.tool_result","id":"evt_04","tool_use_id":"evt_03","content":[...],"is_error":false,...}
data: {"type":"agent.message","id":"evt_05","content":[{"type":"text","text":"React 19 adds..."}],...}
data: {"type":"session.status_idle","id":"evt_06","stop_reason":{"type":"end_turn"},...}See Concepts → Events for the full list of event types.
Long-running sessions are compacted automatically: once a turn's model context
grows past an internal threshold, the runtime summarizes older exchanges into a
durable checkpoint (session.context_compacted) and continues the turn from
there. The visible transcript returned by the events endpoints is never
shortened — compaction only affects what's sent to the model.
Read history
To fetch the timeline so far (rather than stream it live):
curl https://agents.clusterbase.dev/v1/sessions/ses_8b1d4e/events \
-H "Authorization: Bearer $CLUSTER_TOKEN"
# → { "data": [ { "type": "user.message", ... }, { "type": "agent.message", ... } ] }Interrupt a turn
Send a user.interrupt to stop the agent mid-turn. The session goes idle with
stop_reason: interrupted; your next user.message resumes from there.
curl https://agents.clusterbase.dev/v1/sessions/ses_8b1d4e/events \
-H "Authorization: Bearer $CLUSTER_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "events": [{ "type": "user.interrupt" }] }'Usage and lifecycle
GET /v1/sessions/{id} returns the session's current status, cumulative
usage (input/output and cache tokens), and shared (whether the session
currently has a public transcript view — see Share a session
below). When you're done, archive the session to soft-delete it (it must not be
running):
curl -X POST https://agents.clusterbase.dev/v1/sessions/ses_8b1d4e/archive \
-H "Authorization: Bearer $CLUSTER_TOKEN"Share a session
PUT /v1/sessions/{id}/sharing toggles a public, read-only, revocable view of
the session's transcript:
curl -X PUT https://agents.clusterbase.dev/v1/sessions/ses_8b1d4e/sharing \
-H "Authorization: Bearer $CLUSTER_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "shared": true }'
# → { "shared": true }While shared, anyone with the id can fetch the transcript with no authentication:
curl https://agents.clusterbase.dev/v1/shared-sessions/ses_8b1d4e
# → { "id": "ses_8b1d4e", "title": "...", "created_at": "...", "messages": [...] }messages is a projection of only stable user and assistant message content —
tenant metadata, provider internals, and private storage identifiers are never
included. It carries text, reasoning, file, and completed image-generator
parts; every other part, and session errors, are dropped rather than
redacted. Any file attachment is exposed as a public Storage URL rather than a
durable fileId or signed URL. Responses are sent Cache-Control: private, no-store.
Completed web, news, and shopping search results, as well as explicit citation
sources, are also retained (in their original order, so inline citation
markers still resolve correctly): titles, snippets, and result URLs are kept,
but URLs have any credentials and private query parameters stripped, and a
failed fetch is reduced to a boolean error flag rather than its original
error detail. No search input or private tool payload is published.
Sharing is off by default and revoking it ({ "shared": false }) takes effect
immediately: the shared endpoint and every attachment URL it returned start
responding 404. Turning sharing on fails with 400 if the history contains
an attachment referenced only by a raw URL — those can't be re-authorized once
public — so re-upload such a file as a stored attachment before sharing.
Read receipts
Every session carries an activity summary (returned on GET /v1/sessions/{id}
and in each entry of GET /v1/sessions) so clients can show unread state
without diffing the timeline themselves:
{ "activity": { "state": "needs_help", "unseen": true, "attention_event_id": "evt_09" } }state is idle, working, done, needs_help, failed, or interrupted.
unseen is true when the owner hasn't acknowledged the response or approval
request currently at attention_event_id. Sending a user.message or
resolving an approval does not by itself clear unseen — the owner has to have
actually seen it.
Acknowledge what's currently displayed with PUT /v1/sessions/{id}/seen:
curl -X PUT https://agents.clusterbase.dev/v1/sessions/ses_8b1d4e/seen \
-H "Authorization: Bearer $CLUSTER_TOKEN" \
-H "Content-Type: application/json" \
-d '{ "through_event_id": "evt_09" }'
# → { "state": "needs_help", "unseen": false, "attention_event_id": "evt_09" }The cursor only advances — acknowledging an older through_event_id than
what's already been seen is a no-op — and it's account-wide, not per-device.
Because it's keyed to the specific event actually rendered, a receipt sent for
a stale response can't accidentally clear a newer one, and answering a pending
approval doesn't itself mark it seen.