docs(bindings): local API clients and guide - #492
Merged
Merged
Conversation
One server process fronting one engine for several local applications: JSON-RPC 2.0 over a WebSocket, a Unix domain socket by default and TCP on loopback with a per-launch token as the opt-in, a `hello` that declares the client's application id once and stamps it on every send, and the engine's events relayed unchanged as notifications. The chapter partitions the interface definition into the 126 methods a client may call and the 92 platform operations it never can, including the run loop and the drain, which the server owns because the engine delivers and acknowledges a message only from inside the drain. It is the first complete catalogue of the engine's 75 event tags with their fields, vocabularies and routing (by application id, by an identifier the server issued, or to everyone), and it keeps the 25-variant error taxonomy: the JSON-RPC code is the variant's position in the append-only enum. There is no HTTP request path. The pinned WebSocket library's handshake parser accepts only GET and drops any other method without a response, which replaces the program record's H5 wording and is recorded in the chapter so it is not re-added. An optional unauthenticated GET /health through the request hook is allowed. docs/bridges/local-api.md states which shared bridge rules a server inherits (C1, C2, C3, C6, C10, C12) and the new rule that a Rust guard pins the three tables to the definition, the engine and the reference server. Index rows added; docs/README.md corrected to thirteen shared rules and twenty-five ADRs, and given the peer-stream framing row it lacked.
A guide to running the SDK as a service several local applications share (docs/local-api.md), a Python client example over the Unix socket or TCP, a Node client example over TCP with the built-in WebSocket and no dependencies, and a test that runs both against in-process servers so the examples cannot drift from the wire. The Node built-in client dials only ws: and wss: URLs, so that example uses the token-carrying TCP carrier. Two facts found by running the examples and now stated in the guide: data.map_set takes a tagged value while data.doc_json reads back plain JSON, and data.create_doc on an existing document succeeds.
bahdotsh
force-pushed
the
docs/headless-local-api-clients
branch
from
September 30, 2026 17:53
3b43286 to
a9df28c
Compare
bahdotsh
force-pushed
the
feat/headless-local-api-server
branch
from
September 30, 2026 19:36
eeb48d9 to
11a94d1
Compare
bahdotsh
changed the base branch from
feat/headless-local-api-server
to
main
September 30, 2026 19:48
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to subscribe to this conversation on GitHub.
Already have an account?
Sign in.
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The user-facing half of the local API: a guide to running the SDK as a service several local applications share, two dependency-free clients, and a test that runs both against an in-process server so the examples cannot drift from the wire.
docs/local-api.md, the guide. Purpose first (embed, or run one node that several local programs share), the four things that hold whatever a client does, starting the service on either carrier, the policy file with the failure each section prevents, thehellohandshake, sending, subscribing and replay, documents, the one-shot idiom, errors, what is not exposed and why, and the examples. The normative chapter (docs/spec/local-api.md) and the bridge contract (docs/bridges/local-api.md) are linked, not restated. Index row indocs/README.md.bindings/python/examples/local_api_client.py. Python over the Unix socket (--socket) or loopback TCP (--tcp PORT --token-file PATH), using only thewebsocketspackage the SDK depends on.--waitsits and receives one inbound message.examples/local-api/client.mjs. Node 22 or later with the built-inWebSocket, no dependencies. The built-in client accepts onlyws:andwss:URLs (verified:ws+unix:is refused with "Expected a ws: or wss: protocol"), so it uses the TCP carrier and reads the token file; the header says so.bindings/python/tests/local_api/test_examples.py. Seven tests, see Validation.bindings/python/README.mdpoints at the guide and both examples; CHANGELOG entry under Unreleased / Added.What each example shows
Both do the same thing in the same order, and print one JSON object per step so a reader (and the test) can follow them:
hellowithapp_id(and the token on TCP)subscribetomessage_received,message_delivered,message_undeliverablesend_message, then the terminal event naming the returned id,message_deliveredormessage_undeliverable(--to)data.create_doc(a no-op on an existing document),data.map_setwith a tagged value,data.doc_jsonread backhello, one request (local_address), and closesmessage_received(--wait)codeanddata.variant; any other failure as one line with its messageTwo facts the examples had to get right, found by running them:
data.map_settakes a tagged value ({"kind":"text","value":...}) whiledata.doc_jsonreads back plain JSON, anddata.create_docon an existing document succeeds. The guide states both.Review round 1
Eight findings from a fresh-context review, all landed in the same commit:
ConnectionErrorwhen the socket closes; the Node client rejects every pending call and event wait oncloseanderror. A test drives the Pythonhelloagainst a server that reads one frame and closes1011, and the same probe against the Node example is what the mutation check below uses.--tois watched asmessage_undeliverablebesidemessage_deliveredrather than waiting out the timeout, and the printed key follows the outcome.subscribewith"all"resets the filter;unsubscribewith"all"mutes everything./healthbody carriesapi_versiontoo.message_sent/message_deliveredholds while the server that issued the id is running.untilandtwo_serversstay duplicated fromtest_local_api_e2e.py: the sharedconftest.pyis feat(bindings): the Python reference server for the local API #491's file and that PR is still being edited, so this one does not touch it. Worth folding once feat(bindings): the Python reference server for the local API #491 settles.hellono longer leaks the receiver (it moved inside thetry), and a subprocess that outlives its deadline is killed rather than left running.client.mjsrefuses to run on a Node without the built-inWebSocket, naming the version, instead of failing with aReferenceError.Validation
tests/local_api/test_examples.pyon python3.12, 3.13 and 3.14: 7 passed on each, and--count 20on each (140 passed per interpreter, no flake).tests/local_api/once on 3.14: 61 passed. Run withPYTHONDONTWRITEBYTECODE=1and bytecode caches purged around every mutant, so a same-size mutant can never survive in a.pyc.--toserver B: exit 0, the four printed objects in order, and the message received on B.DataDisabledwhen the data layer is off).node --checkpasses, and the Node example runs end to end against a TCP server with the token file, sending to a second server whose client receives it. Both Node tests skip with a reason whennodeis not on the path.send_messagefails two tests; changing the Node example's document key fails the end-to-end test; removing the Python reader's failure of pending calls on close fails the close-hang test; removing the Node client'scloselistener brings back the review's exit-13 "unsettled top-level await" against the same one-frame-then-close probe, where the shipped code exits 1 with one JSON line naming the close code. All restored from scratch copies and verified byte-identical.docs/README.mdor either example path.Not in this PR
offline-protocol-servicebehind a reverse proxy.Notes for reviewers
test_local_api_e2e.pywith the data layer on and an optional TCP carrier for server A (review item 6 above): folding both into the sharedconftest.pywaits until feat(bindings): the Python reference server for the local API #491 stops moving.