Mori re-checks her own work against your disk, your ports, and her action log — and tells you exactly what she could and could not confirm.
A language model's final text is not ground truth. A model can write "done" whether or not the file landed on disk, and Mori is built on the assumption that it sometimes will. So she treats her own reply as a claim to be checked, not a fact to be believed.
After any turn that changed something — files written, folders created, dev servers started — a post-turn verifier re-inspects each successful mutation in the turn's action log against reality and appends a single verdict line to the reply. The checks are plain code running locally on your machine: re-reads of your own disk, a probe of your own ports. The model behind your API key never grades its own homework, and nothing about the check leaves your machine.
Turns that changed nothing checkable — pure conversation, read-only work — get no line at all. Silence means there was nothing to verify, not that verification was skipped.
The verifier walks the turn's successful actions and builds one concrete check per claim, capped at 12 checks and 3 seconds per network probe so verification can never balloon a turn:
create_file, write_file, update_file, edit_file, append_file, create_zip, create_document, create_spreadsheet, create_slides) — the file is re-read from disk. A file too large to re-read still counts as existing.Two outcomes:
✓ Verified: N/N changes confirmed on disk. — every mutation this turn was independently re-confirmed after the reply was written.⚠️ Verification: K/N confirmed — could not verify: … — up to four failing items are named (filename, folder, or URL). The line does not guess at causes; it tells you exactly which artifacts failed the re-check so you can look or ask Mori to retry.The verdict is metadata, not content. It is stripped before text-to-speech reads a reply aloud, before bot-bridge history is stored, and before the memory miner or goal judge see the reply — so Mori's own "✓ Verified" can never be recycled as evidence for anything.
Verification covers things that leave a trace on disk. Sends are the opposite problem: an email that was never sent leaves no trace at all. Two guards in the chat pipeline cover that gap.
send_*, post_*, reply_*, phone tools) succeeded this turn, Mori appends: ⚠️ Honesty check: nothing was actually sent this turn — no send tool ran. That claim above is wrong; tell me to try again and I will really send it.The send check is scoped to the tool loop deliberately: browser and computer-use runs act through the DOM and the screen, not the action log, so judging them by log entries would falsely retract real sends.
This is the mirror image of Mori's approval gates. Risky outbound actions — a bulk email, for instance — refuse to run until your latest message is an explicit yes. Approval controls what Mori *may* do; the honesty guard catches her claiming to have done what she *didn't*.
"It should work now" is not evidence, so a turn is not allowed to end on it. When a turn's action log shows successful edits to code files — real code, judged by extension; prose and config edits do not count — but no passing proof after the last edit (a terminal command exiting 0, a clean script run, a dev server started), the reply does not stand as written. The pipeline sends Mori one synthetic message, explicitly marked as a system check rather than something you said: verify now — run the build, typecheck, test, or open the preview — and report the real result, or state the concrete blocker if verification is genuinely impossible. Restating completion without evidence is not an accepted answer.
The nudge fires at most once per turn, its outcome is appended to the reply, and it can never break the reply itself — if anything in the check path fails, the original answer is delivered untouched. The point is the incentive: the cheapest way to satisfy the check is to actually run the verification, so "done" and "proven" converge.
A model reply that hits its output-token cap can end mid-JSON — and a truncated tool call is dangerous precisely because it can look fine: a string cut right after a closing brace still parses. So Mori refuses to execute on truncation rather than trusting a parse.
stop_reason: max_tokens on Anthropic, finish_reason: length on OpenAI-compatible APIs — so switching providers never reopens the hole.create_file call running with no arguments and a half-written file "succeeding" silently — is structurally gone: nothing runs, and the error tells the model how to redo it.On a plain text reply with no tool calls pending, hitting the cap appends an explicit note — say "continue" and Mori picks up exactly where she left off — instead of ending mid-sentence as if finished.
For every file mutation (create_file, write_file, update_file, edit_file), Mori reads the current on-disk bytes *before* the write executes. After a successful write she computes a real before/after line diff — a dependency-free LCS, entirely local — and attaches it to that step.
In chat, finished work collapses into a single dropdown: "Worked — N steps · 12 searched · 8 read". Expand it and each edited file gets a collapsible card with +/- lines and added/removed counts, alongside grouped chips for the other tools. So "I updated index.html" is never just a sentence; the exact lines that changed are one click away, and they were computed from disk state, not from what the model said it wrote.
The diff is bounded so it stays honest without ever hurting the work: previews cap at 400 lines, long unchanged runs collapse to an ellipsis with two lines of context on each side, and files over 6,000 lines fall back to a coarse "N added, M removed (preview omitted)" summary rather than a frozen panel. Diff computation is cosmetic by design — if it fails, the tool result is untouched.
When you want proof that Mori's own toolbox works, ask her to "test yourself" or "run diagnostics", or use Settings → Diagnostics. The run_self_test tool exercises each capability subsystem on the actual machine — no mocks:
git_status, run_python, and list_dev_servers are invoked through the real tool-dispatch path, not called directly. This proves the dispatch layer routes each action to a live handler on *this* platform, catching the bug class where an action silently returns "Unknown action" on one OS. "Reached, but git/python isn't installed" still passes parity — and the detail line says so.Each check reports pass/fail with a one-line detail, so a failure points at the subsystem, not at a vague "something's wrong".
Self-test on macOS — 10/10 passed:
✅ Terminal / shell — mori-ok
✅ File read/write — wrote + read back ok
✅ Keyboard (AppleScript) — osascript available
✅ Mouse (Quartz) — Quartz available
✅ App detection (Finder) — RUNNING
✅ Active window — Ghostty — mori
✅ Screen capture — 2 display(s)
✅ Dispatch parity: git_status — On branch main…
✅ Dispatch parity: run_python — mori-py-ok
✅ Dispatch parity: list_dev_servers — No dev servers running.All of this runs locally, against your own disk and your own ports, with the models you brought your own keys for. Approval gates decide what Mori is allowed to do before it happens; verification confirms what actually happened after. Between the two, you are never asked to take her word for it.