Managed Agents

Sessions & events

Run an agent by creating a session, sending messages, and streaming the agent's events over SSE.

View as Markdown

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.

On this page