OpenWOP openwop.dev
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.

Source: api/v2/asyncapi.yaml in the repo. Prefer the prose specs (events.md, webhooks.md) for normative claims; the AsyncAPI document is a structured restatement.

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