Repository navigation
Architecture
Marcus Richards edited this page Oct 7, 2026
·
2 revisions
Stdlib-only Python. There are no runtime dependencies: the HTTP layer is http.server, not Flask. State lives in process memory.
agent (LLM, agent/react.py)
│ tools only (agent/tools.py): attempt_spend, deliver, check_permit,
│ delegate_subpermit, check_approval, complete_approved_spend
▼
SpendPipeline (permit/flow.py) ──► PermitStore (permit/permit.py)
│ authorize (only caller) gate, reserve, delegate, tighten,
│ approvals, revoke
▼ │
PayPalClient ◄── ReleaseVerifier ──────────┤ receipts
(mock | sandbox) (settlement/verifier.py) ▼
capture (only caller) Ledger (permit/ledger.py)
| Path | Role |
|---|---|
permit/permit.py |
PermitStore: grant, four-clause gate, delegation, tighten, approvals, e-stop, subtree revoke, carve release. No PayPal imports |
permit/flow.py |
SpendPipeline: the only path from attempt to hold. Approval continuation, e-stop/cascade voiding |
permit/ledger.py |
Hash-chained receipt log |
settlement/verifier.py |
ReleaseVerifier: predicates, the only capture path, reconcile, retry cleanup |
settlement/paypal_client.py |
PayPalClient interface + MockPayPalClient (call recording, fault injection) |
settlement/sandbox_client.py |
SandboxPayPalClient: real REST against api-m.sandbox.paypal.com
|
server.py |
JSON service (see HTTP API Reference) |
dashboard/ |
Live view, delegation graph, e-stop, revoke-cascade, cap tighten, approve/deny |
agent/ |
ReAct loop + permit-bound tools |
scenarios/sla_escrow.py |
Agent-to-agent SLA settlement scenario |
trace.py |
HTTP smoke driver for server.py (free loopback port) |
demo*.py |
Demos. See Running the Demos |
- Rail swap is the only difference between mock and sandbox. The pipeline and verifier are shared.
- Locks: a per-permit lock serializes reservations. Lock order is store → parent and child → ancestor, documented as cycle-free.
-
Counters, not replay:
reserved_centsandcaptured_centsare fields on eachPermit, updated under its lock. They are not recomputed from the ledger. -
Idempotency keys: authorize
{permit_id}:{auth_id}:authorize, capture{permit_id}:{auth_id}:capture. -
Duck-typed approval signal:
flow.pycatchesNeedsPayerApprovalby class name, so the permit package does not import settlement exceptions.
| Port | Who |
|---|---|
| 8741 |
server.py default (trace.py now uses a free port) |
| 8471 | Dashboard default (demo_six_beat.py, demo_delegation.py, standalone) |
| 8474 |
demo_approval.py dashboard |
| Env var | Used by |
|---|---|
PERMIT_PAYPAL_CLIENT_ID / PERMIT_PAYPAL_CLIENT_SECRET
|
server.py --sandbox |
PERMIT_PAYPAL_MERCHANT_ID |
Merchant binding (optional, recommended) |
PERMIT_GROK_CLI |
LLM runner path for live agent demos. Required, no default |
Permit · payment authority for AI agents · MIT · Wiki facts verified against commit 9900817 on 2026-10-07 (America/Edmonton). Where this wiki and the code disagree, the code wins.
Concepts
- Overview and Problem
- The Four-Clause Gate
- Spend Pipeline
- Delegation and Sub-Permits
- Principal Approvals
- Tighten-Only Narrowing
- E-Stop and Revocation
- Claim Ledger
- UNKNOWN and Reconcile
Using it
Design and status
Reference