Stream objective events
Watch an objective work in real time over server-sent events: every message, tool call, and approval request as it happens.
Read the stream
The SDKs hand you an iterator and skip the transport bookkeeping.ObjectiveEvent.data is a discriminated union. Each case narrows data to the matching event interface, so the corresponding payload is available without a cast.
What arrives on the wire
Each event frame carries three SSE fields:id is the event’s ULID, event is the event type (assistantMessage, toolCalled, …), and data is the JSON ObjectiveEvent.
Two control frames carry no id and are not objective events:
open, once, when the stream commits. Its arrival means the connection is live and the status code is settled.ping, every 15 seconds, to keep intermediaries from closing an idle connection.
open and ping before they reach your loop. If you read the raw stream, skip them yourself: they do not parse as an ObjectiveEvent.
Tool results are stripped
This is the one surprise worth internalizing. On the stream,toolResult and toolError events carry only the toolCallId. The content is cleared, because a tool that returns a megabyte of JSON would otherwise push it through every open connection.
result at the top level, or read the event back from List objective events.
Built-in tool calls arrive unnamed
toolCalled.tool is a discriminated union: its type field reads tool for a tool from a tool set, agent for a sub-agent, or cadenyaProvidedTool for a built-in such as get_memory or tool_search, and the payload sits under the matching key. Switch on type, never reach through tool.tool.
The built-in case carries an opaque cpt_... id and an empty name. arguments tells you what it did ({"memoryKey": "policies/us/refunds"}), and Get a tool call returns the same call with callable.cadenyaProvidedTool.name filled in.
Resume where you left off
Send the SSELast-Event-ID header with the last event ULID you processed. Cadenya replays the gap from durable storage, then switches you to the live feed, deduplicating the overlap. Omit the header and you get events from now, with no replay of anything the objective already emitted.
Last-Event-ID fails before the stream commits, so it comes back as a real HTTP status rather than a mid-stream error. Once open arrives the status is committed, and any later failure ends the connection with no status to read.
Every event type
Eighteen types.data is a discriminated union: type names the variant, and the payload sits under a key with the same name.
STATE_WAITING, and the stream stays open. Break on the last assistantMessage when you are driving a conversation rather than waiting on an output.Streaming or webhooks
Both carry the same events. They fail differently, and that is how you choose. Stream when a human is watching: a chat UI, a live transcript, a progress log. It is a live tail with no delivery guarantee beyond theLast-Event-ID resume path, and it needs a process holding a connection open.
Webhook when a machine is reacting: kicking off a job, writing to a database, paging someone. Deliveries retry, and each one records its status, attempt count, and response, so you can audit what your endpoint received.
Reach for both on the same objective when a person watches the work while a system records it.Authorizations
Bearer authentication header of the form Bearer <token>, where <token> is your auth token.
Path Parameters
"workspace_01HXKD2E5NQM3T9AYWCF133E3Q"
"obj_01HXKD2E5NQM3T9AYWCFQAZGFV"
Response
A stream of Server-Sent Events
Metadata for ephemeral operations and activities (e.g., objectives, executions, runs)
- Option 1
- Option 2
- Option 3
- Option 4
- Option 5
- Option 6
- Option 7
- Option 8
- Option 9
- Option 10
- Option 11
- Option 12
- Option 13
- Option 14
- Option 15
- Option 16
- Option 17
"objwin_01HXKD2E5NQM3T9AYWCFN7BSTR"
Elapsed time of the work this event records, when it is known at write time (e.g. assistant message generation, tool execution for result/error events). Unset means the event is instantaneous or the duration is not measurable. Serialized as a canonical duration string (e.g. "4.1s"). Always set together with started_at.
^-?(?:0|[1-9][0-9]{0,11})(?:\.[0-9]{1,9})?s$When the work this event records began. Set together with duration, so the work interval is [started_at, started_at + duration]. The event's created_at remains the time the event was persisted.