Event API
AsyncAPI — streamed event surface
OpenWOP's event surface is documented as an AsyncAPI 3 document. Hosts
stream run.* events over SSE
and fan out to subscribers over signed
webhooks. The wire shape is normative; the AsyncAPI source below is
the same contract in machine-readable form.
asyncapi: 3.1.0
info:
title: OpenWOP v2 event streams
version: 2.45.14
description: 'OpenWOP v2 event streams over SSE. The run-events channel''s address is the OpenAPI path key, and the server
pathname is empty (bare origin). `streamMode` is a stream mode from the closed set, or a comma-separated combination of
them; `values` never combines. `hostEvents` has a default address, `/host/events`; a host MAY declare another under `heartbeat.deliveryChannel`.
Generated from `api/asyncapi.yaml` by `scripts/derive-v2-api.py`; do not edit.'
defaultContentType: text/event-stream
servers:
production:
host: '{host}'
pathname: ''
protocol: https
variables:
host:
default: api.example.com
channels:
runEvents:
address: /runs/{runId}/events
title: Run event stream (SSE)
parameters:
runId:
description: The run to subscribe to (`ids.schema.json#/$defs/runId`).
messages:
runEvent:
$ref: '#/components/messages/RunEvent'
hostEvents:
address: /host/events
title: Host-scoped events (`heartbeat.*`)
messages:
heartbeatEvaluated:
$ref: '#/components/messages/HeartbeatEvaluated'
heartbeatStateChanged:
$ref: '#/components/messages/HeartbeatStateChanged'
operations:
subscribeRunEvents:
action: receive
channel:
$ref: '#/channels/runEvents'
title: Subscribe to a run's events
description: A long-lived SSE subscription that closes after a terminal run event. `Last-Event-ID` resumes from the sequence
after the supplied one. A mixed mode is the union of its filters, and each event carries its own `event:` label (`stream-modes.md`).
bindings:
http:
method: GET
query:
type: object
additionalProperties: false
properties:
streamMode:
type: string
pattern: ^(values|(updates|messages|debug)(,(updates|messages|debug))*)$
default: updates
subscribeHostEvents:
action: receive
channel:
$ref: '#/channels/hostEvents'
title: Subscribe to host events
bindings:
http:
method: GET
components:
securitySchemes:
ApiKeyAuth:
type: http
scheme: bearer
bearerFormat: API key
description: Bearer API key; the same scheme as `ApiKeyAuth` in `api/v2/openapi.yaml`. Subscribing requires the `runs:read`
scope.
parameters:
RunId:
description: The run to subscribe to. Format opaque; clients MUST treat as a string.
messages:
EvalStarted:
name: eval.started
title: Eval run started
summary: 'An eval run began. Content-free: suite provenance and counts only. Gated on `capabilities.agents.evalSuite.supported`.'
contentType: application/json
payload:
$ref: '#/components/schemas/EvalStartedPayload'
EvalScored:
name: eval.scored
title: Eval task scored
summary: 'One eval task was scored, emitted per task after its terminal `agent.decided`. Content-free: `taskId`, score
and scalars only, never task output (`eval-summary-no-content-leak`).'
contentType: application/json
payload:
$ref: '#/components/schemas/EvalScoredPayload'
EvalCompleted:
name: eval.completed
title: Eval run completed
summary: An eval run finished. Content-free aggregate scalars; the full scorecard is the run output, read with `GET
/runs/{runId}/eval-summary`.
contentType: application/json
payload:
$ref: '#/components/schemas/EvalCompletedPayload'
DeploymentPromoted:
name: deployment.promoted
title: Deployment promoted
summary: A version was promoted into a new lifecycle state, gated by the `deploy:*` scope, the approvalGate, and `requiredEval`.
Content-free (`deployment-event-no-content-leak`); principal-stamped and audit-logged.
contentType: application/json
payload:
$ref: '#/components/schemas/DeploymentPromotedPayload'
DeploymentRolledBack:
name: deployment.rolled-back
title: Deployment rolled back
summary: An active version was rolled back and a prior version restored. Content-free; recorded-fact; audit-logged.
contentType: application/json
payload:
$ref: '#/components/schemas/DeploymentRolledBackPayload'
DeploymentCanaryAdjusted:
name: deployment.canary-adjusted
title: Deployment canary adjusted
summary: An active version's canary traffic share changed. Content-free; recorded-fact; audit-logged.
contentType: application/json
payload:
$ref: '#/components/schemas/DeploymentCanaryAdjustedPayload'
DeploymentStateChanged:
name: deployment.state-changed
title: Deployment state changed
summary: A non-promotion lifecycle transition (pause, resume, deprecate). Content-free; recorded-fact; audit-logged.
contentType: application/json
payload:
$ref: '#/components/schemas/DeploymentStateChangedPayload'
ProposalCreated:
name: proposal.created
title: Proposal created
summary: 'The host synthesized a reviewable-learning draft. Content-free: ids, kind and refs only, never the artifact
body or rationale (`proposal-inert-until-applied`). Emitted only when `capabilities.agents.proposals` is advertised.'
contentType: application/json
payload:
$ref: '#/components/schemas/ProposalCreatedPayload'
ProposalActivated:
name: proposal.activated
title: Proposal activated
summary: A proposal was applied, through its approval gate and authorization check. Content-free; the installed artifact
byte-matches the last-persisted draft (`proposal-no-resynthesis`).
contentType: application/json
payload:
$ref: '#/components/schemas/ProposalActivatedPayload'
GoalEvaluated:
name: goal.evaluated
title: Goal evaluated
summary: 'A judge check ran against a standing goal. Content-free: no objective text. The verdict is recorded, not recomputed
on replay.'
contentType: application/json
payload:
$ref: '#/components/schemas/GoalEvaluatedPayload'
GoalClosed:
name: goal.closed
title: Goal closed
summary: A standing goal stopped continuation (satisfied, escalated, abandoned, or bound-exceeded). Content-free.
contentType: application/json
payload:
$ref: '#/components/schemas/GoalClosedPayload'
ImportApplied:
name: import.applied
title: Import applied
summary: 'An estate import was applied. Content-free: counts and refs only, never item payloads or secret values (`export-bundle-no-credential-material`).'
contentType: application/json
payload:
$ref: '#/components/schemas/ImportAppliedPayload'
RunStarted:
name: run.started
title: Run started
summary: A new run was registered and execution began.
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
RunCompleted:
name: run.completed
title: Run completed (terminal)
summary: Run reached terminal success state. SSE connection closes after this event.
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
RunFailed:
name: run.failed
title: Run failed (terminal)
summary: Run reached terminal failure state. SSE connection closes after this event.
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
RunCancelled:
name: run.cancelled
title: Run cancelled (terminal)
summary: Run was cancelled by user or admin. SSE connection closes after this event.
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
CompensationRequested:
name: compensation.requested
title: Compensation plan persisted
summary: 'The unwind plan is durable. This event is required before the first inverse action executes: a host that starts
unwinding first cannot resume after a crash, and a crash is when resumption matters.'
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
CompensationStarted:
name: compensation.started
title: First inverse action started
summary: Compensation began executing. MUST follow `compensation.requested`.
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
CompensationCompleted:
name: compensation.completed
title: Compensation completed
summary: Every inverse action in the plan succeeded.
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
CompensationFailed:
name: compensation.failed
title: Compensation failed
summary: 'An inverse action exhausted its retries. Compensation is a second effect, not an undo: it can fail, and the
run''s `compensationStatus` records that rather than the run silently reporting success.'
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
CompensationPaused:
name: compensation.paused
title: Compensation paused for authorized intervention
summary: Cancelling the parent MUST NOT silently abandon an active compensation. It continues, pauses here, or records
manual intervention required.
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
CompensationManualInterventionRequired:
name: compensation.manual-intervention-required
title: Compensation needs an operator
summary: The host cannot proceed unaided. Recorded explicitly, because an unwind that stops without saying so is indistinguishable
from one that finished.
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
RunPaused:
name: run.paused
title: Run paused
summary: Run paused (e.g., capability limit reached, manual pause).
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
RunResumed:
name: run.resumed
title: Run resumed
summary: Run resumed from pause/suspend.
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
RunAnnotated:
name: run.annotated
title: Run annotated
summary: 'A non-blocking quality annotation was recorded for the run. Live notification only, not a replayable run-event-log
entry: its payload is an `Annotation`, not a `RunEventDoc`, so it is excluded from fork and replay.'
contentType: application/json
payload:
$ref: '#/components/schemas/Annotation'
WorkspaceUpdated:
name: workspace.updated
title: Workspace file updated
summary: 'A workspace file was created, replaced, or deleted through the `host.workspace` store. Content-free: it carries
the file path and resulting version only; the body is served by the read side, SR-1-redacted. A replayable run-event-log
entry, re-read from the log on replay and never regenerated. Gated on `capabilities.workspace.supported`.'
contentType: application/json
payload:
$ref: '#/components/schemas/WorkspaceUpdatedPayload'
HeartbeatEvaluated:
name: heartbeat.evaluated
title: Heartbeat evaluated
summary: A heartbeat predicate was evaluated this tick (status and a changed flag). Heartbeat-scoped observability.
contentType: application/json
payload:
$ref: '#/components/schemas/HeartbeatEvaluated'
HeartbeatStateChanged:
name: heartbeat.stateChanged
title: Heartbeat state changed
summary: A heartbeat predicate's state transitioned. Emitted only on a change, never on an unchanged tick. Heartbeat-scoped.
contentType: application/json
payload:
$ref: '#/components/schemas/HeartbeatStateChanged'
NodeCompleted:
name: node.completed
title: Node completed successfully
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
NodeFailed:
name: node.failed
title: Node failed
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
NodeSkipped:
name: node.skipped
title: Node skipped due to edge condition
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
NodeSuspended:
name: node.suspended
title: Node suspended (HITL or external-event wait)
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
NodeDispatched:
name: node.dispatched
title: '`core.dispatch` spawned a child workflow'
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
DispatchFanOut:
name: dispatch.fanned-out
title: '`core.dispatch` began a parallel fan-out wave'
summary: Emitted when a `fanOutPolicy=parallel` wave begins; the payload (`$defs.dispatchFanOut`) carries `childCount`,
`maxConcurrency` and `joinMode`. Parallel path only.
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
DispatchJoin:
name: dispatch.joined
title: '`core.dispatch` parallel join satisfied'
summary: Emitted when a `fanOutPolicy=parallel` join is satisfied or failed; the payload (`$defs.dispatchJoin`) carries
`joinOutcome` and the replay-deterministic `mergeOrder`.
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
ApprovalRequested:
name: approval.requested
title: Approval requested
summary: Engine emitted an approval interrupt awaiting user resolution.
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
ApprovalReceived:
name: approval.received
title: Approval received
summary: User resolved an approval interrupt (accept/reject/refine/edit).
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
ClarificationRequested:
name: clarification.requested
title: Clarification requested
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
ClarificationResolved:
name: clarification.resolved
title: Clarification resolved
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
InterruptRequested:
name: interrupt.requested
title: Interrupt requested (canonical HITL primitive)
summary: 'The discriminated-union form of the full `interrupt.md` kind union: approval, clarification, external-event,
custom, conversation.start, conversation.exchange, conversation.close, low-confidence. Servers emitting `interrupt.requested`
SHOULD also emit the kind-specific event (`approval.requested` etc.) until consumers migrate.'
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
InterruptResolved:
name: interrupt.resolved
title: Interrupt resolved
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
ArtifactCreated:
name: artifact.created
title: Artifact produced by a node
summary: A typed artifact (PRD, plan, theme, etc) was created and registered.
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
StateSnapshot:
name: state.snapshot
title: Full projected run state
summary: Synthesized event the server emits in `values` mode after each `updates`-tier transition. It is not a member
of the canonical `RunEventType` enum; it is a per-mode synthetic.
contentType: application/json
payload:
$ref: '#/components/schemas/StateSnapshotPayload'
AiMessageChunk:
name: ai.message.chunk
title: AI token chunk
summary: Per-token streaming chunk from a `core.ai.*` node.
contentType: application/json
payload:
$ref: '#/components/schemas/AiMessageChunkPayload'
AnyRunEvent:
name: any
title: Any RunEventDoc
summary: 'Type-erased handler for `debug` mode. Discriminate on the `type` field using the `RunEventType` enum in the
run-event JSON Schema, the authoritative and exhaustive event list; the named messages above are a curated `updates`-tier
subset. Includes events filtered out of `updates`: `log.appended`, `variable.changed`, `version.pinned`, `lease.*`,
`node.retried`, `replay.diverged`, `connector.authorized`, `connector.auth_expired`, `authorization.decided`, `approval.granted`
/ `approval.rejected` / `approval.overridden`, etc.'
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
VoiceSpeechStart:
name: voice.speech-start
title: Inbound user speech onset detected
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
VoiceTranscript:
name: voice.transcript
title: Interim/final transcript part (untrusted ingress; carries contentTrust)
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
VoiceEndpointCandidate:
name: voice.endpoint-candidate
title: Likely end-of-turn boundary (semantic turn detection)
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
VoiceTurnCommit:
name: voice.turn-commit
title: User yielded the floor (callTranscriber Promise resolves here)
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
VoiceSynthesisChunk:
name: voice.synthesis-chunk
title: Clause-boundary streaming-synthesis chunk (metadata only)
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
VoiceBargeIn:
name: voice.barge-in
title: User speech overlapped active assistant playback
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
VoiceCancelled:
name: voice.cancelled
title: Downstream LLM/TTS work cancelled (barge-in or explicit)
contentType: application/json
payload:
$ref: '#/components/schemas/RunEventDoc'
RunEvent:
name: runEvent
title: Run event
contentType: text/event-stream
payload:
$ref: ../../schemas/v2/run-event.schema.json
description: 'One SSE frame per run event: `event:` is the v2 type and `id:` is the sequence.'
schemas:
RunEventDoc:
$ref: ../../schemas/v2/run-event.schema.json
EvalStartedPayload:
$ref: ../../schemas/v2/run-event-payloads.schema.json#/$defs/evalStarted
EvalScoredPayload:
$ref: ../../schemas/v2/run-event-payloads.schema.json#/$defs/evalScored
EvalCompletedPayload:
$ref: ../../schemas/v2/run-event-payloads.schema.json#/$defs/evalCompleted
DeploymentPromotedPayload:
$ref: ../../schemas/v2/run-event-payloads.schema.json#/$defs/deploymentPromoted
DeploymentRolledBackPayload:
$ref: ../../schemas/v2/run-event-payloads.schema.json#/$defs/deploymentRolledBack
DeploymentCanaryAdjustedPayload:
$ref: ../../schemas/v2/run-event-payloads.schema.json#/$defs/deploymentCanaryAdjusted
DeploymentStateChangedPayload:
$ref: ../../schemas/v2/run-event-payloads.schema.json#/$defs/deploymentStateChanged
ProposalCreatedPayload:
$ref: ../../schemas/v2/run-event-payloads.schema.json#/$defs/proposalCreated
ProposalActivatedPayload:
$ref: ../../schemas/v2/run-event-payloads.schema.json#/$defs/proposalActivated
GoalEvaluatedPayload:
$ref: ../../schemas/v2/run-event-payloads.schema.json#/$defs/goalEvaluated
GoalClosedPayload:
$ref: ../../schemas/v2/run-event-payloads.schema.json#/$defs/goalClosed
ImportAppliedPayload:
$ref: ../../schemas/v2/run-event-payloads.schema.json#/$defs/importApplied
Annotation:
$ref: ../../schemas/v2/annotation.schema.json
WorkspaceUpdatedPayload:
$ref: ../../schemas/v2/run-event-payloads.schema.json#/$defs/workspaceUpdated
HeartbeatEvaluated:
$ref: ../../schemas/v2/heartbeat-evaluated.schema.json
HeartbeatStateChanged:
$ref: ../../schemas/v2/heartbeat-state-changed.schema.json
StateSnapshotPayload:
$ref: ../../schemas/v2/run-snapshot.schema.json
AiMessageChunkPayload:
$ref: ../../schemas/v2/run-event-payloads.schema.json#/$defs/outputChunk