Module-by-module walkthrough of the Lumen codebase. For the product pitch, see README.md. For the hands-on demo, see DEMO.md.
Lumen is a pipeline of four deterministic engines wrapped in two transport surfaces. A clinical recommendation enters via either MCP or A2A, fans out into the four engines, and exits as a signed FHIR transaction bundle.
┌─────────────────────────────────────┐
│ transport surfaces │
│ ┌──────────────┐ ┌────────────┐ │
BYO agent ────►│ │ A2A │ │ MCP │ │◄─── direct
│ │ orchestrator │ │ FastMCP │ │ (Claude / Cline / etc.)
│ │ FastAPI │ │ workers=1 │ │
│ └──────┬───────┘ └─────┬──────┘ │
│ └─── self-fan ───┘ │
└────────────────┬────────────────────┘
│
┌──────────────────────────┼──────────────────────────┐
│ │ │
▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌─────────┐
│ VERIFY │ │SIMULATE │ │ VOI │
│ Z3 SMT │ │ JAX MC │ │ entropy │
│ Garden │ │ DoWhy │ │ Sobol │
│ │ │ EconML │ │ │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
└────────────┬────────────┴──────────────────────────┘
│
▼
┌─────────────┐
│ ADVERSARY │ (only LLM call site)
│ RAG + LLM │
│ → re-VERIFY│
└──────┬──────┘
│
▼
┌─────────────┐
│ COMPOSE │
│ FHIR R4 │
│ ed25519 │
│ SHA-256 │
└─────────────┘
The MCP surface exposes five tools to any compatible client (Claude, Cline, MCP-aware orchestrators). Tools:
verify_recommendation→ SMT verdictsimulate_project_trajectory→ 30-day JAX projectionvoi_rank_information_actions→ ranked information-acquisition listadversary_attack→ LLM-drafted counter, re-verifiedcompose_brief→ final signed FHIR bundle
Mounted at both / and /mcp (consistency with mixed routing in upstream MCP clients). Hard rule: --workers 1. FastMCP's in-process session cache breaks under multi-worker uvicorn; CI rejects any other value via tests/test_ci_invariants.py::test_workers_value_is_always_one.
FastAPI service that publishes an A2A agent card at /.well-known/agent-card.json. Receives a consult, self-fans-out into the MCP, runs the full pipeline, and returns a workspace-scoped task response. The fan-out is in-process — the orchestrator does not require the MCP to be a separate deployment.
The Garden is the heart of Lumen. Each guideline is authored in a Python DSL (garden/source/<guideline>.py) whose AST compiles to SMT-LIB2 (garden/compiled/<guideline>.smt2). At verify time, the patient state is encoded as a model, the recommendation as an assertion, and Z3 discharges the theorem set.
Key files:
garden/dsl/— primitive operators, predicates, and DSL types (Patient,Drug,Lab,Guideline, etc.)garden/source/— one file per guideline (NICE NG28, NG203, Beers 2023, STOPP/START v2, KDIGO AKI 2023)garden/compiler.py— AST walker that emits SMT-LIB2garden/compiled/— checked-in.smt2artefacts so re-checking is hermetic
Decidability is enforced: every theorem is in QF_NIA (quantifier-free non-linear integer arithmetic) and the DSL refuses operators that escape that fragment. See tests/test_garden_decidability.py.
A parquet-backed Polars dataframe of workspace patients with PSM (propensity-score matching) for sub-population prior selection. When the simulation engine asks for "patients like Margaret" (CKD 3b + T2DM + HTN, age 70-75), the cohort module returns the matched set if N ≥ 30, otherwise falls back to named meta-analysis priors (and labels the brief accordingly).
Differential-equation PK/PD models compiled with JAX for fast Monte Carlo. Inputs: patient state, candidate drug, dose, frequency. Output: posterior over per-day outcomes (eGFR, serum creatinine, AKI flag, etc.) with 10 000 samples. Causal identification via DoWhy/EconML.
The only LLM call site in the entire repo. shared/llm/adversary_prompt.py issues one call asking the model to attack the current recommendation — propose a better alternative. Whatever it returns is re-encoded as a Garden assertion and re-discharged by Z3. The LLM cannot ship anything past the theorem prover.
Originally extracted as a common module in the source monorepo. Vendored into this repo so it builds standalone.
shared/fhir/— async HTTP FHIR client (R4 transactions), resource builders, intra-bundle reference resolution, graceful 422 retry for proxies that reject_total=accurate.shared/audit/— ed25519 signer/verifier, SHA-256 forward-hash chain,Chain.verify()that raises on tamper, swallowing audit logger primitive.shared/llm/— single LLM client wired throughlitellmwith a hardcoded-model-string CI gate.shared/types/— Pydantic-strict shared types (PatientRef,Recommendation,Verdict,Provenanceenvelope).
| Invariant | Test | What it greps |
|---|---|---|
| Workers count | test_workers_value_is_always_one |
Any --workers N where N != 1 |
| LLM call sites | test_no_hardcoded_llm_model_strings |
claude-3, gpt-4, o1-, haiku, gemini- outside shared/llm/ |
| FHIR fullUrl scheme | test_fhir_bundles_urn_uuid |
Any fullUrl that is not urn:uuid: + UUIDv4 |
| Bundle reference closure | test_fhir_bundles_resolve |
Any intra-bundle reference that doesn't resolve |
| FHIR R4 transaction shape | test_fhir_transactions |
Any bundle that fails strict R4 schema |
If you change LLM call sites, worker counts, or FHIR conformance, the build fails before the test suite even runs.
Two Fly.io apps:
Dockerfile.mcp+fly.toml→ Lumen MCPDockerfile.orchestrator+fly-orchestrator.toml→ Lumen A2A orchestrator
docker-compose.yml boots both locally with the right network wiring. See DEMO.md for the demo invocation.
For honesty: these are the corners where the codebase has more friction than the pitch admits.
- The DSL is incomplete. ≈260 theorems is enough for the five-guideline corpus and the demo patients, but real adoption needs another ~10× theorem count and more sophisticated temporal operators (Allen-style intervals beyond before/after/during).
- Cohort PSM is not adversarially tested. If the workspace is small or skewed, the cohort prior can be wrong and the brief silently uses it. Frances Doyle's
data_insufficientpath is the only case that explicitly surfaces this; better detection is on the roadmap. - The adversary path can be jailbroken. The LLM is asked to attack the recommendation; if it refuses, the brief notes "adversary unhelpful" but the Z3 verdict still ships. Refusal does not invalidate the certificate — it just means we didn't get a free counter-test.
agent-config/contains reference material from earlier iterations. Not used at runtime; preserved because the DSL evolved out of those rules and the lineage is useful to read.