Χ Ά Ρ Ω Ν

No coin,
no crossing.

MCP Authorization Gateway

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.

Embark
The myth

The ferryman who
asks for an obol.

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
charon · v0.1.0-beta · :18484 oauth · :18486 obol
$ charon obol mint principal_8f3a… Obol minted successfully ID: obol_9c2e… Principal: principal_8f3a… Audience: http://127.0.0.1:18486 Expires: 7d Token: obol_····················

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.

The three fares

Every passenger pays
a different price.

ΣΥΓΓΡΑΦΕΎΣ

The Author

propose profile

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.

  • memory.read · search · thread
  • memory.branch · commit · propose
  • memory.review · merge · write
ΚΡΙΤΉΣ

The Reviewer

review profile

The independent check. Inspects the exact changeset, binds its verdict to that snapshot, and only then steers the merge into protected shared memory.

  • memory.read · search · thread
  • memory.review · merge · proposal.review
  • memory.branch · commit · write
ΑΝΑΓΝΏΣΤΗΣ

The Reader

readonly profile

Travels light. Reads accepted memory at exact heads — never touches the cargo, never steers. Wildcard grants stop at the shoreline: read-only.

  • memory.read · search · thread
  • branch · commit · propose
  • review · merge · write
How it works

Every crossing,
accounted for.

01

Connect through the gateway

MCP clients — ChatGPT, Claude, OpenClaw — connect to Charon, never to Lethe. Lethe Git stays loopback-only on :18485.

02

Pay the obol

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.

03

Authors write on owned refs

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.

04

Independent review, always

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.

05

The signed crossing

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.

06

The ledger remembers

Every mutation is audit-logged fail-closed — intent recorded before the write. charon ledger verify re-walks the hash chain whenever you want proof.

MERGE AUTHORIZATION · memory-git-merge/v2single-use
{
  "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"
}
// HMAC-SHA256 · Lethe consumes the nonce atomically with the CAS
The gate

What the ferryman
enforces.

Α

Owned refs & CAS

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.

Π

Protected shared memory

refs/shared/* rejects direct commits from everyone. The only path in is a reviewed, signed merge. There is no back door, in either codebase.

Κ

Independent review

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.

Ν

Replay-proof merges

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.

Μ

Exact context projection

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.

Λ

Fail-closed audit ledger

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.

The price of passage

What each scope
buys.

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.

memory.read
The shoreline. Read accepted history, context, and refs.
every passenger
memory.search
The shallows. Search accepted memory across granted projects.
every passenger
memory.branch
A skiff of your own. Create owned agent, session, or topic refs.
authors
memory.commit
Cargo space. CAS changesets onto refs you own.
authors
memory.propose
A seat on the ferry. Ask for a crossing into protected memory.
authors
memory.review
The lantern. Inspect cargo and record a binding verdict.
reviewers
memory.merge
The helm. Steer an approved crossing into refs/shared/*.
reviewers · rarest fare
memory.write
The master's key. Trusted direct operations — still ledgered.
operators only
"Lethe remembers. Charon carries."The project in one line
Get started

The ferry leaves
in two commands.

Lethe Git first, then Charon. Credentials are generated, never hardcoded.

1 · Lethe Git$ sh scripts/prepare-local-memory-git-env.sh && docker compose -f docker-compose.git.yml --env-file .env.git up -d
2 · Charon$ ./setup.sh # keys, principals, obols, both gateways
3 · Connect an agent$ openclaw mcp add charon --url http://127.0.0.1:18486/mcp --transport streamable-http --header "Authorization=Bearer ${TOKEN}"
Documentation the full charon guide