Skip to content

mokata catches a bad change (60 seconds)

The one-glance demo. An AI coding agent, mid-task, tries three bad changes — ship code with no spec, edit a source file straight past the gates, and stash a secret. mokata stops all three, and every write it governs lands on the audit ledger. Every command below was run end-to-end on a fresh sample repo; the output is exactly what it prints (absolute paths shortened to ., and init's tool-detection preamble elided).

mokata is the memory + seatbelt for your AI coding agent. This page is the seatbelt in action; copy-paste it and watch it catch a bad change in your own terminal.

Run it (copy-paste)

mkdir demo && cd demo && git init -q
cat > checkout.py <<'PY'
def checkout(cart):
    return sum(i["price"] for i in cart)
PY
mokata init --profile standard --yes
export MOKATA_SESSION_ID=demo     # see the note at the bottom
wrote ./.mokata/manifest.json
wrote ./.mokata/constitution.md
wrote ./.mokata/.gitignore
mokata initialized with profile 'standard'.

Bad change #1 — code with no spec, no tests

The agent jumps straight to implementation. mokata blocks it — no code ships without a saved spec whose acceptance criteria each map to a test:

mokata run develop
[BLOCKED] spec-persisted — no saved spec — draft and emit it first (/spec); the completeness gate must pass before implementation.

Bad change #2 — going around the gates with a plain file edit

So the agent does the honest thing and emits a spec — every acceptance criterion mapped to a test, or the completeness gate refuses it:

cat > spec.json <<'JSON'
{"title": "checkout rejects an empty cart",
 "criteria": [{"id": "AC1", "text": "checkout([]) raises ValueError"}],
 "tests": [{"name": "test_empty_cart_raises", "ac_ids": ["AC1"]}]}
JSON
mokata spec emit --file spec.json --yes
spec emitted: 'checkout rejects an empty cart' — 1 acceptance criteria, all mapped to tests.
  saved as this run's spec (run demo), and recorded in the shared spec corpus (1 spec(s)).
  implementation is unblocked once a failing test is on record (/test).

Now the obvious dodge: there's a spec but still no failing test, so instead of mokata's tools the agent reaches for the editor's own Write. That door has a lock. mokata setup claude installs a PreToolUse gate-guard hook, so the run-state gates fire on the agent's native Write/Edit too:

# simulate what Claude Code sends the hook on a native Write
python3 -c "
import json, os
print(json.dumps({'tool_name': 'Write', 'cwd': os.getcwd(), 'session_id': 'w1',
  'tool_input': {'file_path': os.getcwd() + '/checkout.py',
                 'content': 'def checkout(c): return 0'}}))" | mokata-hook gate-guard
BLOCKED [no-code-without-failing-test] no-code-without-failing-test: no failing test is on record for this run — checkout.py is implementation. Write the failing test first and watch it fail (/test), or override: mokata gate override no-code-without-failing-test --reason "<why>"

Exit code 2 — Claude Code refuses the tool call and the file is never touched.

Now send the same write to a test file and watch it sail through:

python3 -c "
import json, os
print(json.dumps({'tool_name': 'Write', 'cwd': os.getcwd(), 'session_id': 'w1',
  'tool_input': {'file_path': os.getcwd() + '/test_checkout.py',
                 'content': 'def test_empty_cart_raises(): ...'}}))" | mokata-hook gate-guard
echo "exit: $?"
exit: 0

That's the whole idea in two commands: RED is the permission to implement, not the prohibition. A test file is always writable — you have to be able to write the failing test. Four run-state gates guard native writes — approach-approval (a run is registered but no approach is approved yet), spec-persisted, no-code-without-failing-test, and spec-scope (a write outside the surface the spec authorized, or a feature you agreed not to build) — and they fire only inside an active run: hand-editing a repo mokata isn't running is never policed. It is a methodology block, not a security one, so a human can lift it, on the record: mokata gate override <gate> --reason "<why>".

Bad change #3 — a secret in the change

Now it tries to stash an AWS key. First it calls the remember tool with approve=True, the way a model used to wave its own writes through. It doesn't commit anything — it can't:

python3 -c "
from mokata import mcp_server as M
key = 'AKIA' + 'IOSFODNN7' + 'EXAMPLE'        # a real-looking AWS key
r = M.remember(path='.', subject='aws.key', value=key, approve=True)
print('status:', r['status'], ' committed:', r['committed'])
print(r['note'])
print(r['hint'])"
status: proposed  committed: False
`approve`/`confirm` no longer commit: an approval is MINTED BY A HUMAN out-of-band (`mokata approve <id>`) and can only be REFERENCED here by id. Typing approve=true is not consent — it never was.
NOTHING was written. Ask the human to run:  mokata approve p-d6af1f0f5d5b  — then re-call remember with proposal_id="p-d6af1f0f5d5b".

The model cannot approve its own write. It can only propose one and hand you an id. So play the careless human and approve it — in your terminal, in a process the model isn't driving:

mokata approve p-d6af1f0f5d5b --yes
approved: p-d6af1f0f5d5b — recorded to the audit ledger.
The model may now commit this ONE write by re-calling remember with proposal_id=p-d6af1f0f5d5b.
It is single-use and expires with this session — nothing to remember to turn off.

Now the model redeems the approval — and the secret scan still refuses it. Approval is a methodology gate; it was never a security override:

python3 -c "
from mokata import mcp_server as M
key = 'AKIA' + 'IOSFODNN7' + 'EXAMPLE'
r = M.remember(path='.', subject='aws.key', value=key, proposal_id='p-d6af1f0f5d5b')
print('status:', r['status'], ' findings:', r['findings'])"
status: blocked  findings: ['aws-access-key', 'high-entropy-token', 'sensitive-location']

The punchline — every decision is on the ledger

mokata audit
audit ledger — 8 entries:
  #1   setup       action=init profile=standard files=['./.mokata/.gitignore', './.mokata/constitution.md', './.mokata/manifest.json'] actor=cli
  #2   gate        gate=spec-persisted phase=develop decision=blocked reason=no saved spec — draft and emit it first (/spec); the completeness gate must pass before implementation. ac_count=0
  #3   write_gate  write_kind=config target=spec:emit actor=agent decision=approved reason=committed
  #4   checkpoint  run=demo phase=completeness_gate
  #5   checkpoint  run=demo phase=emit
  #6   write_approval decision=approved proposal=p-d6af1f0f5d5b tool=remember target=memory:aws.key content_hash=3754acbb… actor=human run=demo scope=session
  #7   write_gate  write_kind=memory target=memory:aws.key actor=mcp decision=blocked reason=secret detected
  #8   write_approval decision=redeemed proposal=p-d6af1f0f5d5b tool=remember target=memory:aws.key content_hash=3754acbb… approved_by=human run=demo committed=False

Note row #1: even mokata init — a plain CLI scaffold, nothing the model touched — is on the ledger. Every durable writer is registered; there is no unledgered side door.

(Each row also carries prev_hash/entry_hash — the ledger is hash-chained. Elided here for width.) Even the approved write is recorded as committed=False: you can see exactly what you approved, and exactly why it still didn't land.

One honest gap worth knowing: the gate-guard's block from bad change #2 is not on the ledger. The hook runs as its own short-lived process at the harness's tool boundary — it stops the write with an exit code, and it is the override (mokata gate override) that gets recorded, permanently, along with who lifted which gate and why. What mokata governs, it logs.

Three bad changes, all caught — local-first, human-gated, nothing silent. That's mokata: it remembers your project and stops the agent shipping the wrong thing.

Why MOKATA_SESSION_ID=demo? An approval is bound to one session, and each python3 -c in a shell is a fresh process. The pin makes this whole terminal one session so the demo can run end to end. Inside Claude Code you never set it — the MCP server is one long-lived session, and you just run mokata approve <id> in your own terminal.

Next: see every differentiator run (graph, memory, governance) in differentiators in action, or get started in your own repo. Inside Claude Code the agent drives the same gates through the slash commands and MCP tools — one engine.