Skip to content

Events

Every observable fact about a session arrives as an event on one ordered log. This page is the catalog: all 19 types, the exact data payload of each, the moment each is appended, and what is deliberately absent from the stream. For the polling loop that reads the log, see Follow a run live.

Every event, from every endpoint and from every webhook delivery, has the same seven-field envelope:

{
"id": "6f1c1d80-6c2a-4f0e-9d5f-0c2a2f0f4b11",
"type": "wamp.run.completed",
"createdAt": "2026-08-11T09:04:18.220Z",
"sequence": 42,
"visibility": "summary",
"subject": {
"sessionId": "3f7d4f4c-2b6a-4a2e-9c1a-1f2b3c4d5e6f",
"runId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d"
},
"data": { "status": "completed", "stopReason": "completed", "costUsd": 0.0123, "turns": 3 }
}
Field Type Notes
id UUID Stable across replays. Use it as your deduplication key.
type string One of the 19 values below. Unknown future values must be ignored, not rejected.
createdAt ISO 8601 When the event was appended, not necessarily when the fact occurred — timeline events carry their own occurredAt.
sequence integer ≥ 1 The total order within the session, and the only cursor.
visibility "summary" Always this literal on the public API.
subject.sessionId UUID Always present.
subject.runId UUID Present when the event belongs to a run; absent on publication and merge events.
data object Type-specific, documented per type below. May carry properties beyond the ones listed.

The same envelope, byte for byte, is the body of a webhook delivery. There is one producer for both, so you can write one handler.

Four properties of the log are worth designing against, and each is enforced rather than best-effort: sequence is total-ordered and gap-free within a session, so you may assert sequence === previous + 1; events are immutable and append-only; the log is exactly-once at rest, because the runtimes that produce events retry at-least-once and the store deduplicates before insert; and after is exclusive, so replaying a cursor never repeats an item. Follow a run live covers what those mean for a loop.

Events subject.runId
Run, interaction, and timeline events always present
wamp.artifact.created for a run result or a presented file present
wamp.artifact.created for a pull request record absent — data.publicationId identifies it instead
wamp.publication.* absent — publications are session-scoped commands

Match on subject.runId when you follow one specific run. A session accumulates runs, and a terminal event from an earlier run will otherwise end your loop early.

Five types, one per run outcome plus the start. Exactly one of the four terminal types closes any run.

Appended when the run begins executing on a sandbox — after the turn was admitted and a workspace was provisioned.

{ "status": "running" }

The agent finished the turn on its own terms.

{ "status": "completed", "stopReason": "completed", "costUsd": 0.0123, "turns": 3 }
Field Type Always present
status "completed" yes
stopReason see below no
costUsd number no
turns integer no

stopReason is one of completed, aborted, max_turns, max_response, error, cost_budget, turn_budget, time_budget, loop_detected. It tells you why the agent stopped, which is not the same as whether it succeeded: a completed run with stopReason: "max_turns" ran out of budget mid-task.

The run ended in an error the platform recognized as terminal. Same payload shape as wamp.run.completed, with status: "failed".

The run stopped because cancellation was requested through POST …/runs/{runId}/cancel. Same payload shape, with status: "cancelled". Cancellation is a request, not an immediate state: the accepted request appears on the run resource as cancellationRequested: true, and this event arrives when the run actually stops.

The run ended without a clean result — the runtime or the sandbox was lost. Same payload shape, with status: "crashed". The platform may retry the run on its own; the run resource exposes attempts so you can see that it did.

Three types, covering the whole life of one blocking question.

The agent needs a decision and the run has stopped to wait. The run’s status moves to awaiting.

{
"interactionId": "ask_user:01H8…",
"kind": "ask_user",
"request": {
"questions": [
{
"question": "Which package should the test live in?",
"options": ["core", "cli"],
"header": "Placement",
"recommendedIndex": 0,
"multiSelect": false,
"allowCustom": true
}
]
}
}
Field Type Always present
interactionId string, ≤ 255 chars yes
kind ask_user | exit_plan_mode yes
request.questions[] array only for ask_user

Within a question, question, options, multiSelect, and allowCustom are always present; header and recommendedIndex are optional. exit_plan_mode carries no request: the agent is asking for approval to leave planning and start editing, and the answer is a normal turn.

You answer by submitting a turn with replyTo: { interactionId }. Answering the wrong or an already-answered interaction is 409 cloud_interaction_conflict; submitting a plain turn while one is open is 409 cloud_turn_unresolved.

Your reply was accepted and the run continues.

{ "interactionId": "ask_user:01H8…", "resolvedByTurnId": "" }

resolvedByTurnId is the turn you submitted with replyTo. One turn resolves at most one interaction.

The question can no longer be answered.

{ "interactionId": "ask_user:01H8…", "reason": "workspace_lost" }

reason is one of workspace_lost (the sandbox went away), session_archived, or run_cancelled. After this event, a replyTo naming that interaction is a conflict. If your process restarted and you do not know what is outstanding, GET /v1/sessions/{sessionId}/interactions returns only the still-open ones.

Two types: one per artifact recorded, one honest report of what was not kept.

A durable output is now readable. data has three shapes, discriminated by kind.

kind When Fields
result.summary at the end of a run — the agent’s written summary of what it did artifactId, role: "output", contentType: "text/markdown; charset=utf-8", size, sha256, state: "available", truncated
presented.file a file the agent chose to hand back artifactId, role: "output", contentType, size, sha256, state: "available", truncated: false
github.pull_request a publication opened or updated a pull request artifactId, publicationId, role: "output", contentType: "application/json", size, sha256, state: "available", truncated: false
{
"artifactId": "b1c2d3e4-…",
"kind": "result.summary",
"role": "output",
"contentType": "text/markdown; charset=utf-8",
"size": 1284,
"sha256": "9f2b…",
"state": "available",
"truncated": false
}

truncated: true on a result.summary means the summary hit its size cap and was cut. The event carries the manifest only; fetch the bytes from GET …/artifacts/{artifactId}/content. sha256 is also the ETag that endpoint returns, so you can skip a download you already have.

The run produced files that were not retained, and this is the platform saying so rather than under-reporting.

{ "omittedArtifacts": 3, "reason": "not_retained" }

reason is always not_retained today. The usual cause is a size cap — a single file over 32 MiB, or a run whose presented files exceed 64 MiB in total (see Limits). subject.runId is always present on this event, and the count is always at least 1.

Four types covering opening a pull request and merging it. None of them carries a subject.runId.

The pull request exists. This event is only emitted on success, so its status is the literal succeeded.

{
"publicationId": "",
"status": "succeeded",
"kind": "github.pull_request",
"branch": "wamp/session-3f7d4f4c",
"commitSha": "6f1c1d8047b2c3d4e5f60718293a4b5c6d7e8f90",
"pullRequest": { "number": 412, "url": "https://github.com/…/pull/412", "draft": true }
}

branch is the head branch with refs/heads/ stripped, commitSha is a 40-hex git object id, and draft reflects what you asked for (publications default to draft). A github.pull_request artifact event is appended alongside this one.

The publish attempt ended terminally.

{ "publicationId": "", "status": "failed", "errorCode": "branch_conflict" }

errorCode is a repository-level machine code, not one of the HTTP error codes in Errors. Treat it as a string to log and surface, not as a closed set to branch on exhaustively.

The exact reviewed head was merged.

{
"publicationId": "",
"mergeId": "",
"status": "succeeded",
"strategy": "squash",
"pullRequestNumber": 412,
"expectedHeadSha": "6f1c1d80…",
"mergedCommitSha": "0a1b2c3d…"
}

strategy is merge, squash, or rebase. expectedHeadSha is the head the merge was authorized against and mergedCommitSha is the 40-hex commit that resulted — a merge only ever integrates the head that was reviewed.

{ "publicationId": "", "mergeId": "", "status": "failed", "errorCode": "head_moved" }

As with wamp.publication.failed, errorCode is a repository-level code. A merge that is merely not ready yet (waiting on checks or branch protection) does not fail — it stays in waiting, and polling it returns 409 cloud_publication_merge_not_ready.

Five types. Together they are the transcript: what the agent said, what it ran, and what it dropped from its own context. They are the events a chat UI is built from.

Timeline events are captured at the run’s terminal boundary, not streamed as the agent works. They are appended in one batch, in occurrence order, immediately before the terminal wamp.run.* event of that run. So a client that pages during a long run sees run and interaction events promptly and receives the transcript when the run ends.

Assistant text.

{
"messageId": "3b8f…",
"role": "assistant",
"text": "I added a regression test for the date parser and ran the suite.",
"truncated": false,
"occurredAt": "2026-08-11T09:03:02.104Z"
}

role is always the literal assistant — there is no user-message event, because your messages are turns and you already have them. text is bounded and truncated: true says it was cut.

One tool call or runtime step finished.

{
"activityId": "7c1a…",
"category": "tool",
"name": "bash",
"status": "success",
"occurredAt": "2026-08-11T09:02:41.900Z"
}

category is tool or runtime. name is the tool name (or the runtime id for runtime), bounded to 128 characters. status is success, error, or unknown. You learn that a tool ran and whether it worked — never its arguments or its output.

The agent compacted its own conversation to stay inside its context window.

{ "factId": "5d2e…", "droppedMessages": 42, "summaryRetained": true, "occurredAt": "" }

summaryRetained: true means a summary of the dropped messages was kept. This is why a later message can reference work you no longer see in full.

The conversation was reset; earlier context is gone from the agent’s view. Your event log still holds everything.

{ "factId": "5d2e…", "occurredAt": "" }

The run produced more timeline facts than were retained.

{ "omittedFacts": 118 }

subject.runId is always present, and omittedFacts is always at least 1. Like wamp.artifact.omitted, this exists so that a gap in the transcript is explicit rather than silent.

What the stream deliberately does not contain

Section titled “What the stream deliberately does not contain”

The timeline is a security-reviewed projection, not a debug log. It carries bounded assistant output and activity metadata; raw tool inputs, raw tool outputs, and model reasoning are intentionally not part of the public contract. No visibility value on /v1 exposes them — summary is the only one served.

Two more absences worth knowing: there is no event for a turn you submitted (the turn resource is your record of that), and there is no event for sandbox lifecycle. Read session.workspace for the sandbox and Sandboxes for what its states mean.

Webhook subscriptions are per family, not per type, and there are four families. Fourteen of the nineteen types map to one:

Family Types delivered
run wamp.run.started, wamp.run.completed, wamp.run.failed, wamp.run.cancelled, wamp.run.crashed
interaction wamp.interaction.opened, wamp.interaction.resolved, wamp.interaction.expired
artifact wamp.artifact.created, wamp.artifact.omitted
publication wamp.publication.created, wamp.publication.failed, wamp.publication.merge_succeeded, wamp.publication.merge_failed

The five timeline types are never delivered by webhook. They are stream-only: wamp.message.created, wamp.activity.completed, wamp.context.compacted, wamp.conversation.cleared, and wamp.timeline.truncated. That is a deliberate consent boundary — pushing transcript content to a subscriber would widen an installation’s data flow, so it waits for an explicit transcript family rather than being folded into run or artifact.

The pattern this implies: subscribe to run, and when a terminal run webhook arrives, page GET /v1/sessions/{sessionId}/events to collect the transcript. Since timeline events are appended just before the terminal run event, they are already durable by the time that webhook reaches you. Registration, signature verification, and retries are in Receive webhooks.

The catalog is closed today and grows additively. Write your handler so that a new type costs you nothing:

switch (event.type) {
case 'wamp.message.created': /* … */ break;
case 'wamp.run.completed': /* … */ break;
default: break; // unknown types are ignored, by contract
}

Do not treat an unrecognized type as an error, do not reject an unrecognized property inside data, and do not assume data fields marked optional above will be present. Deduplicate on event.id, which is stable across replays and across webhook redeliveries.