Skip to main content
OAO exposes one ordered Server-Sent Events feed per project:
Each frame’s SSE event is the product-event kind, such as run.state_changed or tool_call.requested. Its SSE id is an opaque cursor for the committed project position; send it back unchanged rather than constructing one. Its JSON body has this shape:
To receive the same events without holding a connection open, configure an event webhook. OAO then pushes signed, ordered batches to your endpoint and keeps its own durable cursor.

Connection lifecycle

  • Opening: the server writes a : connected comment line as soon as the stream opens, so proxies commit the response before the first event exists.
  • Keepalive: after 10 seconds without other output, the server writes a : keepalive comment line. Standards-compliant SSE parsers ignore lines that start with :. A custom parser must skip them and never treat them as events.
  • Normal close: a healthy stream closes after about 25 seconds. Reconnect with Last-Event-ID; the close is not data loss.
  • Backlog: after a reconnect, the server sends missed events in pages of 200 without pausing between full pages.
  • One page: ?once=true returns a single page of up to 200 events with no comment lines and closes immediately.
Event streams are sent with Cache-Control: no-store, no-transform and X-Accel-Buffering: no. If OAO runs behind your own reverse proxy or load balancer, disable response buffering and compression for text/event-stream, and allow at least 15 seconds between reads.

Resume correctly

  1. Persist the last event ID only after your application has applied that event successfully.
  2. Reconnect with Last-Event-ID after a network error or process restart.
  3. Treat duplicate events as normal and deduplicate by event id or by (aggregateType, aggregateId, aggregateSequence).
  4. Refresh the affected resource with a normal API read when your application does not understand a new event kind.
The cursor is backed by committed PostgreSQL state. LISTEN/NOTIFY is only a latency optimization inside OAO, so missed notifications do not create gaps. Each API process shares one dedicated LISTEN connection across all of its open streams and wakes only the streams of the project that committed the event. If that connection drops, streams keep polling committed events every second while the listener reconnects.

Model usage events

model.invocation_completed and model.invocation_failed payloads include inputTokens, outputTokens, cacheReadTokens, cacheWriteTokens, and costMicrounits, plus OAO’s normalized finishReason. Cache reads represent prompt tokens reused from the provider cache; cache writes represent prompt tokens newly stored there. A provider that does not report cache usage contributes zero for those fields. When a model call fails because of a provider finish condition, the failed event also includes the provider’s exact bounded providerFinishReason when it is available, plus a safe errorExplanation describing why OAO classified the response as failed. For example, providerFinishReason: "content_filter" explains that the provider’s content filter stopped the response and OAO could not treat the partial output as a completed reply. These payloads never include prompt or response content, raw provider error messages, or credentials.

Harness Operation events

The runtime emits harness.operation_started, harness.operation_completed, harness.operation_failed, and the applicable cancellation or timeout event for focused scratch-loop work. It also emits one harness.operation_step event for each completed inner model or tool step. Safe payloads can include operationKey, toolCallId, taskCharacters, timeoutMs, durationMs, and resultValidated. Step payloads use harnessToolCallId, stepKind, stepId, stepIndex, status, duration, bounded token counts, and a safe summary. Model-step summaries are derived only from bounded tool names—for example, Requested activate_skill. or Returned the structured result for validation. Tool steps can include a safe tool label or path summary. These events are correlated to the aggregate run and tool call. They never contain the detailed task, scratch conversation, structured result, Skill instructions/resources, tool payloads, authorization material, or raw shared documents. The authorized console groups the start and terminal lifecycle events by toolCallId into one Harness transcript row. Opening that row shows a details modal with safe, sequential inner-turn metadata. New events carry the owning Harness tool-call ID, so concurrent scratch loops are attributed exactly instead of by timing. Lifecycle windows that overlap are shown as one parallel group with a shared colored rail and count in the console. Legacy activity that does not contain explicit correlation is folded only when its timing identifies one unambiguous invocation; ambiguous legacy activity remains in the parent timeline and the affected Harness modals are marked as partial.

Filter in the client

The server feed is project-wide. A session UI normally stores its cursor, then filters frames whose aggregateId equals a known run, thread, tool call, approval, or sandbox ID. On relevant events, re-fetch GET /sessions/{sessionId} to obtain the current transcript and debug view.
Event bodies contain public state and identifiers, not raw model reasoning, credentials, authorization headers, unredacted tool payloads, or attached file bytes. A user message.created event may include only the safe fileCount; re-fetch the session for filename, content type, size, and SHA-256 metadata.

Delegation events

Payloads contain only delegate/version/session/run identifiers, ordinal, state, and bounded status metadata. They never contain the delegated prompt, child response, workspace contents, credentials, or authorization headers.

Skill events

Publication events may include Skill/version identifiers, version number, and content hash. Runtime events contain only safe identifiers, the requested resource path, outcome, and duration. They never contain instructions, resource contents, decoded bytes, model text, credentials, or authorization headers. Audit records follow the same boundary.

MCP events

MCP configuration emits mcp.server_created, mcp.server_version_published, mcp.discovery_completed, mcp.discovery_failed, mcp.toolset_published, mcp.credential_created, mcp.credential_rotated, and mcp.credential_revoked. Runtime calls emit mcp.call_started, mcp.call_completed, mcp.call_failed, or mcp.call_cancelled. Payloads contain only tenant resource IDs, remote tool name, safe outcome/error code, response byte count, and timing/state metadata. They exclude credentials, headers, tool arguments, tool results, model content, and sandbox state.

TypeScript SDK

The workspace SDK reconnects automatically unless disabled:
Abort the supplied signal during application shutdown. For a browser using the WorkOS cookie, configure the client with credentials: "include"; for a server-side integration, provide apiKey.