MoriMori
Mori

Proof, not promises

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.

Why replies end with a verification line

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 verdict line

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:

  • File-producing tools (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.
  • `create_folder` — the folder is re-listed.
  • `delete_file` — the check is inverted: the file must now be gone.
  • `start_dev_server` — the URL from the tool result gets a real HTTP probe; anything answering on the port passes.

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.

The honesty guard

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.

  • No-tools validator. If a reply arrives from a turn in which no tools ran but the text matches a hallucinated-success pattern ("message has been sent", "successfully deployed", "I've posted your tweet"), the reply is blocked outright and replaced with a plain admission that nothing was executed.
  • Post-turn send check. On the tool-loop route, a reply that claims something was sent, emailed, or delivered — and references an address or channel, and does not negate the claim itself — is cross-checked against the action log. If no send-capable tool (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*.

Verify-on-stop

"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.

Truncation guards

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.

  • When a reply is cut at the token cap while tool calls are pending, the entire batch is refused. Each call returns "NOT EXECUTED — your output hit the token cap, so these arguments may be incomplete", with instructions to re-issue the work in smaller pieces, and a follow-up note tells the model not to assume the last action completed.
  • The guard is enforced on both provider paths — stop_reason: max_tokens on Anthropic, finish_reason: length on OpenAI-compatible APIs — so switching providers never reopens the hole.
  • Arguments that arrive as invalid JSON never execute with empty defaults. The old failure mode — a truncated 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.

Diff cards: see the exact change

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.

run_self_test: diagnostics on the real machine

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:

  • Terminal/shell — runs a real command and checks the echo round-trip.
  • File read/write — writes a temp file, reads it back, compares bytes, deletes it.
  • Keyboard and mouse — probes the platform's real input layer (AppleScript and Quartz on macOS; SendKeys and user32 on Windows).
  • App detection — checks against a process guaranteed to be running (Finder on macOS, explorer on Windows), so a failure means the probe is broken, not the target.
  • Active window and screen capture — queries the frontmost window and captures a real thumbnail from the display.
  • Dispatch-parity probesgit_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.

Reading the signals

  • No line at the end — the turn changed nothing checkable. Nothing was claimed, so nothing needed proving.
  • `✓ Verified: N/N changes confirmed on disk.` — every artifact Mori claims to have produced this turn was independently re-found on your machine after the fact.
  • `⚠️ Verification: K/N confirmed — could not verify: …` — treat the named items as not done. Ask Mori to retry them, or check the paths yourself; the label is the filename, folder, or server URL that failed its re-check.
  • `⚠️ Honesty check: nothing was actually sent this turn…` — the send claim above it is retracted in-line. No send tool ran; tell her to try again and the real tool will run — through the same approval gate as always.
  • A ❌ row in the self-test — that subsystem is broken on this machine, and the detail text says how it failed.

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.