---
title: conflicts
description: "The conflicts block of the CLI's guide."
---

## 1. The block, verbatim

Pass `--detect-conflicts` to `memhtml apply` and each result gains a `conflict` field naming what that op's claim contradicts. Dedupe catches an op whose content is IDENTICAL to something stored; this catches an op that says something DIFFERENT about the same thing, the case dedupe is blind to and the one that actually rots a corpus. The match is grammatical, not semantic: a claim is split into a frame (the subject and relation, up to its last `of`/`is`/`in`/`to`/`by`/`as`) and a value, and two claims conflict when they share a frame. `The pool ceiling is 64` and `The pool ceiling is 128` share `the pool ceiling is`. `conflict.path` names an ACTIVE memory already holding that slot; `conflict.batch_index` names an EARLIER op in this same call, which is the case nothing else can see because neither op is stored yet; `conflict.claim` is the other claim's own text, so you can decide without a second read. It is null when nothing matched, and also when the claim has no frame shape. The rule refuses frames under three tokens and values over six, so short claims and claims trailed by a clause are deliberately unmatched rather than loosely matched. On a line using `article_html` instead of `body` it is always null, because the claim lives inside your markup and is not read until the store renders it. THE ASSIST NEVER CHANGES WHAT IS WRITTEN. An op carrying a conflict is written exactly as it would have been without the flag: nothing is archived, nothing is refused, and later does not win. That is deliberate, because sometimes the contradiction IS the answer. A memory recording that a runbook step changed necessarily contradicts the memory stating the old step, and a system that resolved that for you would delete the pair a reader needs in order to see the change at all. You decide per conflict: keep both (they are about different things, or both are true), `memhtml correct <path>` instead (the new claim supersedes the old one, and the old one stays readable under archive/), or drop the line (you were about to restate something already stored). Archived memories never match, so a superseded claim stops contradicting the claim that superseded it.
When you have already decided that later wins (a re-scrape, a settings sync, any stream where each line is the newest statement of its slot), pass `--consolidate last-wins` (the batch tool's `consolidate: "last-wins"`) and the batch RESOLVES those matches instead of reporting them. Ops sharing a frame key write ONE file carrying the LATER value at the FIRST index that claimed the slot; each later restatement reports `consolidated_into` naming that slot and the summary counts it under `consolidated`, neither written nor failed. A stored ACTIVE memory occupying a surviving slot is archived with a supersedes link from the new file, its archive path reported as `superseded_path`, the same chain `memhtml correct` leaves, so ancestry reads identically. OFF by default, and the key is the conflict rule's own: the frame split is a rule measured in the eval harness before it was believed and ported verbatim into `@memhtml/domain`'s frame.ts, which detection and consolidation share, so anything the rule refuses to key (short frames, clause values) is never consolidated, and what you saw reported with `--detect-conflicts` is exactly what this flag would have acted on.
Every supersede, `memhtml correct` and `--consolidate last-wins` alike, also stamps a VALIDITY WINDOW, in the same one commit. The superseded memory gains `memhtml-valid-until` set to the moment the new fact became true (the winner's own `memhtml-valid-from`, else its first `<time datetime>`, else the operation's instant), and the winner gains `memhtml-valid-from` at that same moment, so one window closes exactly where the next opens. Min-wins: a memory already stating an EARLIER `memhtml-valid-until` keeps it, because a fact cannot outlive its earliest stated bound. That is what `--as-of` on `memhtml search` reads: pass an ISO instant and the result is what was believed valid AT THAT MOMENT. Since-superseded memories return, each marked `superseded_by` naming what replaced it, and facts not yet valid then are absent. History is read from the files, not replayed from git, so it survives a full index rebuild.
The frame rule deliberately refuses to match a REWORDING — same fact, different words shares no frame key — and dedupe refuses it too, because the bytes differ. That gap is `--detect-near-duplicates` (the batch tool's `detect_near_duplicates`): each result gains a `near_duplicates` list naming ACTIVE memories (or earlier ops in this same stream) whose text sits at or above cosine 0.92 against the op's claim and body, best first, each entry carrying the other claim's text, the measured `similarity`, and one of `path` or `batch_index`, the same split `conflict` uses. PROPOSE-ONLY, exactly like `--detect-conflicts`, and for one more reason: the score is geometry, and geometry is weak on the tokens that carry polarity — a claim and its negation also sit above 0.92 — so read the paired claim before folding anything. Left alone, the next sleep's `dedup-merge` folds true rewordings under its divergence guards; this flag is for a batch writer that should not have to wait for a sleep run to learn it restated the corpus. It costs one embedding call per batch. On an `article_html` line it is always null (the claim lives inside your markup), and when the embedder cannot run at all (`MEMHTML_EMBED=off`, or the call failed) the result carries `near_duplicates_degraded: true` and every `near_duplicates` is null, meaning UNCHECKED rather than unique.

## 2. Commands it names

* [`memhtml apply`](/reference/commands/apply/)
* [`memhtml search`](/reference/commands/search/)
* [`memhtml correct`](/reference/commands/correct/)

## 3. Provenance

A loader generates this page from `apps/cli/src/commands.ts` while the site builds, so no file in the repository holds it: the block is authored beside `COMMANDS` and is served verbatim by `memhtml manifest`, so this page and the live answer are the same bytes. Change the registry and this page changes with it.