Enforce human approval gates and audit trails for AI agents
Open-source security layer for AI agents: deny-by-default tool grants, human approval gates and a signed, hash-chained audit trail.
1.2.0Add to Favorites
Why it matters
Prevent AI agents from executing high-risk actions without human authorization by wrapping tool calls in deny-by-default governance, routing sensitive operations to separate approval roles, and maintaining a cryptographically signed, tamper-evident audit log that proves no agent can approve its own work.
Outcomes
What it gets done
Scan codebases to identify and classify risky agent capabilities by severity tier
Block unauthorized tool calls before execution using role-based skill grants
Route high-risk actions to human reviewers who cannot be the requesting agent
Generate verifiable audit trails with Ed25519 signatures and hash-chained event logs
Source
Get it from source
Spark does not host a copy of it.
Open sourceReports
Agent outcome reports
No reports yet
Overview
MakerChecker is an open-source security layer for AI agents. It sits in front of every tool call as a checkpoint and behind it as a signed ledger: an agent acts only through a role, runs only the skills it was granted, cannot exceed its limits and cannot approve its own work. Agents keep running in their existing framework, such as LangChain, the Claude SDK or CrewAI. Use it when an agent's consequential actions, such as deleting data, moving money, running shell commands or filing regulatory reports, need to be flagged, denied by default or held behind a named human approver, with an audit trail that can be verified offline. In the oncology example it does not judge whether an appeal will win or a funding stack is the best one.
What it does
MakerChecker is an open-source security layer for AI agents. The agent keeps running in its existing framework (LangChain, Claude SDK, CrewAI); MakerChecker sits in front of every tool call as a checkpoint and behind it as a signed ledger. It applies three controls.
- Deny by default: an agent acts only through a role and runs only the skills that role was granted. A call the agent is not allowed to make is denied before it executes, and the agent cannot exceed its limits.
- Human approval: high-risk skills go to a separate role, so an agent can never approve its own work. A separation-of-duties constraint binds the roles; for example, the processor who triages a case may not perform its medical review.
- Signed audit: every decision, allowed or denied, and every tool call commits to a hash-chained log. Each event is a SHA-256 over the RFC 8785 canonical JSON of the event, chained through prev_hash from genesis and Ed25519-signed. Changing any row breaks verification.
Skills are referenced as name@version (for example place-order@1) and carry a risk tier. Two enforcement primitives fail closed at the proxy. skill_not_granted refuses a call when the agent's role holds no grant for the skill. high_risk_requires_gate refuses a high-risk skill run inline on the proxy even when the role holds the grant: it must run through a governed flow with a preceding approval gate, and the flow engine will not publish a high-risk skill unless such a separation-enforcing gate precedes the step that uses it.
In the pv-icsr-processing and mdr-reportability-triage flows the approval gate is identity-mode (forbid_requester): the user who triggered a run gets a 403 when deciding it, and unauthenticated decisions are refused outright. A different, authenticated approver is needed for the gated action to proceed.
When to use - and when NOT to
The pattern the governed flows apply is a split by reversibility. Low-risk, reversible skills run before the gate: triaging and proposing candidates, verifying benefits, matching a funding stack, drafting a medical-necessity appeal. Benefits verification and appeal drafting commit nothing. Irreversible acts are published at riskTier high and are refused on the proxy: binding determinations that start a regulatory clock, transmitting a report to a regulatory gateway, submitting an appeal to a payer, and enrolling a patient into a copay program or charitable foundation. They run only through a governed flow with a preceding approval gate, where a named human signs each one before it executes. A role that holds no grant for a skill, such as the coordinator role with no submit or enroll grant, is refused by deny-by-default before the call reaches a tool body. A role that does hold the grant, such as the access-specialist role, still cannot run a high-risk skill inline on the proxy.
When a segregation-of-duties rule binds the preparer's role and the approver's role, a run that tries to have the same side approve is structurally blocked, and the violation lands in the audit chain.
Limits: in the oncology access flow, MakerChecker does not judge whether an appeal will win or whether a funding stack is the best one. A pharmacist who signs an enrollment the patient was not eligible for still creates the exposure. It forces a named human to attest each submission and enrollment and records who approved it on what basis: accountability, not adjudication.
The server is optional. The scanner, the embedded library and the server are three independent packages that enforce the same controls and write the same signed audit format, and any one can be adopted on its own.
Inputs and outputs
mc scan reads your code and flags every consequential action, such as deleting data, moving money, running shell commands and exfiltrating secrets. It classifies each by risk, names it against the real incident it resembles, and can write the governance code for you with --fix. It needs no install and nothing leaves your machine:
npx @makerchecker/scan .
The embedded library is installed with npm i @makerchecker/embedded. You define skills, roles, grants and agents with createGovernor(), defineSkill, defineRole, grant and defineAgent, then wrap a tool once with governedTool(agent, skillRef, fn). A denied call throws GovernanceDeniedError, whose code is for example skill_not_granted. Every decision, allowed or denied, is committed to the signed audit log.
The self-hosted server is started with docker compose up. It brings up Postgres, the server on localhost:3000 and a seeded demo, and prints two API keys, each shown once at first boot: an admin key, used by your agent to authenticate runs, and an officer key, used by a human reviewer to approve gated actions. The HTTP API includes POST /api/flows/ followed by the flow name and /runs to start a run, GET /api/approvals to see what is parked at a gate, a decision endpoint under /api/approvals for a given approval id that takes a JSON body of decision and reason, GET /api/runs followed by the run id to inspect a run, and GET /api/audit/verify to check the audit chain. The pv-icsr-processing flow parks at a medical-review gate; approving with the admin key that triggered the run returns 403 (forbid_requester), and the same decision from the officer key lets the action proceed. Locally, the server can run without auth by setting MAKERCHECKER_AUTH_DISABLED=1; running the oncology example script additionally needs the SDK built with corepack pnpm run build.
In the daily-cash-reconciliation flow, with GEMINI_API_KEY or ANTHROPIC_API_KEY set, the agents run on a real model that decides which granted skills to call; without a key the steps execute deterministically and the flow runs air-gapped.
Evidence is exported as a signed bundle. From the server container this is the audit export subcommand of the server CLI (node dist/cli.js, run through docker compose exec server), given the run id with --run and an output file with --out. For the pv-icsr-processing run, the bundle covers every agent action, the high-risk skills held behind the gate, grant state and the reviewer's recorded decision. It is verified offline, with no database and no trust in the process that produced it, by running npx @makerchecker/proof-verifier verify bundle.json.
Integrations
Drop-in connectors govern the tools you already have: a LangChain connector and a Claude Agent SDK connector, plus a TypeScript SDK (@makerchecker/sdk) and a Python SDK with governed_tool. When you run the server, the SDK's governedTool routes each call through a proxy session for centralized authorization and recording. You create a client with createClient using a baseUrl and API key, open a session with client.proxy.openSession, wrap a tool with governedTool(client, session.id, agent, skillRef, fn), and close the session with client.proxy.closeSession. A denied call throws GovernanceDeniedError.
The server is a Fastify and Postgres gateway with a flow engine and an audit writer, and the web console is a React app with an approvals inbox, a run log and a registry.
Licensing is split between Apache-2.0 for mc scan, the embedded library, the proof verifier, the SDKs and the connectors, and AGPL-3.0 for the server, web console and shared code. The Apache-2.0 packages can be embedded in closed-source agents freely. Commercial, non-copyleft licensing is available at hello@makerchecker.ai.
Source README
🛡️ MakerChecker
The open-source security layer for AI agents.
Deny-by-default enforcement, human approvals, and a cryptographically signed audit trail - so your agent runs only what it's granted and provably can't approve its own work.
Website · Live Demo · mc scan · Docs
Your agents keep running in their existing framework (LangChain, Claude SDK, CrewAI). MakerChecker sits in front of every tool call as a checkpoint and behind it as a signed ledger: an agent acts only through a role, runs only the skills it was granted, cannot exceed its limits, and cannot approve its own work.
🚀 Quick Start
1 - Scan your code
Find what your agent can already do on its own, classified by risk. No install, nothing leaves your machine:
npx @makerchecker/scan .
It flags every consequential action - deleting data, moving money, running shell commands, exfiltrating secrets - names each against the real incident it resembles, and can write the governance code for you with --fix. → packages/scan
2 - Guarantee its behavior
Import the controls and wrap any tool. The agent can now only run what its role was granted - a call it isn't allowed is denied before it executes:
npm i @makerchecker/embedded
import { createGovernor, GovernanceDeniedError } from "@makerchecker/embedded";
const gov = createGovernor()
.defineSkill("place-order@1", { riskTier: "high" })
.defineRole("agent")
.defineRole("risk-desk")
.grant("risk-desk", "place-order@1") // the agent is NOT granted it — deny by default
.defineAgent("trader", "agent");
// Wrap your tool once. Now the agent structurally can't fire it.
const placeOrder = gov.governedTool("trader", "place-order@1", (order) => broker.submit(order));
try {
await placeOrder({ symbol: "BTC", qty: 10 });
} catch (err) {
if (err instanceof GovernanceDeniedError) console.log(err.code); // "skill_not_granted"
}
High-risk skills go to a separate role, so an agent can never approve its own work - and every decision, allowed or denied, commits to a signed audit log. → packages/embedded
3 - Working with auditors?
Step 2 already writes a signed log. When auditors need a durable, queryable, tamper-evident record - plus a human-approval inbox and a review console - run the self-hosted server:
docker compose up
Every decision is Ed25519-signed and hash-chained: change any row and verification breaks. Export a bundle and anyone verifies it offline - no database, no trust in the process that produced it. → full server setup below
These are three independent packages -
mc scan,@makerchecker/embedded, and the server - that enforce the same controls and write the same signed audit format. Adopt any one on its own.
🔬 Governed Use Cases
Runnable examples of agents doing consequential work behind a human gate:
- Pharmacovigilance case processing - an agent triages adverse-event reports, but a medical reviewer signs before an expedited 15-day regulatory report transmits. examples/pv-icsr-processing
- Medical-device (MDR) complaint triage - a regulatory officer decides reportability behind a gate before draft reports are generated. examples/mdr-reportability-triage
- Oncology patient access - an agent handles benefit matching but is blocked from submitting copay enrollments without a specialist signing. examples/oncology-patient-access
- Daily cash reconciliation - a finance agent reconciles transactions but locks at exception gates until a cash officer signs off. examples/daily-cash-reconciliation
🔌 Integrate With Your Framework
Drop-in connectors govern the tools you already have:
- LangChain →
packages/connector-langchain - Claude Agent SDK →
packages/connector-claude-agent - TypeScript / Python SDKs →
packages/sdk·packages/sdk-python
When you run the server, the SDK's governedTool routes each call through a proxy session for centralized authorization and recording:
import { createClient, governedTool, GovernanceDeniedError } from "@makerchecker/sdk";
const client = createClient({ baseUrl: "http://localhost:3000", apiKey: "mk_..." });
const { session } = await client.proxy.openSession({ label: "recon-run" });
const match = governedTool(
client, session.id,
"recon-preparer", // agent whose role grants are evaluated
"txn-match@1", // skillRef: name@version
(input) => matchTxns(input),
);
await match({ statement, ledger }); // throws GovernanceDeniedError if denied
await client.proxy.closeSession(session.id);
🖥️ Self-Hosted Server (optional)
Run the full gateway when you need centralized enforcement across many agents, a human-approval inbox, and a review console. docker compose up brings up Postgres, the server on localhost:3000, and a seeded demo, printing two API keys - an admin key (your agent authenticates runs) and an officer key (a human reviewer approves gated actions).
The seeded pharmacovigilance flow parks at a medical-review gate where the requester is refused as its own approver:
export H='authorization: Bearer mk_...' # admin key
export OFFICER='authorization: Bearer mk_...' # officer key
curl -X POST localhost:3000/api/flows/pv-icsr-processing/runs -H "$H" -H 'content-type: application/json' -d '{}'
curl localhost:3000/api/approvals -H "$H"
# The requester cannot approve their own run — rejected with 403
curl -X POST localhost:3000/api/approvals/<id>/decision -H "$H" -H 'content-type: application/json' \
-d '{"decision":"approved","reason":"self-approval attempt"}'
# A separate officer signs; only now does the action proceed
curl -X POST localhost:3000/api/approvals/<id>/decision -H "$OFFICER" -H 'content-type: application/json' \
-d '{"decision":"approved","reason":"Seriousness confirmed; file 15-day expedited ICSRs."}'
curl localhost:3000/api/audit/verify -H "$H"
Full setup, Kubernetes/Helm, and running with live models: docs/quickstart.md.
🔒 Verifiable Audit Trail
Every decision and tool call commits to a hash-chained log - each event a SHA-256 over the RFC 8785 canonical JSON of the event, chained through prev_hash from genesis and Ed25519-signed. Change any row and verification breaks. Anyone can verify an exported bundle offline - no database, and no trust in the process that produced it:
npx @makerchecker/proof-verifier verify bundle.json
Spec: docs/audit-spec.md.
🗂️ Packages
| Package | License | What it is |
|---|---|---|
packages/scan |
Apache-2.0 | mc scan - finds and classifies what your agent can do. |
packages/embedded |
Apache-2.0 | Importable enforcement primitives - governance in your code. |
packages/proof-verifier |
Apache-2.0 | Independently verify a signed audit bundle offline. |
packages/sdk |
Apache-2.0 | TypeScript client + governedTool for the server. |
packages/sdk-python |
Apache-2.0 | Python client + governed_tool. |
packages/connector-langchain |
Apache-2.0 | Govern LangChain tools. |
packages/connector-claude-agent |
Apache-2.0 | Govern Claude Agent SDK tools. |
packages/server |
AGPL-3.0 | Self-hosted Fastify + Postgres gateway, flow engine, audit writer. |
packages/web |
AGPL-3.0 | React console: approvals inbox, run log, registry. |
packages/shared |
AGPL-3.0 | Domain types, canonical JSON, crypto utilities. |
📄 License & Contributing
- Server, Web, Shared: AGPL-3.0.
mc scan,embedded, SDKs, connectors, examples: Apache-2.0 - embed them in closed-source agents freely.- Commercial (non-copyleft) licensing: hello@makerchecker.ai.
Contributing: CONTRIBUTING.md · Security: SECURITY.md · Code of Conduct: CODE_OF_CONDUCT.md
examples/pv-icsr-processing/README.md
PV ICSR Processing - the medicines hero demo
Pharmacovigilance runs on expedited clocks. 21 CFR 314.80 requires 15-day expedited ICSRs ("Alert reports") for serious, unexpected adverse experiences, transmitted in ICH E2B(R3) format. A late expedited report is a per-case, dated, inspectable failure. Draft EU GMP Annex 22 (published July 7, 2025) permits generative AI in non-critical tasks only with a qualified human reviewing the output. AI drafts, a named human signs - this flow's architecture.
The safe / consequential asymmetry
The case-processor agent does the high-volume, low-stakes work itself: it
ingests the day's adverse-event cases and proposes which look serious and
unexpected (case-triage, low risk, ungated). Proposing carries no regulatory
weight, so the agent never waits on a human to surface a candidate.
The two acts that do carry weight are gated:
seriousness-assessmakes the binding serious-and-unexpected determination that starts the 15-day expedited clock (21 CFR 314.80).e2b-submittransmits the ICSR to the regulatory gateway in E2B(R3) format - the irreversible, one-way filing to FDA FAERS / EMA EudraVigilance.
Both are risk_tier: high. The engine's flow grammar will not publish a
high-risk skill unless a separation-enforcing approval gate precedes the step
that uses it (the high_risk_requires_gate rule), so the run parks at the
medical-review gate before either can run. AI drafts; a named physician
signs; only then does the clock start and the report file.
Separation of duties
The SoD constraint enforces independent medical review - a quality-system
control under EU GVP (Module I) and the QPPV's oversight (US postmarketing
safety reporting: 21 CFR 314.80) - so the processor who triages a case may not
perform its medical review. It is enforced at runtime and
fails closed: the gate is identity-mode (forbid_requester), so the user who
triggered the run gets a 403 deciding it, and unauthenticated decisions are
refused.
The planted cases
Triage is rule-based: serious AND unexpected is a candidate for the 15-day expedited clock; everything else routes to periodic reporting. The data plants two expedited cases among ten (the other eight are routine - non-serious and expected):
P-4003(Cardevol 10mg): acute liver failure, serious and unexpected - a 15-day expedited report under 21 CFR 314.80.P-4009(Gastrelin 40mg): anaphylactic reaction, serious and unexpected, received from a foreign source (DE) - also 15-day. The expedited clock is source-independent.
P-4005 (serious but expected) and P-4007 (non-serious but
unexpected) are the deliberate near-misses: each trips one half of the test but
not both, so each routes to periodic reporting - exactly the line the medical
reviewer confirms at the gate.
Run it
docker compose up # from the repo root; seeds everything on first boot
# First boot prints two keys, each shown once: a DEMO ADMIN API KEY and a
# DEMO OFFICER API KEY. The admin triggers the run; the officer decides the gate.
export H='authorization: Bearer mk_...' # DEMO ADMIN API KEY (triggers the run)
export OFFICER='authorization: Bearer mk_...' # DEMO OFFICER API KEY (approves the medical-review gate)
curl -X POST localhost:3000/api/flows/pv-icsr-processing/runs -H "$H" -H 'content-type: application/json' -d '{}'
curl localhost:3000/api/approvals -H "$H"
curl -X POST localhost:3000/api/approvals/<id>/decision -H "$OFFICER" \
-H 'content-type: application/json' -d '{"decision":"approved","reason":"Seriousness and expectedness confirmed for P-4003 and P-4009; file 15-day expedited ICSRs per 21 CFR 314.80"}'
curl localhost:3000/api/runs/<runId> -H "$H"
curl localhost:3000/api/audit/verify -H "$H"
Try to approve with the same key that triggered the run (-H "$H"): you get
a 403 (forbid_requester). The binding seriousness call and the E2B(R3)
submission only run after a different, authenticated medical reviewer - the
DEMO OFFICER API KEY here - signs off.
Export the signed, offline-verifiable evidence for the run - every agent action, the high-risk skills held behind the gate, grant state, and the reviewer's recorded decision, verifiable with no system access:
docker compose exec server node dist/cli.js audit export --run <runId> --out icsr-evidence.json
examples/mdr-reportability-triage/README.md
MDR Reportability Triage - medical-devices demo
An agent triages a complaint queue but cannot decide reportability. The regulatory officer does, behind an approval gate, with evidence an auditor verifies offline.
The reportability decision starts a statutory timer the moment anyone becomes aware - 21 CFR 803.50: 30 calendar days for death/serious-injury/malfunction reports; 803.53: 5 work days where remedial action is needed. Since February 2, 2026 QMSR is in force: Part 820 now incorporates ISO 13485, and management reviews plus internal and supplier audit reports are newly FDA-inspectable. At Philips Respironics, over 3,700 foam-degradation complaints were withheld 2010-2021, ending in a federal consent decree on April 9, 2024.
The risk
The gap between complaint intake and the reportability decision is where the liability sits, and under-reporting is the failure that ends companies.
The MakerChecker configuration
A complaint analyst agent ingests and triages the day's complaint queue. The run parks at the reportability-decision gate for the regulatory officer, and only then are the MDR report skeletons drafted and delivered. An SoD constraint binds the two roles: the analyst who triages a complaint may not decide its reportability.
Triage is rule-based - death or serious injury escalates, as does a malfunction likely to recur. The data plants two escalations among ten complaints:
C-3004(InsuFlow MX insulin pump): over-delivered insulin overnight, patient hospitalized - a serious injury on the 30-calendar-day MDR clock of 21 CFR 803.50, running from awareness.C-3008(VentAssist 300 ventilator): low-pressure alarm failed to sound, no patient harm this time, recurrence risk high - malfunction-reportable, because it would be likely to cause or contribute to a death or serious injury if it recurred.
Run it
docker compose up # from the repo root; seeds everything on first boot
# First boot prints two keys, each shown once: a DEMO ADMIN API KEY and a
# DEMO OFFICER API KEY. The admin triggers the run; the officer decides the gate.
export H='authorization: Bearer mk_...' # DEMO ADMIN API KEY (triggers the run)
export OFFICER='authorization: Bearer mk_...' # DEMO OFFICER API KEY (approves the reportability gate)
curl -X POST localhost:3000/api/flows/mdr-reportability-triage/runs -H "$H" -H 'content-type: application/json' -d '{}'
curl localhost:3000/api/approvals -H "$H"
curl -X POST localhost:3000/api/approvals/<id>/decision -H "$OFFICER" \
-H 'content-type: application/json' -d '{"decision":"approved","reason":"C-3004 and C-3008 are reportable; file MDRs within their clocks"}'
curl localhost:3000/api/runs/<runId> -H "$H"
curl localhost:3000/api/audit/verify -H "$H"
The gate is identity-mode (forbid_requester): the user who triggered the run
gets a 403 deciding it, and unauthenticated decisions are refused outright (fail
closed). The DEMO OFFICER API KEY is the eligible approver, a different user
than the one that triggered.
Export the signed, offline-verifiable evidence bundle for inspection. An investigator verifies it with no access to your systems:
docker compose exec server node dist/cli.js audit export --run <runId> --out mdr-evidence.json
examples/oncology-patient-access/README.md
Oncology Patient Access: Who Signs the Enrollment and the Appeal
Specialty-pharmacy "hub" teams move patients onto $100k+/yr oncology therapies when coverage is thin. The work is bureaucratic and high-volume: benefits investigation, prior authorization, denials and appeals, and stitching funding across manufacturer copay cards and independent charitable foundations (PAN, HealthWell, LLS). An AI agent can do most of it. The two acts it must not do alone are the two that carry legal weight: submitting an appeal or prior-auth to the payer (an attestation of medical necessity) and enrolling a patient into a copay program or charitable foundation.
The hazard is specific. Routing a government-insured patient - Medicare or Medicaid - into manufacturer copay support is Anti-Kickback Statute and False Claims Act exposure. An agent that auto-enrolls at scale turns a clerical shortcut into a federal liability, with no named human attesting that this patient, on this plan, was eligible for this program. So a named access specialist or pharmacist signs before any submit or enroll runs.
The risk
The system that drafts an appeal and assembles a funding stack also files the appeal and enrolls the patient, with no named clinician attesting eligibility and no per-patient record of who approved which enrollment on what basis. Nothing separates proposing a funding stack from committing a patient into a program, and a wrong enrollment - a Medicare patient in a manufacturer copay card - stands as billed until someone catches it.
The mis-enrollment guardrail
Even at the reversible proposal stage, the agent encodes the rule: manufacturer
copay cards are barred for government plans. The oncology-funding-match@1 skill
routes a Medicare or Medicaid patient to an independent charitable foundation
(PAN) and marks copayCardEligible: false; only a commercial patient gets a
manufacturer copay card in the proposed stack. The demo runs the match for both a
commercial patient (PT-7001, copay card permitted) and a Medicare patient
(PT-7004, copay card excluded). The proposal is correct - but it is still only
a proposal. Nothing is enrolled until the pharmacist signs.
The MakerChecker configuration
The work splits by reversibility. Verifying benefits, matching a funding stack,
and drafting a medical-necessity appeal are reversible: low-risk skills the
oncology-hub-access-coordinator role holds and runs pre-gate. It holds no
submit or enroll grant, so an attempt to file the appeal is refused by
deny-by-default before it reaches a tool body.
Submitting the appeal and enrolling the patient are irreversible, so
oncology-appeal-submit@1 and oncology-foundation-enroll@1 are published at
riskTier: high and refused on the proxy. They run only through a governed flow
with a preceding approval gate, where a named access specialist / pharmacist
signs each attestation before it executes. The
oncology-access-specialist-pharmacist role holds both grants but cannot run
either through the bare proxy.
Two enforcement primitives close the gap, both checked at the proxy and failing closed:
skill_not_granted- the coordinator role holds no submit grant, so its attempt to file the appeal is refused by deny-by-default before it reaches a tool body.high_risk_requires_gate- the access-specialist role holds the submit and enroll grants but still cannot run a high-risk skill inline on the proxy. It must run through a governed flow with a preceding approval gate. No path submits an appeal or enrolls a patient without a named human signing.
Skills:
oncology-benefits-verify@1,riskTier: low(run benefits verification and identify the denial reason; commits nothing)oncology-funding-match@1,riskTier: low(match an eligible funding stack to the plan type; encodes the copay-card-for-government-plans guardrail)oncology-appeal-draft@1,riskTier: low(draft the medical-necessity appeal letter; commits nothing)oncology-appeal-submit@1,riskTier: high(attest medical necessity and submit the prior-auth / appeal to the payer; not granted to the coordinator, and refused outside a gated flow)oncology-foundation-enroll@1,riskTier: high(enroll the patient into a copay program or charitable foundation; the AKS / FCA exposure point; refused outside a gated flow)
Roles and grants:
oncology-hub-access-coordinator- grantedoncology-benefits-verify@1,oncology-funding-match@1,oncology-appeal-draft@1. No submit/enroll grant.oncology-access-specialist-pharmacist- grantedoncology-appeal-submit@1andoncology-foundation-enroll@1. The named signer, but only at a gate.
The mock data
The demo logic runs on inline mock objects; the CSVs beside this README are illustrative data for the prose:
denials.csv- ten patient denials across commercial, medicare, medicaid, and uninsured plan types (patient_id, diagnosis, drug, plan_type, annual_cost_usd, denial_reason).PT-7004(melanoma, Opdivo, medicare) is the planted case the guardrail must catch: it must never be auto-enrolled into manufacturer copay support.funding_sources.csv- copay cards and charitable foundations with open and closed enrollment windows (source, type, window_status, max_award_usd, eligible_plan_types).manufacturer_copaylistscommercialonly;lls_copayandgood_daysare closed windows the match must skip.plan_criteria.csv- the plan/appeal rules (criterion_id, plan_type, field, rule, description), includingPC-002/PC-003: manufacturer copay support is prohibited for Medicare and Medicaid under the Anti-Kickback Statute.
Run it
Boot the server (docker compose up, or locally with
MAKERCHECKER_AUTH_DISABLED=1) and build the SDK (corepack pnpm run build),
then:
node examples/oncology-patient-access/demo.mjs
What happens
proxy session 3f9c1a20-7d44-4e1b-9c6a-2b8e51d0a7f3 opened
coordinator verifies benefits: {"patient":"PT-7001","drug":"Keytruda","planType":"commercial","annualCostUsd":235200,"denialReason":"prior_authorization_required","coverage":"denied"}
coordinator matches funding (commercial): {"patient":"PT-7001","planType":"commercial","proposedStack":["copay_card:manufacturer","charitable_foundation:HealthWell"],"copayCardEligible":true,"note":"commercial plan: copay card permitted"}
coordinator matches funding (medicare): {"patient":"PT-7004","planType":"medicare","proposedStack":["charitable_foundation:PAN"],"copayCardEligible":false,"note":"government plan: manufacturer copay support EXCLUDED (AKS); route to independent foundation only"}
coordinator drafts appeal: {"patient":"PT-7001","drug":"Keytruda","letter":"drafted","basis":"medical necessity per NCCN guideline; prior therapy failed"}
coordinator submit DENIED (skill_not_granted): skill "oncology-appeal-submit@1" is not granted to the role of agent "oncology-hub-access-bot"
specialist submit DENIED (high_risk_requires_gate): skill "oncology-appeal-submit@1" is high-risk and cannot run through the proxy; run it in a governed flow with a preceding approval gate
specialist enroll DENIED (high_risk_requires_gate): skill "oncology-foundation-enroll@1" is high-risk and cannot run through the proxy; run it in a governed flow with a preceding approval gate
audit trail:
412 proxy.session.opened
413 proxy.check.allowed oncology-hub-access-bot -> oncology-benefits-verify@1
414 proxy.result.recorded -> oncology-benefits-verify@1
415 proxy.check.allowed oncology-hub-access-bot -> oncology-funding-match@1
416 proxy.result.recorded -> oncology-funding-match@1
417 proxy.check.allowed oncology-hub-access-bot -> oncology-funding-match@1
418 proxy.result.recorded -> oncology-funding-match@1
419 proxy.check.allowed oncology-hub-access-bot -> oncology-appeal-draft@1
420 proxy.result.recorded -> oncology-appeal-draft@1
421 enforcement.blocked oncology-hub-access-bot -> oncology-appeal-submit@1 [skill_not_granted]
422 enforcement.blocked oncology-access-specialist-bot -> oncology-appeal-submit@1 [high_risk_requires_gate]
423 enforcement.blocked oncology-access-specialist-bot -> oncology-foundation-enroll@1 [high_risk_requires_gate]
424 proxy.session.closed
audit chain: ok=true events=424
The coordinator verifies benefits, matches funding, and drafts the appeal pre-gate because all three skills are reversible and granted - and the Medicare patient is routed away from manufacturer copay support at the proposal stage. Its attempt to submit the appeal is refused by deny-by-default; the high-risk submit and enroll are refused on the proxy even for the access specialist that holds the grants. Every attempt - allowed, ungranted, and refused-high-risk - is written to the hash-chained, Ed25519-signed audit chain.
What this does not prevent
It does not judge whether the appeal will win or whether the funding stack is the best one. A pharmacist who signs an enrollment the patient was not eligible for still creates the exposure. MakerChecker forces a named human to attest each submission and enrollment and records who approved it on what basis; it does not decide eligibility for them. Accountability, not adjudication.
examples/daily-cash-reconciliation/README.md
Daily Cash Reconciliation
A governed bank middle-office flow. A preparer agent ingests the bank statement and ledger, matches transactions, and flags exceptions; a human reviews the exception list at an approval gate; a reporter agent renders the summary and delivers it over a real MCP notification server.
A second seeded flow, self-approval-attempt, exercises the maker-checker
constraint. The preparer's role and the approver's role are bound by a
segregation-of-duties rule, so the run is structurally blocked and the
violation lands in the audit chain.
The data is planted with exactly two exceptions:
T-1009(Globex supplier payment): statement says −7800.00, ledger says −7080.00 - an amount mismatch (a transposition typo).T-1012(unidentified credit, ref 88231): on the statement, missing from the ledger entirely.
Run it
docker compose up # from the repo root; seeds everything on first boot
export H='authorization: Bearer mk_...' # admin key printed at first boot
# trigger the flow (paths default to the bundled CSVs)
curl -X POST localhost:3000/api/flows/daily-cash-reconciliation/runs -H "$H" -H 'content-type: application/json' -d '{}'
# watch it park at the gate, then approve
curl localhost:3000/api/approvals -H "$H"
curl -X POST localhost:3000/api/approvals/<id>/decision -H "$H" \
-H 'content-type: application/json' \
-d '{"decision":"approved","reason":"Both exceptions explained"}'
# inspect every step, skill call, and gate decision
curl localhost:3000/api/runs/<runId> -H "$H"
# the maker-checker block, live
curl -X POST localhost:3000/api/flows/self-approval-attempt/runs -H "$H" -H 'content-type: application/json' -d '{}'
# tamper-evidence
curl localhost:3000/api/audit/verify -H "$H"
With GEMINI_API_KEY or ANTHROPIC_API_KEY set, the agents run on a real
model deciding which granted skills to call. Without a key, steps execute
deterministically and the demo runs air-gapped.
The MCP notification server lives at packages/server/mcp/notify-server.mjs,
shipped with the server package so its imports resolve everywhere. The seeded
notify@1 skill invokes its notify tool over stdio.
The seeded flow definition is reproduced in flow.yaml.
FAQ
Common questions
Discussion
Questions & comments · 0
Sign In Sign in to leave a comment.