Local IPC

The daemon's canonical wire protocol. Clients on the same machine — the CLI, an operator UI, third-party tooling — speak length-prefixed JSON over a Unix socket at $COVENANT_HOME/sock. The HTTP gateway is a thin adapter on top of the same surface.

Frame format

Each frame is a 4-byte big-endian unsigned integer length prefix followed by exactly that many bytes of UTF-8 JSON. Frames over 8 MiB are rejected at the read boundary.

+---------+---------+---------+---------+---------- … ----------+
| len[31..24] | len[23..16] | len[15..8] | len[7..0] | JSON payload |
+-------------+-------------+------------+-----------+--------------+
        4-byte big-endian length              up to 8 MiB

The framing applies in both directions: the request frame is followed by exactly one response frame, and a long-lived connection can carry many request/response pairs in sequence. Connections are not pooled by the daemon; clients are expected to reuse a single connection or open one per request as convenient.

Request shapes

A request is a JSON object tagged with kind. The full set today:

{ "kind": "ping" }

{ "kind": "submit_intent",
  "text": "…" }

{ "kind": "recent_memory",
  "tier": "working" | "episodic" | "longterm" | null,
  "limit": 10 }

{ "kind": "search_memory",
  "query": "…",
  "tier":  "working" | "episodic" | "longterm" | null,
  "limit": 10 }

{ "kind": "purge_memory",
  "tier": "working" | "episodic" | "longterm" | null,
  "before_ms": 1714938000000 }

{ "kind": "recent_receipts",
  "limit": 10 }

{ "kind": "recent_capabilities",
  "limit": 10 }

{ "kind": "grant_capability",
  "action": "tool.web_search",
  "scope": null | { ... },
  "expires_at": null | 1714938000000 }

{ "kind": "revoke_capability",
  "signature_b58": "…" }

{ "kind": "verify",
  "window": 100 }

{ "kind": "ignore_check",
  "text": "…" }

{ "kind": "list_tools" }

{ "kind": "call_tool",
  "name": "echo",
  "arguments": { ... } }

{ "kind": "recent_audit",
  "limit": 20 }

{ "kind": "send_a2a_task",      "task":   { ... } }
{ "kind": "try_recv_a2a_task" }

{ "kind": "post_a2a_result",    "result": { ... } }
{ "kind": "try_recv_a2a_result" }

Response shapes

Responses are also kind-tagged. One canonical response shape per request, plus a generic error for any handler-level failure.

{ "kind": "pong" }

{ "kind": "intent_result",
  "intent_id": "uuid",
  "status": "ok" | "ignored",
  "text": "…",
  "sources": ["…"],
  "settlement": null | { ... } }

{ "kind": "memories",        "records":   [ ... ] }
{ "kind": "memory_purged",   "purged":    42 }
{ "kind": "receipts",        "receipts":  [ ... ] }
{ "kind": "capabilities",    "capabilities": [ ... ] }
{ "kind": "capability_granted",
  "signature_b58":   "…",
  "subject_display": "user@local",
  "action":          "tool.web_search" }
{ "kind": "capability_revoked",
  "signature_b58": "…",
  "removed":       true }
{ "kind": "verify_report",
  "window": 100,
  "checks": [ { "name": "…", "passed": true, "message": "…" } ],
  "orphans_total": 0 }
{ "kind": "ignore_report",
  "ignored":         false,
  "matched_pattern": null,
  "rules_loaded":    3 }
{ "kind": "tool_list",       "tools":    [ ... ] }
{ "kind": "tool_result",     "content":  [ ... ], "is_error": false }
{ "kind": "audit_events",    "events":   [ ... ] }
{ "kind": "a2a_task_queued",   "task_id": "uuid" }
{ "kind": "a2a_task_opt",      "task":    null | { ... } }
{ "kind": "a2a_result_posted", "task_id": "uuid" }
{ "kind": "a2a_result_opt",    "result":  null | { ... } }

{ "kind": "error", "message": "…" }

Implementation notes

  • Backpressure. The daemon reads one frame at a time per connection; long-running operations hold the connection open until they complete, so a slow handler will delay the next request on that connection.
  • Frame size. The 8 MiB cap applies in both directions. Returning a memory record set that exceeds it should not happen in normal operation, but a verification window over millions of records can. Use limit arguments where they exist.
  • Timeouts. The daemon does not impose a per-request timeout. Clients should set their own.
  • Authentication. Connecting to the Unix socket is the credential. Anything with read access to $COVENANT_HOME/sock can submit intents.

Reference implementation

The covenant-ipc Rust crate provides read_frame and write_frame helpers plus the Request and Response enums. See CLI for an end-to-end example using both.

Related

  • HTTP API — the same surface for clients that prefer JSON over HTTP.
  • Security model — what the socket-as-credential design costs you.