SDK wire protocol

An Oda runtime can serve an out-of-process client over a newline-delimited JSON-RPC connection instead of an interactive chat. This page describes the messages on that connection, so you can read a trace, debug an integration or build a client of your own.

If you are new to the family, start with the Oda SDK overview.

How the connection works

Your program owns the connection. The runtime reads requests on its standard input and writes messages back on its standard output.

Note

Standard output carries protocol frames only. Keep anything else off that stream.

The three requests

initialize

Sets up the connection and resolves the model route.

session/prompt

Queues one user message and returns a receipt right away.

json
{ "type": "image", "mimeType": "image/png", "data": "..." }

shutdown

Flushes what the runtime has written, winds its work down and exits. The reply carries an empty result.

Notifications

Notifications travel from the runtime to your client.

How a turn runs from start to finish

  1. Send initialize with the working folder and the model route you want. The result confirms the route; a route that cannot be resolved fails here.
  2. Send session/prompt and keep the messageId from the receipt.
  3. Read the notifications: session.event streams the session's events, session.status reports work as running, and sub-agent runs announce themselves with subagent.started.
  4. Wait for session.status to report idle: the turn has settled. The last committed assistant message of the session is the run's final text, and it is not assigned to one prompt.
  5. Send shutdown to end the connection.

The client tells the run's own session events apart from its wider tree by following the lineage in subagent.started. A turn end carries a reason envelope; when a turn is aborted, the reason names the kind (user, parent, disposed, legacy or hook), and a hook abort carries its reason string.

Error codes

Limits

For the client side of the SDK, see the TypeScript client guide. To serve clients from a runtime, see Run an Oda runtime server.

Questions, answered

Can I cancel a turn over the protocol?

No. There is no cancel method and no per-session close. A turn runs until the agent reports idle, and shutdown ends the connection itself.

Can I tie an answer to the prompt I sent?

Not at the connection level. session/prompt returns only the enqueue receipt, and its messageId does not identify a later assistant message, a turn ending or the prompt's result. Read the stream until the agent is idle.

Do I need to call initialize before prompting?

Yes. session/prompt is refused until one initialize has completed, and the handshake is where the model route is resolved. A route problem fails the handshake instead of the prompt.

Does the protocol negotiate versions?

No. The handshake result reports the server's identity, but there is no version negotiation; a client must be built for the same version as the runtime it drives.

What happens to a request I abort?

Its pending state is dropped, so nothing is retained for a response that may never come. The rejection names the reason: JSON-RPC request aborted: <reason>.

Which images can a prompt include?

Inline images with image/png, image/jpeg, image/webp or image/gif. Each inline image becomes a durable reference in the session before the prompt is enqueued.

What happens to a line the runtime cannot parse?

It is ignored. A line that does not parse as a JSON-RPC message gets no answer; check that each message is a single line of valid JSON.

How are responses matched to requests?

By id. Every request carries an id (req_ followed by a UUID) and the response returns the same id. Notifications carry no id.

What happens to pending requests when the connection closes?

They fail. A closed input reports JSON-RPC input closed, and a closed transport reports JSON-RPC transport closed; nothing is retried for you.

Why did initialize fail with a missing adapter?

The provider you named has no registered adapter, so the route could not be resolved. Check the provider and model you send; a route that cannot be resolved fails the handshake and cannot fall back to a default.