Remote calls, streams and events
DataMind Oda's screens and its extension packages call the host that serves them through one layer: short calls that carry a request and an answer, long-lived streams that carry items in both directions, and events the host forwards to the interface. This page is the reference for that layer, so you can read a trace, debug a connection or build a client of your own. For the authoring side, see Remote calls for extension authors and Remote APIs.
Calls
- A call is addressed. It names an endpoint, a namespace and a method, and carries its arguments as one plain object.
- Arguments are checked before the method runs. A call whose shape does not match the endpoint fails instead of reaching the method.
- One request, one answer. A call travels as one request message and is answered by one response message.
- Answers are result envelopes. An answer reports success as
{ ok: true, value }, or failure as { ok: false, error }.
- An aborted call settles as cancelled. It reports
gateway/cancelled with the message Remote invocation "<endpoint>" was aborted, not a failure from the method.
Streams
Streams are logical: they share the one WebSocket connection the client keeps open, each under its own stream id.
On a stream, each side sends a small set of frames:
| From | Frame | What it does |
|---|
| Client | open | Opens a logical stream, naming the method it calls and its arguments. |
| Client | item | Sends one uplink item; its value may be omitted. |
| Client | end | Half-closes the uplink: no further items follow. |
| Client | cancel | The consumer has left; the stream is torn down. |
| Host | item | Delivers one downlink value. |
| Host | error | Ends the stream with a failure: a code, a message and details. |
| Host | end | Ends the stream normally. |
- Items wait for the reader. An item that arrives before the method reads its uplink waits in the stream's inbox instead of being lost.
- Late items fail only their stream. An uplink item sent after
end fails that stream with gateway/protocol; the connection stays open for its other streams.
- Stray frames are dropped. A frame that names a stream the connection does not know, or has already finished, is ignored. The one violation that closes the whole connection is a second
open for a stream id that is already open.
- One consumer per stream. A logical stream serves exactly one consumer, and opening a second one fails. When several parts of the interface need the same data, the client shares one stream between them instead of opening another.
- A mismatch fails early. A method that is a stream must be opened as a stream, and a plain call must not be opened as one; the mismatch fails before the method runs.
- Reconnects start fresh. When the connection drops, the client reconnects and each stream reopens on the new connection. While a replacement is being established, the interface keeps showing the state it already had; nothing from the old connection is replayed, so a consumer re-reads the state it needs. Connection states and recovery covers the reconnection itself.
- Only admitted callers connect. Before any stream opens, the connection must come from a caller the host trusts and can authenticate: a caller it does not trust gets
403, and one it cannot authenticate gets 401.
The session surface
- One namespace carries most of the interface. The session namespace lists and searches sessions, creates and renames them, forks them and works with branches, selects models, sends prompts, updates the queue, cancels work, and pages history.
- Streams keep a chat current. The session's
follow and control streams are how a chat stays current in the interface; both obey the reconnect rules above.
- Session changes also arrive as events. A session added or removed, its status, its activity, its branches and its errors are forwarded to the interface, as the next section describes.
Forwarded events
- A fixed list. The interface subscribes to events from a list fixed at build time, twenty-five entries in the current set; a name outside the list is never forwarded.
- What the list carries. Session changes (a session added or removed, its status, its activity, its branches and its errors), preset selection, goal activation changes, model adapter updates, permission catalogue changes, scheduled-task changes, settings updates, and the interface's own components (commands, credentials and plugins).
- Two entries answer back. An approval request and a user question arrive as requests: the first client to answer settles the request, the other clients are told it was cancelled, and a client that does not answer can hand the request to the next handler. When every client hands it on, the host's own fallback answers.
- The stream opens with a readiness frame. Its first item confirms readiness and carries the host's home folder, which the interface uses to shorten displayed paths.
- Only requests are replayed. A one-way event missed while disconnected is gone; a request still waiting for an answer is sent again to the replacement connection.
Account states
- The state and its changes. The interface can read the account state, and subscribe to it: it receives an initial complete state, then each change.
- Profile and balance. Both can be read; each reads as
null when the grant to the account is absent or has changed.
- Starting a sign-in. A sign-in can be started from the web interface or from the desktop app.
- Cancelling names the attempt. A cancellation names the specific attempt, so a stale screen cannot cancel a newer sign-in.
- Disconnecting stops the watching, not the sign-in. An interface that disconnects stops observing the account; it finds the current state again by subscribing, but it cannot resume a sign-in attempt after the host has exited.
- Signing out is local and backgrounded. Signing out removes the local grant and revokes it in the background; the saved API keys are not deleted.
- Credentials stay out of reach. Account credentials never enter model prompts, session logs or tool results.
Limits
- Lossless JSON only. Every value that crosses the layer must round-trip unchanged: call arguments, uplink items and forwarded-event payloads. Plain objects with enumerable fields, dense arrays and ordinary JSON values cross;
undefined fields, big integers, NaN, infinities, negative zero, cycles, class instances, functions, accessor or hidden fields, symbol keys and sparse arrays are refused.
- An item may omit its value. At the top level, an item sent as
undefined crosses as an item with no value; nested values are still checked.
- A bounded uplink. A stream's uplink buffers at most 262,144 bytes (256 KiB) of frames by default. A client that overruns this fails its own stream with
gateway/uplink-overflow, and later items are dropped; uplink items are never replayed.
- A refused item fails its stream. An uplink item that fails the value checks fails the whole stream with
gateway/input-invalid, and the failure names the uplink field.
- The heartbeat is also the deadline. The connection is pinged every two seconds by default, and that interval is both the ping cadence and the answer deadline: a peer that misses two intervals in a row has its connection terminated. The interval is a deployment setting, so it can be raised where the default is too quick for the machines involved.
- Text messages only. The connection carries text messages; a binary message closes it.
Close codes
A connection ends with a close code and, usually, a reason:
| Code | Reason | What it means |
|---|
1000 | disposed | The client was disposed; the connection is closed for good. |
1001 | peer left | The peer's session ended, and its streams close with it. |
1003 | text messages required | A binary message arrived; the connection carries text only. |
1008 | invalid Remote stream request | A malformed message, an unknown message type, or a duplicate open. |
1011 | Remote stream failure could not be delivered | A stream failed and its failure could not be written, so the connection closed. |
4000 | reconnect requested | The client is making a fresh connection attempt; a new connection follows on its own. |
4002 | an invalid frame | A frame did not parse; every active stream fails and the connection closes. |
When a call or stream fails
- A failure names its place. It carries a code, the endpoint it belongs to and, when one field is at fault, that field's name.
- The layer's own vocabulary. The codes that mark the layer's problems include
gateway/cancelled for an aborted call, gateway/uplink-overflow for an overrun uplink, gateway/protocol for an uplink item after the stream had ended, and gateway/input-invalid for a value or field that failed validation. A failure the layer cannot classify reports gateway/internal with empty details.
- The rest of the vocabulary covers dispatch problems. An endpoint that cannot be found or is ambiguous, a service that is not available, and arguments or results that fail validation.
- Method failures pass through. A failure from the method itself passes through unchanged; the layer marks only its own problems.
- Broken streams fail rather than guess. A stream whose sequence breaks fails instead of being applied: for example,
session assistant stream skipped revision <n> and session event stream emitted an entry before its opening cursor.
Questions, answered
What happens to open streams when the connection drops?
Nothing is replayed. When the client reconnects, each stream reopens on the new connection and re-reads the state it needs; items and notifications from the old connection are never sent again. While the replacement is being established, the interface keeps showing the state it already had. Connection states and recovery covers the recovery itself.
Why did the connection close with code 1008?
The reason invalid Remote stream request covers a malformed message, an unknown message type, or a second open for a stream id that is already open. Check the frames against the shapes on this page; a duplicate open is the one violation that closes the whole connection.
Why was my uplink item not lossless JSON?
The value used something the JSON subset does not admit, such as an undefined field, a big integer, NaN, an infinity, negative zero, a cycle, a class instance, or an accessor, hidden or symbol field. Send plain objects, dense arrays and ordinary JSON values instead. The stream the item was sent on fails with gateway/input-invalid.
Can two consumers watch the same stream?
No. A logical stream serves exactly one consumer, and opening a second one fails. When several parts of the interface need the same data, the client shares one stream between them instead of opening another.
How long may the connection stay silent?
It is pinged every two seconds by default, and that interval is also the deadline for the answer. A peer that misses two intervals in a row loses the connection. The interval is a deployment setting, so it can be raised where the default is too quick for the machines involved.
How much can a stream's uplink hold?
262,144 bytes (256 KiB) of frames by default. Over that, the stream fails with gateway/uplink-overflow and later items are dropped. Uplink items are never replayed, so anything that did not get through is lost.
Are forwarded events replayed after a reconnect?
One-way events are not: a notification missed while disconnected is gone. A request that is still waiting for an answer is different; it is sent again to the replacement connection so it can still be answered.
Which events can the interface subscribe to?
A fixed list, twenty-five entries in the current set: session changes (added, removed, status, activity, branches and errors), preset selection, goal activation changes, model adapter updates, permission catalogue changes, scheduled-task changes, settings updates, and the interface's own components (commands, credentials and plugins). An approval request and a user question are on the list as requests. A name outside the list is never forwarded.
What error does an aborted call settle as?
A cancelled failure, not a failure from the method: the code is gateway/cancelled and the message reads Remote invocation "<endpoint>" was aborted. Nothing further is kept for the answer.
Can a stale sign-in screen cancel a newer sign-in?
No. A cancellation names the specific sign-in attempt, so a screen that is out of date cannot cancel a sign-in that started after it, and an interface that reconnects reads the current account state again rather than resuming an old attempt.
How is this different from the Oda SDK?
They are different surfaces. This layer connects DataMind Oda's own screens and extensions to the host that serves them. The SDK is for a program of your own: a client process talks to a runtime over standard input and output, with the messages in the SDK wire protocol. Start from the Oda SDK overview, or read Automation clients for the serving profiles.