Comms requires the
comms Cargo feature to be compiled in.This page is the task-first guide. For the low-level distinction between agent-side comms tools and host-side ingress APIs, see Comms reference.
What this guide is for
Use this guide when you want:- long-lived keep-alive sessions
- peer-to-peer agent messaging
- host-side external event ingress
- a practical operational understanding of the comms system
Overview
The comms system provides:- Four LLM-facing tools:
send_message,send_request,send_response, andpeers - Three transport layers: Unix Domain Sockets (UDS), TCP, and in-process (
inproc) - Ed25519 cryptographic identity: Every agent has a keypair; all messages are signed
- Trust-based peer model: Agents only accept messages from explicitly trusted peers
- External event ingestion: Push plain-text events from stdin, webhooks, RPC, or TCP/UDS listeners
- Auth-optional mode: Signed listeners for agent-to-agent comms; separate plain listeners for external events when
auth = "none" - Keep-alive: A runtime-backed session processes its initial prompt, then remains alive waiting for future admitted work
- Runtime-backed queueing: Incoming messages and external events are queued once and admitted through runtime ingress
Architecture
| Crate | Role |
|---|---|
meerkat-comms | Core comms: identity, trust, transport, router, inbox, runtime, MCP tools, agent integration |
meerkat-tools (builtin::comms) | CommsToolSurface, CommsToolSet, individual BuiltinTool implementations |
meerkat-core | CommsRuntime trait, CommsRuntimeConfig, CommsRuntimeMode enum, substrate integration points |
meerkat (facade) | build_comms_runtime_from_config(), compose_tools_with_comms(), factory wiring |
Setup
Enable comms feature
Ensure the
comms Cargo feature is compiled in (enabled by default in the CLI; opt-in on the meerkat facade crate).Generate identity
Identity is auto-generated on first run. Keys are stored under the realm runtime root (workspace
.rkat/identity/ for the default CLI workspace realm, realm data dir for non-workspace realms).Configure trusted peers
Add peers to
trusted_peers.json under the active runtime root with their name, public key, and address.Identity and cryptography
Each agent has an Ed25519 keypair managed by theKeypair type (meerkat-comms/src/identity.rs).
- Key generation:
Keypair::generate()creates a new random keypair usingOsRng. - Key persistence:
Keypair::save(dir)writesidentity.key(mode0600on Unix) andidentity.pubto disk.Keypair::load(dir)reads them back.Keypair::load_or_generate(dir)is the canonical entry point. - Public key format:
PubKeyis a 32-byte Ed25519 public key. The canonical string format ised25519:<base64>(standard Base64 with padding). - Default identity directory:
<runtime-root>/.rkat/identity/.
Envelope signing
Envelope signing
Every message is wrapped in a signed The signable bytes are computed by serializing
Envelope:(id, from, to, kind) as CBOR, then recursively sorting all map keys by canonical order (RFC 8949) before encoding. This ensures deterministic signing across implementations.Trust model
Agents maintain trusted peers in aTrustStore (meerkat-comms/src/trust.rs), keyed by the canonical PeerId — a UUIDv5 derived from the peer’s Ed25519 public key. PeerName is display-only metadata: duplicate names across entries are legal, while a duplicate PeerId is a hard error. Resolving a name to a PeerId (TrustStore::resolve_name) returns a typed TrustResolveError::Ambiguous when more than one entry shares the name — the store never guesses.
TrustEntry structure and file format
TrustEntry structure and file format
<runtime-root>/.rkat/trusted_peers.json with the stable persisted row shape { name, pubkey, addr, meta }; the PeerId is re-derived from the pubkey on load, and rows with invalid names, addresses, key material, or duplicate canonical identities are rejected fail-closed:Trust enforcement
Incoming connections are validated inhandle_connection() (meerkat-comms/src/io_task.rs):
Verify signature
Verify the Ed25519 signature (
envelope.verify()) when peer auth is enabled (require_peer_auth).Admit through the inbox seam
Pass the envelope to inbox admission (
inbox_sender.send_connection_ingress(...)), where ingress classification checks the sender against the TrustStore. Untrusted senders are dropped with a typed admission reason (untrusted_sender).Transport layer
All transports use a length-prefixed CBOR framing protocol implemented byTransportCodec.
Wire format: 4 bytes (big-endian) payload length followed by CBOR-encoded Envelope (up to 1 MB max).
Address formats
| Scheme | Format | Use case |
|---|---|---|
uds:// | uds:///path/to/socket.sock | Same-machine, lowest latency |
tcp:// | tcp://host:port | Cross-machine |
inproc:// | inproc://agent-name | In-process peer communication |
Transport details
Transport details
UDS transport: Unix Domain Socket listeners are spawned by
spawn_uds_listener(). The socket file is created at the configured path (existing files are removed first). Parent directories are created automatically.TCP transport: TCP listeners are spawned by spawn_tcp_listener(). Accepts connections and processes each in a dedicated tokio task.Inproc transport: The InprocRegistry (meerkat-comms/src/inproc.rs) is a process-global registry segmented by namespace. Meerkat uses realm-scoped namespaces so inproc peers from different realms are isolated by default. Messages are delivered directly in-memory without serialization.InprocRegistry::global()returns the singletonregister_with_meta_in_namespace(...)adds an agent in a namespaceunregister_in_namespace(namespace, pubkey)removes an agent from a namespace- delivery within a namespace is internal to the registry (crate-private send paths keyed by pubkey)
CommsRuntime is created, it automatically registers itself in the active namespace. When dropped, it unregisters from that namespace.Message types
MessageKind and Status
MessageKind and Status
ACK behavior
| Message Kind | Sender waits for ACK? | Receiver sends ACK? |
|---|---|---|
Message | Yes (with timeout) | Yes |
Request | Yes (with timeout) | Yes |
Lifecycle | No | No |
Response | No | No |
Ack | No | No (would loop) |
SendError::PeerOffline.
MessageIntent variants
MessageIntent variants
The
Standard strings are parsed into their enum variants; unknown strings become
MessageIntent enum (meerkat-comms/src/agent/types.rs) provides type-safe intent values for classifying incoming requests:| Variant | String | Description |
|---|---|---|
Delegate | "delegate" | Delegate a task |
Status | "status" | Request status update |
Cancel | "cancel" | Cancel an operation |
Ack | "ack" | Request acknowledgment |
Review | "review" | Review something |
Calculate | "calculate" | Request computation |
Query | "query" | Request information |
PeerAdded | "mob.peer_added" | Peer added lifecycle event |
PeerRetired | "mob.peer_retired" | Peer retired lifecycle event |
Custom(String) | (any string) | User-defined |
Custom.The LLM-facing send_request tool accepts a separate, closed intent vocabulary (CommsPeerRequestIntent in meerkat-core): "checksum_token" and "supervisor.bridge". Unknown intents fail at the serde boundary.LLM-facing tools
Four tools are exposed to the LLM when comms is enabled.send_message
send_message
Send a collaboration message to a peer.Response:
Canonical peer ID (from the
peers tool) to send the message to.Optional display name, retained only for diagnostics.
Message content.
Optional multimodal blocks:
{"type":"text","text":...} or {"type":"image_ref","source":"current_turn","index":0} / {"type":"image_ref","source":"blob","blob_id":"sha256:...","media_type":"image/png"}.Delivery mode. Use
"queue" for ordinary delivery or "steer" for immediate steer processing on runtime-backed sessions.{"status": "sent", "kind": "peer_message", "receipt": {...}}send_request
send_request
Send a structured ask to a peer when you want explicit Response:
intent + params and a correlated response via send_response. Requires a runtime-bound command authority; without it the tool reports a typed RuntimeCommandAuthorityUnavailable reason.Canonical peer ID (from the
peers tool) to send the request to.Optional display name, retained only for diagnostics.
Typed request intent:
"checksum_token" or "supervisor.bridge". Unknown intents are rejected at the serde boundary.Request parameters, validated against the selected intent (e.g.
{"subject": "..."} for checksum_token).Optional multimodal blocks (text and
image_ref entries).Delivery mode. Use
"queue" for ordinary delivery or "steer" for immediate steer processing on runtime-backed sessions.{"status": "sent", "kind": "peer_request", "receipt": {...}}Use
send_message by default for ordinary collaboration. send_request is for structured ask/reply semantics only. It is not task tracking, a stronger delivery mode, or a reserved response channel.send_response
send_response
Send a response to a previously received request. Requires a runtime-bound command authority, like Response:
send_request.Canonical peer ID (from the
peers tool) to send the response to.Optional display name, retained only for diagnostics.
ID of the request being responded to (UUID).
One of
"accepted", "completed", "failed".Typed response payload, validated against the original request contract. Defaults to
null.Optional multimodal blocks (text and
image_ref entries).Optional delivery mode override for terminal responses. Use
"queue" for normal delivery or "steer" when you need immediate steer processing on runtime-backed sessions. Forbidden on "accepted" progress responses.{"status": "sent", "kind": "peer_response", "receipt": {...}}The LLM-facing comms tools return lightweight tool receipts. Host-side
comms/send returns richer typed receipts such as peer_message_sent, peer_request_sent, and peer_response_sent, with identifiers and ACK-related data for application code.peers
peers
List all discoverable peers and their addresses.Entries come from the
TrustStore (plus runtime-resolved sources when a
runtime command authority is attached). The current agent and private
control-plane routes are excluded. Names are display labels and may not be
unique — always use peer_id for sends.Input: Empty object {}Response:Tool availability
Comms tool availability is reported per tool via the catalog (comms_tool_unavailable_reason in meerkat-comms). send_message and
peers are advertised even before any peers are configured, so live providers
can discover later wiring. send_request and send_response require a
runtime-bound command authority; without one they are marked unavailable with
the typed reason RuntimeCommandAuthorityUnavailable.
Inbox
TheInbox (meerkat-comms/src/inbox.rs) is a bounded queue (default capacity: 1024) with a Notify mechanism for waking waiting tasks.
Inbox::new()creates a raw transport-only inbox; runtime peer/event ingress uses the classified form with an installed machine handleInboxSender::send(item)enqueues an item and returns a typedAdmissionOutcome(AdmittedorDropped { reason }) — inbox-full, closed-session, and untrusted-sender drops are explicit, never silent- the classified queue is drained by the runtime as the sole consumer
- queueing is transport/inbox work
- consumption is runtime-backed admission work
Configuration
Config file (realm config.toml)
Config file (realm config.toml)
| Field | Type | Default | Description |
|---|---|---|---|
mode | CommsRuntimeMode | Inproc | Transport mode |
address | Option<String> | None | Listen address for signed agent-to-agent comms |
advertise_address | Option<String> | None | Address advertised to signed peers (for wildcard/NAT binds) |
auth | CommsAuthMode | Open | Auth mode: "none" (open) or "ed25519" |
require_peer_auth | bool | true | Whether peer traffic requires signatures and trusted-sender checks |
event_address | Option<String> | None | Plain-text event listener address (requires auth = "none") |
CoreCommsConfig (internal)
CoreCommsConfig (internal)
The Paths support
CoreCommsConfig is the internal config used by CommsRuntime:{name} interpolation (replaced with the agent’s comms name). Relative paths are resolved against the base directory via resolve_paths(base_dir).CLI usage
| Flag | Description |
|---|---|
--comms-name <NAME> | Agent name for peer identification. Enables comms if set. |
--agent-description <TEXT> | Human-readable description shown to peers via peers() |
--agent-label key=value | Metadata label (repeatable). Shown to peers via peers() |
--comms-listen-tcp <ADDR> | TCP address to listen on for signed comms (e.g., "0.0.0.0:4200") |
--comms-advertise-tcp <ADDR> | TCP address advertised to signed peers. Required when binding a wildcard address. |
--comms-binding-out <PATH> | Write this session’s external runtime binding JSON for mob hosts or other supervisors. |
--comms-pairing-password <PASSWORD> | Accept initial signed comms enrollment with a shared one-time password. |
--comms-pairing-password-env <ENV> | Read the pairing password from an environment variable. |
--comms-pairing-password-file <PATH> | Read the pairing password from a file. |
--no-comms | Disable inter-agent communication entirely |
--keep-alive | Run in keep-alive mode (stay alive for comms messages and events after initial prompt) |
--stdin | Read external events from stdin (newline-delimited, only meaningful with --keep-alive) |
All comms flags require the
comms feature at compile time (#[cfg(feature = "comms")]).Signed TCP remote peer
Userkat run --comms-listen-tcp when the Meerkat itself should expose the
signed agent-to-agent comms channel. This is the path for remote mob members
and other peer-addressable agents; it is separate from rkat-rpc --tcp, which
only exposes the JSON-RPC control plane.
kind: "external" runtime binding containing the
advertised address, the target’s Ed25519 public identity, and the typed
bootstrap_token used by mob supervisor bridge binding. Prefer generating this
file with --comms-binding-out; a binding that only carries an address or a
query-string bootstrap token is not enough for current external mob members.
Peer metadata
Agents can advertise a description and arbitrary labels so that peers can discover what each agent does — not just its name. This metadata flows through to thepeers() tool output.
peers(), the output includes the metadata:
Keep-alive mode
Keep-alive keeps the agent alive after processing the initial prompt, waiting for runtime-backed comms messages and external events to be admitted as future turns:Enter idle runtime-backed session
After the first run completes, the session remains alive in keep-alive mode and waits for future admitted work.
Handle incoming messages and events
When comms messages or external events arrive, runtime ingress queues them and admits them as later turns. External events are queue-only runtime-backed inputs; they do not create a second direct execution loop.
SDK / programmatic usage
Building a comms runtime
Building a comms runtime
config.comms.mode and creates the appropriate runtime:Inproc— creates an inproc-only runtime (usebuild_comms_runtime_from_config_scopedto pass a realm namespace)Tcp— creates a full runtime withCommsRuntime::new()and starts TCP listenersUds— creates a full runtime withCommsRuntime::new()and starts UDS listeners
Composing tools with comms
Composing tools with comms
CommsToolSurface (built from CommsToolMaterial) via DynamicToolComposite, registering send_message, send_request, send_response, and peers.Using AgentFactory
Using AgentFactory
comms_name, creates the runtime, composes tools, attaches the runtime to the agent, and records keep_alive and peer_meta in SessionMetadata.CommsBootstrap (nested runtime integration)
CommsBootstrap (nested runtime integration)
PreparedComms contains:runtime: CommsRuntime— ready to useadvertise: Option<CommsAdvertise>— for child agents, contains the name/pubkey/addr to register with the parent
Agent loop integration
Inbox consumption
The important architectural split is:CommsRuntimeand the inbox own delivery- the active keep-alive/drain lifecycle path owns consumption
- formatted message injection into the session is a projection of that lifecycle truth
Transcript and model projection
Incoming comms are persisted as typedsystem_notice blocks with comms payloads. role=user remains reserved for human/operator-authored text. The runtime projects those typed blocks into provider-facing text only while assembling model input:
- Message:
comms.kind = "message"plus peer identity and optional content blocks. - Request:
comms.kind = "request"plus peer identity, request id, intent, params payload, and response guidance in the model projection. - Response:
comms.kind = "response_terminal"orresponse_progressplus peer identity, request id, status, and payload.
Typed peer lifecycle notices
Some peer lifecycle notices are informational and should not trigger an LLM turn. Mob lifecycle routing is typed at peer ingress (PeerLifecycleKind) instead of relying on silent_comms_intents folklore:
mob.peer_addedmob.peer_retiredmob.peer_unwiredmob.dismiss(supervisor-directed dismissal of a live executor)
mob.kickoff_failedmob.kickoff_cancelled
max_inline_peer_notifications:
None(default): use runtime default (50)0: never inline peer lifecycle updates-1: always inline>0: inline only when current peer count is less than or equal to the threshold< -1: invalid (rejected by mob definition validation; factory builds also reject)
peers() on demand to inspect the current roster.
External event ingestion
External systems can push events into a running agent without Ed25519 authentication. Surface-level convenience routes now admit those events through runtime-ownedExternalEvent inputs instead of a separate injector-owned execution path.
Event sources
| Source | Surface | How to use | Auth |
|---|---|---|---|
| Stdin | CLI | --stdin flag (with --keep-alive) | None |
| Webhook | REST | POST /sessions/{id}/external-events | RKAT_WEBHOOK_SECRET env var |
| RPC | JSON-RPC | session/external_event method | None (implicit) |
| TCP listener | Comms | Configure event_address in config | auth = "none" required |
| UDS listener | Comms | Configure event_address in config | auth = "none" required |
CLI stdin events
Read newline-delimited events from stdin. Each line is parsed as JSON (extractingbody field if present) or treated as plain text.
REST webhook
Push events to a running session via HTTP. Auth is optional viaRKAT_WEBHOOK_SECRET env var with constant-time comparison.
202 Accepted with {"queued": true}. Runtime admission failures are returned as ordinary REST errors.
RPC session/external_event
Queue a runtime-backed external event for a running session via JSON-RPC.kind (generic_json), event_type, payload, and optional blocks. The method returns a runtime acceptance envelope on success.
TCP/UDS plain event listeners
Whenauth = "none" in config, a separate plain-text listener starts on event_address for unauthenticated external events. The signed agent-to-agent listener is never replaced.
config.toml (active realm)
Event transcript format
External events are persisted as typedexternal_event system-notice blocks with source, event_type, optional body, payload, and content blocks. The provider-facing source/body text is an internal projection and is not stored as user-authored transcript text.
- Stdin:
source = "stdin" - Webhook:
source = "webhook" - RPC:
source = "rpc"with optional structured source metadata. - TCP/UDS:
source = "tcp"orsource = "uds"
Delivery vs observation
When injecting events from application code, delivery and observation are separate concerns. Delivery — the service’sevent_injector() returns an Arc<dyn EventInjector>. Use inject() to push an external event into the session inbox:
- session-scoped: use the session’s primary
event_txorsession/stream_open(RPC) /Session.subscribe()(Web SDK) /session.subscribe_events()(Python SDK) /session.subscribeEvents()(TypeScript SDK) - agent-scoped: use
MobHandle::subscribe_agent_events(...)ormob/stream_open(RPC) /Member.subscribe()orMob.subscribeMemberEvents(agentIdentity)(Web SDK) /mob.subscribe_member_events(agent_identity)(Python/TypeScript SDK) - mob-scoped: use
MobHandle::subscribe_mob_events(...)ormob/stream_open(RPC) /Mob.subscribeEvents()(Web SDK) /mob.subscribe_events()(Python/TypeScript SDK)
- The agent must be running in keep-alive mode if you expect inbox events to be drained promptly.
- Comms must be enabled. Without comms,
event_injector()returnsNone. - Observation subscriptions are independent of delivery — configure them separately.
Security
- All messages are signed with Ed25519 using canonical CBOR encoding
- Trust is explicit: only messages from peers in
trusted_peers.jsonare accepted - Misaddressed messages are dropped: the receiver verifies
envelope.tomatches its own public key - Private keys are stored with restrictive permissions:
identity.keyis written with mode0600on Unix - Secret bytes are zeroized:
Keypair::from_secret()zeroizes the input after copying - ACK validation: ACK signatures, sender, recipient, and
in_reply_toID are all verified
See also
- Built-in tools reference - comms tool parameter details
- Configuration: comms - config file settings
- Examples: comms - keep-alive mode and messaging across surfaces
