# Events The complete catalog of the 23 WAMP Cloud session event types, their payloads, and when each one is appended. Source: https://docs.cloud.vampikez.fun/reference/events/ Every observable fact about a session arrives as an event on one ordered log. This page is the catalog: all 23 types, the exact `data` payload of each, the moment each is appended, and what is deliberately absent from the stream. For the live follow loop that reads the log, see [Follow a run live](/guides/follow-a-run/). ## The envelope Every event returned by the endpoint has the same eight-field envelope: ```json { "id": "6f1c1d80-6c2a-4f0e-9d5f-0c2a2f0f4b11", "type": "wamp.run.completed", "createdAt": "2026-08-11T09:04:18.220Z", "sequence": 42, "subject": { "sessionId": "3f7d4f4c-2b6a-4a2e-9c1a-1f2b3c4d5e6f", "runId": "9a8b7c6d-5e4f-4a3b-8c2d-1e0f9a8b7c6d" }, "data": { "status": "completed", "stopReason": "completed", "costUsd": 0.0123, "costComplete": true, "turns": 3 } } ``` | Field | Type | Notes | |---|---|---| | `id` | UUID | Stable across replays. Use it as your deduplication key. | | `type` | string | One of the 23 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. | | `subject.sessionId` | UUID | Always present. | | `subject.runId` | UUID | Present on run and interaction events; absent on publication and merge events. Timeline events carry it in practice but do not guarantee it — treat it as optional there. | | `data` | object | Type-specific, documented per type below. May carry properties beyond the ones listed. | 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](/guides/follow-a-run/) covers what those mean for a loop. `GET /v1/sessions/{sessionId}/events/stream` replays from the exclusive `Last-Event-ID` header (or `after` query, default 0), then delivers each append as `id: ` and `data: `. It carries this same envelope and the same visibility as the JSON page. Keep the last sequence your handler committed; reconnect with it after a stream ends. ## Which subject each event carries | 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. ## Run events Six types: the start, four terminal outcomes, and a progress fact that may repeat. Exactly one of the four terminal types closes any run. ### `wamp.run.started` Appended when the run begins executing on a sandbox — after the turn was admitted and a workspace was provisioned. ```json { "status": "running" } ``` ### `wamp.environment.apply` `started` and a terminal `succeeded` or `failed` event describe the pinned environment revision applied to a sandbox lease. A lease and revision get one `started` and one terminal event, however many Turns join the apply; each names the Run whose call wrote it, so the two may name different Runs, or none for a sandbox brought up without one. A terminal event may include a redacted setup tail and a content-free cache skip reason; secret values and signed storage URLs are never event data. When the apply was offered a cache restore and discarded it whole, so `setup` ran without the cache, a terminal event carries `cacheDiscarded`: `{ "reason": "the archive does not match its sha256" }`. The reason is a redacted sentence of at most 1,024 characters: the download failed, the bytes did not match their sha256, or the archive was malformed or could not be installed. `cache` then reports the save, or `miss` when nothing was saved. When that revision enables GitHub access, a terminal event also reports `github`: `{ "status": "granted", "owners": ["acme"] }` or `{ "status": "unavailable", "reason": "no_grant" }`. The other unavailable reasons are `authority` (the owner lost access), `temporary` (retried before the next message), and `runtime_outdated` (the engine cannot hold access). `owners` contains 1–16 GitHub account logins, never tokens. The field is absent on `started` and when the revision did not enable GitHub access. When the revision lists `repositories`, a terminal event reports what happened to each one as `{ "repository", "path", "state" }`. In a Session with a launch repository every entry is `skipped`. Otherwise the entries follow the list order, each `cloned` or `kept` (something was already at its path, for example restored from a checkpoint), up to and including the first `failed` clone; that failure is `errorCode: "environment_setup_failed"` and git's redacted output is the `outputTail`. The field is absent on `started`. When the revision lists `extensions`, a succeeded event reports each one in list order as `{ "ref", "id"?, "version"?, "sha256"?, "state", "reason"? }`: `ref` is the catalog slug or the archive's `sha256:`, `state` is `active` or `failed`, `sha256` is the digest of the catalog package installed, and a `failed` entry carries a redacted `reason`. A failed extension does not fail the apply. The field is absent on `started` and on `failed`. ```json { "leaseId": "...", "environmentId": "...", "name": "backend", "revision": 3, "status": "succeeded", "cache": "skipped_secret", "cacheSkip": { "reason": "secret", "path": "~/.cache/npm/token" }, "setupExitCode": 0, "durationMs": 1200, "outputTail": "setup complete", "github": { "status": "granted", "owners": ["acme"] }, "repositories": [ { "repository": "acme/backend", "path": "backend", "state": "cloned" }, { "repository": "acme/web", "path": "site", "state": "kept" } ], "extensions": [ { "ref": "wamp-mcp-postgres", "id": "wamp-mcp-postgres", "version": "1.4.0", "sha256": "…", "state": "active" }, { "ref": "sha256:…", "state": "failed", "reason": "Could not download the archive: the download answered HTTP 404." } ] } ``` ### `wamp.run.progress` A bounded tool-lifecycle fact, appended while the run is still working. It may appear many times. ```json { "activityId": "a3f1…", "category": "tool", "name": "bash", "status": "started", "occurredAt": "2026-08-11T09:02:38.410Z" } ``` | Field | Type | Always present | |---|---|---| | `activityId` | string | yes — stable per tool call, so `started`, its outcome and the call's [`wamp.activity.completed`](#wampactivitycompleted) share it | | `category` | `"tool"` | yes — the only value | | `name` | string, ≤ 128 chars | yes | | `status` | `started` \| `succeeded` \| `failed` | yes | | `delegationId` | string, ≤ 255 chars | no — set when an agent the session delegated to made the call; the same id as that lane's [`wamp.delegation.completed`](#wampdelegationcompleted) | | `occurredAt` | ISO 8601 | yes | It carries no arguments, no output, no command line, and no path. Use it to show that something is happening; use `wamp.activity.completed` for the retained transcript. ### `wamp.run.completed` The agent finished the turn on its own terms. ```json { "status": "completed", "stopReason": "completed", "costUsd": 0.0123, "costComplete": true, "turns": 3 } ``` | Field | Type | Always present | |---|---|---| | `status` | `"completed"` | yes | | `stopReason` | see below | no | | `errorCode` | stable remediation code | no; failed/crashed only | | `costUsd` | number | no | | `costComplete` | boolean | no; `false` means `costUsd` is only a known subtotal | | `turns` | integer | no | `stopReason` is one of `completed`, `aborted`, `max_turns`, `max_response`, `error`, `cost_budget`, `cost_unknown`, `turn_budget`, `time_budget`, `repeated_tool_failure`, `engine_unreachable`, `execution_deadline`. 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. `repeated_tool_failure` means the agent sent the same tool call five times in a row and it failed with the same error each time. The last two are only ever produced by the platform, and only on `wamp.run.crashed`. ### `wamp.run.failed` The run ended in an error the platform recognized as terminal. Same payload shape as `wamp.run.completed`, with `status: "failed"`. When the platform can classify the failure safely, `errorCode` carries a bounded machine code such as `authority_revoked_dispatch` or `resume_unavailable`; it never carries an exception message. Use the same field on `wamp.run.crashed`. ### `wamp.run.cancelled` 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. ### `wamp.run.crashed` 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. When the platform itself declared the run dead, `stopReason` says which way, and `errorCode` is `turn_execution_lost`: | `stopReason` | What happened | What to do | |---|---|---| | `engine_unreachable` | The sandbox stopped answering. The runner was lost, not the work. | Retry. Read `continuation` first if the session must resume prior state. | | `execution_deadline` | The run hit the sandbox lifetime ceiling — see [Limits](/reference/limits/). | Split the work, or continue in a follow-up turn on a fresh lease. | ```json { "status": "crashed", "stopReason": "execution_deadline", "errorCode": "turn_execution_lost" } ``` A crash with no `stopReason` is one the platform could not classify. Do not branch on the absence of the field. ### Run error codes `errorCode` appears on `wamp.run.failed` and `wamp.run.crashed`, and on the run resource as `run.lastError.code`. It is a bounded, non-sensitive remediation code — never an exception message. Event payloads carry only the modelled values below; the run resource can carry a few more, documented after the table. | `errorCode` | What happened | What to do | |---|---|---| | `authorization_unavailable` | The identity service could not be reached to authorize the run | Transient. Retry with backoff | | `authorization_rejected` | The identity service refused **WAMP's own** workload credential | Not yours to fix. Retrying cannot help; report it to the operator | | `authority_revoked_dispatch` | The caller's authority was revoked before the run could start | Re-consent the installation, then submit a new turn | | `authority_revoked_cancel` | The caller's authority was revoked while a cancellation was in flight | As above | | `dispatch_failed` | Handing the turn to a sandbox failed | Retry by submitting a new turn | | `dispatch_attempts_exhausted` | Dispatch failed its bounded attempt budget | The run is terminal. Submit a new turn; if it recurs, escalate | | `runtime_account_unavailable` | No vendor runtime account could be leased | Check `availability` in discovery; ask an administrator to connect an account | | `runtime_outdated` | The runtime in the sandbox cannot serve this session as configured | Create a new session | | `environment_setup_failed` | The pinned environment setup failed or timed out | Inspect the redacted `wamp.environment.apply` tail, fix the environment, then submit a new Turn | | `environment_forbidden` | The stored dispatch authority can no longer apply the environment | Restore the authority, then submit a new turn | | `compute_outdated` | The organization's machine is reachable but holds an engine image older than this release | Not retryable. An administrator updates that machine's images, and sees which machine needs it under **Runners** in WAMP Cloud Settings. No `/v1` endpoint reports it | | `turn_execution_lost` | The execution backing the turn died with its sandbox | Submit a new turn. Read `continuation` first if prior state matters | | `interaction_conflict` | The reply did not match the open interaction | Re-read `GET …/interactions` and answer the one that is actually open | | `agent_locked` | The execution path refused the requested agent change | Inspect the run error. Select an agent on a new Turn; Session `PATCH` cannot change it after a Turn exists | | `resume_unavailable` | The session's previous state could not be restored | Start a new session | | `session_not_found` | The session disappeared beneath the run | Terminal. Do not retry | | `turn_not_found` | The turn disappeared beneath the run | Terminal. Do not retry | Engine failures use the following codes. The engine chooses them from a closed list; provider messages and arbitrary gateway codes are never copied into an event. These codes also appear on `run.lastError.code`. | `errorCode` | What happened | What to do | |---|---|---| | `upstream_429` | The model provider rate-limited the request | Submit a new turn after the limit clears | | `upstream_5xx` | The model provider or gateway returned a server error | Submit a new turn; escalate if it persists | | `upstream_4xx` | The provider rejected a request outside the specific gateway refusals below | Check the model and request before retrying | | `upstream_timeout` | The provider did not respond before a transport deadline | Submit a new turn | | `upstream_network` | A connection, DNS lookup or socket failed | Submit a new turn after connectivity recovers | | `stream_incomplete` | The model stream ended without completion | Submit a new turn | | `refused_invalid_request` | The gateway rejected the request | Check the selected model and request settings | | `refused_unsupported_parameter` | The gateway does not support a request parameter | Choose another model or setting | | `refused_unsupported_tool_type` | The gateway does not support a tool type the agent sent | Choose a model that supports the agent's tools | | `refused_invalid_tool_arguments` | The gateway rejected tool arguments in the request | Submit a new turn; report it if it recurs | | `refused_model_not_found` | The gateway could not find the selected model | Select an available model | | `refused_end_user_id_conflict` | The gateway found a conflicting end-user identity | Report an identity configuration error | | `refused_invalid_end_user_id` | The gateway rejected the end-user identity | Report an identity configuration error | | `refused_too_many_concurrent` | The gateway concurrency limit was reached | Submit a new turn after other work settles | | `refused_service_unavailable` | The gateway was temporarily unavailable | Submit a new turn | | `refused_missing_api_key` | The gateway has no API key for this inference request | Restore the account credential | | `refused_invalid_api_key` | The gateway refused the API key | Reconnect the account credential | | `refused_malformed_request_body` | The gateway rejected the request format | Report a client defect if it recurs | | `refused_request_entity_too_large` | The gateway rejected the request size | Shorten the input or attachments | | `context_overflow` | Compaction could not fit the conversation in the model context | Start a smaller session or shorten the input | | `model_no_tools` | The selected model cannot call the agent's tools | Select a tool-capable model | | `tool_loop_limit` | The engine reached its loop step limit | Review the partial work before submitting another turn | | `repeated_tool_failure` | The agent sent the same tool call five times in a row and it failed the same way each time | Read the repeated error and change the request, the environment or its credentials before submitting another turn | | `response_limit` | The response passed the engine's single-turn size limit | Ask for a shorter answer | | `budget_exhausted` | The engine reached a cost, turn or time budget | Review the budget before retrying | | `runtime_error` | A foreign agent runtime failed or could not be resolved | Check runtime availability and retry | | `unknown` | The engine could not classify the failure | Review the session and report repeated failures | Seven further codes appear on the run resource only, and never on an event, for one reason in two shapes: **the run is still alive when they are written.** An unobserved cancellation leaves the run `running`, and a dispatch still retrying leaves it `queued`. Neither has produced a terminal event for a code to ride on, so read these from `run.lastError.code`. Two of them are written while cancellation is pending or revoked: | `errorCode` | What happened | What to do | |---|---|---| | `cancel_not_observed` | Cancellation was acknowledged but no stop was observed before the worker's claim ended; the platform re-asks on its retry curve | Keep watching for `cancelled` | | `cancel_authority_revoked` | The caller's authority was revoked mid-cancellation; the request was abandoned | Re-consent the installation, then cancel again | The other five are the placement and lifecycle conditions a dispatch attempt hits and then retries. Seeing one means the run has not started yet, not that it failed — the run is still `queued` and the platform is still trying: | `errorCode` | What happened | What to do | |---|---|---| | `workspace_unavailable` | A sandbox for this session could not be started on this attempt | Nothing. The platform retries; watch the run's status | | `workspace_expired` | The workspace this turn wanted to continue from is gone | Restore the workspace, or accept a fresh one — see [Sandboxes](/concepts/sandboxes/) | | `compute_not_configured` | The organization has no compute it may be placed on | An administrator must configure compute; retrying will not help | | `conversation_busy` | Another turn on this session is still holding the conversation | Wait for the active run to end, then submit again | | `repository_grant_required` | The repository authority this turn needs is missing or no longer valid | Re-grant repository access, then submit again | The tables above are the whole vocabulary. A run failure on a condition that has no code of its own is recorded as `dispatch_failed` — the platform does not mint a code by stripping the `cloud_` prefix off an HTTP error, which is what once put values on this field that appeared in no table, no enum and no SDK. The cause of such a failure is in the platform's own logs, not on the resource. Do not render an `errorCode` as prose. Map the values you handle to your own copy, and treat an unrecognized one as a generic failure — the set is additive. `isKnownCloudRunErrorCode` in `@wamp/app-sdk` tests membership of the table above for you. Publication and merge failures use different families, catalogued in [Errors](/reference/errors/#failure-codes-inside-successful-responses). ## Interaction events Three types cover a suspended question or a live approval. ### `wamp.interaction.opened` The agent needs input. A question or plan moves its Run to `awaiting`; an approval leaves its Run `running` while it waits for a decision. ```json { "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` \| `approval` | yes | | `request.questions[]` | array | only for `ask_user` | | `request.prompt` | string | optional for `ask_user` | | `request.plan` | string, ≤ 64 KiB | only for `exit_plan_mode`, and only when the agent wrote a plan | | `toolName`, `title`, `actionKind`, `deadline` | tool metadata and Unix milliseconds | approval metadata, when present | The opened approval event never carries action arguments. Read `GET /v1/sessions/{sessionId}/interactions` with `wamp.cloud.sessions:read` and show `request.approval.input`, the complete proposed action. The request also contains its `requestId`, deadline, options, and allowed memory scopes. Answer through `PUT …/interactions/{interactionId}/decision` with `wamp.cloud.approvals:respond` and live Session access. Send `requestId`, a Cloud choice (`allow_once`, `allow_turn`, `allow_chat`, or `deny`), and a caller-owned `idempotencyKey`; reuse the key on retry. The answer resumes the same Run without creating a reply Turn. A vendor `allow_always` or `reject_always` option is never a Cloud choice. Within a question, `question`, `options`, `multiSelect`, and `allowCustom` are always present; `header` and `recommendedIndex` are optional. An empty `options` array is a free-text or numeric field. ACP form questions also carry `fieldId`, `valueType`, typed `optionValues`, defaults and constraints; `customFieldId` is the optional “Other” text field. Answer a structured form with a JSON object in the Turn's `message`: use `fieldId` keys and the typed wire values, use `customFieldId` instead for “Other”, and omit optional fields. Plain text also answers it: one question takes its answer as the whole message, several take one `Label: answer` line each, labelled by `header` or `question`. Text naming an option's label or value selects that option; other text becomes the “Other” answer. A reply that fits none of these cancels the question, and the agent may ask again. Questions without `fieldId` keep the ordinary human-readable answer string. An `exit_plan_mode` request carries `plan` instead: the plan markdown to show before you ask someone to approve leaving planning and starting to edit. It is truncated at 64 KiB, and `request` is absent entirely when the agent produced no plan file to read. Either way the answer is a normal turn. Answer a question or plan 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_conversation_busy`, which never clears on its own — see [Errors](/reference/errors/#state-conflicts--act-then-retry). ### `wamp.interaction.resolved` The answer was accepted. A question has `resolvedByTurnId`; an approval has `resolution`, optional vendor `optionId`, the winning `choice`, and `answeredBy` (`HUMAN` or `APP_INSTALLATION` with its id). ```json { "interactionId": "ask_user:01H8…", "resolvedByTurnId": "…" } ``` `resolvedByTurnId` is the turn you submitted with `replyTo`. One turn resolves at most one interaction. ### `wamp.interaction.expired` The question can no longer be answered. ```json { "interactionId": "ask_user:01H8…", "reason": "workspace_lost" } ``` `reason` is one of `workspace_lost`, `session_archived`, `run_cancelled`, `run_ended`, `timed_out`, `cancelled`, `request_gone`, or `authority_revoked`. After this event, a `replyTo` or decision 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. ## Artifact events Two types: one per artifact recorded, one honest report of what was not kept. ### `wamp.artifact.created` A durable output is now readable. `data` has three shapes, discriminated by `kind`, which is always present. | `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` | ```json { "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. ### `wamp.artifact.omitted` The run produced files that were **not** retained, and this is the platform saying so rather than under-reporting. ```json { "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](/reference/limits/)). `subject.runId` is always present on this event, and the count is always at least 1. ## Publication events Four types covering reviewed delivery and pull-request merging. None of them carries a `subject.runId`. ### `wamp.publication.created` The exact reviewed revision was delivered. This event is only emitted on success, so its `status` is the literal `succeeded`. ```json { "publicationId": "…", "status": "succeeded", "kind": "github.pull_request", "delivery": "pull_request", "branch": "wamp/publications-v1/cloud-3f7d4f4c-2b6a-4a2e-9c1a-1f2b3c-6b031ca6", "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. An explicitly authorized direct delivery uses the same event type but a discriminated payload and does not create a pull-request artifact: ```json { "publicationId": "…", "status": "succeeded", "kind": "github.branch", "delivery": "direct_branch", "branch": "main", "commitSha": "6f1c1d8047b2c3d4e5f60718293a4b5c6d7e8f90" } ``` ### `wamp.publication.failed` The publish attempt ended terminally. ```json { "publicationId": "…", "status": "failed", "errorCode": "branch_conflict" } ``` `errorCode` is a repository-level machine code, not one of the HTTP error codes in [Errors](/reference/errors/). Treat it as a string to log and surface, not as a closed set to branch on exhaustively. ### `wamp.publication.merge_succeeded` The exact reviewed head was merged. ```json { "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. ### `wamp.publication.merge_failed` ```json { "publicationId": "…", "mergeId": "…", "status": "failed", "errorCode": "pull_request_changed" } ``` 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` with `lastError.code: merge_not_ready`, and polling it is a `200` carrying that status. The HTTP code `409 cloud_publication_merge_not_ready` belongs to the `PUT` that opens a merge, and means the publication is not a succeeded pull request yet. ## Timeline events Six 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. These six are the retained transcript. `wamp.message.created` arrives while the run works, as each message completes; the other five are committed at the terminal boundary, in occurrence order, immediately **before** the terminal `wamp.run.*` event, which is always last for that run. The live view of a call is [`wamp.run.progress`](#wamprunprogress), which arrives during the run and is a recovery signal rather than a record. ### `wamp.message.created` Assistant text. ```json { "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. It always has something to read: text that is only whitespace is not published as a message. Each message is appended once, shortly after the agent writes it. WAMP's own agent writes one after each model response, as the calls it made start; an agent runtime such as Claude Code or Codex writes at its segment boundaries, usually the end of its turn. A message the run had not delivered by its end is appended at the terminal boundary instead, with the same `messageId`. For WAMP's own agent, `sequence` is the order the agent worked in: a message comes before the `wamp.run.progress` and presented files of the calls it announced. If reading a message from the sandbox takes longer than three seconds, the call's progress is published without waiting and that message follows it. An agent runtime writes its prose at segment boundaries, so its calls' progress usually comes before the message that framed them. `occurredAt` is when the agent wrote the message. The run's answer is its `result.summary` artifact — see [the summary artifact and the last message](/concepts/artifacts/#the-summary-artifact-and-the-last-message-are-not-duplicates). ### `wamp.activity.completed` One tool call or runtime step finished. ```json { "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. A call the run also reported as [`wamp.run.progress`](#wamprunprogress) keeps that event's `activityId` and `name`, so draw the two as one row and let this event settle it. An agent runtime's own tools are named by action class, such as `runtime__execute`. Events written before 2026-09-15 carry an `activityId` of their own, and name an agent runtime's call by the bare action class (`execute`, `other`). `delegationId` is present only when an agent the session delegated to made this call, and names the [`wamp.delegation.completed`](#wampdelegationcompleted) it belongs to. Group by it to show a subagent's work as its own; treat its absence as the session's own work, which is what every event written before delegated lanes were attributed carries. ### `wamp.context.compacted` The agent compacted its own conversation to stay inside its context window. ```json { "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. ### `wamp.delegation.completed` The session handed work to another agent. ```json { "delegationId": "0f2b…", "name": "explorer", "status": "success", "occurredAt": "2026-09-06T09:02:41.900Z" } ``` `delegationId` identifies the lane, an opaque id of at most 255 characters; every [`wamp.activity.completed`](#wampactivitycompleted) and [`wamp.run.progress`](#wamprunprogress) that agent produced carries the same id. `name` is the agent as the delegating side named it, bounded to 128 characters. `status` is `success` or `error`, and is absent when the lane never reported an outcome. Like every activity event, this says what happened and not what was asked: the task the delegating agent wrote is never published. ### `wamp.conversation.cleared` The conversation was reset; earlier context is gone from the agent's view. Your event log still holds everything. ```json { "factId": "5d2e…", "occurredAt": "…" } ``` ### `wamp.timeline.truncated` The run produced more timeline facts than were retained. ```json { "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. ## Child session events Two types. An agent can start a **child session** — a separate Session, with its own sandbox, its own turns and its own events, that outlives the turn which started it. These two events are the parent's record of that: which conversations it started, and what each one reported back to it. They are not committed at the terminal boundary as most timeline types are. A `started` event is written when the child is created, and a `completed` event when the control plane hands that child's outcome to the parent's agent — which may be at the start of a later turn, or when the agent asks for it. Both are delivered through the Event stream, like the timeline types. `subject.runId` is the **parent's** Run in both; the child's own id is in the payload, and its own events are read from its own Session. ### `wamp.child_session.started` The session's agent started a conversation of its own. ```json { "childSessionId": "1f0c…", "title": "Reproduce the checkout timeout", "occurredAt": "2026-09-14T09:02:41.900Z" } ``` `childSessionId` is a Session id you can read with `GET /v1/sessions/{sessionId}` and whose `parent` names this Session and this Run. `title` is the agent's own short name for the work, bounded to 160 characters. Written once per child, so a retried spawn adds no second event. Like every other event here, this says what happened and not what was asked: the task the agent wrote for its child is never published. ### `wamp.child_session.completed` A child session finished, and this Session's agent has been told. ```json { "childSessionId": "1f0c…", "title": "Reproduce the checkout timeout", "state": "idle", "finalMessage": "The timeout reproduces on any cart with more than 40 lines…", "occurredAt": "2026-09-14T09:31:08.120Z" } ``` `state` is `idle` (the child ended its turn normally), `stopped` (it was aborted) or `failed`. `finalMessage` is the child's last assistant message, bounded to 4 000 characters, and is present when the hand-over included that text. A delta carries at most five terminal outcomes at once, each with its message when one exists; further outcomes reach later turns. Written once per child run, after the parent Run accepting a delta is confirmed or after a `session_wait` or one-id `session_status` reply is sent. `subject.runId` names that parent Run. An attempted parent Run that never started leaves no completion event; the next accepted Run can deliver it. **What this means for access.** A reader of this Session's events can read those final messages without being able to open the child Session itself, whose access is granted separately. That is deliberate and it is the same boundary `wamp.message.created` draws: a Session's events are the record of what that Session's agent was told and said, and this text was told to it. The child's own transcript stays behind the child's own access. ## Change notices An App installation that follows many Sessions holds one `GET /v1/sessions/stream` connection instead of one Event stream per Session. That stream carries no events. Each message's `data` is a notice that one of the installation's Sessions moved: ```json { "type": "wamp.session.events_appended", "timestamp": "2026-08-12T09:21:07.000Z", "data": { "sessionId": "9f2b7c14-59d3-4f7a-b8e1-2a6c05d4e731", "lastEventSequence": 12 } } ``` | Field | Type | Notes | |---|---|---| | `type` | string | `wamp.session.events_appended`. It is not one of the 23 event types and never appears in a Session's log. Ignore any other value. | | `timestamp` | ISO 8601 | When Cloud observed the append. | | `data.sessionId` | UUID | A Session this installation created, including child Sessions and forks of its Sessions. | | `data.lastEventSequence` | integer ≥ 1 | The Session's log reached at least this sequence. | A notice means "read this Session's log from your cursor" with `GET /v1/sessions/{sessionId}/events?after=`. Cloud sends at least one notice per append. One notice may stand for several appends and carries the newest sequence. Notices may repeat and arrive out of order across Sessions, so a notice at or below your cursor needs no work. The watermark that resumes the stream is described in [Follow many Sessions](/guides/follow-a-run/#follow-many-sessions). ## 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. The `/v1` stream is the only public event projection; raw traces are not a selectable access level and are never exposed by this resource. 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](/concepts/sandboxes/) for what its states mean. ## Forward compatibility The catalog is closed today and grows additively. Write your handler so that a new type costs you nothing: ```js 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. ## Related - [Follow a run live](/guides/follow-a-run/) — the stream, cursors, resuming after a disconnect, and following many Sessions at once. - [Events operations](/api/operations/tags/events/) — the generated endpoint reference for reading the log. - [the API reference](/api/) — the `SessionEvent` and `EventPage` object definitions. - [Errors](/reference/errors/) — the HTTP failures you meet while reading it.