Protocol and observation
Developer docs
The wire contract lives in
platonic-protocoland its exact-byte tests. This page groups that contract for navigation; it is not an API reference.
Local typed handshake
Section titled “Local typed handshake”Protocol v2 is a closed, versioned, newline-delimited JSON protocol over local IPC. Each request and response is a typed Envelope; unknown methods, fields, and incompatible versions fail at the envelope boundary. A v2 daemon rejects a v1 envelope before method dispatch, and the v1 fixture rejects v2 the same way; there is no agent.* alias surface. The source owns serialization and validation but performs no I/O or policy. See the crate README, Envelope, and every_current_method_keeps_exact_v2_request_and_response_bytes.
On a host connection, workspace and profile registry calls plus daemon.shutdown_if_idle can be dispatched without selecting a workspace. Other calls must begin with hello. The server resolves the registered workspace and returns its stable identity, ledger path, daemon scope, build identity, and advertised capabilities. The server-side gate is handle_host_line; the transport call is DaemonClient::hello.
Capabilities report what this peer implements. They do not grant workspace, thread, tool, or approval authority; the server still enforces those checks in its handlers.
Callable methods and capabilities
Section titled “Callable methods and capabilities”At this source head, ProtocolMethod contains 29 callable methods:
- Handshake:
hello - Run and event flow:
run.start,message.append,issue-prep.start,events.stream,run.cancel - Voice facts:
voice.events.commit,voice.events.read - Approval and session readback:
approval.decide,sessions.list,transcript.read,session.approval_profile.set - Host control:
daemon.status,daemon.shutdown_if_idle - Threads:
thread.spawn,thread.list,thread.status,thread.authority,thread.send,thread.events,thread.stop - Workspace registry:
workspace.create,workspace.list,workspace.status - Profiles:
profile.create,profile.list,profile.status,profile.update,profile.open
The hello result advertises 31 capabilities. Twenty-nine correspond to callable methods. The two additional flags, transcript.read.typed and transcript.read.pending_approval, describe additive fields returned by the callable transcript.read; they are not methods. The exhaustive inventory is the typed Capability, CAPABILITIES, and ProtocolMethod source, locked by capability_names_and_error_codes_keep_exact_v2_literals.
Internal modules such as handlers/runs.rs or runtime/thread.rs are implementations behind those methods, not extra protocol surface. Add or change wire behavior in the typed protocol first, then route it through the server handler that owns the semantics.
One controller, many observers
Section titled “One controller, many observers”profile.open resolves, starts, or decides the one home reservation for a
profile. thread.spawn requires a parent and preserves that parent’s
profile_id; a child can narrow authority but cannot cross profiles. Thread
list and status project profile_id, thread_kind, the home relation, return
availability, and live epoch. The model-facing profile.read, thread.tree,
thread.events, and thread.transcript tools expose bounded same-profile
readback. thread.return and thread.answer expose the already-owned immediate
parent/child channel rather than a generic messaging system.
thread.send establishes one controller for an active turn. That controller may queue a bounded steer only with the matching turn id. A different controller receives controller_owned; after the turn becomes idle, another controller may start the next turn. The rule and rejection shapes live in runtime/thread.rs and are proven end to end by thread_send_and_three_observers_are_semantically_conformant_on_host_daemon.
thread.events is independently observable by multiple clients. Each observer advances its own offset through the same ordered, bounded live buffer and pairs it with live_epoch_id. Omitting an offset tails from the current position; an evicted offset returns lagged, while a cursor from an old server epoch must reset from current status or durable readback. Durable transcript readback, not a private observer cache, is the recovery path after lag or reconnect. See LiveThread::events and TranscriptReadResult.
Gateways are peer clients
Section titled “Gateways are peer clients”A gateway is a server-owned connector module, but it reaches run authority through the same client and protocol boundary as any other peer. The Discord bridge connects, completes hello, checks required capabilities, calls thread.status, thread.send, thread.events, approval.decide, and transcript.read, and falls back to durable readback after lag or reconnect. It does not acquire sessions, policy, approvals, fallback, or run semantics. Read gateway/discord/daemon_bridge.rs and the gateway boundary.
Local is not remote
Section titled “Local is not remote”The daemon endpoint is a user-owned Unix socket, with private parent directories and a 0600 socket. It is not a network listener. The implementation is in server/socket.rs, while the accepted no-network-listener boundary is decision P017.
The authenticated HTTP/SSE gateway is a translating /v2 adapter over this
native boundary, not the native protocol exposed on a network socket. Its bearer
principal supplies fixed workspace scope and may further narrow that ceiling
with profile ids; route ids never widen either scope. The generated contract is
gateway-v2.yaml. The merged /v1 gateway was never released, so v2 replaced it without a compatibility adapter. JSON-RPC, ACP, cookies, proxy-header identity, in-process TLS, and direct remote daemon access remain outside this boundary. Operational socket and gateway setup belongs to the User operations guide.