1. Executive Summary
The source of this report no longer exists upstream.
Perseus-Computing-LLC/perseus-vault returns 404, and so
does the pinned commit 9c829207. Unlike AMITY, the atlas holds a copy: the fork at agent-memory-atlas-archive/Perseus-Computing-LLC--perseus-vault
was taken on 2026-09-09 with every branch, so 9c829207 and
every file path below are readable there. Links in the body point at the
dead upstream because that is where the reading happened; the archive is
the working route.
The deletion also cost the project its identity in GitHub's own
records. An orphaned fork network is reparented onto a surviving sibling
rather than left headless, so the fork's parent reads
johan--/mneme, an unrelated repository, and the project's
own 2026-09-06 head sits on a preserved branch beside
main.
Perseus Vault is roughly 63,000 lines of Rust over 666 commits, MIT-licensed, shipping one binary and one SQLite file with no services. It exposes memory through an MCP stdio server plus LangGraph, CrewAI and AutoGen adapters, stores entities with bi-temporal bounds, retrieves with BM25 and dense vectors fused by RRF, and encrypts bodies with AES-256-GCM by default on a fresh install. It carries all seven of this atlas's capability marks.
That would make it a substantial report on mechanism alone. It is a more interesting one because of how it handles its own claims.
It ships a CLAIMS-AUDIT.md that retires claims
it cannot back. The file audits the README against code and
committed artifacts, and its 2026-07-16 entry records what that
cost:
- "Retired the 'sub-millisecond recall' entry" — with the reason stated plainly: no committed artifact supported it, and the old justification, bundled offline embeddings, "said nothing about latency." The replacement is measured and cited: FTS5 recall p50 3.14 ms at 10K entities, dense p50 194.5 ms at 1M.
- "Removed the unbacked 100K-entity insert-rate figure from the README (no artifact anywhere in the repo backed it)."
- Reworded "signed results" to "content-hashed
(sha256)", because
signature_sha256is a self-computed content hash "not a cryptographic signature." Downgrading your own security-adjacent wording is not a common edit. - Clarified that
federateis a local export and re-import, "file based, no network peers."
This atlas's benchmarks page has spent most of its length on figures with no traceable artifact. Here is a project that went looking for its own and deleted the ones that failed.
Its benchmark discipline is the strongest in the
corpus. The README leads with 73.8% on LongMemEval against
Zep's published 63.8% and Mem0's 49.0%. That number is the mean of
three independent full 500-question runs, each with its
own committed report carrying dataset, split, n_instances,
mock_llm: false, the pinned answerer and judge model
snapshots, temperature, retrieval mode and k, commit,
binary version, platform, hardware, elapsed time and a run signature.
Recomputed from those three artifacts at this commit:
qa_report.json 0.736
qa_report_seed2.json 0.750
qa_report_seed3.json 0.728
mean 0.738 ← the published figure, exactly
And the headline is the lower of its two numbers.
The same directory holds three official-cot runs meaning
79.0% (0.800 / 0.786 / 0.784, also recomputed exactly),
and the README quotes the plain-prompt 73.8% instead. The comparison
document explains why in a section headed "Caveats (read before
quoting the number)": the two prompts are different official
conditions, "every quote must carry its answer_prompt
label", and — on the competitors — "Zep's publication does not
state their variant; flag that when comparing, never blend." Zep's
and Mem0's figures are labelled as their publishers' claims and cited to
an issue rather than reproduced as head-to-heads, which is the reporting
policy this atlas credits Daimon for writing
down, implemented.
And the count claim is derived rather than asserted.
The audit's check is scripts/registry_metadata_check.py,
which extracts the embedded registry literal from
src/mcp.rs and parses it exactly as the Rust implementation
does, rather than grepping for a name pattern. From that one derivation
it asserts the same number appears in README.md,
CLAIMS-AUDIT.md, glama.json,
manifest.json and server.json, and it fails on
a duplicate name or a non-canonical one. Run at this commit it exits
zero and prints
{"canonical_tools_list_count": 90, "compatibility_manifest_count": 270, "registry_count": 90}.
What makes it a check rather than a documented command is
.github/workflows/mcp-registry.yml, whose
verify-registry job runs on every push and
pull_request and executes both the parser and the Rust
uniqueness test. The audit describes it in those terms: "this parser
is formatting-insensitive and runs in CI".
That is the difference between a count that agrees today and a count
that cannot silently drift, and the second is the property worth having.
The canonical/legacy split does not live in the literal —
tool_registry_base calls the mimir_* names
"an implementation migration detail" and
legacy_alias_tool synthesizes aliases by prefix rewriting
at advertise time — so the parser rewrites a mimir_ prefix
to perseus_vault_ before counting, and rejects the result
if any name survives that is not canonical. The advertised surface
remains a runtime decision, and
compatibility_manifest_count is the parser's own name for
it: 270, three aliases per canonical tool.
A fresh install encrypts, and a test says what that
means. The first default startup creates an owner-only key and
an encrypted canary row. tests/encryption_bootstrap.rs pins
the behaviour rather than the intent: the key file must be mode
0600 and hold exactly 32 bytes, the canary must be
established, and the stored body must not equal the
plaintext JSON that was written —
assert_ne!(stored, r#"{"note":"bootstrap encrypted body"}"#).
A second case covers the opt-out, asserting that an explicit plaintext
choice suppresses key creation and stores the body verbatim.
Two things the default does not cover, and both are stated
upstream. A database created before the default flipped stays
plaintext until an explicit init --rekey, so "encrypted by
default" is a claim about fresh installs and the claims audit says
exactly that. And the key sits beside the database it protects, at
owner-only permissions — which defends the file carried off a disk, not
an attacker who already reads the filesystem as that user. The threat
model is narrower than the banner, disclosed, and the narrower claim is
the one to quote.
2. Mental Model
An entity's standing is three separate things — a discrete
status, a boolean verified, and a
certainty float — over a bi-temporal record that keeps its
own history.
Diagram source
%% caption: world time and transaction time are separate column pairs on both the entity and its history, so supersession leaves the prior version readable as-of a past date
stateDiagram-v2
[*] --> candidate: trust admission
candidate --> active: accepted
active: status active<br/>verified 0 or 1, certainty float
active --> active: correct, score, promote, demote
active --> superseded: supersede writes a history row<br/>and sets superseded_by
superseded: valid_to and invalidated_at closed<br/>prior version readable via as_of
active --> deprecated: demote
active --> archived: archive with archive_reason
archived --> active: restore
superseded --> [*]: purge erases history<br/>and redacts the journal
archived --> [*]: forget
note right of superseded
valid_from / valid_to is world time.
recorded_at / invalidated_at is
transaction time. Both are columns,
on entities and on entity_history.
end noteThe schema comments say it directly — valid_from_unix_ms
is "when the fact became true in the world" and
recorded_at_unix_ms is "transaction time: when Mneme
first knew it" — which is the distinction the bi-temporal fact
validity pattern exists for, present on both the live table and its
history.
What the diagram has no state for is a value that was judged wrong.
purge and forget remove records; nothing
records the content so a later ingest cannot re-assert it. That
is the one mark missing and the one gap the rest of the design makes
conspicuous.
3. Architecture
One Rust binary over one SQLite file. Tables: entities
and entity_history, journal with
audit_chain_state, communities,
keystones, artifacts and
artifact_bindings, agents,
authority_manifests with authorized_actions
and authorized_action_leases,
dedup_signatures, encryption_canary, and a
state key-value table.
Entity columns carry the whole model: category,
key, body_json, status,
type, tags, decay_score,
retrieval_count, layer,
topic_path, archived with
archive_reason, links, verified,
source, certainty, always_on,
workspace_hash, visibility, the four temporal
columns, an embedding blob and an emb_sig
sign-bit signature.
Deployment and ergonomics
curl | sh to ~/.local/bin, then
serve --db. No Docker, no Postgres, no network dependency
in Cargo.toml. Encryption requires an explicit
init that creates the key and the canary row;
serve warns when an encrypted vault is opened without a
key.
The cost of the single-file model is the usual one and the project
does not hide it: federate is export and re-import rather
than peer sync, so multi-machine use is a file operation.
4. Essential Implementation Paths
Three runs, one signature, and a caveat section
benchmark/longmemeval/ holds run.py, the
per-run reports, COMPARISON.md and a retrieval diagnostic.
Each report is a full config stamp rather than a score, and the
comparison document records the provenance per run — "plain run 2
signature 929623670d8bcc67…; CoT runs
20327b31b5940f58… / 7c8ce1b406c0cc4b… /
eb848e786677a8d1… — each over the per-question verdict
set."
The design decision worth stealing is that the answer prompt is "folded into the run signature (a CoT number can never be silently blended with a plain-prompt one)". Blending incomparable conditions is the most common way a benchmark table misleads without anybody lying, and making the condition part of the hash removes the possibility rather than warning against it.
Publishing per-question-type results is the second. The committed
report shows single-session-user at 0.957 and
single-session-preference at 0.300 on 30
questions. A weak category is visible because they published the
breakdown; in most of this corpus it would be inside the headline
average.
The count, derived from source and asserted in CI
Three surfaces say 65. The audit's designated command returns 76, and
the tool names it lists are unmistakably real — recall,
supersede, correct, forget,
purge, bitemporal, valid_at,
operator_review, journal,
beliefs, conflicts, promote,
demote, keystone_get, and so on.
The instructive part is where the fix landed. This is a project that retired a latency claim for lack of an artifact and downgraded "signed" to "content-hashed" on its own initiative. The claim that had gone stale was the one with a check attached — because a command written down in a Markdown file runs only when somebody remembers, and the discipline that catches unbacked claims is a different discipline from the one that keeps backed claims current. It is the same failure this atlas has recorded in its own count sweeps: the guard exists, and nothing runs it.
Trust as three axes, not one float
status carries active,
candidate, superseded,
deprecated, compacted, dead and
useful; verified is a separate boolean;
certainty is a separate float; source records
where it came from. src/trust_admission.rs gates what
enters.
Keeping "we checked this" apart from "how confident are we" apart
from "what state is this record in" is a distinction most systems here
collapse, and it is what makes the trust_state mark
straightforward: candidate and superseded are
states that withhold a memory from being treated as current, expressed
as a column rather than inferred from a number.
A tombstone that stores the digest, not the value
rejected_value_tombstones is keyed on
(workspace_hash, subject, predicate, value_sha256) and
carries reason, evidence_ref,
author_agent_id, a creation timestamp and an optional
expiry. The value itself is never written — only
value_sha256, over a normalized form:
fn normalize_rejected_value(value: &str) -> String {
let canonical = match serde_json::from_str::<serde_json::Value>(value) {
Ok(v) => v.to_string(),
Err(_) => value.to_string(),
};
canonical.split_whitespace().collect::<Vec<_>>().join(" ").to_lowercase()
}
JSON is canonicalised when it parses, whitespace is collapsed, and the result is lower-cased — so a re-indented body, a re-ordered object and a case variant all hash to the same tombstone. That is the normalization the rejected-value tombstone pattern calls the part where the real work is, done in nine lines.
Storing only the digest is the part worth taking. The pattern page lists as a tradeoff that "the tombstone itself can contain sensitive data and must follow deletion policy" — a rejection record for a leaked credential is a copy of the credential. A digest refuses the value without retaining it, and the doc says so in the same terms: the raw value is never stored, so "rejection records cannot leak the content they suppress."
is_value_rejected runs the lookup as
workspace_hash IN ('', ?1), so a tombstone written with an
empty workspace is global and a named one binds to its workspace, and
one workspace's rejection cannot censor another. Expired rows are
deleted on the way past.
The enforcement point is remember_impl, with the reach
stated in a comment:
scoped rejected-value tombstones are enforced on every remember-path write (agent remember, capture, ingest, connectors, derived writers) so a corrected/deleted value cannot be laundered back in under a new key. A deliberate trusted override passes
allow_rejected=trueand is journaled below for audit.
And the check follows provenance, which is the gap every
digest-keyed tombstone has. A digest refuses the value it was
written for and nothing else, so a consolidation pass that summarises a
rejected source produces a body with a different digest and walks
straight through. value_suppressed_with_connections is
shared by ordinary reads and maintenance scans on exactly that ground —
"a derived body is not safe merely because its own digest differs
from a rejected source body's digest" — and
lineage_source_ids_from_body pulls the source references
out of three JSON fields (source_ids,
evidence_for, promoted_from) that are
"intentionally hash-free provenance references, not a new authority
mechanism", so propagating suppression never requires storing the
rejected value anywhere.
The walk is recursive with a seen set for cycles and a
depth cap of eight, and both edges fail closed: hitting the cap
suppresses, and a source whose encrypted bytes are missing or
unauthentic suppresses, because "serving a derived record whose
evidence cannot be checked would recreate the same bypass." A
maintenance source row selected by a global run takes its workspace from
the row rather than from the run's optional scope. The committed test is
named for the case:
rejected_value_does_not_flow_through_maintenance_derived_writes.
Two further details separate this from the other holders. The lookup
keys on the predicate and the digest and not the
subject, so a rejected value is refused under any key in scope
— broader than the identity index it is written under, and named in a
test as
rejected_value_tombstone_blocks_same_value_under_any_key_in_scope.
And the override is not a bypass flag but an audited act, journaled in
the same hash-chained ledger as everything else, which is the shape the
pattern page asks for when a trusted human correction must win.
Four tests cover it: blocking under any key in scope, workspace
isolation, normalization and expiry, and — at the MCP layer —
rejected_value_tombstones_block_laundering_and_support_audited_override,
whose name is the whole mechanism.
Deletion that is tested to have happened
purge_erases_history_and_redacts_journal_for_purged_entities
seeds an entity with fake PII, supersedes it several times so
entity_history accumulates, then purges and asserts the
history rows are gone, the journal rows are marked
redacted, and category, key and
entity_id are scrubbed to empty strings. A companion test,
purge_does_not_redact_other_workspace_live_journal_rows,
asserts the blast radius stops at the workspace boundary.
An audit log that reproduces the content a user asked to delete is a
real hazard and one this atlas has flagged elsewhere. Testing both that
the secret is gone and that the erasure did not over-reach is the pair
of assertions the problem needs, and it is why the
negative_eval mark applies.
5. Memory Data Model
The unit is an entity keyed by
(category, key, workspace_hash) — an identity index the
schema comments date to issue #339 and credit with "~66x on
workspace-scoped browse at 30k rows."
entity_history mirrors the entity columns and adds the
transaction interval, with a comment describing it as
[recorded_at_unix_ms, invalidated_at_unix_ms) and
superseded_by pointing at the replacement. So a superseded
value is readable as of a past instant rather than lost, and
mimir_as_of, mimir_valid_at,
mimir_recall_when, mimir_timeline and
mimir_history are the tools over it.
The journal is hash-chained — SHA-256 plus a keyed MAC,
with audit_chain_state holding the chain head — and rows
are stamped with workspace_hash at write time "so purge
can scope journal redaction per-workspace." That is an append-only
mutation record in the system's own store with a tamper-evident chain,
which is the audit_log mark and a strong instance of
it.
What is absent is the tombstone. forget and
purge are removals; supersession records a replacement.
Nothing is keyed on the rejected value, so a later ingest of
the same wrong content creates a new entity, and the rejected-value
tombstone page's argument applies unchanged.
6. Retrieval Mechanics
FTS5 for lexical, dense vectors for semantic, fused by reciprocal
rank. The performance detail worth noting is emb_sig: a
sign-bit signature of each embedding, one bit per dimension, so
dense_search Hamming-prefilters candidates "instead of
reading every full embedding blob once the vault is large enough."
Written on store and backfilled by a migration.
Scope reaches the query: workspace_hash appears in read
predicates across tools.rs, communities.rs and
the trust-admission path, alongside a visibility column.
The usual caveat applies — the caller supplies the workspace — so the
mark certifies the key reaches the query, not that a caller cannot pass
a different one.
Measured latencies, from the artifacts the claims audit points at rather than from prose: FTS5 recall p50 3.14 ms at 10K entities; dense recall p50 194.5 ms at 1M entities on the uniform arm.
The projection that says why a memory was not returned
include_selection_decisions: true on a fused recall
attaches a bounded per-candidate record of the selection, specified in
docs/specs/selection-decisions-v1.md and implemented in
src/selection_decisions.rs. Several systems in this corpus
record why a candidate was refused at write time — Knowledge Worker's validator enumerates
bad_type:<t> and orphan_src:<id>,
Wenlan types its extraction refusals. This is
the same discipline on the read path, and the read path is where it is
scarce: the artifact answers not what came back but why
this did not.
Each candidate carries one disposition from a closed set —
selected, dropped_budget,
dropped_type_cap, dropped_caller_limit,
dropped_coverage, filtered_lifecycle,
filtered_scope, filtered_policy,
abstained, unavailable,
not_in_candidate_pool — beside its source-arm ranks, its
fused, rerank and validity components where those exist, a token
estimate with the estimator named, and a one-based
final_rank. Six design decisions in the spec are what make
it evidence rather than telemetry.
- It reconciles after the serving gates, not before them. The fused database path records arm, fusion, type-allocation, coverage, caller-limit and budget decisions; the tool layer then walks the six governed gates — requesting-agent visibility, retrieval profile and workspace scope, external-reference filters, post-ranking and caller limit, temporal reconstruction and valid-time filtering, confirmed-query fallback — snapshotting the surviving ids before each one, so a candidate is attributed to the first gate that removed it and the reported order is the delivered order. A projection built from the pre-filter fused ranking would be a different document and a misleading one.
- A missing score is omitted rather than zeroed.
missing_scores_are_explicit_and_raw_content_has_no_serialization_slotasserts the serialized trace contains nofused_scoreand notoken_estimatekey when neither was computed — the discipline the atlas's benchmarks page keeps asking for, applied to a per-request artifact. - A failed arm is reported and produces no
candidates.
armscarries each engaged source arm'sok,empty,degraded,skippedorunavailablestate, andunavailable_arms_are_explicit_without_fabricating_candidatespins that an arm that could not run contributes nothing rather than an empty slot that reads as a considered-and-rejected candidate. - It fails closed rather than truncating. The candidate list caps at 4,096, retaining delivered candidates first; if the final served order itself exceeds the bound, the projection refuses rather than returning what the spec calls "an unverifiable partial order."
- Tampering breaks the fingerprint.
replay_fingerprint_sha256covers membership, policy digest, arm state, ranks, score components, dispositions, counts, token accounting and final order;tampering_with_a_decision_invalidates_the_replay_fingerprintflips one disposition and then increments the token budget, and assertsvalidate()fails on each. - It cannot change what was served. The spec states that the gates remain authoritative and the projection "cannot resurrect a superseded, expired, quarantined, redacted, invisible, or out-of-scope entity", and — the sentence worth the whole document — that it "must not be treated as evidence of model-internal causal reasoning." A system publishing an explanation surface and bounding what the explanation is evidence of is rarer than the surface.
The projection is hash-only: candidate_id is the
existing opaque entity identifier, and no body, query, prompt,
credential or tool argument enters it, with policy inputs represented
only inside the one-way policy_digest. It introduces no new
MCP tool and no persistence table, and it defaults to off, so the
ordinary recall response shape is unchanged.
The consumer is in the other repository.
Perseus-Computing-LLC/perseus, the context engine this
atlas triaged
and excluded, projects the same vocabulary through
src/perseus/context_inspector.py and the MCP tool
perseus_context_inspect — and carries no producer of its
own, which is what a reader looking at either repository alone would
conclude was an unwired mechanism.
7. Write Mechanics
Writes arrive as MCP tool calls and are synchronous against SQLite.
Supersession writes a history row, closes the prior interval and sets
superseded_by; correct, promote,
demote and score move the trust axes;
archive sets a flag and a reason; forget and
purge remove.
Background work is extensive — decay ticks, cohere,
dream, consolidate, hygiene,
community detection — and score sets a persistent
importance floor that decay and cohere respect, with the reasoning in
the schema comment: "an explicit score survives the recency-based
recompute instead of being erased by the next tick (fidelity >
recency)." An explicit human judgement outranking an automatic
recompute is the right default and is written down where the column is
declared.
Operational cost
No model in the read path; embeddings are bundled ONNX and run
locally. The background passes are the variable cost and several are
LLM-assisted. Storage grows with history, which is the price of the
bi-temporal model, and purge is the release valve.
8. Agent Integration
An MCP stdio server implementing initialize,
tools/list and tools/call, with adapters
committed for LangGraph, CrewAI and AutoGen. Legacy mimir_*
and mneme_* aliases remain callable and are counted
separately from the canonical set — and mimir_alias_usage
exposes counters for canonical versus alias calls, so a maintainer can
see whether the old names can be retired. Instrumenting a deprecation
rather than announcing one is a small, unusually practical touch.
9. Reliability, Safety, and Trust
Strengths:
- A claims audit that retires unbacked claims, naming the retired figure, the reason, and the artifact that replaced it.
- Three full runs per benchmark condition, each with a complete config stamp, and published means that recompute exactly from the committed reports.
- The answer prompt folded into the run signature, so incomparable conditions cannot be blended.
- Competitor figures labelled as published claims and cited, not reproduced as head-to-heads.
- Per-question-type results published, including the weak category.
- Bi-temporal columns on the live table and its
history, with
superseded_by. - A hash-chained journal with a keyed MAC and per-workspace stamping.
- Purge tested both ways — the content is gone, and the erasure did not cross the workspace boundary.
- Trust split into status, verified and certainty, three axes rather than one.
- An explicit score that outranks the decay recompute, with the rule stated at the column.
- Encryption on by default for a fresh install, with a committed test asserting the stored body is not the plaintext.
- A rejected-value tombstone storing a digest rather than the value, refused on every remember-path write, with a trusted override that is journaled.
Gaps:
The tombstone is enforced on the predicate and the value, not the subject. A rejected value is blocked under any subject in scope, which is deliberate and named in a test — and it is the pattern page's first tradeoff, a normalization that can refuse a legitimately different fact, taken further than any other holder here takes it.
A tombstone can expire.
expires_at_unix_msis honoured and reaped on lookup, which the pattern page's round-8 lesson calls the way a tombstone stops being one. Nothing in the default path sets it, so this is a facility rather than a live gap.The sequence nothing yet covers end to end is the one the project's author named: reject value A, replace it with B, re-ingest A through a different write path, run the background consolidation passes, and check that A remains rejected. The committed tests cover the write paths and the override; the background passes are the untested leg.
A pre-existing database stays plaintext until an explicit
init --rekey, so the default covers fresh installs rather than upgrades; and the key lives beside the database at owner-only permissions.Federation is file-based, so the multi-machine story is manual.
10. Tests, Evals, and Benchmarks
592 #[test] functions inline across src/,
plus three integration files under tests/. Nothing was run
for this review — the suite is Rust and the benchmark harness calls
gpt-4o-2024-08-06 for both answering and judging, so
reproducing the LongMemEval figure means paying for 500 questions × 3
runs × 2 model calls.
What was verified is the arithmetic, and it holds. The published 73.8% and 79.0% are the exact means of the three committed reports in each condition. That is a weaker claim than a re-run and a much stronger one than the corpus norm: it establishes that the headline figure is a function of artifacts in the repository rather than a number in a README, and that the runs behind it were three rather than one.
benchmark/ also carries scale, contention, temporal,
recall, quality, beam, context-selection and embedding-quantization
suites with committed JSON, and GAUNTLET.md over the top.
PERF.md and the claims audit both point at artifacts rather
than restating figures.
The untested surface is the one the tool-count finding exposes: nothing in CI runs the audit's verification command, so a claim with a documented check went stale anyway.
The last twenty-four commits, which survive only in the archive
The archive fork holds 24 commits and about 92,600 added lines past the pin, the project's final weeks before the repository was deleted. Four benchmark lanes arrived in them, and one is worth reading on its own terms.
benchmark/hostile_memory_gauntlet/ grades
lifecycle rather than answers. Its README states the separation
directly — it is "deliberately separate from the answer-facing
LongMemEval arm" and grades "memory evidence and lifecycle
decisions, not generated natural-language answers" — and its case
protocol runs down the same axes this atlas's rubric does: current
versus superseded evidence, out-of-order valid-time writes,
foreign-scope refusal, scope isolation, prompt-injection and low-trust
admission, replay idempotency, deletion and tombstone behaviour,
provenance preservation. Every probe names required and
forbidden record IDs, every record carries a SHA-256 of its
exact text with its scope, actor, trust class, valid-time interval and
record time, and the loader refuses a probe that asks for evidence from
the wrong scope. A valid run must declare network_calls: 0
and offline: true; there is no judge model anywhere in
it.
What makes it different from the gold set on this atlas's benchmarks page is the
provider boundary. The contract is four functions — reset,
ingest, forget, retrieve — with
six written semantics an adapter must translate rather than silently
drop, including that a late older write "must not resurrect stale
evidence at a later as_of" and that hostile or
low-trust writes must return
quarantined/rejected with a reason code rather
than becoming serveable. It is built to be pointed at somebody else's
store.
It does not escape the authorship problem, and it says so. The committed fixture is "a small synthetic control suite… not the private holdout and cannot establish a general product claim by itself", so the grading corpus is not in the repository, and no report for any provider — Perseus Vault included — is committed under it. The contract is portable; the cases are not. A benchmark whose corpus lives outside the repository and whose repository has been deleted is now unreachable in both halves.
The other three lanes are benchmark/amr/ (a
provider-free AMR 0.1 conformance lane),
benchmark/experience_transfer/ (a corpus with
corpus.sha256, label_commitments.json and a
reference workflow carrying receipts.json), and a
provider-free LongMemEval-V2 readiness lane.
src/task_state.rs arrived at 2,056 lines in the same
window, alongside an evidence-sufficiency gate at the answer
boundary.
10a. Mechanisms beside the store
Three pieces of machinery sit next to the entity store rather than inside it, and each answers a question the store cannot.
A zero-token write gate.
src/write_gate.rs is a deterministic precheck the provider
flow calls before LLM enrichment: content-hash dedup, key
supersession, and a stored-signature near-duplicate scan return
Store, Duplicate, Supersede,
Forget or Adjudicate, and only the last
escalates to a model or an operator. Two properties are stated as
contract rather than left to reading: the gate is read-only by
construction — it never mutates — and Forget is
"deliberately conservative (only vague/empty notes) so the gate can
never drop a substantive fact". Putting the cheap deterministic
layer in front of the expensive one is the zero-LLM capture pattern,
applied to the decision rather than to the capture.
A self-audit that distinguishes "could not check" from
"passed." src/verify.rs re-asserts the store's
invariants against a live database over a
SQLITE_OPEN_READ_ONLY connection, across eight checks, and
its status enum is three-valued: Pass,
Unverified, Fail. The exit contract makes the
middle state load-bearing — 0 all pass, 2 a check could not run
(UNVERIFIED, never PASS), 3 an invariant is violated — so a
verifier that cannot reach a check reports that rather than reporting
success. Findings print path:key only, never values, so
running it against a real store does not spill one. This is the same
discipline the benchmarks page credits a40-labs/memory for,
and the two are the only instances in the corpus.
A memory red-team harness, honest about being a
skeleton. benchmark/redteam/ aims three published
attack families — MAFIA, MemCollusion and Chronos — at the recall and
admission surfaces, with a manifest.json fixing budgets
(300 probes, 90 poison writes) and an outcome taxonomy, worked probe and
scenario datasets, and harness.py carrying deterministic
validators for the four MemCollusion construction constraints plus
sha256 pinning of harness and datasets. Its README opens by calling
itself a skeleton, and lists what exists rather than
what it found: no results are committed and no run is claimed. An
adversarial suite for a memory store is something this atlas has asked
for and not found; this is the first, and it has not been run.
Four subsystems around the entity
Four modules extend what an entity can carry without changing what an entity is, and each is versioned, bounded and opt-in.
src/evidence_lanes.rs separates the summary from
the span it came from. Two governed representations —
Derived and Verbatim — are requestable over
already-ranked candidates, and the docstring is explicit that "the
ordinary recall path never calls this function." The verbatim lane
recovers a source span out of a retained entity body and refuses to
guess: "A bad hash returns a structured result with no text;
malformed storage or bounds are errors that the caller maps to a
non-disclosing exclusion reason." The receipt over the selection is
content-free, order-independent and digest-verified. The detail worth
stealing is temporal: residual spans carry their own creation time, and
residual_projection_respects_temporal_anchor_and_bounds
asserts that a valid-time view does not pick up a residual recorded
after the coordinate being asked about — "Do not attach current
residuals to a valid-time view." A bitemporal store that gets this
wrong reports today's evidence for a question about last month, and
nothing about the answer looks wrong.
src/task_lineage.rs carries an action's lineage
across a session boundary as a deny_unknown_fields
request with a transition, an action class, a budget cost and an impact
count, bounded at 64 history entries.
src/declared_graph.rs makes the graph declared
and attested rather than inferred — nodes and edges submitted
with a namespace, a canonical id, a revision and a span reference,
capped at 256 nodes and 512 edges per submission. Every other graph
memory in this corpus derives its edges from an extractor; this one
accepts them under attestation and bounds what it will accept.
src/provider_source.rs keeps provider-native
identity and event lifecycle — five event types
(upsert, comment, reply,
attachment, delete) and two states
(active, deleted) — as hash-only metadata
embedded in an existing entity body, under a comment that draws the
boundary: "Provider bodies never belong in this type."
11. For Your Own Build
Steal
- Keep a claims audit file and let it delete things. Naming a retired claim, why it failed, and what replaced it is worth more than any number the README could have kept.
- Run your benchmark three times and commit all three. A single run is a sample; a mean with its members committed is a result, and it lets a reader recompute your headline without rerunning anything.
- Fold the condition into the run signature. If the prompt variant is part of the hash, a chain-of-thought number cannot be quoted beside a plain one by accident.
- Publish the per-category breakdown, including the category you do badly on. It costs a table and it is the difference between a score and a result.
- Cite competitors as their publishers' claims. Reproducing someone else's number as a head-to-head asserts a comparability you did not establish.
- Split trust into status, verified and confidence. One float cannot express "checked and wrong".
- Let an explicit human score outrank the automatic recompute, and write the rule where the column is declared.
- Test a deletion in both directions — the content is gone, and the erasure stopped at the boundary.
- Instrument your deprecation. A counter for canonical versus alias calls tells you when the alias can go.
Avoid
- Documenting a verification command and not running it. A check that lives in Markdown runs when somebody remembers; the count it guards went stale by eleven while the audit around it stayed sharp. Put it in CI or accept that it is a comment.
- Treating supersession and purge as protection against re-assertion. Both are keyed on the record. The next ingest of the same wrong value creates a new one.
- Leaving a count's only check in a Markdown file. The fix here was to derive the number from source and run the derivation in CI; until that happened the audit around it stayed sharp while the count drifted.
Fit
Right if you want a single-binary local memory with real correction semantics — bi-temporal history, supersession with a readable past, a tamper-evident journal, tested purge — and you would rather read a project's own audit of its claims than take its README on faith. All seven marks is a position several other systems here hold.
Wrong if you need multi-machine memory without manual file movement, or encryption on a database that predates the default. The rejection guarantee is the most privacy-careful instance of one in the atlas — a digest rather than the value — and the open question about it is reach rather than existence: the background consolidation passes are the leg no committed test walks.
12. Open Questions
- The lineage walk reads three JSON fields by name. What writes them, and what fraction of derived bodies in a real store carry any of the three — a suppression that propagates only along declared provenance is exactly as complete as the declarations are.
- The tombstone matches on predicate and digest without the subject. What is the false-positive surface — the same value legitimately true of two subjects under one predicate?
- Nothing sets
expires_at_unix_mson the default path. Is a lapsing rejection intended, or is the column there for a caller that does not exist yet? single-session-preferencescores 0.300 in the committed report. Is that a retrieval failure, a prompt failure, or a category the design does not target?- Does
emb_sigHamming prefiltering change recall, and is there an artifact measuring the loss rather than the speedup? - What would it take to make
federatea sync rather than an export? enforce_strict_workspace_bindingsits beside the compatibility method rather than replacing it. Which callers take which, and what would it cost to retire the lenient one?
Appendix: File Index
- Schema and temporal model:
src/schema.rs(entities,entity_history,journal,audit_chain_state,encryption_canary, thevalid_*andrecorded_at/invalidated_atcolumns). - Tools and review surface:
src/mcp.rs(registry),src/tools.rs(handle_operator_review, recall, supersede, correct, purge). - Trust admission:
src/trust_admission.rs. - Selection explanation:
src/selection_decisions.rs,docs/specs/selection-decisions-v1.md, and theinclude_selection_decisionsreconciliation insrc/tools.rs. - Evidence, lineage, graph and provider identity:
src/evidence_lanes.rs,src/task_lineage.rs,src/declared_graph.rs,src/provider_source.rs. - Storage and deletion tests:
src/db.rs(purge_erases_history_and_redacts_journal_for_purged_entities,purge_does_not_redact_other_workspace_live_journal_rows). - Benchmarks:
benchmark/longmemeval/(qa_report.json,qa_report_seed2.json,qa_report_seed3.json, the threeqa_report_cot*.json,COMPARISON.md,run.py),benchmark/scale/report.json,benchmark/lambda/results/,benchmark/GAUNTLET.md,PERF.md. - Claims:
CLAIMS-AUDIT.md; encryption behaviour:docs/ENCRYPTION.md. - Integrations:
integrations/langgraph/,integrations/crewai/,integrations/autogen/.
History
2026-09-19 — trust_state re-tested at
the unchanged pin, read from the atlas's fork since the upstream and the
commit both still return 404. Nothing has moved; the corrections are the
atlas's own. The record named the field and the vocabulary and cited the
test, and the test is a good one —
recall_filters_by_epistemic_state_on_all_paths asserts in
both directions, that a candidate is absent from a verified-only query
and a verified entity absent from a candidate query, on the FTS and
dense arms alike, which is the negative control most systems in this
corpus leave out. Two things belong beside it. The write-side polarity
is right in a way worth copying: a value outside the vocabulary coerces
to candidate, the least-trusted member, so an unrecognised
state fails closed, and an enriched memory is pushed back to
candidate unless it already sits in a terminal state. The
read-side polarity is the weaker one, and the record should say so: the
filter runs only when the caller supplies a state, so a recall naming
none returns every state, rejected included, and the same
field separately feeds a trust_rank in the projection.
Naming a state to narrow a default that admits everything is a different
guarantee from a flag that widens a default that excludes, and this
system has the first. The mark stands on a predicate that exists, is
reachable from the MCP surface and is tested both ways; the limit is
that nothing applies it unasked. Nothing was installed and no suite was
run.
2026-09-18 — re-read with the subject still gone.
git ls-remote on
Perseus-Computing-LLC/perseus-vault answers "Repository
not found", as it did at the last reading, and the fork network is
still being reparented onto the unrelated johan--/mneme —
scripts/state/archive-sync.jsonl records
upstream-reparented again on 2026-09-16 and 2026-09-17, so
this is a recurring condition rather than a settled one. What matters
for the seven marks is that the code they rest on is still reachable,
and it is: the pinned revision
9c829207… fetches by sha
from
agent-memory-atlas-archive/Perseus-Computing-LLC--perseus-vault
and carries tree 653183dd….
Spot-checked against that fetch: src/db.rs is 61,402 lines
and both cited negative_eval cases are present, at
:52985 and :53755 — line numbers the record
now carries, because in a file that size a test name alone is not a
locator. Marks kept, on the Letta rule: an
archive branch holds the code.
2026-09-13 — the upstream repository is gone.
Perseus-Computing-LLC/perseus-vault and the pinned commit
9c829207… both return 404.
The pin is deliberately not moved: the reading below was of that commit,
and it is still readable in the atlas's fork, taken 2026-09-09 with
every branch rather than the default one. All seven marks were
re-verified there at the pin — rejected_value_tombstones
and normalize_rejected_value, epistemic_state,
valid_from_unix_ms beside recorded_at_unix_ms,
the workspace_hash recall predicate, the
prev_hash journal chain and admission_decide
are all present and unchanged. The fork also holds 24 commits and
~92,600 added lines past the pin, which exist nowhere else; section 10
records what arrived in them, the hostile memory gauntlet chief among
it. analyzed_at is unchanged because the body was
re-verified rather than re-derived.
Two archive facts belong with it. GitHub reparents an orphaned fork
network onto a surviving sibling, so this fork's parent
became johan--/mneme, and a sync that trusted that field
reset the fork's default branch onto an unrelated 2026-06-29 head; the
real 2026-09-06 head was preserved first, at
agent-memory-atlas-archive/2026-09-13-00-38-39, and has
been restored to main. archive_sync.py now
refuses to follow a reparent.
2026-08-27 — 9c829207…
— fifth reading, twenty commits on, five new modules totalling roughly
7,000 lines of Rust. All seven marks stand and none changed; no claim in
this report went stale.
The addition worth the re-pin is
src/selection_decisions.rs with its own spec, described in
section 6: an opt-in per-candidate record of why each recall candidate
was or was not delivered, reconciled after the six governed
serving gates rather than from the pre-filter fused order, with omitted
rather than zeroed scores, failed arms reported without fabricated
candidates, a fail-closed bound, and a replay fingerprint a committed
test tampers with. Its consumer is the context engine in the sibling
repository, which carries the disposition vocabulary and no producer —
half a mechanism in each repository, and the reason reading either alone
gives the wrong answer.
Four more subsystems arrived beside the entity, described in section
10a: evidence lanes separating a derived summary from the verbatim span
with a temporal rule that keeps current residuals out of a valid-time
view; task lineage across session boundaries; a declared and attested
graph, bounded per submission; and provider-native source identity kept
as hash-only metadata. Screened again first: two auto-run publication
manifests, one build-time execution point, nine unpinned surfaces and
four files inside the seven-day cooldown; nothing was built, installed
or executed. stack_source promoted from seeded
to reviewed.
2026-08-22 — 88c14ed9…
— fourth reading, two commits on. Screened again first: two auto-run
surfaces, one build-time execution point, eight unpinned surfaces and
one file inside the seven-day cooldown; nothing was built or executed.
The tombstone gained lineage propagation — src/db.rs grows
a suppression check shared by ordinary reads and maintenance scans,
following source_ids, evidence_for and
promoted_from recursively with a cycle set, a depth cap of
eight and fail-closed behaviour at both the cap and an unauthentic
source body, with
rejected_value_does_not_flow_through_maintenance_derived_writes
committed beside it. enforce_strict_workspace_binding adds
a scope path that refuses an unbound profile instead of treating it as
legacy. The official Python client gains a temp-owned, per-run-key
ephemeral admitted fixture exercised against the real binary in CI, and
tests/encryption_bootstrap.rs grows. All seven marks stand;
the tombstone's evidence is stronger than it was, because the mechanism
now covers the derivation path that a digest alone cannot.
2026-08-20 — 1bf7041e…
— re-pinned nine commits on, 46 files and +6,561 lines, most of it under
benchmark/. Screened again: two auto-run surfaces, one
build-time execution point, one manifest inside the cooldown across
eight unpinned surfaces; nothing was installed and no benchmark was run.
Marks unchanged at seven of seven. The addition is the artifact
this atlas specified and has not built.
benchmark/scoped_memory/ is a portable scoped-memory
capability contract, versioned
perseus-vault-scoped-memory-contract/v1 and described in
its own README as "a capability boundary, not a second memory
API". Four properties make it worth reading whatever you think of
the rest of the system.
- Scope is bound out of band and the model cannot reach
it. The contract binds
user_id,workspace_hash,agent_idandsession_idsupplied by the host, and states that "model-authored arguments cannot supply or override any of those fields";contract.py:190returns_result("deny", "caller_scope_injection")when they try. "Scope and policy filtering happens before a ranker receives candidate IDs", andRecordingRankerexists so a test can prove what the ranker was handed. - The outcome vocabulary refuses to collapse an absence into a
pass.
OUTCOMESisallow,deny,scope_mismatch,stale_conflict,abstain,unavailable, and the README states the rule directly: "A missing semantic provider or surface is represented asunavailable; it is never converted to a fabricated zero or pass."test_publication.py::test_failed_surface_is_explicitly_partial_not_zeropins it. - It runs against two surfaces.
InProcessSurfaceis a deterministic reference, andMcpSurfaceis an adapter over the shippedVaultClientand the canonical MCP toolsrecall,context,get_entity,remember,correctandsupersede— so the same contract exercises a reference implementation and the real system, which is what makes a passing run mean anything. - The published artifact is hash-only and stable.
test_report_is_hash_only_and_repeated_signature_is_stableasserts the report carries hashes rather than content and that a repeated run signs identically.
Writes require a trusted authority whose capability set includes the operation, a stale expected version fails closed, and corrections and supersessions retain the prior record with a deterministic successor relationship — the same semantics the report describes in the store, restated as an executable contract.
Also at this pin:
benchmark/recall/test_fusion_regression.py adds
conflict-magnet fusion regressions, test_replay.py a shared
retrieval replay envelope, evidence-sufficiency curves and
protocol-comparability lanes under longmemeval, corpus
inputs certified and redacted before use, proposal-admission writes
isolated, and src/db.rs and src/tools.rs grown
by 237 and 157 lines.
2026-08-19 — 443239f4…
— re-read 125 commits on, roughly 109,000 added lines in
src/ across 69 files. Every one of the seven marks was
re-verified at this pin and every one holds, but one evidence record had
gone stale under it: human_review named
mimir_action_approve in src/mcp.rs, which
existed at the previous pin and does not exist at this one. The approval
surface is admission_decide in src/tools.rs,
refusing any decision that is not approve or
reject, and the record is re-anchored to it with
none for its test, because no committed test names the
path. Nothing in the previous reading was found stale, so this is an
extension rather than a correction: section 10a adds the three
mechanisms that are new in kind — write_gate.rs, a
deterministic read-only precheck that decides
Store/Duplicate/Supersede/Forget before any model call and refuses to
let Forget drop a substantive fact; verify.rs,
a runtime self-audit whose three-valued status makes
Unverified a distinct exit code from Pass; and
benchmark/redteam/, an adversarial harness against MAFIA,
MemCollusion and Chronos whose README calls itself a skeleton and
commits datasets and validators without results.
The remaining growth was read at the level of what it changes about
this report's claims rather than line by line, and it changed none of
them. The screen reported two auto-run publication manifests —
server.json and smithery.yaml, both registry
metadata — plus a build.rs and a Cargo.lock
inside its cooldown; nothing was installed and no test was run. One skew
worth recording: server.json declares version 2.23.0 while
the OCI package it lists pins 2.20.2.
2026-08-08 — 60d7ac4f…
— re-pinned three commits after the previous reading, which was one day
old. Nothing was run; the delta is +5,093/-420 lines over 17 files, read
against the previous pin.
A claim in this report was wrong at the pin it was made on,
and is retracted. The scoping row said
workspace_hash was applied in read queries on entities
and journal rows. At 4008228 it was not:
get_recent_journal(limit) selected
FROM journal ORDER BY created_at_unix_ms DESC LIMIT ?1 with
no workspace predicate and blanked the field on the way out, under a
comment reading "Not selected by this listing query; purge-scoping
metadata only." A caller in one workspace listing recent journal
events received every workspace's. 8e26619 (#877) fixed it
upstream: the reader now takes a workspace_hash, and the
unscoped variant is get_recent_journal_admin, gated on an
in-process JournalAdminAuthorization whose own comment says
no public transport mints one. The scope_enforced mark
stands — entity recall was scoped throughout, and the rubric's mark
measures the memory read path — but the matrix sentence overstated it
and now says which pin changed it.
Three mechanisms arrived that the previous reading could not
have seen. #880 adds an
epistemic_state column at schema v27 —
candidate, verified,
corroborated, rejected,
defensively_recalled — explicitly orthogonal to the
lifecycle status, with
recall_filters_by_epistemic_state_on_all_paths committed
beside it. defensively_recalled is the one this atlas has
no other instance of: every trust state in the corpus withholds a
memory, and this one serves it while framing it as untrusted.
#864 adds a RecallOutcome carrying
abstained, a machine-readable reason
(no_match, db_unhealthy,
embedding_unavailable, deadline_elapsed,
partial_arms), a deadline flag and embedding-backend health
— so an empty recall now distinguishes nothing matched from
the backend was down, which the benchmarks page records as measured
nowhere. And #865 splits
memory.read/propose/commit, types
retrieved memory as untrusted below verified, and adds
versioned per-agent authority manifests; the split is documented
fail-open when no manifest exists, which is why it is
in the risks row rather than the strengths one. The admission evaluator
also gained a proposed outcome, keeping an unvalidated
authoritative claim a reviewable proposal rather than a fact.
#878 is the one worth copying. A
memory-quality scorecard, blocking on any pull request touching vault
behaviour, whose release_ready demands accuracy of exactly
1.0 over a committed 24-case manifest and requires every category to be
present: long-horizon recall, contradiction and supersession,
shared-memory visibility, adversarial contamination, temporal validity,
scope validity, and provenance. That list is close to the acceptance
suite this atlas publishes and has never run against anything.
Said plainly, because the atlas would otherwise be marking its own homework. These commits use this atlas's vocabulary — admission contract, epistemic trust axis, recall outcome, untrusted recall — and the rubric already warns that a mark count partly measures who has read it. Two things bound that here. This report carried all seven marks before these commits, so no mark was added by them and none could be. And the commits carry the project's own issue numbers, which is evidence of an internal plan rather than of a checklist. What changed is the prose, and the prose now says when.
2026-08-07 — 4008228e…
— one commit past the previous pin, and it adds a file rather than
changing one: a 251-line research memo under
docs/research/. No source, schema, test or manifest moved,
so every published claim and all seven marks stand unexamined by this
reading rather than re-confirmed by it. Screened first: 2 auto-run
surfaces (server.json, smithery.yaml), 1
build-time exec (build.rs), 8 unpinned surfaces and 2 files
inside the seven-day cooldown — the same shape as the previous screen,
with the auto-run pair still the MCP publication manifests. Nothing was
built or run. The memo is worth one sentence because of what it argues
rather than what it changes: it reviews five 2026 memory papers and
concludes that "memory is a state machine, not just a search
index", listing revision, forgetting, temporal validity and
provenance as the properties that matter — which is a description of the
mechanisms this system already carries marks for, arrived at from the
literature rather than from the code. It also repeats the MemoryAgentBench mislabel
this atlas has traced twice before, citing the benchmark as evaluating
"selective forgetting" where the repository itself calls that
competency conflict resolution.
2026-08-05 — 5d6cecf9…
— second reading, two commits on. Screened before reading: 2 auto-run
surfaces (server.json, smithery.yaml), 1
build-time exec (build.rs), 8 unpinned surfaces, and
Cargo.toml and Cargo.lock both changed the day
of the pin. Both auto-run findings predate the previous pin and are MCP
publication manifests declaring how a client starts the published image;
neither reads outside the tree nor reaches the network with anything it
reads. Nothing was built or executed from the checkout — the only thing
run was scripts/registry_metadata_check.py, which is stdlib
Python that reads files in the tree and prints JSON.
6d6c6c8 is titled "implement Atlas review
follow-ups" and closes three upstream issues; each was verified
here against code rather than against the commit message. The tool count
is derived rather than asserted: the parser extracts the embedded
registry literal from src/mcp.rs and parses it as the
implementation does, then asserts the same figure in five published
surfaces, and .github/workflows/mcp-registry.yml runs it on
every push and pull request alongside a Rust uniqueness test. Run at
this commit it exits zero and reports registry_count: 90,
canonical_tools_list_count: 90,
compatibility_manifest_count: 270.
CLAIMS-AUDIT.md replaced its
grep -o '"name": "mimir_[a-z_]*"' instruction with that
command, described as formatting-insensitive and CI-run. Encryption is
on by default for a fresh install, with
tests/encryption_bootstrap.rs asserting a 32-byte key at
mode 0600, an established canary, and a stored body that is
not the plaintext that was written, plus a second case for the explicit
opt-out; a database created before the flip stays plaintext until
init --rekey, which the audit states.
tombstone moves to present and the report carries all seven
marks: rejected_value_tombstones is keyed on
(workspace_hash, subject, predicate, value_sha256) over a
value normalized by JSON canonicalisation, whitespace collapse and
lower-casing, stores only the digest so a rejection record cannot leak
what it suppresses, is enforced in remember_impl across the
remember paths, honours a global-or-workspace scope through
workspace_hash IN ('', ?1), and admits a trusted override
that is journaled — with four committed tests including one for
laundering and the override. 5d6cecf adds Ed25519-signed
policy/authority profiles verified before a manifest takes effect,
failing closed, which is new context rather than a change to any
published claim.
2026-08-04 — same pin, corrected after the project's
author reviewed the report. The tool-count finding was stated as "stale
by eleven", which asserts that 76 is the true canonical figure; nothing
established that. Parsing the embedded registry literal rather than
grepping it returns 88 top-level entries — 87
mimir_-named, one perseus_vault_-named — so
the claim (65), its designated command (76) and the registry (88)
disagree three ways. The canonical/legacy split is not present in the
literal: tool_registry_base calls the mimir_*
names "an implementation migration detail" and
legacy_alias_tool synthesizes aliases by prefix rewriting
at advertise time, so what is advertised is a runtime decision no static
count can see. The finding is drift between a count definition and its
verification command, not a corrected number. The tombstone gap is
additionally qualified against the pattern page's own "not an
established best practice" header, and the falsifying test the author
proposes — reject A, replace with B, re-ingest A by another path, run
consolidation, check whether A stays rejected — is recorded in section
9.
2026-08-04 — 838c63da…
— first reading. The published LongMemEval means were recomputed from
the six committed per-run reports and match exactly (73.8% plain, 79.0%
CoT); the benchmark itself was not re-run, as it requires paid model
calls. The claims audit's own tool-count command was executed and
returns 76 against a claim of 65.