1. Executive Summary
At this commit Silica is an evidence-retrieval tool, not a
memory system. On 8 September 2026 commit 3ee7a53c…,
titled "the potato cut — five tools, no model on any path",
deleted the state machine, the agent loop and model client, the curation
capabilities, the web UI, the memory tools, the recall lanes that needed
a model or a vault convention, and "the write
journal/undo/templates/provenance", the report lanes and the evals
tree — 79,000 lines down to 25,000 — and later commits renamed the
package silica_core. What ships is five functions over one
root, files, search, read,
code_pack and write_note, served as MCP tools
and CLI commands that locate a passage, a symbol or a page and return it
with its path and line. That is a corpus index, which this atlas keeps
outside its boundary. What follows is the governed vault as it stood at
300fab2e…,
and every path below refers to that tree. It stays because the contested
layer, the per-claim clock and the metric-discrimination harness are
reusable designs whatever happened to the product.
Silica is an AGPL-3.0 harness that governs a folder of markdown — an Obsidian vault, a codebase's docs, research material — as agent-writable memory. About 72,700 lines of Python across 208 modules, 955 commits since 25 May 2026, with a CLI, a Claude Code plugin, an MCP server and a web UI over one vault. It targets the Open Knowledge Format v0.2 and runs local inference optionally.
Its thesis is in one sentence of the README: "the harness guides, the LLM proposes, a parser and an FSM verify and execute, and every write is verified against a source, reverted if corrupted." The model never writes; it proposes into a state machine that does.
No marks at this commit. At the earlier pin the design carried four —
bitemporal, audit_log,
human_review and negative_eval — on the
mechanisms described below, all of which the cut removed.
A contradiction is kept, not settled.
contested.py records it on the existing note as a
frontmatter flag and a warning callout and leaves it "until a human
resolves it", with an Unresolved. tail, and one
contradiction can be closed while its siblings stay open. Pure
functions, no I/O, no model.
The auto-resolver is asymmetric on purpose, and says
why. suppress_contest will let reliability settle
a contest, but recency can only veto — "recency never
resolves a contest here, it only refuses to let reliability resolve one
it would get wrong." An unknown clock on the target vetoes a dated
incoming claim, because "silence about when a note was last true is
not evidence that it still is", and the trade is stated:
"declining to auto-resolve leaves a visible contest, while resolving
wrongly buries a live claim under ## Superseded."
The per-claim clock is a comment, and the reason is
good. valid_from rides in
<!-- silica: valid_from=2023-05-08 run=b07f1268 -->
rather than in frontmatter, because frontmatter is per note "while a
note accumulates claims from many sources on different dates", and
a comment is invisible in preview, greppable, and survives every write
path byte-for-byte with no YAML round-trip.
And the evaluation harness has something this atlas has been
asking for without a name.
evals/negative_controls.py does not check whether the
system answers correctly. It checks whether each deterministic gate
metric can still fail — see section 10, which is the part of
this report to read if you read one.
Weakest: a contested note is still retrieved. The
flag reaches the reader as a rendered
| contested: <reason> on the recall block rather than
as a gate on admissibility, so the disputed claim goes into the prompt
with its dispute attached. That is a defensible design and a different
one from what the trust_state mark measures, which is why
the mark is withheld.
2. Mental Model
source document ──► harness guides ──► LLM proposes ops
│
parser + FSM │ setup → distill → collision
│ → linking → write → finalize
▼
validate against source
│
┌───────────────────┴───────────────────┐
verified corrupted
│ │
atomic write revert from
+ inverse row undo journal
+ ledger row (prior_content)
│
contradiction found?
│
┌───────────────┴────────────────┐
strictly outranked otherwise
AND no fresher loser │
│ flag the note:
auto-resolve contested: <reason>
(`## Superseded`) + Unresolved.
+ register the path
│
run digest ──► a human decides
The design's premise is that the expensive failure is not a missing memory but a wrong write into a folder the user also edits by hand. Everything follows from that: the FSM, the source verification, the inverse per path, the atomic write, and a contradiction policy that would rather leave a visible mess than bury a live claim.
3. Architecture
Diagram source
flowchart TD
%% caption: the model proposes and a finite-state machine executes — each write verified against its source, an inverse recorded before it lands, and a contradiction flagged onto the note and registered for a person rather than resolved away
SRC["source document"] --> HARNESS["harness prompt"]
HARNESS --> LLM["LLM proposes ops"]
LLM --> PARSE["recipe_parser"]
PARSE --> FSM["router FSM<br/>setup · distill · collision<br/>linking · write · finalize"]
FSM --> VAL["kernel/write/validate.py<br/>checked against the source"]
VAL -->|"fails"| REV["revert from undo journal<br/>prior_content"]
VAL -->|"passes"| ATOM["atomic_write"]
ATOM --> INV[("undo_journal.db<br/>runs + inverses")]
ATOM --> LED[("ledger.db<br/>committed | failed | rolled_back")]
ATOM --> VAULT[("markdown vault<br/>+ sources/ verbatim")]
ATOM --> CONTEST{"contradiction?"}
CONTEST -->|"target strictly outranks<br/>AND loser not fresher"| SUP["auto-resolve<br/>## Superseded"]
CONTEST -->|"otherwise"| FLAG["frontmatter flag<br/>+ warning callout<br/>+ Unresolved."]
FLAG --> REG[("contested_register.json<br/>rebuildable worklist")]
REG --> DIGEST["run digest"]
DIGEST --> HUMAN(["a person resolves"])
HUMAN --> VAULT
Q["query"] --> BM25["hand-rolled BM25<br/>+ optional embeddings + graph"]
VAULT --> BM25
BM25 --> PERC["perception blocks<br/>rendered with | contested: reason"]
PERC --> OUT["context for the model"]Runtime. silica/ holds
kernel (write, recall, link, organize, report, code,
calendar), router (the FSM, orchestrator, coordinator),
driver, tools, sources,
capabilities, agent, ui (a web
server and renderer), skills, recipes,
overlays and onboarding. Four interfaces: a
CLI, an MCP server, a Claude Code plugin registering SessionStart /
PreCompact / Stop hooks, and a web UI.
Persistence. The memory is the vault — markdown the
user owns and can edit outside the tool. SQLite sits beside it for
machinery: ~/.silica/ledger.db for per-op outcomes and
~/.silica/undo_journal.db for inverses, plus per-vault
index directories. sources/ keeps the verbatim original of
every ingested document and is retrieval-invisible by
construction — is_source_leaf excludes it from
search, search context and embeddings — so the source is reachable for
verification without competing with the notes derived from it. That is
the cleanest separation of evidence from belief in the
corpus, and it is what reliability_tier reads.
4. Essential Implementation Paths
The FSM. router/base_fsm.py with states
in router/states/ — setup,
distill, collision, linking,
write, finalize — driven by
orchestrator.py and coordinator.py. A proposal
that does not parse does not reach a state that writes.
Verification and atomicity.
kernel/write/validate.py is a thousand lines of checks
against the source; atomic_write.py and
tools/atomic.py do the write; undo_journal.py
records an inverse — kind, prior content, post-hash, and a
to_path for moves — before the change is considered done,
which is what makes /revert mechanical.
The contested layer.
CONTESTED_KEY = "contested",
CONTRADICTIONS_KEY = "contradictions",
_UNRESOLVED_TAIL = "Unresolved.", and
resolve_contested for closing one contradiction at a time.
mark_superseded_by exists because of a named prior failure:
"The merge loser used to be left on disk with overlapping content
and no link to the winner: two notes saying the same thing and no record
that one replaced the other."
suppress_contest, quoted at length in section
1, is the atlas's favourite kind of function: a policy, its two
conditions, the asymmetry, the reason for the asymmetry, and its
measured precision with and without the guard — all in the docstring,
over a named fixture directory.
The claim stamp. stamp(**fields)
renders <!-- silica: valid_from=… run=… --> with
caller-ordered keys so the line is deterministic and empty fields drop
out. note_clock returns the freshest
valid_from or an OKF verified.at, and treats
neither as silence rather than freshness.
5. Memory Data Model
A note is a markdown file with OKF-shaped frontmatter. The
atlas-relevant fields are the contested flag with its
reason, the contradictions list, an OKF
verified.at a person writes when they read the note, and
the per-claim valid_from stamps in the body.
Reliability is derived, not declared.
reliability_tier reads whether the note's verbatim source
is retained under sources/ — so the tier is a fact about
what can be checked, not a number a writer chose. Keeping the source
costs disk and "nothing else" because the folder is
retrieval-invisible, and --no-keep-sources opts out at the
cost of the tier.
Two clocks and they are separate.
valid_from is when a claim held, from the source document's
own date supplied at ingest as --seen; the run id in the
same stamp, and the ledger and journal rows, say when the system wrote
it. That is the bi-temporal split, and the CLI guards the parse of
--seen because a typo would "poison note_clock
vault-wide."
No principal key inside a vault. Separation is per vault — an index directory, a ledger and a manifest each — which is partition-shaped isolation and the reason the scope mark is withheld.
6. Retrieval Mechanics
A hand-rolled BM25 with optional embeddings, a graph layer
(kernel/recall/graph_export.py, mindmap.py,
tools/graph.py), and a context builder that assembles
blocks for the prompt. The README puts the embedding advantage at about
six percentage points over the CPU-only fallback, which is an unusually
modest claim to make for one's own optional component.
A contested note is retrieved and labelled.
kernel/recall/perception.py carries
contested: str | None as "correction reason when
flagged, else None", and renders it into the block head as
| contested: <reason>. So the model sees the claim
and the dispute together.
That is a real choice with a real cost, and it is worth stating both
ways. Against withholding: a disputed claim is often the best available
answer, and hiding it produces a confident silence rather than a hedged
answer. For withholding: nothing prevents the model from using the claim
anyway, and the atlas's rubric asks for a state that can refuse. Silica
labels; it does not refuse. trust_state is withheld on
that, not on absence.
sources/ is invisible by construction,
so a query never retrieves the raw document over the note distilled from
it — the failure mode where a vault's search results collapse into whole
source files.
7. Write Mechanics
Every write is a proposal validated against its source, executed atomically, and recorded twice: an inverse row that can undo it and a ledger row that says what happened.
The ledger records refusals, and then overwrites
them. status ∈ {committed, failed, rolled_back} is
better than most stores here manage — a failure is a row, not a log
line. But the schema carries a UNIQUE constraint on
(source_canonical, path) with UPSERT semantics, for a good
reason (idempotent resume: a re-run skips a source whose ops all
committed with a matching content hash and whose outputs are still on
disk). The consequence is that the record of a failed attempt is
replaced by the success that follows it, so the ledger answers what
is the state of this path and cannot answer how many times did
this fail before it worked. The undo journal, which is per run and
append-only, is where that history actually survives.
Corruption is handled rather than assumed away. A
corrupt undo journal is quarantined and recreated with a warning, on the
stated ground that it "must not brick startup or the /revert of
future runs", and git is named as the durable backstop for older
history via SILICA_GIT_COMMIT=auto. Naming your own
mechanism's backstop is rarer than shipping the mechanism.
8. Agent Integration
Four surfaces over one vault: a CLI, an MCP server
(mcp.json, plus a mcp.codex.json), a Claude
Code plugin whose hooks/hooks.json registers SessionStart,
PreCompact and Stop, and a web UI. The screen flagged all of these as
auto-run surfaces, correctly — a plugin manifest and a hook registration
are exactly the things that execute without a command being typed, and a
reader installing this should look at hooks/hooks.json
first.
The screen also produced one false positive worth recording, because
it is a useful demonstration of what the tool does and does not know:
silica/router/states/setup.py was flagged as "executes
arbitrary Python at install time" on the strength of its filename.
It is an FSM state named setup, not a packaging script. The
heuristic is right to be filename-driven and a reader still has to open
the file.
9. Reliability, Safety, and Trust
The write path is the safety story and it is layered: parse, verify against source, atomic write, inverse recorded, ledger updated, revert available. The README's "100% write integrity across a real 796-note vault" is a claim about that path; it is one run over one vault, which the README says.
The contested layer is the trust story and it is honest about being partial. A contradiction is visible, attributed, dated per claim, and left for a person — and while it waits, it is still retrievable.
The evaluation harness is the third layer and section 10 is about it.
What is missing: no value-keyed refusal, so a claim
a person deleted can be re-ingested from the same source on the next
run; no principal scoping inside a vault; and the reliability tier
depends on sources/ being kept, which
--no-keep-sources turns off with the consequence stated but
not enforced.
10. Tests, Evals, and Benchmarks
387 test files under tests/, plus an evals/
tree carrying LoCoMo, LongMemEval, MuSiQue, FactScore, a golden 796-note
vault with its own probes, paired statistics, and a set of
probe_* modules. The README reports 82.1% answerable
accuracy and 87.2% correct refusals on LoCoMo, "one run, both
numbers" — the qualifier is the project's own.
evals/negative_controls.py is the reason to read
this repository even if you never install it. It is not a
negative control on the system; it is a negative control on the
metrics, and its opening line states the problem exactly:
"A metric that cannot fail reports PASS regardless of the arm, and
the gate reads as a result."
Each entry pins a metric against cases it must score exactly, and the
rule is that at least two cases must disagree — "a
metric stuck at 1.0 and a metric stuck at 0.0 are both dead, and only a
pair of fixtures separates a live metric from either."
assert_metrics_discriminate takes the names the runner is
about to compute and refuses any it does not recognise, so adding a gate
metric without a control fails the run rather than passing quietly. It
runs before any model work, "so a dead metric costs zero
tokens."
The docstring then lists the times this bit them, with commit shas:
a333ce0, where the L3 gate scored the recomposed floor and
not the note; e8ddf63, where the decompose cap cut long
notes mid-fact and never judged the tail; a PPR phase-0 kill gate that
was vacuous because 3-hop reached 98% of the vault; and the pure form —
two eval metrics matching \d+ against citation IDs
guaranteed to contain a letter, so both scored 1.0 on every input and
"two rows of its summary table were decoration." It also names
the hole it cannot close: a runner that never mentions its new metric in
the call at all. And it scopes itself out of LLM judges, because "a
judge cannot be pinned to an expected value."
This atlas has repeatedly found the failure this module exists to
prevent — most recently one report ago, in a negative retrieval test
that asserted every over an array a fresh database
guaranteed to be empty. Silica has generalised that into a registry with
a rule that fails the build.
evals/golden/probe_supersede.py is the second
one, and it reports its own insufficiency. Over the golden
vault — 796 notes, 1,064 pairs, 54 tier-split — it measures 0 resolution
inversions under merge_rank = (tier, len) against 43 under
the bare len(body) it replaced, rates 0.0000 and 0.0209.
Then: "the gate does catch a revert of §6.2, though only just:
2.09pp against a 2pp tolerance. A partial revert would slip under it.
Tighten the tolerance for this key." A probe that publishes the
margin by which it barely works is doing the thing this atlas asks of
benchmark authors.
probe_abstention_rubric.py re-judges
stored responses with the current abstention rubric and asserts three
known false negatives flip to true while a synthetic confabulation stays
false — a calibration check on the judge rather than on the system, and
correctly labelled as such.
The negative_eval mark itself rests on the ordinary
tests: test_embed_search_topk.py asserts a named note is
absent from a top-k result over a seeded vault,
test_context_builder.py asserts a heading is absent from
assembled context, and test_cohesion.py asserts specific
notes are not among a note's related list.
11. For Your Own Build
Steal
- Write a negative control for every deterministic gate metric, and make the registry refuse an unknown name. Two fixtures that must disagree, checked before any model work. This is the cheapest defence against an eval suite that reports PASS because it cannot report anything else, and almost nothing in this corpus has it.
- Make the auto-resolver asymmetric and say which way. Reliability may settle a contest; recency may only veto. An unknown clock on the incumbent vetoes a dated challenger, because silence is not evidence of freshness.
- Keep the contradiction visible instead of resolving it
away. A flag, a reason, an
Unresolved.tail, and a rebuildable worklist the digest reads — and the ability to close one contradiction while its siblings stay open. - Put the claim clock on the claim, not the note. A note accumulates claims from many sources on many dates; one frontmatter date cannot carry that. An HTML comment is invisible in preview, greppable, and survives write paths that a YAML round-trip would perturb.
- Keep the verbatim source and make it retrieval-invisible. Evidence you can check without it competing with the notes derived from it — and a reliability tier that reads whether the evidence is still there rather than what a writer claimed.
- Guard the one string that poisons everything. The
--seendate becomesvalid_fromon every claim of a run; parsing it at the boundary with the blast radius written in the comment is four lines. - Publish the margin by which your gate works. 2.09pp against a 2pp tolerance, with "a partial revert would slip under it" written next to it.
Avoid
- Do not let a label stand in for a gate if you need one. A contested claim rendered with its reason still reaches the model; if the requirement is that a disputed memory not be acted on, a rendered reason is not that.
- Do not let an idempotency index double as an audit. The op ledger's UPSERT is right for resume and wrong for history: the record of a failure is replaced by the success that follows it.
- Do not report one run as a rate. The README is explicit that its LoCoMo numbers are one run each, which is the right disclosure and still one run.
Fit
Take Silica if your memory is a folder a person also edits, and the failure you fear is a wrong write rather than a missing recall. The FSM, the source verification and the inverse-per-write are built for exactly that, and the contested layer is the most careful treatment of "two claims disagree" in this corpus.
Take negative_controls.py regardless of what you are
building. It is 146 lines and it is separable from everything else
here.
Look elsewhere if you need multi-principal scoping inside one store, or a state that can refuse to serve a disputed claim rather than annotate it.
12. Open Questions
- Should
contestedgate admissibility as well as render? The flag, the reason and the register all exist; the recall path chooses to label. An opt-in that withholds contested claims from assembled context would cost one predicate and would make the mark's question answerable either way. - What does the ledger's UPSERT hide? A path that failed four times and committed on the fifth is indistinguishable from one that committed first time. The undo journal keeps per-run history; the two are not joined.
- How often does
suppress_contestdecline? Its precision with the veto is measured on a fixture directory; its rate on a live vault — how many contests a person is actually asked to resolve per run — is the number that decides whether the design is usable at scale. - Does
--no-keep-sourcesdegrade the tier silently? The comment saysreliability_tierreads exactly the retained source. Whether a vault built without sources reports its tiers as unknown or as low is the difference between a caveat and a wrong number. - Are the LoCoMo numbers stable? One run each, stated
as such. The harness has
paired_stats.pyand the fixtures to say more.
Appendix: File Index
- Write kernel:
silica/kernel/write/—validate.py,atomic_write.py,ops.py,ops_io.py,bulk.py,merge.py,frontmatter.py,checkpoints.py,session_changes.py,timeline.py,templates.py - Contested layer:
silica/kernel/write/contested.py(stamp,note_clock,suppress_contest,resolve_contested,mark_superseded_by),silica/kernel/contested_register.py - Audit and revert:
silica/kernel/write/undo_journal.py,silica/kernel/write/ledger.py,silica/kernel/write/provenance.py,silica/router/warning_ledger.py - FSM:
silica/router/base_fsm.py,orchestrator.py,coordinator.py,recipe_parser.py,states/(setup,distill,collision,linking,write,finalize) - Recall:
silica/kernel/recall/—perception.py(the contested rendering),curator.py,mindmap.py,graph_export.py,episodic.py,paths.py(is_source_leaf) - Evals:
evals/negative_controls.py,evals/golden/probe_supersede.py,evals/probe_abstention_rubric.py,evals/paired_stats.py,evals/locomo/,evals/longmemeval/,evals/musique/,evals/factscore/,evals/golden/fixtures/contests - Tests:
tests/— 387 files;test_embed_search_topk.py,test_context_builder.py,test_cohesion.pycarry the negative retrieval assertions,test_bitemporal_invariants.pythe stamp invariants
History
2026-09-15 — 3fd11a00…
— 139 commits on, 2026-09-15. Screened before reading: five auto-run
surfaces (plugin manifests and hooks), one build-time execution point,
two unpinned surfaces and four dependency surfaces inside the cooldown;
nothing was installed or run. The subject of this report is gone: 3ee7a53c…
(2026-09-08) cut Silica to a five-tool evidence-retrieval core, deleting
the write journal and undo, the contested layer, the claim stamping, the
curation and memory tools and the evals/ tree, and the
package became silica_core. The three test files the
negative_eval record cited no longer exist. All four marks
are withdrawn at this pin — bitemporal,
audit_log, human_review,
negative_eval — and the body keeps the design as it was at
the previous pin, framed as removed.
2026-09-13 — the repository was renamed from
kiycoh/silica-harness to kiycoh/silica-core,
upstream of the pinned commit and after the reading below. The rename
reaches the code rather than the README alone:
pyproject.toml declares name = "silica-core",
and the old spelling survives nowhere in the tree — at
300fab2e… it appeared in the
banner URLs, the DeepWiki and licence badges, both plugin-marketplace
commands and the uvx --from 'silica-harness[mcp]'
invocation. The distribution silica-harness resolves on
PyPI beside silica-core. No re-reading: the pin,
analyzed_at and every finding are unchanged, and only
source_name, source_url,
revision_url, archive_name and the
repositories-inspected entry moved. The slug stays silica,
so no published URL moved and no redirect stub was needed. The archive
fork was renamed to
agent-memory-atlas-archive/kiycoh--silica-core to match,
after the owner wrote in to report the stale name.
2026-08-23 — 300fab2e…
— first reading, AGPL-3.0, ~72,700 lines of Python across 208 modules,
955 commits since 25 May 2026. Screened before anything was read: four
auto-run surfaces — a .claude-plugin/ marketplace and
plugin manifest, a hooks/hooks.json registering
SessionStart, PreCompact and Stop, and an mcp.json — three
build-time execution points, and both pyproject.toml and
uv.lock changed the day of the pin, inside the cooldown.
Nothing was installed, no hook was registered, no eval was run and no
vault was opened. One screen finding is a false positive worth
recording: silica/router/states/setup.py was flagged as
install-time execution on its filename and is an FSM state. Four marks.
trust_state is withheld deliberately rather than for
absence — the contested flag is rendered into the recall block as
| contested: <reason> instead of gating
admissibility, so a disputed claim reaches the model annotated rather
than withheld. scope_enforced is withheld because
separation is per vault; tombstone because nothing keys on
a removed value.