Proposals a person applies, and a delete that is final

TraceDecay

A Rust semantic code-intelligence daemon whose fact store ranks on a feedback-moved trust score and filters below it, proposes every deletion — dedup, hygiene and supersession alike — as a review-required candidate nothing applies without an explicit flag, and hard-deletes by stated policy, with twelve committed memory-eval scenarios each citing the upstream it was adapted from.

Carries 2 of 7 rubric mechanisms. Most systems here carry none or one (41%), and a dash means the mechanism was not found at this commit — not that the system needed it. Each mark is one LLM reviewer's reading of the code at this commit rather than a run of it — known limits.

  • Tombstone
  • Trust state
  • Bi-temporal
  • Scope enforced
  • Mutation audit
  • Human review
  • Negative evals

1. Executive Summary

TraceDecay is semantic code intelligence for coding agents: a Rust daemon that builds a local graph of a repository so an agent can ask for symbols, callers and impact radius instead of grepping, with a fact store attached for project memory. MIT, version 0.0.74, 10,937 commits since 6 June 2026, 483,833 lines of Rust across the binary and its crates against 125,958 lines of integration tests. Storage is local libSQL, and the README's framing is "[f]ewer tokens • fewer tool calls • local by default".

The memory subsystem is 5,755 lines in tracedecay-runtime-core, and two things about it are worth reading.

Every deletion is a proposal. The hygiene module states its own boundary: the rules are "conservative, rule-based checks — no model is ever invoked from Rust", and standalone TraceDecay only rejects secret-like writes and proposes hygiene deletions in a dry-run plan; any LLM review of those proposals lives in a wrapper layer, with capabilities reporting llm_curation: false in the core. The dashboard's supersession analysis emits "recommended_op": "delete" alongside "review_required": true, "status": "candidate" and a reason that ends "confirm which fact is current before applying". The CLI states the contract in its help text: "Curation defaults to a dry-run preview; nothing is deleted without --apply."

The eval suite is committed and attributed. Twelve scenarios in eval/scenarios/ each declare a well-behaved path and a violation with a stated expectation. memory-no-pollution is titled "Transient run noise must not become facts" and guards that a throwaway token produces no fact "while genuinely durable decisions still can" — a positive control and a must-not in one file. Alongside it: secret rejection, supersede-without-duplicate, curation conservatism, skip-local, and five ranking scenarios. Each carries an adapted_from naming the upstream project, its licence and the exact commit — memory-no-pollution credits mnemon at 41a9612 under Apache-2.0. Borrowing another project's eval and saying so at that grain is rarer here than it should be.

What the store does not do is model belief. A FactRecord has no status. It has a trust_score that starts at 0.5 and moves only on explicit feedback — +0.05 helpful, −0.10 unhelpful, clamped to [0,1] — and retrieval filters below a minimum that defaults to 0.3. That is a real filter on a stored value, which is more than most confidence scores earn, but it is a continuous score rather than a discrete state, so a fact that is simply wrong needs three unhelpful votes before it stops being retrieved, and nothing distinguishes wrong from unhelpful.

Supersession exists as vocabulary rather than as mechanism. FactRelationKind has Supports, Contradicts, Supersedes and DerivedFrom, constrained at the database with a CHECK, and the dashboard's curate API accepts them. But the curation planner cannot emit a supersede operation: the changelog records an "[a]rchive-semantics purge" whose stated policy is that "deleted memories are permanently hard-deleted", with the archive and supersede ops removed because "neither planner can produce them; the curate ops contract is delete/merge only". So a supersedes edge is whatever a caller asserts, and the read path ranks on trust and recency rather than on it.

That policy has a consequence the atlas asks about everywhere: a hard delete leaves nothing behind. The cascade is careful and pinned by a store-level test — the fact row, its FTS mirror row, its entity links and its feedback events — but there is no record that a fact was rejected, so nothing stops the same content arriving again tomorrow.

Two marks: human_review, negative_eval.

2. Mental Model

A fact is content with a category, tags, extracted entities, a trust score and counters. It has no state; its standing is a number.

Feedback is the only thing that moves trust: helpful +0.05, unhelpful −0.10, clamped. A bucket label — low, medium, high — is derived for display at 0.3 and 0.75.

A relation joins two facts as supports, contradicts, supersedes or derived-from, with its own confidence and source.

Curation is a plan: dedup merges, hygiene deletions and supersession candidates, each with a confidence and a review flag, applied only on request.

A store is a database file — one per project, plus a per-profile user-memory.db for conversations with no project. Separation is by file, not by predicate.

Diagram — nothing is deleted without an explicit apply; trust is the only thing that gates retrieval, and a delete leaves no record
Diagram source
%% caption: nothing is deleted without an explicit apply; trust is the only thing that gates retrieval, and a delete leaves no record
flowchart TB
    AGENT["agent via MCP fact_store"] --> ADD["add fact"]
    ADD --> HYG{"hygiene: secret-like?<br/>rule-based, no model in Rust"}
    HYG -->|"yes"| REJ["write rejected"]
    HYG -->|"no"| F[("FactRecord<br/>content, category, tags, entities,<br/>trust_score = 0.5, counters,<br/>no status")]
    FB["feedback"] --> DELTA["helpful +0.05<br/>unhelpful -0.10<br/>clamped to 0..1"]
    DELTA --> F
    Q["search"] --> FTS["FTS candidates + similarity"]
    F --> FTS
    FTS --> FLOOR{"trust_score >= min_trust<br/>default 0.3"}
    FLOOR -->|"below"| GONE["not returned"]
    FLOOR -->|"at or above"| OUT["ranked and returned"]
    REL[("fact relations<br/>supports / contradicts /<br/>supersedes / derived_from<br/>DB CHECK constrained")] -.->|"asserted by a caller;<br/>the read path does not act on it"| F
    CUR["tracedecay memory curate"] --> PLAN[("dry-run plan<br/>dedup merges, hygiene deletions,<br/>supersession candidates")]
    F --> PLAN
    PLAN --> FLAG["each candidate:<br/>review_required = true,<br/>status = candidate,<br/>'confirm which fact is current'"]
    FLAG --> PERSON{"person: --apply,<br/>or the dashboard panel"}
    PERSON -->|"no"| NOOP["nothing changes"]
    PERSON -->|"yes"| HARD["hard delete cascade:<br/>fact row, FTS mirror,<br/>entity links, feedback events"]
    HARD -.->|"no record kept"| REWRITE["the same content can be written again"]

3. Architecture

Area Role
crates/tracedecay-runtime-core/src/memory/ The fact store: store.rs, retrieval.rs, trust.rs, hygiene.rs, entities.rs, similarity.rs, diff.rs, types.rs, user.rs
crates/tracedecay-dashboard-api/ memory_curate.rs and memory_analysis.rs — the proposal surface behind the dashboard
crates/tracedecay-runtime-core/src/db/migrations.rs The schema, including the relation CHECK and the archive-column cleanup
src/ The CLI, daemon, MCP server, extraction workers, branch handling, automation
eval/scenarios/, tests/memory_suite/ Twelve attributed memory scenarios and the runner
tests/ Suites per area — memory, graph, storage, mcp, dashboard, agent, hermes

4. Essential Implementation Paths

  • memory/types.rs:94-180 — the fact and the four relation kinds.
  • memory/trust.rs:1-45 — the deltas, the clamp and the buckets.
  • memory/retrieval.rs:40-120 — where min_trust gates the result set.
  • memory/hygiene.rs:1-9 — reject on write, propose on delete, no model here.
  • dashboard-api/memory_analysis.rs:600-636 — the supersession candidate.
  • db/migrations.rs:702 — the relation CHECK constraint.
  • src/cli/help.rs:466-479 — the dry-run contract in the user-facing text.

5. Memory Data Model

The counters are the interesting part: retrieval_count, access_count, helpful_count, unhelpful_count, last_retrieved_at, last_recalled_at, last_feedback_at. TraceDecay records not just that a fact exists but how it has been used and judged, and the ranking scenarios in the eval suite (memory-ranking-retrieval-reinforcement, memory-ranking-feedback-promotes) test that those signals move results.

What is absent is any field that says a fact is retracted, pending or superseded. The four relation kinds could carry that meaning and the read path does not consult them.

6. Retrieval Mechanics

Full-text candidates are fetched at five times the limit, fused with a similarity pass over a wider list, and filtered on category and min_trust. The default floor of 0.3 means an untouched fact at 0.5 is visible and one voted down three times is not.

7. Write Mechanics

The hygiene gate refuses secret-like content at the write, deterministically. Facts arrive from the agent through fact_store, or as proposals from the automation loop's memory curator, which is explicitly a review-and-validate path rather than a direct writer.

8. Agent Integration

An MCP server whose code tools are the headline — tracedecay_context, tracedecay_search, tracedecay_callers, tracedecay_impact — with fact_store beside them, plus a CLI, a daemon and a local dashboard for the graph, memory curation, session analytics and cost.

9. Reliability, Safety, and Trust

Hard delete is a stated policy, not an oversight. The changelog records the archive feature being purged and the ops contract narrowed to delete and merge, with permanent deletion as the policy. The cascade is pinned by a test, which is the right way to hold a destructive path. What it costs is the tombstone: nothing records that a fact was rejected, so the dedup and hygiene work is repeatable rather than durable, and an agent that re-derives the same transient noise writes it again.

Trust is a score, not a state. It filters, which matters, and it is moved only by explicit feedback rather than by inference — a deliberate choice. But three unhelpful votes are needed before a fact disappears, and a fact that is flatly wrong is indistinguishable from one that was merely unhelpful.

The model is kept out of the Rust path. Hygiene is rule-based and says so, capabilities report llm_curation: false, and the LLM review loop is a separate, opt-in step (--llm, --llm-ops). For a system that deletes, that separation is the conservative choice.

10. Tests, Evals, and Benchmarks

125,958 lines of integration tests across suites per area, plus benches and benchmark data. The memory-eval suite is the part to copy: scenario files with a schema version, a stable contract marker, a setup, a deterministic well-behaved path, a violation with a declared expectation, and an adapted_from line naming the upstream, its licence and its commit. The runner asserts the well-behaved end state before running the violation, so a scenario cannot pass by breaking both halves.

11. For Your Own Build

Steal

  • Make every deletion a proposal with the review flag in the payload. "review_required": true and "status": "candidate" travelling with the recommended op means no consumer can mistake the plan for a decision.
  • Say where the model is not. "[N]o model is ever invoked from Rust", with a capability flag reporting it, tells an operator exactly what the core will do on its own.
  • Attribute an adapted eval to its source and commit. Each scenario names the project it came from, the licence and the sha.
  • Pin the destructive cascade with a test. If delete is permanent, the test that enumerates every table it must touch is the only thing standing between a policy and an orphan.

Avoid

  • Hard-deleting without a record when a curator will run again. The same content arrives tomorrow and the same work is done twice, with no memory that a person already said no.
  • Declaring relation kinds the read path ignores. supersedes is in the type, the parser and the database CHECK; nothing in retrieval consults it.

Fit

Reach for this if you want a local code-intelligence graph with a modest fact store attached, and you value a curator that never acts alone. Look elsewhere if you need rejected memories to stay rejected, or a read path that knows a fact has been superseded.

12. Open Questions

  • Should the read path act on a supersedes relation, or should the kind be removed from the vocabulary until it does?
  • With hard delete as policy, is a rejection record — content hash and reason, no payload — compatible with the intent, so that curation does not re-propose what a person already declined?
  • The trust floor treats wrong and unhelpful alike. Is a separate negative signal planned?

Appendix: File Index

Path What to read it for
crates/tracedecay-runtime-core/src/memory/types.rs The fact and relation model
.../memory/trust.rs The bounded score and its deltas
.../memory/retrieval.rs The trust floor on the read path
.../memory/hygiene.rs What the core will and will not do alone
crates/tracedecay-dashboard-api/src/memory_analysis.rs Supersession as a review-required candidate
crates/tracedecay-runtime-core/src/db/migrations.rs The relation CHECK and the archive purge
eval/scenarios/ Twelve attributed memory scenarios
tests/memory_suite/memory_eval_test.rs The runner and its two-phase assertion

History

2026-09-1661ca3b08… — first reading, at a commit dated 15 September 2026, scoped to the memory subsystem rather than the code graph. Screened before opening, from a shallow clone: sixty-eight files, three auto-run surfaces (a .githooks/ directory, a .gitmodules and an MCP server manifest), nine build-time execution points, three unpinned surfaces, forty-nine dependency files inside the cooldown, and AGENTS.md and CLAUDE.md read as data. The codegraph submodule was not fetched. Nothing was installed, built or run.