cbcl message router in lisp flavoured erlang
  • LFE 91.5%
  • Shell 2.4%
  • Python 2%
  • Erlang 2%
  • C 1.1%
  • Other 1%
Find a file
Hugo O'Connor 9b0b5577af
spec(SPEC-041): pluggable message persistence for receipt log
Define cbcl_storage_backend behaviour with durability, append-only,
idempotency, schema-versioning, and crash-safety requirements per
PROTO-001 (USDD). Default backend disk_log with fsync-then-ack
semantics for RPO=0; ETS retained for tests. Dialect store remains
in-memory (separation enforced). On-disk format is canonical CBCL
S-expression so the Lean-verified parser is the recogniser at the
storage trust boundary (LangSec).

Companion hence plan in plans/message-persistence-impl.spl coordinates
the three implementation slices: behaviour + ETS adapter, disk_log
backend, operational tooling.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 10:41:47 +10:00
.github/workflows feat(SPEC-008): vector-diff conformance gate + rollback runbook 2026-04-29 16:34:54 +10:00
.hence hence: complete task 'shell-enrollment-session' 2026-04-25 20:22:09 +10:00
apps/cbcl_router fix: reject arg-bearing (query (list ...)) forms 2026-05-12 12:58:46 +10:00
config docs: rename project cbcl-lfe-router → cbcl-router 2026-05-11 17:07:47 +10:00
docs docs(pitch): remove old pitch / explainer decks 2026-05-12 14:04:16 +10:00
plans spec(SPEC-041): pluggable message persistence for receipt log 2026-05-13 10:41:47 +10:00
scripts docs: rename project cbcl-lfe-router → cbcl-router 2026-05-11 17:07:47 +10:00
specs spec(SPEC-041): pluggable message persistence for receipt log 2026-05-13 10:41:47 +10:00
.dockerignore build: fix Dockerfile for clean Linux builds from a Mac checkout 2026-04-29 21:07:21 +10:00
.gitignore build: bump cbcl-rs pin to origin/main HEAD; untrack rebar3.crashdump 2026-05-11 16:49:01 +10:00
cbcl-crypto-challenge.dis hence: complete task 'core-challenge-msg' 2026-04-25 12:16:40 +10:00
cbcl-rs.sha build: bump cbcl-rs pin to origin/main HEAD; untrack rebar3.crashdump 2026-05-11 16:49:01 +10:00
Dockerfile build: bump Dockerfile CBCL_RS_SHA to match cbcl-rs.sha 2026-05-11 16:51:52 +10:00
fly.toml docs: update fly config 2026-04-20 17:14:51 +10:00
LICENSE README: make standalone; add LICENSE 2026-04-21 10:25:09 +10:00
Makefile build: add Makefile mirroring hark's task layout 2026-05-12 13:42:07 +10:00
README.md docs: add HTTP-API reference, split Status into docs/status.md 2026-05-12 13:51:41 +10:00
rebar.config docs: rename project cbcl-lfe-router → cbcl-router 2026-05-11 17:07:47 +10:00

cbcl-router logo

cbcl-router

A CBCL router on BEAM with capability-based routing between untrusted producers and trusted agents, a Lean-verified parser and resource-bounded dispatch.

License: Apache-2.0 Stage: experimental Auth: unaudited Parser: 156/156 vs Lean oracle Runtime: OTP 26+ / LFE

cbcl-router is an HTTP + WebSocket router for CBCL — the Common Business Communication Language — implemented in Lisp Flavoured Erlang on Erlang/OTP. Producers authenticate over HTTPS and POST CBCL messages to /ingress/v1/messages; agents register capabilities over the /agent/v1 WebSocket; the router dispatches each ask to a matching agent, appends every message to a per-receipt log, and returns receipt IDs to producers.

The router is the security + routing boundary between untrusted ingress (Slack, webhooks, CLI tools) and trusted internal agents. Every message crossing it is parsed against cbcl-rs's Lean-verified grammar, canonicalised, authenticated, and resource-bounded before it reaches an agent process. See Status for what is and isn't implemented.

  • cbcl-rs — reference Rust CBCL implementation: parser, canonicaliser, R1R5 validators, and Lean 4 proofs. This router tracks cbcl-rs's canonical EBNF and passes its JSON-oracle corpus.
  • hark — local CLI and per-user daemon for agents connecting to this router. The daemon owns the /agent/v1 WebSocket and the inbound queue per handle; the CLI is a thin loopback client.

Architecture

 PRODUCERS (anywhere)                                        CONSUMERS (anywhere)
 ─────────────────────                                       ─────────────────────

 Slack slash / DM ─────┐
                       │
 chat/webhook          │
 bridges               │
                       │    HTTPS                                 WSS
 CLI agent ────────────┤   POST /ingress/v1/messages    /agent/v1
 (claude code, etc.)   │   Authorization: Bearer shr_*
                       │
 webhooks / cron ──────┤   ┌───────────────────────┐
                       │   │                       │
 other bridges ────────┼──▶│  cbcl-router          │◀──────── hello-elf agent
                           │  cbcl-lfe.anuna.io    │          (elf:hello)
 GET /receipts/:id ───────▶│  (Fly, sin)           │
                           │                       │◀──────── calendar-agent
                           │  ingress → dispatch   │          (elf:calendar)
                           │  → WS fan-out         │
                           │  → receipt log (ETS)  │◀──────── ticket-agent
                           │                       │          (elf:ticket)
                           │  shared-secret auth   │
                           │  (JWT / DID planned)  │◀──────── ... any agent
                           └───────────────────────┘          with a bearer

Producers authenticate once over HTTPS. Agents register a capability over WSS. The router fans messages between them and persists receipts. Each WebSocket is one BEAM process (~4 KB); a disconnect kills that process and the dispatcher learns via registry monitor. Capability ≡ dialect — agents only see frames for the dialects they registered, and producers can't address agents by name.

Endpoints

Endpoint Method Purpose
/ingress/v1/messages POST Producer entry — submit a CBCL ask, get {receipt_id, dispatched_to} back
/receipts/:receipt_id GET Retrieve the full message sequence for a receipt as newline-delimited CBCL
/agent/v1 GET (WS) Agent connection — hello, dispatch, reply, meta frames
/auth/v1/* GET / POST Challenge issuance, signed-challenge verification, router pubkey
/admin/v1/* GET / POST Enrolment mnemonics, agent listing, revocation
/healthz, /readyz GET Liveness + readiness probes
/metrics GET Prometheus exposition

Full request/response contract for the public endpoints in docs/HTTP-API.md; auth-shell surfaces in docs/AGENT-AUTH.md.

For the OTP supervision tree, request flow, deployment topology, and SPEC-009 dialect-distribution detail, see:

Layout

cbcl-router/
├── rebar.config                          Project + dependency manifest
├── apps/cbcl_router/
│   ├── src/
│   │   ├── cbcl-router-app.lfe           application behaviour
│   │   ├── cbcl-router-sup.lfe           top-level supervisor
│   │   ├── cbcl-parser.lfe               LFE facade over cbcl-erl NIF (cbcl-rs)
│   │   ├── cbcl-csexp.lfe                RFC 9804 CSEXP parser
│   │   ├── cbcl-vector-diff.lfe          differential harness + pipeline evaluator
│   │   ├── cbcl-ingress-handler.lfe      HTTP POST /ingress/v1/messages
│   │   ├── cbcl-agent-ws.lfe             WebSocket /agent/v1
│   │   ├── cbcl-dispatcher.lfe           dialect → agent routing + meta dispatch
│   │   ├── cbcl-agent-registry.lfe       connected-agent tracking
│   │   ├── cbcl-storage.lfe              receipt log (ETS for now)
│   │   ├── cbcl-dialect-store.lfe        SPEC-009 content-addressed dialect registry
│   │   ├── cbcl-subscription-store.lfe   SPEC-009 push-subscription registry
│   │   ├── cbcl-sexpr-render.lfe         canonical SExpr renderer (digest input)
│   │   ├── cbcl-auth.lfe                 shared-secret auth stub
│   │   └── cbcl-health-handler.lfe       /healthz + /readyz
│   └── test/                             205 tests (parser oracle, crypto, auth, dispatch, SPEC-009)
├── config/sys.config                     runtime config
├── scripts/
│   ├── fetch-test-vectors.sh             pin oracle corpus from cbcl-rs
│   ├── fuzz_against_lean.py              live fuzz vs Lean binary
│   ├── demo.sh                           end-to-end round trip
│   └── keygen.sh                         shared-secret generator (stub)
└── docs/
    ├── PARSER-STATUS.md                  156/156 parity + fuzz regime
    ├── DEPLOY-FLY.md                     deployment guide
    └── AGENT-AUTH.md                     bootstrap, invite, enrol, revoke

Building

Requires:

  • Erlang/OTP 26+, rebar3, and the rebar3_lfe plugin
  • libsodium + headers (used by enacl and the in-tree Ristretto255 NIF for SPEC-007 agent auth):
    • macOS: brew install libsodium pkg-config
    • Debian/Ubuntu: sudo apt-get install libsodium-dev pkg-config
  • pkg-config (rebar.config resolves sodium flags via pkg-config --cflags/--libs libsodium). For environments without pkg-config, set CBCL_SODIUM_CFLAGS / CBCL_SODIUM_LDFLAGS before invoking rebar3.
make build                        # compile LFE + Erlang + Ristretto NIF
make shell                        # interactive rebar3 shell
make test                         # run eunit suite (205 cases)
make release                      # production OTP release

Run make help for the full target list (parser-check, pin-check, fuzz, demo, clean).

Parser-parity check (requires the cbcl-rs test vectors; fetched on demand, not vendored):

make parser-check
# → GRAND TOTAL: total=156 pass=156 fail=0

End-to-end round trip (HTTP ingress → WS agent → receipt):

make demo

Deploy with fly deploy. See docs/DEPLOY-FLY.md for the full procedure and docs/AGENT-AUTH.md for the agent enrolment walkthrough.

Status

Working substrate. The router parses, dispatches, distributes dialects, authenticates agents via SPAKE2 onboarding + Ed25519 challenge/response, and stays up under supervision; 205 tests pass.

See docs/status.md for the full implemented / missing checklist and capacity estimates.

References

  • cbcl-rs — reference CBCL implementation (parser, canonicaliser, R1R4 verifiers, Lean 4 proofs). This LFE port tracks cbcl-rs's canonical EBNF at cbcl-rs/docs/cbcl-grammar.ebnf and passes the JSON-oracle corpus derived from the Lean-verified cbcl-parse binary.
  • O'Connor, "CBCL: Safe Self-Extending Agent Communication", arXiv:2604.14512, LangSec 2026. "Formally bounding what agents can express to each other is a precondition for oversight."
  • docs/PARSER-STATUS.md — parser-parity report and fuzz regime.
  • docs/cbcl-router-deck.md — technical presentation.
  • docs/cbcl-router-pitch-deck.md — pitch presentation.

License

Apache-2.0. See LICENSE.