fig. 00 — five rings, gates never aligned. The dot is the LLM.
77end-to-end chaos scenarios
5deterministic guardrail rings
1key destroyed = customer erased
0double-disbursements tolerated
Every shortcut becomes an incident report.
INCIDENT #001 the agent retried a failed call. The loan disbursed twice.
RESOLVED idempotency keys inside an orchestrated saga — a retry cannot double-write.
INCIDENT #002 a customer's name surfaced in graph state, checkpoints, logs and traces.
RESOLVED one PII vault, crypto-shredded — erasure is the destruction of a single key.
INCIDENT #003 the prompt said "only disburse after acceptance." The prompt lost.
RESOLVED five deterministic guardrail rings the LLM cannot route around.
INCIDENT #004 the write committed; the network ate the 200. Did it happen?
RESOLVED transactional outbox + idempotent projection — the ledger converges.
BALANCEDthe LLM can be wrong, safely.
The fix is not a better prompt. It's architecture.
A guardrail is only a guardrail if the LLM cannot route around it. Anything the model can skip by phrasing a tool call differently is a convention, not a control.
This isn't hypothetical.
A full conversational loan agent — LangGraph and MCP over a durable, event-driven banking core — running entirely on a laptop with onedocker compose up. Below: follow one request through it, hop by hop — rendered from the samearchitecture.drawio that documents the repo.
Eventing · Key Custody
Core Banking Services
MCP Server Layer
MCP Host
Identity Plane ⤷ p.2
OpenBao 2.6
per-party keys · crypto-shredding (:8200, TLS)
party-api :5043 (HTTPS)
BIAN Party Reference Data Directory · PII as ciphertext · blind indexes · right-to-erasure (crypto-shred)
A customer types “yes — disburse it.” From here on, every hop is a place this request could double-pay, leak, or lie.
02 / 12 · POST /invocations · Bearer token
The UI adds nothing and knows nothing: one endpoint, and the customer’s own token rides along.
03 / 12 · resume from checkpoint
LangGraph wakes the journey exactly where it paused — state lives in Postgres, not in the prompt window.
04 / 12 · the LLM proposes
The model decides to call submit_application. Deciding is the only power it has.
05 / 12 · MCP streamable HTTP
The proposal crosses to the tool server still speaking as the customer — no service account, no ambient authority.
06 / 12 · gate 1 · JWKS verify
A detour before anything happens: is this token genuine, unexpired, issued to this person? No proof, no tool.
07 / 12 · REST /applications/*
What was a prompt is now an API call — typed, logged, and subject to every rule the bank already trusts.
08 / 12 · gate 2 · party gate
Does this journey belong to this caller? If the check cannot even run, the answer is no. Fail closed.
09 / 12 · disburse + idempotency key
The command lands carrying an idempotency key, and loans-api answers. Ask twice, pay once — retries are safe by construction.
10 / 12 · meanwhile · outbox → SNS
Behind the scenes, the event had already committed in the same transaction as the write. It cannot be lost.
11 / 12 · SQS backstop + DLQ
And it converges by a second road: even if the direct response had been eaten, the ledger still heals itself.
12 / 12 · the response — the road home
And back: tool result → LangGraph reply → streamed tokens. The customer sees the money land, over the same guarded road in reverse.
∎
Twelve hops there and back. Five deterministic gates. Zero trust in the model’s good behavior — and 77 chaos-tested scenarios proving it. That is what lets an LLM near real money. Read how each hop is built, below.
Software engineer. I build event-driven banking systems and the agentic AI that can be trusted to sit on top of them. Currently writing the production loan-agent series.