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, 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.

On this page