Verified completion

Stop agents reporting work as finished when it is not - the claim is checked against what the turn actually produced, and the stop is denied when the evidence contradicts it.

Agents say “done” when they are not. A module never written, a hard function replaced by a placeholder, a real parser quietly swapped for a regex because the faithful port was tedious - and the final message still reads everything is complete. You only find out later, reading the code yourself.

Telling the model to be honest does not fix this. enigma’s always-on memory already says exactly that, and it still happens: a claim is free to make, and nothing checks it. So enigma checks the claim outside the model.

It is on by default and everything runs locally.

How it works

At the end of a turn, enigma reads the agent’s own final message.

  1. Does it claim the work is finished? If not, nothing else happens - the check is one regex over one string, so an ordinary turn costs nothing.
  2. If it does, enigma looks for evidence to the contrary in the code that turn actually produced: everything not committed yet, plus what this branch has already committed since it left the default branch. (Both halves matter - an agent that commits before finishing its turn would otherwise leave an empty diff and sail through.) It matches unfinished-work markers (TODO/FIXME), unimplemented paths (not implemented, NotImplementedError, todo!()) and placeholders left behind. When you have configured a verification command, it runs that too.
  3. If the evidence contradicts the claim, the stop is denied: the findings go back to the model, which has to either finish the work properly or state exactly what is missing and why.

No model tokens are spent unless the gate actually fires, and then only on the findings.

A message that already discloses what is left - “the port is complete except for two modules I could not finish” - is an honest report, and the gate leaves it alone. It exists to catch silence about gaps, never to punish naming them.

The other half: stopping to ask

The same failure has a second face. Instead of claiming done, the turn ends with a question: “Shall I continue with tasks 5-8 in this order, or do you prefer another?”. Nothing in the code is marked unfinished - the work simply stopped, and you spend a turn saying yes to a question with only one answer.

So the gate denies that stop too, and answers the question itself: continue, in whatever order makes sense. Sequencing and priority among items you already asked for are the agent’s call.

It needs no diff - the message announces it - so this check is free as well. It stands aside whenever stopping is legitimate:

  • The message names a genuine blocker: credentials or access the agent does not have, an irreversible or destructive action (a force-push, a deletion, a deploy), or a decision that is genuinely yours (business, legal, cost). Naming the blocker is also the way out of a block: an agent told to continue only has to say what actually stops it.
  • The agent is planning - a plan is presented precisely so you can approve it.
  • You asked to be checked in with, and the message says so.
  • The question is a design question (“Zod or valibot?”), not a request for permission to keep going.

Same loop safety as above: after two blocks on the same question the gate stands down.

The third face: a skipped quality gate

The quality gate is on, the agent’s memory tells it to drive the gate once a code task is committed, and the turn still ends with the work never sent through it. That arrives two ways, and the hook denies both:

  • Announced - the final message reports the gate as not run, or offers to run it: “the gate has not run, tell me if you want me to launch it”. Switching the gate on is the decision, so there is nothing left to ask. Like the check above, this one needs no diff (the message announces it) and stands aside while the agent is planning.
  • Silent - the turn reports the work as finished, and the branch carries commits no gate run has ever looked at. The gate daemon records its runs per repository in a small local file, and the hook reads it: a run at least as new as the newest commit, on the branch that commit is on, and not one that ended failed or cancelled, is what says the pipeline already saw this work.

Naming a reason stands it down, and the block message lists them: you told the agent to skip it, the repository sets gate: false, the branch is listed in gate-protected-branches, there is nothing committed to validate, a run parked on an ask-user finding, or the gate cannot be stood up here at all (no origin remote for enigma gate init, or init or the daemon failing). A finished run that leaves a PR for you to review and merge is the prescribed ending, not a skip.

The silent half only fires where a block cannot be a false one. The working tree must be clean, because axi run refuses a dirty one and a block there would order a run that cannot start. Only commits ahead of the branch point count, so once the pipeline pushes, the finding clears itself. And the first turn enigma sees in a repository only starts watching it - a backlog that predates the check, or the gate being switched on, is never charged to the turn being judged. Gate blocks have their own loop-safety budget, so they can never spend the completion gate’s.

The fourth check: conventions

The same hook checks one thing that has nothing to do with a claim: whether the code the turn added breaks a convention this project enforces. It runs the guardrails rules over the added lines and denies the stop on a blocking finding - a broken convention is a defect whether or not the turn said “done”, and this is the only channel that can hand one back to the model at all (a post-edit warning exits zero, where nothing reads it).

Severity is not redefined here. A block rule denies the stop; a warn rides along in the same message when something blocking already fired, and is otherwise only recorded. Stopping a turn over advice is how a gate gets switched off, which would cost the blocking rules too - so a convention that genuinely must not be skipped is written as a block rule instead.

It costs one diff on a turn that produced code and nothing on a turn that produced none, it has its own loop-safety budget (so convention findings can never spend the completion gate’s), and guardrails: false in a repository’s .enigma.json stands it down there.

The fifth check: reply style

The output-style compression level (see Configuration) was the one always-on rule enigma shipped with nothing checking it - a block of prose in the agent’s memory, read once and easy to drift away from over a long session. The hook already reads the final message for the checks above, so checking it against the style level as well costs nothing extra: no model call, no git, one more regex sweep over a string already in hand.

It looks for a closed, mechanical list: filler the style rule itself names (in English and Spanish), an opening line that announces the work instead of reporting it, and a table row whose status cell only says something was skipped or unchanged. Only the first two can deny a stop - each is a pleasantry or an opening form with no other reading in a reply. The table-row check is recorded but never blocks: after several rounds of tuning it kept firing on ordinary result and coverage tables, because whether a row is padding depends on context a regex does not have, and a false block over prose is how a gate like this gets switched off. It still counts: enigma guardrails stats prints it as its own table, alongside the guardrail rules - that is what makes “does the agent keep padding replies” answerable with a number.

It has its own loop-safety budget, so a cosmetic block can never spend the one the completion checks depend on, and it is checked last of everything in this hook - a turn that both padded its reply and left work unfinished hears about the work, not the prose. A line that is load-bearing quoted text rather than actual padding (documenting the check itself, for instance) can be marked with enigma:style-ignore, which exempts only that line and nothing else - deliberately not the same marker enigma:verify-ignore uses, so a cosmetic exemption can never also silence the checks above it.

Precision

A false block would just train you to switch this off, so the checks only match unambiguous evidence:

  • Only added lines are scanned, measured from where your branch left the default branch - so a repository’s pre-existing TODOs never block a turn. On a long-lived branch that scope also covers earlier commits on the same branch, so a marker someone added there days ago can surface; that is the price of also catching work the agent commits mid-turn, and enigma:verify-ignore settles an intentional one.
  • It never fails open quietly: if the final message cannot be read, the change was too large to scan in full, or there is no repository to read a change from at all, it says so rather than reporting a clean pass.
  • Documents (.md, .txt, …) are skipped, because a plan legitimately tracks TODOs.
  • Language idioms are recognised - a Python abstract method raising NotImplementedError is by design, not a stub.
  • A line carrying enigma:verify-ignore is never treated as evidence.
  • The verification command runs only when the turn actually produced code and something has moved since it last passed - an uncommitted change, or a commit made since. So answering a question never pays for your test suite, a suite that was already red never blocks a turn that did not touch it, and a passing suite is not re-run for a commit it already cleared.
  • After two blocks on the same findings the gate stands down, so it can never trap a turn in a loop. A different problem gets a fresh budget, so standing down once does not silence the rest of the session - up to an absolute ceiling of ten blocks per session, past which the gate stays quiet, because a finding set that keeps changing shape would otherwise earn a fresh budget forever.

Ports and clones

The gate sees what the agent wrote. It cannot see what the agent never wrote - the module skipped entirely, which is the worst version of this failure. Comparing against the original is the only thing that can:

enigma verify parity <source> <target>

Compares two codebases by symbol, reusing enigma’s multi-language code-graph extraction, and reports per source module how much was carried over. Names are normalised first, so a port renaming parse_config to parseConfig still matches. Modules at zero coverage are the headline: those were never written. Modules below 60% are listed after them, as partially ported. It exits non-zero when either list is non-empty.

$ enigma verify parity ../original-repo .
$ enigma verify parity ./py-service ./go-service

Coverage matches symbol names: it proves a counterpart exists, never that its behaviour was ported faithfully. It is a coverage signal, not a proof of correctness - claiming otherwise would recreate the exact problem this feature exists to fix. For the same reason a comparison that could not be made is reported as a failure rather than a pass: if the source yields no symbols at all (a mistyped path, a language the scanner does not read) or either tree hit a scan limit, it says so and exits non-zero instead of reporting 100%.

Use it

enigma verify [cmd]

No argument checks the current change, sweeps it for convention violations, and runs your verification command when there is new work to check - the same code checks the turn-end hook runs, on demand. The checks that judge a turn instead of the code (the question that stops short, a skipped quality gate, and reply style) only exist in the hook, since there is no final message to read here. Advisory convention findings are printed as suggestions and never affect the exit code. --all sweeps every tracked file for unfinished work (the convention sweep is scoped to the change, so it is skipped there). It returns a non-zero exit code when something is unfinished, and also when it could not check at all (outside a git repository), so it works as a pre-merge or CI check.

$ enigma verify
$ enigma verify --all
$ enigma verify parity ../original .
$ enigma config verify-command "npm test"
$ enigma config verify off

Set verify-command to whatever proves your project works (npm test, make check, pytest -q). It is read from your global config only, never from a repository’s own .enigma.json, and it is excluded from config import and export: a cloned repository or an imported bundle must never be able to choose a command to run on your machine.

Be aware of what that does not cover. A command like npm test names an entry point that each repository defines, so setting it globally means a cloned repo’s own scripts.test runs automatically at the end of a claiming turn. That is the same trust you extend by running npm test in that repo yourself - but here it happens without you typing it. It is off by default for exactly this reason. If you work in repositories you have not read, either leave it unset (marker checks still run) or point it at something you control rather than something the repo defines.

Where it runs

  • Claude Code - a Stop hook, the one documented turn-end hook that can deny a stop.
  • Codex has no turn-end hook, and opencode’s session.idle event has no documented way to block a turn or return anything to the model. Wiring either would only pretend to gate them. Both are covered by enigma verify plus the always-on memory rule instead, which is persuasion rather than enforcement, and is described as such.

What it does not do

The gate is on globally (enigma config verify off turns it off); to skip one project, put verify: false in that repository’s .enigma.json.

Both of its escape hatches - that per-project switch and the enigma:verify-ignore marker - are writable by the agent being gated. An agent that decided to route around it could set either, and the gate would go quiet. That is deliberate: this stops accidental false claims, which is what actually happens, and both bypasses leave a visible line in the diff you review. It is not a defence against a model actively working to evade it, and nothing that runs on the same machine as the agent could be.

It also cannot judge whether an implementation is correct - only whether the work shows signs of being unfinished. A wrong-but-complete implementation passes, which is what code review and tests are for.

Privacy

Everything runs in-process on your machine; nothing is sent anywhere. The gate reads your working tree and the agent’s final message locally. Turning the setting off removes the hook from your agents.