Comms requires the
comms Cargo feature to be compiled in.Overview
The comms system provides:- Two LLM-facing tools:
send(withkindparameter for message/request/response),peers - 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 and 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 a list of trusted peers in aTrustedPeers collection (meerkat-comms/src/trust.rs).
TrustedPeer structure and file format
TrustedPeer structure and file format
<runtime-root>/.rkat/trusted_peers.json:Trust enforcement
Incoming connections are validated inhandle_connection() (meerkat-comms/src/io_task.rs):
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(namespace, name, pubkey, sender, meta)adds an agent in a namespaceunregister_in_namespace(namespace, pubkey)removes an agent from a namespacesend_with_signature_in_namespace(namespace, from_keypair, to_name, kind, sign)delivers in-memory within that namespace
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 |
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 provides type-safe intent values for 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 |
Custom(String) | (any string) | User-defined |
Custom.LLM-facing tools
Two tools are exposed to the LLM when comms is enabled.send (kind=peer_message)
send (kind=peer_message)
send (kind=peer_request)
send (kind=peer_request)
Send a request to a peer. The sender waits for an ACK (not the response itself).Response:
Peer name to send request to.
Must be
"peer_request".Request intent/action (e.g.,
"review", "delegate").Request parameters. Defaults to
null.{"status": "sent"}send (kind=peer_response)
send (kind=peer_response)
Send a response to a previously received request.Response:
Peer name to send response to.
Must be
"peer_response".ID of the request being responded to (UUID).
One of
"accepted", "completed", "failed".Response result data. Defaults to
null.{"status": "sent"}peers
peers
List all discoverable peers and their addresses.This includes both configured trusted peers and in-process peers currently
registered in
InprocRegistry. Results are de-duplicated by name and exclude
the current agent.Input: Empty object {}Response:Tool availability
Comms tools are conditionally available based on peer/trust configuration in the surface.CommsToolSurface::peer_availability() checks whether TrustedPeers
has any configured peers (TrustedPeers::has_peers()). Tools are hidden when no
peers are configured.
Inbox
TheInbox (meerkat-comms/src/inbox.rs) is a bounded MPSC channel (default capacity: 1024) with a Notify mechanism for waking waiting tasks.
InboxSender::send(item)enqueues an item and callsnotify.notify_waiters()Inbox::try_drain()returns all currently available items without blockingInbox::recv()blocks until a message is available
CommsMessage for the agent loop, ACKs and messages from unknown peers are filtered out.
Queueing an inbox item and consuming it are separate truths:
- 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 |
auth | CommsAuthMode | Open | Auth mode: "none" (open) or "ed25519" |
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") |
--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")]).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— calls the scoped inproc constructor (CommsRuntime::inproc_only_scoped) with realm namespace when availableTcp— 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
ToolGateway, registering send 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.Using CommsAgent directly
Using CommsAgent directly
For low-level control:
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
Message injection format
Incoming messages are formatted as text for the LLM:- Message:
[COMMS MESSAGE from <peer>]\n<body> - Request:
[COMMS REQUEST from <peer> (id: <uuid>)]\nIntent: <intent>\nParams: <json>\n\nTo respond, use send with kind=peer_response, peer="<peer>", request_id="<uuid>" - Response:
[COMMS RESPONSE from <peer> (to request: <uuid>)]\nStatus: <status>\nResult: <json>
Silent comms intents
Some comms intents are informational and should not trigger an LLM turn. For example,mob.peer_added and mob.peer_retired notifications tell the agent about peer lifecycle changes but don’t require a response.
Silent comms intents are configured via AgentBuildConfig.silent_comms_intents. When a Request arrives whose intent matches a silent intent, it is injected into the session context (like a response) instead of being processed through the LLM. This avoids wasting an LLM turn on a notification that the prompt already says to ignore.
mob.peer_added and mob.peer_retired configured as silent intents by default.
Silent intents are only checked in the host-mode classification phase. Non-matching intents are processed normally through the LLM.
mob.peer_added, mob.peer_retired) are compacted per inbox drain cycle into a single [PEER UPDATE] session entry. This compaction is presentation-only: the underlying comms request stream remains one event per peer change.
You can also control whether these peer lifecycle updates are inlined by peer-count threshold using 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. If peer count later drops back below threshold, the next inline update includes a “resumed” notice.
Inline summaries show up to 10 peer names per category (connected / retired), then append (+N more) to cap token usage.
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.source field becomes the runtime external-event source. 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 injection format
Events are injected into the agent’s context with source tagging:- Stdin:
[EVENT via stdin] <body> - Webhook:
[EVENT via webhook] <body> - RPC:
[EVENT via rpc] <body>(with optional[source: name]prefix) - TCP/UDS:
[EVENT via tcp] <body>or[EVENT via uds] <body>
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(meerkatId)(Web SDK) /mob.subscribe_member_events(meerkat_id)(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
