Implementation: DESIGN (server) · Exposure: PUBLIC PREVIEW (contract). The Agency API is not live yet. This page describes the published v0.3 contract so you can review and build against it; details may change before alpha.
Every material transition in a run is published as a typed event. Events are durable, ordered by a gap-free sequence number, and can be read as a live Server-Sent Events (SSE) stream or as pages of JSON. Work-runtime runs have no event stream today; this is one of the items the alpha gate (restart, resume, verify, replay, receipt-backed completion) depends on.
Stream or list events
One endpoint serves both modes. The Accept header selects which.
Parameters
types takes a comma-separated list, for example ?types=run.status_changed,attention.requested. debug.trace events are delivered only with view=debug (scope debug:read).
Responses
410 is returned when the cursor or Last-Event-ID is older than the retention window (ERR_CURSOR_EXPIRED).
Each message has three fields:
- The server sends a
retry: hint when the connection opens.
- The server sends a comment heartbeat (
: ping) at least every 15 seconds. Comment lines carry no event; ignore them.
- Messages are separated by a blank line.
- The stream closes after the terminal
run.status_changed event.
Example stream:
The retry value and the IDs are illustrative, and the hash is shortened.
Resume
Events are durable for the life of the run plus the retention window. A client that was offline, including across a server restart, can replay what it missed.
- Remember the
id of the last event you processed.
- Reconnect with the
Last-Event-ID header set to that id. You can use ?cursor= instead; if both are sent, Last-Event-ID takes precedence.
- Delivery restarts strictly after that event, in
seq order, with no gaps.
seq starts at 1 and increases by one per event within a run. If you ever see a gap, reconnect from your last processed id.
If the resume point is older than the retention window the server returns 410 with ERR_CURSOR_EXPIRED. Fetch the run with GET /v1/runs/{run_id} to get current state, then start a new stream.
Polling
Pass next_cursor as ?cursor= to get the next page.
Event envelope
Every event has the same base fields (RunEventBase) plus a data object whose shape depends on type. RunEvent is the union of all event types, discriminated on type.
Clients must ignore event types they do not recognize.
Event types
run.status_changed
RunStatusChangedEvent. The event with data.terminal: true is the last event of the stream. data.run_receipt_id is set on the terminal event.
run.progress
RunProgressEvent
run.message
RunMessageEvent. There are no Thread endpoints; messages arrive here. When delta is true, append text to the previous message.
attention.requested
AttentionRequestedEvent. data is an AttentionRequest.
attention.resolved
AttentionResolvedEvent. data is the AttentionRequest with status and resolution set.
InputAcceptedEvent. data is an InputAccepted.
artifact.created
ArtifactCreatedEvent. data is an Artifact.
receipt.appended
ReceiptAppendedEvent. receipt_type is a ReceiptType.
verification.completed
VerificationCompletedEvent. result is a VerificationResult: pending, pass, fail, inconclusive or error.
budget.threshold
BudgetThresholdEvent. Emitted at fractions 0.5, 0.8 and 1.0 of a budget dimension.
debug.trace
DebugTraceEvent. Only delivered with view=debug. data is an open object and may change without a major version.
Client checklist
- Track the last
id and resume with Last-Event-ID.
- Process events in
seq order and tolerate redelivery by deduplicating on id.
- Ignore comment lines and unknown event types.
- Treat the stream as finished only after a
run.status_changed event with data.terminal: true.