Agents never touch Lethe directly. Charon checks every credential, scopes every principal, and ferries each reviewed change across. Nothing reaches shared memory unpaid — unscoped, unreviewed, or unsigned.
In the old stories, the dead reached the river and found Χάρων — Charon waiting with his skiff. The crossing was never free: one obol, placed with the dead for exactly this purpose. Those who could not pay wandered the shore — present, but never across.
Every agent that reaches Charon is asked the same question. The modern obol is a credential: an OAuth authorization with S256 PKCE, or an audience-bound Obol token. No credential, no crossing — the shore is all an unauthenticated client will ever see.
Payment was never about the coin. It was proof the crossing was intended, accounted, and allowed.Why the gateway exists
The obol is the fare: minted for one principal, bound to one gateway, digest-stored, revocable. The token value is shown once — then never again.
Writes durable memory on refs it owns, then asks for a crossing. Can never review or merge its own work — the ferryman refuses on principle.
The independent check. Inspects the exact changeset, binds its verdict to that snapshot, and only then steers the merge into protected shared memory.
Travels light. Reads accepted memory at exact heads — never touches the cargo, never steers. Wildcard grants stop at the shoreline: read-only.
MCP clients — ChatGPT, Claude, OpenClaw — connect to Charon, never to Lethe. Lethe Git stays loopback-only on :18485.
OAuth authorization-code with mandatory S256 PKCE on :18484, or an audience-bound Obol on :18486. Tokens resolve to a principal with exact project grants and scopes.
Changesets commit with compare-and-swap and idempotency keys. Every operation is semantically validated at Charon — and again at Lethe before it becomes immutable history.
A different principal must approve. Self-review and self-merge are denied in code — denied: self review, denied: self approval. Verdicts bind to the exact proposal snapshot.
Approved merges get a memory-git-merge/v2 envelope: HMAC-signed, 2-minute TTL, single-use nonce, bound to project, ref, heads, and proposal digest. Lethe verifies independently and consumes the nonce with the CAS — replays are rejected forever.
Every mutation is audit-logged fail-closed — intent recorded before the write. charon ledger verify re-walks the hash chain whenever you want proof.
{
"version": "memory-git-merge/v2",
"project": "my-project",
"ref": "refs/shared/main",
"expected_head": "cs_9f2a…",
"new_head": "cs_41bd…",
"proposal_id": "prop_77c1…",
"proposal_digest": "sha256:8d3e…",
"reviewer": "principal_2a7f…",
"merger": "principal_2a7f…",
"strategy": "fast-forward",
"expires_at": "+2m",
"nonce": "16b·random·single-use"
}
Agents commit only to refs they own — refs/agents/*, refs/sessions/*, refs/topics/*. Compare-and-swap on every write; conflicts surface, never silently lose work.
refs/shared/* rejects direct commits from everyone. The only path in is a reviewed, signed merge. There is no back door, in either codebase.
Approval must come from a non-originator principal and stay current — a later objection flips the outcome to blocked. Stale approvals die with the snapshot they reviewed.
Single-use nonces, short expiry, expected-head CAS, proposal-digest binding. A captured authorization is worthless — even if the ref cycles back to the same head.
memory_context_at reconstructs accepted memory at an exact ref head, with optional manifest pinning. Readers get precisely what was approved — nothing unmerged leaks in.
Hash-chained, MAC'd entries written before the mutation they describe. If the ledger can't write, neither can you. Anchor and verify it any time.
Scopes are the fares of the underworld, set per principal per project. Mutations demand an exact project grant — a wildcard buys the view, never the helm.
Lethe Git first, then Charon. Credentials are generated, never hardcoded.
$ sh scripts/prepare-local-memory-git-env.sh && docker compose -f docker-compose.git.yml --env-file .env.git up -d$ ./setup.sh # keys, principals, obols, both gateways$ openclaw mcp add charon --url http://127.0.0.1:18486/mcp --transport streamable-http --header "Authorization=Bearer ${TOKEN}"→ the full charon guide