Tutorials
Connect an HTTP client
Use the TUI, gateway, or raw HTTP with private credentials and a restricted observer.
View Markdown source ↗Use an authenticated loopback listener, establish a logical connection, and observe its permitted live updates.
Prerequisites and command context
Section titled “Prerequisites and command context”Complete installation, private example setup, and the stdio protocol walkthrough. Run checkout commands from the repository root with its active opam environment. Terminal B and later terminals must set OCHAT_DEMO to Terminal A’s absolute path. The local listener is plain HTTP on loopback, not public HTTPS. Discovery/attachment use no model calls; sending a chat request requires credentials in the daemon and incurs charges.
Read the current provider TLS and permission boundaries before model work or deployment. Build troubleshooting includes the Apple Silicon/OpenBLAS setup path.
Start an isolated authenticated listener
Section titled “Start an isolated authenticated listener”Prepare the private examples. Stop any Unix-only daemon using that root, then in Terminal A:
dune exec bin/ochat_agent_server.exe -- -config "$OCHAT_DEMO/http.sexp" -validate-onlydune exec bin/ochat_agent_server.exe -- -config "$OCHAT_DEMO/http.sexp"This binds 127.0.0.1:8787 and uses generated hashed credentials. If the port is
occupied, choose another port in the private config and all client URIs; do not
terminate an unrelated process. Keep admin.token and observer.token private.
Use the maintained client adapter
Section titled “Use the maintained client adapter”Terminal B can create a durable session directly through the TUI:
dune exec bin/chat_tui.exe -- --no-config --connect http://127.0.0.1:8787 \ --bearer-token-file "$OCHAT_DEMO/admin.token" --new-daemon-session \ --prompt hello --workspace project --detachedOr use the raw-envelope gateway over HTTP:
dune exec bin/ochat_agent_stdio.exe -- --connect http://127.0.0.1:8787 \ --bearer-token-file "$OCHAT_DEMO/admin.token"Initialize using discover.ndjson,
then list/attach as in the stdio tutorial. The transport adapter
handles HTTP connection IDs and the connection notification SSE reader.
The compiled docs_example client demonstrates the same embedding API.
Raw HTTP integration
Section titled “Raw HTTP integration”With curl installed, the setup helper supplies a validated handshake and a
private curl credential config. Do not use verbose/header tracing with secrets:
curl --fail-with-body --silent --show-error --config "$OCHAT_DEMO/admin.curl" \ -D "$OCHAT_DEMO/initialize.headers" \ -H 'Content-Type: application/json' -H 'ochat-protocol-version: 1.0' \ --data-binary "@$OCHAT_DEMO/initialize.json" http://127.0.0.1:8787/v1/rpcOCHAT_CONNECTION=$(awk 'tolower($1)=="ochat-connection-id:" {gsub("\r", "", $2); print $2}' "$OCHAT_DEMO/initialize.headers")curl --fail-with-body --silent --show-error --config "$OCHAT_DEMO/admin.curl" \ -H 'Content-Type: application/json' -H 'ochat-protocol-version: 1.0' \ -H "ochat-connection-id: $OCHAT_CONNECTION" \ --data '{"jsonrpc":"2.0","id":"sessions","method":"session.list","params":{"limit":20}}' \ http://127.0.0.1:8787/v1/rpcIn another terminal (with the same variables), observe connection notifications:
curl --no-buffer --silent --show-error --config "$OCHAT_DEMO/admin.curl" \ -H "ochat-connection-id: $OCHAT_CONNECTION" http://127.0.0.1:8787/v1/eventsThat stream stays open until interrupted/closed; it may contain no session updates
until this logical connection subscribes through create/attach. For an independent
session observer after setting OCHAT_SESSION from the session list:
curl --fail-with-body --silent --show-error --config "$OCHAT_DEMO/observer.curl" \ "http://127.0.0.1:8787/v1/sessions/$OCHAT_SESSION/snapshot"Read latest_event_sequence from the snapshot into OCHAT_SEQUENCE, then:
curl --no-buffer --silent --show-error --config "$OCHAT_DEMO/observer.curl" \ "http://127.0.0.1:8787/v1/sessions/$OCHAT_SESSION/events?after_sequence=$OCHAT_SEQUENCE"After interrupting streams, close the operator logical connection deliberately:
curl --fail-with-body --silent --show-error --config "$OCHAT_DEMO/admin.curl" \ -X DELETE -H "ochat-connection-id: $OCHAT_CONNECTION" http://127.0.0.1:8787/v1/connectionFor an implementation independent of Ochat’s client library:
- Load the raw token from its private file without logging it.
- POST the fixture’s initialization envelope to
/v1/rpcwith bearer auth, JSON content type, andochat-protocol-version: 1.0. - Capture
ochat-connection-idfrom the response headers. Supply it on subsequent RPCs,/v1/events, andDELETE /v1/connection. - Open
/v1/eventsfor notifications or use the per-session snapshot/event routes for a separate read-only projection. Do not give connection notifications a durable replay guarantee they do not have. - Discover prompt/workspace IDs, create or attach a session, and retain attachment IDs for mutations. Follow the protocol reference, not the friendly catalog names used by TUI convenience flags.
- Correlate response IDs while receiving events. A message acknowledgement can say deferred; wait for terminal operation/history events to report completion.
The complete route/header and SSE reference specifies error handling, body/batch limits, and payload boundaries. Implement SSE parsing at frame boundaries, not network chunks. Use a library that supports incremental streaming rather than reading the entire SSE body before processing it.
Second observer and reconnect
Section titled “Second observer and reconnect”Quit the writer TUI, list sessions with its operator token, and assign the returned
ID to OCHAT_SESSION. In a second terminal:
dune exec bin/chat_tui.exe -- --no-config --connect http://127.0.0.1:8787 \ --bearer-token-file "$OCHAT_DEMO/observer.token" --session "$OCHAT_SESSION" --read-onlyThe observer token has only transcript-read scope. Finalized transcript updates are visible; protected tool/permission/provider-delta detail is not. Attempts to write are rejected. A separate read-only attachment with the admin token would still have that token’s broader read scopes.
The generated operator and observer tokens represent the same principal with different scopes. A token for a different principal cannot view this session merely by holding transcript-read scope. Regenerate fixtures created by the old helper that assigned separate principals. Each TUI initializes its own HTTP connection; do not pass an operator connection ID to the restricted observer.
For a raw session subscriber, remember the latest durable sequence, disconnect,
then request events after that sequence. On snapshot_required, fetch/replace
the snapshot and retry after its new sequence. Do not reuse a connection ID after
server restart; initialize again. Unsent editor drafts belong to the client and
should not be resent automatically as new mutations during reconnect.
Close logical connections deliberately and quit clients before Ctrl+C in Terminal A. Detached sessions remain in the private store for the next startup. No public listener or real-model request is required to test discovery/attachment.
Checkpoint, troubleshooting, and next step
Section titled “Checkpoint, troubleshooting, and next step”Success means an initialized RPC returns its matching response, and an authorized observer receives permitted transcript updates after a session actually produces them. An idle SSE stream is normal without events; an acknowledgement is not completed model work. For 401/403 errors, check the private token file, principal, scopes, and whether the logical connection was initialized with that credential. On restart initialize again. For a port conflict choose another port consistently. Close streams/connections, quit clients, and stop Terminal A before archiving/removing the private demo root and its credentials. The durable store remains until removed. Next, read HTTP transport contracts and permissions.
View source · Protocol discovery2 files
Complete source files. Open a filename to read it here.
discover.ndjsonStart here
{"jsonrpc":"2.0","id":"initialize","method":"protocol.initialize","params":{"implementation":{"name":"tutorial","version":"1"},"protocol_min":{"major":1,"minor":0},"protocol_max":{"major":1,"minor":0},"features":[],"event_encodings":["json"],"max_inbound_event_bytes":16777216}}
{"jsonrpc":"2.0","id":"info","method":"server.info","params":{}}
{"jsonrpc":"2.0","id":"prompts","method":"prompt.list","params":{"limit":20}}
{"jsonrpc":"2.0","id":"workspaces","method":"workspace.list","params":{"limit":20}}
{"jsonrpc":"2.0","id":"sessions","method":"session.list","params":{"limit":20}}
LICENSE.txtnotice
MIT License
Copyright (c) 2025
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.