Skip to content
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

With no model API key set, the stack runs deterministically and fully offline: agent steps execute in a scripted mode, so nothing leaves your machine. Set 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/makerchecker
cd makerchecker
docker 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

The admin and officer keys are printed once, at first boot, and only their hash is stored. Copy them out of the startup logs now. If you lose a key there is no way to read it back, and you will need to reseed to get a new one.

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-preparer role does not hold report-gen@1, so calling it through the proxy is denied.
  • Segregation of duties. The agent that prepared a case cannot also approve it. recon-preparer is 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

The denial happens at the check, not after the side effect. When the catch block runs, 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.
Stuck or evaluating for a regulated team?Book a walkthroughOpen an issue on GitHub