- LFE 91.5%
- Shell 2.4%
- Python 2%
- Erlang 2%
- C 1.1%
- Other 1%
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> |
||
|---|---|---|
| .github/workflows | ||
| .hence | ||
| apps/cbcl_router | ||
| config | ||
| docs | ||
| plans | ||
| scripts | ||
| specs | ||
| .dockerignore | ||
| .gitignore | ||
| cbcl-crypto-challenge.dis | ||
| cbcl-rs.sha | ||
| Dockerfile | ||
| fly.toml | ||
| LICENSE | ||
| Makefile | ||
| README.md | ||
| rebar.config | ||
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.
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.
Related projects
cbcl-rs— reference Rust CBCL implementation: parser, canonicaliser, R1–R5 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/v1WebSocket 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:
- specs/SPEC-001-router-spec.md — request flow + capability model
- specs/SPEC-009-dialect-distribution.md —
(meta (teach/query/subscribe))grammar - docs/DEPLOY-FLY.md — current deployment
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
enacland 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
- macOS:
- pkg-config (rebar.config resolves sodium flags via
pkg-config --cflags/--libs libsodium). For environments without pkg-config, setCBCL_SODIUM_CFLAGS/CBCL_SODIUM_LDFLAGSbefore 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, R1–R4 verifiers, Lean 4 proofs). This LFE port tracks cbcl-rs's canonical EBNF at
cbcl-rs/docs/cbcl-grammar.ebnfand passes the JSON-oracle corpus derived from the Lean-verifiedcbcl-parsebinary. - 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.