Session Handshake (Crux Daemon)
May 24, 2026 · View on GitHub
Crux Daemon supports the VaultCrux Session Handshake v1 protocol — one endpoint
and one MCP tool that together give an agent a receipted plan describing
what it's allowed to do. This document is the operator-facing summary —
where state lives on disk, what the wire formats look like, and how to
verify a session plan or invocation receipt outside the daemon. The
canonical protocol invariants are encoded in the crux-session crate
(crates/crux-session/src/lib.rs).
What Crux Daemon Gives You
- One MCP tool:
cuecrux_session. Registered at the head of thetools/listcatalogue; collapses per-service discovery into one call. - One HTTP endpoint:
POST /session(unversioned path; local-daemon specific, matches master-plan §5.1). - One verification endpoint:
POST /invocation/verify. Decodes a hex-encoded invocation receipt and returns the governance verdict. - Durable session state under
$CORECRUXD_DATA_DIR:.install-uuid— per-install random UUID; hashed into the principal.sessions/{session_id}.json— one sealed plan per session.session-events.jsonl— append-only sealed-event log (one JSON line perSessionPlanSealedorInvocationReceiptedevent).
Plans on Crux Daemon run in "local" receipt mode: BLAKE3 hash over canonical
CBOR, no ed25519 signature. Local mode covers integrity; signed plans
are a hosted-only thing because the local-daemon threat model trusts the local
machine.
Opening a session
POST /session HTTP/1.1
Host: localhost:14800
Content-Type: application/json
Accept: application/json
{
"client_id": "my-agent",
"client_version": "1.2.3",
"accepts": ["application/json"],
"intent": "document_ingest",
"hints": { "prefer_bulk": false }
}
Response:
HTTP/1.1 200 OK
Content-Type: application/json
X-CueCrux-Session-Id: 0102030405060708090a0b0c0d0e0f10
X-CueCrux-Plan-Hash: b8e1...
Cache-Control: no-store
{ ...SessionPlan... }
The response body is the canonical JSON form of the SessionPlan; the
X-CueCrux-Plan-Hash header mirrors receipt.hash. All subsequent calls
that should chain to this session include X-CueCrux-Session-Id in their
headers (or the equivalent in MCP metadata).
What's in a SessionPlan
Fields an agent should care about:
| Field | Meaning |
|---|---|
session_id | Opaque 16-byte ULID; used as the bearer for subsequent calls. |
capability_graph | Array of {cap, prefer, shape, min_tier, cost_class, impl_path}. Everything the passport is entitled to invoke this session. |
capability_graph_hash | BLAKE3 over the canonical-CBOR of capability_graph. Exposed so MemoryCrux + audit tools can index by surface. |
channels | {bulk, mcp} — where to route calls. bulk is null on Crux Daemon until Layer 2 ships. |
receipt.hash | BLAKE3 over canonical-CBOR of the plan with hash/signature/signer_kid zeroed. |
receipt.mode | "local" on Crux Daemon. "verified" on hosted, with an ed25519 signature over the hash. |
intent_hint | Echoes back the intent field from the request, if supplied. |
The Crux Daemon capability graph is shaped by:
- Affinity — the passport has
["*"]so every catalogue entry passes the affinity filter. - Tier — Crux Daemon runs at tier
"local". Catalogue entries withmin_tier: "free"or above are filtered out. Baseline free capabilities (session_context,journal_append, etc.) are in;retrieve/proof_document/audit_replayare hosted-only.
Invocation receipts
Every call made under an active session should produce an
InvocationReceipt — a 200-byte receipt chained to the plan via
parent_plan_receipt_hash.
To verify one:
POST /invocation/verify HTTP/1.1
Host: localhost:14800
Content-Type: application/json
{
"invocation_id": "<16 bytes hex>",
"session_id": "<16 bytes hex>",
"parent_plan_receipt_hash": "<32 bytes hex>",
"capability": "retrieve",
"channel": "bulk",
"invoked_at": 1745000001000,
"completed_at": 1745000001100,
"input_hash": "<32 bytes hex>",
"output_hash": "<32 bytes hex>",
"outcome": "ok",
"receipt_hash": "<32 bytes hex>"
}
Response:
{
"verified": true,
"integrity_ok": true,
"capability_ok": true,
"channel_ok": true,
"governance_faults": [],
"parent_plan_found": true,
"parent_plan_principal_id": "ce:a4f3b1c2:tester"
}
The endpoint returns 200 even when the verdict flags governance faults
(wrong capability, wrong channel). Master-plan §8.2 — faults are
evidence, not reasons to drop the receipt. The caller decides what
enforcement to apply.
On-disk format
Crux Daemon is designed so that an operator can jq through state without
spinning up a database:
# Every session you've ever opened:
ls "$CORECRUXD_DATA_DIR/sessions/"
# Sealed events in write order (1 JSON line each):
cat "$CORECRUXD_DATA_DIR/session-events.jsonl" | jq -r .event_type | sort | uniq -c
Session files are rewritten atomically (temp-file + rename) on every close/revoke; the event log is append-only and fsync'd per write.
Verifying a receipt offline
The crux-session crate's CBOR + BLAKE3 primitives are pure; a verifier
can be built with zero I/O. Sketch:
use crux_session::{plan_receipt_hash, verify_invocation_receipt, SessionPlan};
let plan = SessionPlan::from_canonical_cbor(&plan_cbor_bytes)?;
// Plan-level integrity:
assert_eq!(plan_receipt_hash(&plan), plan.receipt.hash);
// Invocation-level chain:
let verdict = verify_invocation_receipt(&receipt, &plan);
assert!(verdict.verified_overall());
The TypeScript mirror at @cuecrux-shared/session produces byte-identical
canonical CBOR, so the same receipt verifies in either runtime.
Feature flags
Crux Daemon has no plan-level feature flags at M6 — everything is always on. On
hosted the session feature is gated behind FEATURE_SESSION_HANDSHAKE
per tenant; Crux Daemon ships with it enabled by default.
What's missing (follow-ups)
- Layer 2 bulk channel. Crux Daemon surfaces
channels.bulk: null; the HTTP/2 + CBOR bulk transport is a separate plan. - Automatic MCP interceptor. Invocation receipts are minted by an agent that chooses to chain them; there is no built-in trap-door that silently mints one for every tool call. See the M4 deferred-to-follow-up note in the ExecPlan.
- Hosted import migration. The import event type and the
imported_principal_maptable are pre-positioned for Phase 8; the actual upload pipeline is not yet wired. The persistedce:principal prefix remains as a wire-compatibility identifier for existing local receipts.