Cloud and BYOC for Orca Agent Engine are in Private Preview — request an invite
Docs

Events and streaming

Send events to sessions in Orca Agent Engine and stream their responses as Server-Sent Events.

Sessions in Orca Agent Engine communicate through events. Your application sends events to drive the agent (user messages, tool results, steers, interrupts) and receives events from the session (agent messages, tool calls, status updates). The registry exposes both a list endpoint and a Server-Sent Events (SSE) stream for receiving events as they happen.

$SESSION_ID and sessionId below are the id returned when you create a session.

Get your registry endpoint

Registry endpoint

Examples on this page target your registry endpoint - the deployment host root, with no path suffix. For CLI, set ORCA_REGISTRY_URL and exactly one of ORCA_ACCESS_TOKEN (Bearer) or ORCA_API_KEY (x-api-key). For TypeScript SDK, set ORCA_BASE_URL / ORCA_API_KEY (Bearer). To find the endpoint, see Connect to the registry.

Event types

Every event carries a server-assigned id, a type, and a type-specific body.

User events are the events your application sends into the session.

TypeWhen to send
user.messageSend user input - a question, an instruction, a follow-up. The agent's next turn begins with this content.
user.tool_confirmationApprove or refuse a tool call that an always_ask permission policy paused.
user.custom_tool_resultReturn the result of a custom tool call the agent made.
user.tool_resultReturn the result of a built-in tool your application executed on the agent's behalf. Requires the claude_agent_sdk harness in separate mode on a self_hosted environment; anything else returns 400.
user.define_outcomeAttach a goal and a rubric to the session. The rubric is re-evaluated after every turn, against the conversation so far, for the rest of the session - it does not stop once satisfied. max_iterations (default 3, maximum 20) only changes the verdict label from needs_revision to max_iterations_reached; it does not end the evaluation, which keeps costing a model call per turn.
user.interruptStop the agent's current turn. See Interrupt the agent. Honored on the default harness only - claude_code accepts the event and then rejects it with a session.error without stopping the turn. That error arrives as unknown_error unless you send the orca-beta header, which surfaces the underlying unapplied_event.

Default list and stream responses expose only the Claude event taxonomy and remove Orca transcript-envelope fields such as seq and subpath. With orca-beta, event lists return every indexed public event type with the full Orca envelope; streams retain the full envelope for public events but still omit internal events. The header also enables legacy subpath and session_thread_id thread controls. Treat it as an Orca-specific integration surface, not as a portable Claude Managed Agents API feature.

Send an event

Sessions are addressed by session ID alone, so events POST to /v1/sessions/{sessionId}/events. The body wraps events in an events array, so several can go in one call.

Message content is an array of content blocks - text, image, or document - not a bare string. search_result is a tool-result block: valid in user.tool_result and user.custom_tool_result, rejected in user.message.

ork agent sessions events send message \
  --session "$SESSION_ID" \
  --text "Summarize the latest support ticket."

A successful send returns 200 OK with the appended events under data:

{
  "data": [
    { "id": "evt_01H8...", "type": "user.message", "processed_at": null }
  ]
}

That response is the only place a caller sees the server-assigned event IDs without re-reading the list, which matters when you need to reference an event later - a tool confirmation names the event it answers. The event becomes visible through the list and stream endpoints once the runtime acknowledges it.

Stream events

Stream events as they are produced from /v1/sessions/{sessionId}/events/stream. The response uses Content-Type: text/event-stream, and each frame carries three fields:

id: 42
event: agent.message
data: {"type":"agent.message", ...}

id is the event's sequence number, event is its type - one of the names in the tables above, so an EventSource client can attach per-type listeners rather than parsing every payload - and data is the event body.

The sequence number is in id and nowhere else: the body a default-dialect client receives has it stripped, so read it from the frame rather than looking for a field.

Token-level deltas are opt-in. Add ?event_deltas=agent.message (or agent.thinking) to the stream request and the session additionally emits event_start and event_delta frames for that type; without it, no delta frame is sent at all. The whole agent.message still arrives once per turn either way, so deltas supplement it rather than replace it.

To resume after a dropped connection, send a Last-Event-ID header or a from_cursor query parameter. The cursor is inclusive: the stream restarts at that sequence number, so passing the last id you processed re-delivers it. Pass last_id + 1 to continue without a duplicate, or keep the cursor you were given and make your handler idempotent.

ork agent sessions events stream --session "$SESSION_ID" --timeout 30s

The stream stays open as long as the session is producing events. The registry sets Cache-Control: no-cache and X-Accel-Buffering: no so intermediate proxies do not buffer the response.

To read events that have already been processed rather than streaming live, call GET /v1/sessions/{sessionId}/events with limit, page, and order=asc|desc.

Handle custom tool calls

When the agent calls a custom tool, the runtime hands the call to your application and waits:

  1. Your app opens the SSE stream.
  2. The session emits agent.custom_tool_use carrying the call's id, name, and input.
  3. Your app runs the tool and sends user.custom_tool_result with that id as custom_tool_use_id.
  4. The agent continues its turn using the result.
const stream = await orca.sessions.events.stream(sessionId);

for await (const event of stream) {
  if (
    event.type !== 'agent.custom_tool_use' ||
    !('name' in event) ||
    event['name'] !== 'lookup_ticket' ||
    !('input' in event)
  ) continue;

  const input = event['input'];
  if (
    !input ||
    typeof input !== 'object' ||
    Array.isArray(input) ||
    !('ticket_id' in input) ||
    typeof input['ticket_id'] !== 'string'
  ) continue;

  const result = await lookupTicket(input['ticket_id']);

  await orca.sessions.events.send(sessionId, {
    events: [
      {
        type: 'user.custom_tool_result',
        custom_tool_use_id: event.id,
        content: [{ type: 'text', text: JSON.stringify(result) }],
      },
    ],
  });
}

Set is_error: true on the result when the tool failed.

On the default claude_agent_sdk harness in separate mode, tools that route through MCP servers are called by the runtime directly - your application sees agent.mcp_tool_use and the resulting agent.mcp_tool_result but does not respond to them, unless a permission policy paused the call for confirmation.

Redirect the agent

To change direction, send another user.message. The session applies it at the next turn boundary, so the agent picks it up without losing the work it has already done.

ork agent sessions events send message \
  --session "$SESSION_ID" \
  --text "Use the most recent ticket only."

Interrupt the agent

An interrupt stops the agent's current turn immediately. Use it to abort a long-running response.

cURL
curl -fsS "$ORCA_REGISTRY_URL/v1/sessions/$SESSION_ID/events" \
  -H "Authorization: Bearer $ORCA_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "events": [ { "type": "user.interrupt" } ] }'

The session emits session.status_idle once the turn unwinds. Send a new user.message to start the next turn.

This works on the default harness only. The claude_code harness accepts user.interrupt and then rejects it: the session emits a session.error, is marked idle, and the turn already in flight runs to completion. There is no way to stop a turn early there - archive the session instead.

Permissions

Events belong to their session. Treat every Workspace API key as full access to its Workspace's resources, and separate access with Workspaces. See Control registry access.

What's next

On this page