The Oda 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, so a deployment must not compose a stdout logger.

The three requests

initialize

Sets up the connection and resolves the model route.

session/prompt

Queues one user message and returns a receipt straight 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.

What the runtime sends back

Notifications travel from the runtime to your client; they carry no id and expect no answer.

How a turn runs from start to finish

  1. Send initialize with the working folder and the model route you want. A route the runtime cannot serve fails here, not at the prompt.
  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 a sub-agent run announces itself with subagent.started.
  4. Wait for session.status to report idle: the turn has settled. The last committed assistant message of the session is its final text, and it is not assigned to one prompt.
  5. Send shutdown to end the connection.

Follow the lineage in subagent.started to tell a run's own session events apart from the wider tree. A turn ending carries a reason; 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

What you receiveWhat it means
method not found: <m>, code -32601No handler is registered for that method.
The handler's message, code -32603The handler failed, and its error message is returned.
JSON-RPC input closedThe input side of the connection closed, so no further requests are accepted.
JSON-RPC transport closedThe transport closed; pending requests fail and nothing is retried for you.
JSON-RPC request aborted: <reason>The request was aborted and its pending state dropped: no state is retained for a response that may never come.
JSON-RPC errorThe fallback text for a failure that carries no other message.

Limits

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

Questions, answered

Can I pretty-print the JSON I send?

No. Every message must be one line of valid JSON terminated by \n, and a line that does not parse as a message gets no answer. An object spread over several lines never arrives as a message.

Do I need to call initialize before sending a prompt?

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.

What values does maxTokens accept?

A positive safe integer. Zero, negative values, fractions and oversized values are refused with initialize maxTokens must be a positive safe integer. The field is optional; send it only when you want a cap on the work the handshake starts.

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.

Which images can I send in a prompt?

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.

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.

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.

What happens to a request I abort?

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

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.

Can I cancel a prompt without closing the connection?

No. The protocol has no cancel method and no way to close a single session. A turn runs until the agent reports idle, and shutdown ends the connection itself.

Does the protocol negotiate versions?

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