Skip to content

daemon: extend IPC with typed-channel + RPC + capabilities ops#16

Merged
mrjeeves merged 1 commit into
mainfrom
claude/daemon-rpc-channel-ipc
May 27, 2026
Merged

daemon: extend IPC with typed-channel + RPC + capabilities ops#16
mrjeeves merged 1 commit into
mainfrom
claude/daemon-rpc-channel-ipc

Conversation

@mrjeeves
Copy link
Copy Markdown
Owner

Summary

Foundation for porting MyOwnLLM off Trystero onto the daemon. The existing IPC was complete for control-plane (networks, peers, roster, governance, events) but typed channels and RPC were library-only — clients had to embed myownmesh-core directly to use them, which made the LLM app's "use the daemon for all mesh" goal impossible. This PR adds the missing IPC surface so multiple clients (the existing GUI, the future LLM Tauri backend) can share one daemon, one identity, one set of networks.

Once this merges, MyOwnLLM bumps its myownmesh-core git pin and a follow-up PR there rewrites mesh-client.svelte.ts to talk to the daemon — no second Mesh instance, no duplicate Trystero patches, both apps live on the same RPC/channel/governance plumbing.

New IPC ops

All additive on control::Request — no breaking changes to existing variants:

Op Purpose
rpc_register { client_id, network, method, streaming } Claim a method. Daemon installs a synthetic Rpc::serve/serve_stream on the network's dispatcher that emits RpcInbound to the claiming client and awaits the response via the registry's pending_inbound table. Last-claim-wins.
rpc_unregister Release a method claim.
rpc_respond / rpc_stream_chunk / rpc_stream_end Client resolves an in-flight inbound RPC.
rpc_call Outbound single-shot. Synchronous response on the command socket.
rpc_call_stream Outbound streaming. Daemon assigns a request_id and forwards chunks as events.
channel_subscribe / channel_unsubscribe Per-(network, channel) fan-out. First subscriber spawns a pump task; last unsubscribe drops it.
channel_send_to / channel_send_all Fire-and-forget publishes.
capabilities_set Replace the network's advertised capabilities at runtime.

New event-stream variants

ipc::ServerOut, written on the event socket after EventsSubscribe. Existing clients (the GUI) ignore unknown kinds by default, so this is additive on the wire.

  • rpc_inbound — peer called a method this client owns
  • rpc_call_stream_chunk / rpc_call_stream_end — chunks of an outbound stream this client initiated
  • channel_inbound — frame on a subscribed channel
  • handler_displaced — a more-recent client took your method

Architecture

  • Each event-subscribed connection gets a ClientId (monotonic, c<n> on the wire) echoed back in the EventsSubscribe ack.
  • Subsequent RPC/channel-management ops on other command sockets pass that client_id so the daemon routes inbound events to the right event socket.
  • Per-request oneshot::Sender / mpsc::Sender entries live in ClientRegistry::pending_inbound keyed only by request_id — any client may answer any id, which keeps streaming responses decoupled from a particular connection if it bounces.
  • Synthetic handlers stay installed on the engine's Rpc dispatcher forever once a method is first claimed; if invoked with no current owner they answer the peer with "no handler" rather than panicking. Simpler than safe teardown across re-claims.

Tests

13 new tests, all passing alongside the existing suite (no regressions):

  • 10 unit tests in ipc::clients::tests — registry mechanics (claim/release/displace, subscribe/unsubscribe with first/last subscriber flags, pending-inbound resolve/reject, stream chunk/end, ClientId roundtrip, monotonic id allocation, disconnect-cleanup that doesn't collateral-drop a displacing claim).
  • 3 end-to-end bridge tests in ipc::bridge::tests — spin up two engines + LocalBroker, wait for PeerEvent::Approved, then validate the full round-trip:
    • Single-shot RPC: Alice registers echo, Bob calls it, the simulated client receives RpcInbound + responds, Bob's rpc.call() future resolves with the payload.
    • Streaming RPC: same but the client pushes three chunks then closes; Bob's call_stream receiver yields all three plus end-of-stream.
    • Channel pub/sub: client subscribes to catalog, Bob sends a frame, the ChannelInbound event arrives with correct sender + payload.

Files

crates/myownmesh/Cargo.toml         | +5 (dashmap, tempfile dev-dep)
crates/myownmesh/src/control.rs     | +487 / -37   (new variants + dispatch arms, duplex event stream)
crates/myownmesh/src/ipc/mod.rs     | +45    (new module)
crates/myownmesh/src/ipc/wire.rs    | +83    (new — ServerOut)
crates/myownmesh/src/ipc/clients.rs | +541   (new — ClientRegistry + tests)
crates/myownmesh/src/ipc/bridge.rs  | +649   (new — synthetic handlers + channel pump + tests)
crates/myownmesh/src/main.rs        | +1     (mod ipc;)

Test plan

  • cargo fmt --all --check
  • cargo clippy --workspace --all-targets -- -D warnings
  • cargo test --workspace (147 tests total, all pass — 13 new, 134 pre-existing untouched)
  • After merge: MyOwnLLM bumps myownmesh-core git pin and starts the Tauri-backend migration in its own PR

https://claude.ai/code/session_01Vp4cvRTaLYd3162EwwcCXg


Generated by Claude Code

Adds the missing IPC surface so a client that's not embedding
myownmesh-core directly can still use the engine's typed channels
and RPC dispatcher — the parts of the library API that were
previously lib-only. This is the foundation for porting the
MyOwnLLM app off Trystero onto the daemon (separate PR over there
once this lands and the LLM bumps its git pin).

**New IPC ops** (added to `control::Request`, all additive — no
existing op changed):

- `RpcRegister { client_id, network, method, streaming }` —
  claim a method name. The daemon installs a synthetic
  `Rpc::serve` (or `serve_stream`) on the network's dispatcher
  that emits `RpcInbound` to the claiming client's event socket
  and awaits an `RpcRespond` / `RpcStreamChunk` / `RpcStreamEnd`
  routed back through a per-request pending table. Last-claim-wins
  with a `HandlerDisplaced` event to the displaced client.
- `RpcUnregister` — release a method claim.
- `RpcRespond` / `RpcStreamChunk` / `RpcStreamEnd` — client
  resolves an in-flight inbound RPC.
- `RpcCall` — outbound single-shot; daemon awaits `Rpc::call`
  and returns the response synchronously on the command socket.
- `RpcCallStream` — outbound streaming; daemon assigns a
  request_id, spawns a forwarder that drains the engine's
  `mpsc::Receiver` and emits `RpcCallStreamChunk` /
  `RpcCallStreamEnd` events to the client's event socket.
- `ChannelSubscribe` / `ChannelUnsubscribe` — typed-channel
  fan-out. First subscriber spawns a pump task; last unsubscribe
  drops it.
- `ChannelSendTo` / `ChannelSendAll` — fire-and-forget channel
  publishes.
- `CapabilitiesSet` — replace the network's advertised
  capabilities at runtime.

**New wire-out variants** (`ipc::ServerOut`, written on the event
socket after `EventsSubscribe`):

- `RpcInbound` — peer called a method this client owns.
- `RpcCallStreamChunk` / `RpcCallStreamEnd` — chunks of an
  outbound stream call this client initiated.
- `ChannelInbound` — frame on a subscribed channel.
- `HandlerDisplaced` — a more-recent client took your method.

**Architecture**: each event-subscribed connection gets a
`ClientId` echoed back in the `EventsSubscribe` ack. Subsequent
RPC/channel-management ops on other command sockets pass that
`client_id` so the daemon routes inbound events to the right
event socket. Per-request `oneshot::Sender` / `mpsc::Sender`
entries live in `ClientRegistry::pending_inbound` keyed only by
`request_id` — any client may answer any id, which keeps
streaming responses decoupled from a particular connection if it
bounces.

**Tests** (13 new): unit tests for the registry mechanics
(claim/release/displace/subscribe/unsubscribe, pending-inbound
resolve/reject, stream chunk/end) plus three end-to-end bridge
tests that build two engines connected via LocalBroker and
validate the full round-trip for single-shot RPC, streaming RPC,
and channel pub/sub through the synthetic-handler bridge.

https://claude.ai/code/session_01Vp4cvRTaLYd3162EwwcCXg
@mrjeeves mrjeeves merged commit 93b5362 into main May 27, 2026
6 checks passed
@mrjeeves mrjeeves deleted the claude/daemon-rpc-channel-ipc branch May 27, 2026 16:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants