Documentation menu▾
Documentation
Quickstart
Bring the control plane up, wrap a tool your agent already calls, watch the agent get denied before it acts, and verify the audit chain offline. This takes about five minutes with Docker, and you end with a blocked action you can prove never ran.
Runs offline by default
ANTHROPIC_API_KEY or GEMINI_API_KEY to run those steps against a live model. The governance checks and the audit chain behave the same either way.Prerequisites
Pick one path. Docker is the simplest and what the rest of this page assumes.
- Docker. Docker and the Compose plugin. Nothing else to install: the image brings its own Node and Postgres.
- Local dev. Node 22 or newer, pnpm 9.15.4 (enable it with
corepack enable), and PostgreSQL 17. Use this if you intend to build the packages or run the test suite.
Run the control plane
Clone the repository and start it. The packages are not published yet, so you run the server from source.
shell
# Clone and start the control plane.git clone https://github.com/sammysltd/makercheckercd makercheckerdocker compose up
Compose starts postgres:17 and the server, runs the migrations, and seeds the demo: a cash-reconciliation flow, the demo agents, and an admin and an officer account. The server listens on http://localhost:3000, which serves both the API and the web UI. Open that URL to watch runs and approvals in the browser.
On first boot the logs print two API keys, an admin key and an officer key, both in the form mk_.... You pass one as a Bearer token on API calls.
Copy the keys
Wrap your first tool
The fastest integration is a proxy session: wrap a tool your agent already calls, and every invocation passes the MakerChecker check first. The tool keeps its name and schema, so the agent code does not change.
The SDK and the LangChain connector are Apache-2.0 packages in the repository under packages/sdk and packages/connector-langchain. They are not on npm yet, so build them from the clone and import them locally. The package names shown here (@makerchecker/sdk, @makerchecker/connector-langchain, and the Python makerchecker) are the real ones.
import { createClient } from "@makerchecker/sdk";import { governLangChainTool } from "@makerchecker/connector-langchain";// MAKERCHECKER_URL = http://localhost:3000// MAKERCHECKER_API_KEY = mk_... (the officer or admin key from boot)const mc = createClient({baseUrl: process.env.MAKERCHECKER_URL,apiKey: process.env.MAKERCHECKER_API_KEY,});const { session } = await mc.proxy.openSession({ label: "recon-run" });// Wrap a LangChain tool your agent already has. Name and schema do not change.const matchTxns = governLangChainTool(mc,{ sessionId: session.id, agentName: "recon-preparer", skillRef: "txn-match@1" },rawMatchTxns,);// recon-preparer holds txn-match@1, so the check passes, the tool runs,// and the call is signed into the audit chain.await matchTxns.invoke({ statement });
The demo grants txn-match@1 to the recon-preparer role, so this call passes the check, runs the wrapped tool, and records the outcome in the audit chain.
Watch it get blocked
Two rules turn a check into a denial. Both fire before the tool body runs.
- Deny by default.A skill the agent's role was never granted is refused. The
recon-preparerrole does not holdreport-gen@1, so calling it through the proxy is denied. - Segregation of duties. The agent that prepared a case cannot also approve it.
recon-prepareris blocked from the approver role (recon-approver-role), so it can never sign off its own work.
A denied call raises GovernanceDeniedError, carrying a code and a reason, before the wrapped tool executes. Catch it and the tool body never ran:
TypeScript
import { GovernanceDeniedError } from "@makerchecker/sdk";// recon-preparer's role was never granted report-gen@1, so the proxy denies it.const report = governLangChainTool(mc,{ sessionId: session.id, agentName: "recon-preparer", skillRef: "report-gen@1" },rawGenerateReport,);try {await report.invoke({ exceptions });} catch (err) {if (!(err instanceof GovernanceDeniedError)) throw err;// The check denied BEFORE the tool ran; the tool body never executed.console.log(`denied (${err.code}): ${err.reason}`);}
The thing to verify
report-gen@1 was never invoked: no report was generated, and the attempt is recorded in the audit chain as a denied check.Verify the audit
Every check, run, and denial appends to a hash-chained, Ed25519-signed audit trail. You can confirm the chain three ways, all returning { ok, count, headHash }:
// Walk the chain and confirm nothing was tampered with.const verdict = await mc.audit.verify();// { ok: true, count: 7, headHash: "9f2c..." }console.log(verdict.ok, verdict.count, verdict.headHash);
The exported bundle is signed and self-contained, so verify-bundle recomputes the chain with no database and no network. That is how an inspector confirms the record without trusting your server, or us. For the chain format, the signing scheme, and the full verification procedure, see The audit trail.
Next
- Concepts and model: agents, roles, skills, grants, risk tiers, segregation of duties, gates, and limits.
- Wrap your agent: LangChain, the Claude Agent SDK, the generic wrapper, and Python.
- Self-hosting: run it on your own infrastructure, hardened and air-gapped.