Agents APISessions
Events
Follow what an agent does as it works, and pick up exactly where you left off.
sessions.events(id) yields everything that happens in a session, in order, for as long as the session is alive. It reconnects by itself if the connection drops, and ends when the session is deleted. Break out of the loop once you have what you need.
What you receive
| Item | Has seq | What it is |
|---|---|---|
| session.status | no | The session's status. It comes first, and again whenever the status changes, with status, error and any pending_approvals. |
| approval.requested | no | The agent is waiting for your permission. See Approvals. |
| event | yes | Something the agent did: { seq, type, data, time }. Events are stored and numbered from 1. |
JSON
{ "type": "session.status", "status": "running", "error": null, "pending_approvals": [], … }
{ "seq": 7, "type": "tool.called", "data": { "tool": "bash", "input": { "command": "python3 --version" }, … }, "time": "2026-09-28T07:18:16Z" }
{ "seq": 12, "type": "text.ended", "data": { "text": "The installed Python version is 3.11.2." }, "time": "…" }
{ "seq": 13, "type": "run.completed", "data": { … }, "time": "…" }Only answer requests without a seq
An approval.requested item without a seq is a live request. Check "seq" in event before you answer, as every example here does.Follow a session
Show progress, count tokens, keep your place
let answer = ""
let outputTokens = 0
let position = 0 // the last event you handled; store it to resume later
for await (const event of sessions.events(session.id)) {
if (event.type === "session.status" && "status" in event) {
console.log("status:", event.status)
continue
}
if (!("seq" in event)) continue // an approval or question: see Approvals and questions
position = event.seq
if (event.type === "tool.called") console.log("tool:", event.data?.tool, JSON.stringify(event.data?.input))
if (event.type === "step.ended") outputTokens += Number((event.data?.tokens as { output?: number } | undefined)?.output ?? 0)
if (event.type === "text.ended") answer = String(event.data?.text)
if (event.type === "run.failed") console.error("run failed:", event.data?.error)
if (event.type === "run.completed" || event.type === "run.failed") break
}
console.log(answer, `(${outputTokens} output tokens)`)status: provisioning
status: idle
status: running
tool: bash {"command":"python3 --version","description":"Check Python version"}
The installed Python version is 3.11.2. (91 output tokens)Resume without gaps
Every event has a seq. Pass the last one you handled as after, and the stream continues with the next event: nothing missed and nothing repeated, whether after a dropped connection, a restart of your own service, or days later. session.last_seq is always the newest number.
Continue from where you stopped
await sessions.message(session.id, "And which version of Node.js?")
// Only events after `position`: nothing missed, nothing repeated.
for await (const event of sessions.events(session.id, { after: position })) {
if ("seq" in event) position = event.seq
if (event.type === "text.ended") console.log(event.data?.text)
if (event.type === "run.completed" || event.type === "run.failed") break
}Event types
| type | data | Meaning |
|---|---|---|
| run.started | The agent picked up a message. | |
| run.completed | The run finished, or was cancelled. | |
| run.failed | error | The run stopped on an error. The session is still usable. |
| text.ended | text | A complete block of the agent's reply. Text arrives whole; there are no partial deltas. |
| tool.called | tool, input | The agent called a tool, e.g. bash with { command }. |
| tool.success · tool.failed | content · error | A tool call finished, or failed. After a failure the agent usually carries on. |
| step.ended | tokens, finish | One model call ended. tokens has input, output, reasoning and cache counts. |
| prompt.admitted | prompt, delivery | A message you sent entered the conversation (steer or queue). |
| step.started · text.started | A model call, or a reply block, began. | |
| tool.input.started · tool.input.ended | name | The model is writing a tool call. |
| reasoning.started · reasoning.ended | text | The model's reasoning, for models that expose it. |
| compaction.started · compaction.ended | text | Earlier conversation was summarized to fit the context window. |
| retried | attempt, error | A model call was retried after a transient error. |
New event types can appear over time. Ignore any you don't recognise.