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, and select git
repos and a Storage folder for its workspace under resources. To attach MCP
credentials, pass vault_ids — see Vaults & MCP.
skills sets the session's default skills — up to eight ids from the
skill catalog. The model reads a selected
skill's instructions on demand with the skills_read tool; nothing is
injected into the prompt up front. Omit skills to inherit the optional skills
you have added to your personal library, plus — on a workspace-backed session —
the core Build actions (build-commit, build-create-pr, build-create-repo,
build-deploy, build-publish, build-restart, build-review-changes),
which are always available and cannot be removed. Skills that need a workspace
are left out of plain chat sessions. Pass an explicit list to pin exactly those
skills instead; [] selects none. PATCH /v1/sessions/{id} with a skills
array replaces an existing session's selection the same way, and changes to
your library only affect sessions created afterwards.
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 search, news, shopping, places, and web fetch results, as well as
explicit citation sources, are also retained in their original order, together
with each result's explicit citation ID where one was assigned, 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. Place results keep only public place details such as name, address,
coordinates, rating, opening hours, and website. 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.