Events and streaming
A session's transcript is an append-only log. The session writes everything the agent does to it, whether or not anything is reading. Every entry carries an id and a sequence number.
Writing to a session
POST /v1/sessions/:id/events takes 1 to 50 events and answers with an acknowledgement for each, carrying the id the event will have in the transcript and its type.
| Type | What it does |
|---|---|
| user.message | A message for the agent. Carries content blocks. The agent reads it on the next turn. |
| user.interrupt | Stops the turn that is running. |
| user.tool_confirmation | Answers a tool that asked. Carries tool_use_id and a result of allow or deny. |
| user.custom_tool_result | Returns a custom tool's result. Carries custom_tool_use_id, content and is_error. |
await client.beta.sessions.events.send(session.id, {
events: [
{
type: "user.message",
content: [{ type: "text", text: "Check the tests too." }],
},
],
});The agent reads a message sent mid-turn at the start of the next turn. To stop the current turn, send an interrupt.
await client.beta.sessions.events.send(session.id, {
events: [{ type: "user.interrupt" }],
});Images and documents
A message or a custom tool result takes image and document blocks. A document's base64 source must be a PDF; a text source is plain text. A base64 image over 10 MB of base64 is refused with a 400.
A url source goes to the model's provider, which fetches it: Anthropic, OpenAI for images, and OpenRouter for Claude models. For any other model or kind, coreloop downloads the url before the call, over https only, as its robots.txt allows, up to 22 MiB. A url that cannot be fetched ends the session with a terminal session.error in the fetcher's words.
On Anthropic, an image or PDF sent as base64, or read in the sandbox, is uploaded once to your organization's Files API and read by its file id. A file the Files API refuses ends the session the same way; a refused key fails only the turn. The copy expires after 30 days. It is deleted when the session is deleted, or archived before it ended.
coreloop has no Files API of its own. A document's file_id is refused with a 400, and an image's ends the session with a terminal session.error.
Reading the transcript
GET /v1/sessions/:id/events returns a page of events, oldest first.
const page = await client.beta.sessions.events.list(session.id, {
limit: 100,
});
for (const event of page.data) console.log(event.type);| Parameter | Notes |
|---|---|
| limit | 1 to 1000. The default is 20. |
| page | The cursor from the previous page's next_page. |
| created_at[gt] | An ISO timestamp. Returns the events after it. |
The response carries data and, while more remains, next_page. The last page has no next_page at all. A page ends on a log-row boundary, so one page can hold a few more events than the limit asked for.
Streaming
GET /v1/sessions/:id/events/stream is server-sent events. The SSE id is the sequence number and the SSE event name is the event type, so a client can switch on the name alone.
id: 12
event: agent.message
data: {"id":"sevt_...","type":"agent.message","content":[{"type":"text","text":"Done."}]}
id: 13
event: session.status_idle
data: {"id":"sevt_...","type":"session.status_idle","stop_reason":{"type":"end_turn"},"stop_details":null}The stream carries the events written after it opened, and it takes no cursor. To read a session from the start, open the stream first and then page the history. The stream pins its start before it answers, so no event falls between the two. coreloop refuses any query parameter other than event_deltas with invalid_request_error.
Previews
An agent message reaches the transcript when the turn finishes writing it. Ask for previews to see it arrive as it is written.
const stream = await client.beta.sessions.events.stream(session.id, {
event_deltas: ["agent.message", "agent.thinking"],
});The stream then sends an event_start frame naming the event id, then event_delta frames carrying the text, and finally the finished event under the same id. A preview is not part of the transcript, and a later read of the log returns the finished event only.
Event types
The full table is in the reference.
Soon: system.message, user.define_outcome.
Endpoints
- POST/v1/sessions/:id/events
- GET/v1/sessions/:id/events
- GET/v1/sessions/:id/events/stream