Skip to content

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.

Every event returned by the endpoint has the same eight-field envelope:

{
"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 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: <sequence> and data: <SessionEvent JSON>. 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.

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.

Six types: the start, four terminal outcomes, and a progress fact that may repeat. 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" }

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

{
"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." }
]
}

A bounded tool-lifecycle fact, appended while the run is still working. It may appear many times.

{
"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 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
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.

The agent finished the turn on its own terms.

{ "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.

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.

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.

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. Split the work, or continue in a follow-up turn on a fresh lease.
{ "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.

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

Three types cover a suspended question or a live approval.

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.

{
"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.

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

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

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, 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
{
"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 reviewed delivery and pull-request merging. None of them carries a subject.runId.

The exact reviewed revision was delivered. This event is only emitted on success, so its status is the literal succeeded.

{
"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:

{
"publicationId": "…",
"status": "succeeded",
"kind": "github.branch",
"delivery": "direct_branch",
"branch": "main",
"commitSha": "6f1c1d8047b2c3d4e5f60718293a4b5c6d7e8f90"
}

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": "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.

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, which arrives during the run and is a recovery signal rather than a record.

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

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.

A call the run also reported as wamp.run.progress 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 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.

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 session handed work to another agent.

{
"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 and wamp.run.progress 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.

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.

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.

The session’s agent started a conversation of its own.

{
"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.

A child session finished, and this Session’s agent has been told.

{
"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.

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:

{
"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=<cursor>. 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.

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. 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 for what its states mean.

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.

  • Follow a run live — the stream, cursors, resuming after a disconnect, and following many Sessions at once.
  • Events operations — the generated endpoint reference for reading the log.
  • the API reference — the SessionEvent and EventPage object definitions.
  • Errors — the HTTP failures you meet while reading it.