mem_recall renders fact ids so mem_correct can supersede them
I had no way to correct a false fact from a session. mem_correct takes an integer id, but mem_recall stripped the id before rendering.
The id existed but was dropped
mem_recall selected the id with SELECT *, used it twice in the handler for record_recall and get_fact, then dropped it at render. The id was in the result and was stripped at formatting.
I added the id to the render output as #<id>, matching the format mem_inspect already uses.
A second problem has no fix yet
mem_inspect is the other half of the correction path, but it is keyed on the one thing no read path produced. That is now fixed.
retract_fact is implemented in memory_engine.py:558 but exposed by no MCP tool, handler, or dashboard route. A fact that is simply false, with no truer replacement, cannot be retired at all. I reported it but did not fix it.
A trap in memory_search
memory_search queries the legacy memory_fts, fed by temporal_events, session_summaries, procedural_memory, and context_facts. It renders hits as [context_facts#12]. That integer sits next to the word "facts" but belongs to a different table with a different id space. Feeding it to mem_correct either 404s or supersedes an unrelated fact.
The diary ingester now reports what it skipped
A FACTS line containing a pipe is silently skipped. I traced the skip to _parse_fact_line and confirmed exit 0 was unconditional across all three return paths in main. The summary dict was computed and then discarded. Nothing was ever written to stdout.
I changed the ingester to print one summary line per diary with a FACTS block, one indented reason per skipped line, and a three-way exit code. Exit 0 is nothing lost, exit 1 is content total loss, exit 2 is store failure.
A MemoryEngineError from the store is a bad fact and is skipped. Any other exception is a bad store and aborts at the first occurrence with exit 2.
The reason strings never interpolate the claim and print subject or method tokens only when they clear an allowlist. The claim is where a pasted command or key would sit.
Pipe escaping is still open
I did not ship pipe escaping. It changes the FACTS format contract, which lives in the sibling memstack repo. That needs a backward-compatibility decision that has not been made.
I corrected a fact before storing it
The instruction stated all 1,244 subjects were distinct. I measured against the live database: 1369 live facts under 1155 distinct subjects, 91 subjects with more than one live fact, worst case 50 under memstack_skill_loader.agent_runner. The decline of lookup-by-subject was right for a stronger reason than the one given: it is already ambiguous, not ambiguous-in-future.