A veto is an Option, not a zero

chitta-field

An associative memory substrate whose statuses return None rather than a low weight when they exclude, whose contradiction detector compares claims rather than text, and whose write path is a per-writer hash-chained op log.

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

chitta-field is an "[o]rganic associative memory substrate for cognitive AI companions" — MIT, Rust, version 2.7.12, 57,134 lines across ninety-five files with 287 test functions, a C FFI, and a stated design target of shared NFS storage, multiple concurrent writers, and sub-millisecond in-process recall. It is the backing store for a companion daemon that lives in a separate repository.

Three layers: an append-only op log as the durable write path, in-RAM indexes (semantic, BM25, a cortical sparse index, triplets, a symbol and call graph, a temporal index), and periodic binary snapshots so startup does not replay the whole log. The cortical index encodes memories as Sparse Distributed Representations — 64 active bits out of 16,384 — which makes associative recall a bitwise overlap rather than an approximate-nearest-neighbour search, and is a genuinely uncommon choice in this corpus.

A veto is an Option, not a zero. status_multiplier maps the seven memory statuses to a scoring factor, and three of them do not have one:

MemoryStatus::Superseded | MemoryStatus::Contradicted | MemoryStatus::Archived => None,

The recall loop reads that as ... .is_none() => continue. The distinction matters more than it looks: a zero weight is a number, and any later stage that normalises, re-ranks or blends can multiply it back up. None is not a number, and the compiler makes every caller decide what to do about it. The comment at the type it guards uses the right word — the status "vetoes the memory (excluded from results)".

Where a memory came from weights it and never withholds it. EpistemicStatus is UserStated | ToolDerived | ModelInferred | AutonomousSynthesis, under a comment that states the design: "[h]ow a memory was obtained — orthogonal to confidence." Its helper returns a plain f32, not an Option, so provenance can rank a model-inferred memory below a user-stated one and can never silently suppress it. Two axes, two return types, and the type signatures carry the policy.

Contradiction is about claims, not text. The detector's header draws the line this atlas keeps looking for:

"Design: claim-centric, not text-centric. Two memories contradict when they make incompatible claims under overlapping scope (same subject+predicate), not merely when they are semantically similar."

Memory content is parsed into claim atoms and indexed by claim scope. Most systems here that advertise contradiction detection are running a cosine threshold, which finds restatements and misses genuine disagreements phrased differently.

The write path is a hash-chained op log. Every mutation is an Op — the enum covers payloads, state deltas, batched maintenance drains, deletes, association edges, triplets and their invalidation, symbols and call edges, demotion, project clearing, and six event kinds — appended from sixty-nine call sites in the store. Records chain as H = SHA256(seqno || op_type || prev_hash || payload) with a CRC each, and the V3 segment header carries a vector_space_id so replay can "fence out segments written in a foreign vector space (model/dim/text-format)". That last one is worth copying on its own: an embedding model or dimension change otherwise surfaces as quietly wrong neighbours rather than as an error.

The caveat on that chain is structural and follows from the concurrency target. There is one segment file per writer process, so the chain is per writer: each writer's own history is tamper-evident, and the interleaving of several writers is not a single chained order.

Two marks. What is absent: nothing is consulted at write time against a contradicted or superseded memory, so the same claim can be written again and is caught by the next reconcile pass rather than refused at the door; the remaining state — strength, decay rate, confidence, and the configurable multipliers per kind and per epistemic status — is continuous, and no ceiling was found on the multipliers the way a ranking factor needs one; and there is no tenancy, the unit being a project with its own clear op.

2. Mental Model

A memory has a status that can veto it and an epistemic status that only weights it.

A contradiction is two claims about the same subject and predicate, not two similar sentences.

The log is the truth; the indexes and snapshots are how you avoid re-reading it.

A segment belongs to one writer.

Diagram — the status helper returns None for the three excluded statuses so the recall loop skips them as control flow, while the epistemic helper returns a plain multiplier and can never veto
Diagram source
%% caption: the status helper returns None for the three excluded statuses so the recall loop skips them as control flow, while the epistemic helper returns a plain multiplier and can never veto
flowchart TB
    W["any mutation"] --> OP["one Op: put payload · update state ·<br/>batched drain · delete · assoc edge ·<br/>triplet + invalidate · symbol + call edge ·<br/>demote · clear project · six event kinds"]
    OP --> LOG[("append-only segment,<br/>one file per writer process")]
    LOG --> CHAIN["H = SHA256(seqno ‖ op_type ‖<br/>prev_hash ‖ payload), CRC per record"]
    CHAIN -.->|"per segment, so per writer —<br/>tamper-evident alone, no single<br/>chained order across writers"| CAVEAT["the concurrency target's cost"]
    LOG --> REPLAY{"replay / snapshot load"}
    REPLAY -->|"V3 header carries vector_space_id"| FENCE["segments from a foreign model,<br/>dimension or text format are fenced out<br/>instead of producing wrong neighbours"]
    REPLAY --> IDX["in-RAM indexes: cortical SDR<br/>(64 of 16,384 bits) · semantic · BM25 ·<br/>triplets · symbol graph · temporal"]
    IDX --> RECALL{"recall scoring"}
    RECALL --> SM["status_multiplier(status)"]
    SM -->|"Active · Verified · Observed · Proposed"| WEIGHT["Some(weight) — configurable"]
    SM -->|"Superseded · Contradicted · Archived"| NONE["None → `continue`<br/>a veto in control flow, not a zero<br/>a later stage could multiply back up"]
    RECALL --> EM["epistemic_multiplier(status)"]
    EM -->|"UserStated · ToolDerived ·<br/>ModelInferred · AutonomousSynthesis"| F32["a plain f32 — always weights,<br/>never vetoes: 'orthogonal to confidence'"]
    WEIGHT & F32 --> OUT["ranked results"]
    CD["contradiction detector"] -->|"'claim-centric, not text-centric' —<br/>incompatible claims under overlapping<br/>scope (same subject+predicate)"| STATUS["sets Contradicted"]
    STATUS --> SM

3. Architecture

File Role
src/store.rs The store, recall, and the maintenance passes (12,206 lines)
src/ffi.rs The C surface (10,361)
src/log.rs Segments, chaining, replay, fencing
src/ops.rs The complete mutation vocabulary
src/scoring/mod.rs Where a veto is an Option
src/contradiction.rs Claim atoms and claim scope
src/hdc.rs, src/hnsw.rs Hyperdimensional codes and the ANN index
src/organ/ Triplets, spans, epistemic debt

4. Essential Implementation Paths

src/scoring/mod.rs:163-182 — two helpers, two return types, one policy.

src/state.rs:58-77 — the two enums, and the comment that keeps them apart.

src/contradiction.rs:1-10 — claim-centric, in six lines.

src/log.rs:11-30 — the chain, the CRC, and the lineage stamp.

5. Memory Data Model

A payload plus a state: version, chunk hash, deleted flag, strength, decay rate, confidence, access count and timestamps, pin, tier (0=L1 hippocampus, 1=L2 cortex, 2=L3 archive), a retrieval history, an embed-pending flag, and the two statuses. Pinning is exemption from decay.

An EpistemicDebtStore sits under organ/ with its own DebtStatus — the project tracks what it owes itself epistemically, which is an idea worth a longer look than this reading gave it.

6. Retrieval Mechanics

The cortical SDR index is the hot path — overlap counting on 64-of-16,384 bit codes, with no learned index to warm up or rebuild — with the semantic, BM25 and graph indexes beside it, and a scoring decomposition that reports each multiplier separately so a result can be explained rather than just ranked.

7. Write Mechanics

Append an op, update the in-RAM state, snapshot periodically. The maintenance drain is explicitly documented as preserving each access timestamp and count, which is the detail that keeps a batch pass from erasing the very signal decay depends on.

8. Agent Integration

A library with a C FFI; the agent-facing daemon is a separate project. That split is why this report stops at the substrate: what is asked of it, and how often, is decided elsewhere.

9. Reliability, Safety, and Trust

The chain and the CRC are integrity; the vector-space fence is correctness under model change; the veto is policy. The gap is at the door — nothing refuses a write that restates a contradicted claim, so the reconcile pass carries the whole burden of noticing.

10. Tests, Evals, and Benchmarks

287 test functions across the source and benches, with snapshot-migration paths for five prior formats — the kind of code that only exists once a format has actually shipped and changed. Nothing was built or run for this reading.

11. For Your Own Build

Return None, not 0.0, when a state excludes. A zero weight survives normalisation, re-ranking and blending; an Option forces every caller to handle the exclusion, and the compiler checks that they did.

Keep "how it was obtained" and "whether to believe it" in different types. A multiplier that can never be a veto is a design decision you can enforce in a return type instead of a review comment.

Detect contradiction on claims, not similarity. Two sentences about the same subject and predicate that disagree are what you want; two sentences that sound alike are what cosine gives you.

And stamp the embedding lineage into the log header. A model or dimension change without a fence does not fail — it quietly returns the wrong neighbours, which is worse.

12. Open Questions

Whether anything orders the writers. The per-writer segment chain is tamper-evident within a process; how several writers' histories are reconciled was not traced.

What the epistemic-debt store does. It has its own status enum under organ/ and looks like a deliberate mechanism rather than a scratch table.

Whether the scoring multipliers are bounded. They are configurable per kind and per epistemic status, and no ceiling was found.

Appendix: File Index

Path What to read it for
src/scoring/mod.rs:163-182 A veto as an Option, beside a weight as an f32
src/state.rs:58-77 Two orthogonal enums, and the comment that says so
src/contradiction.rs:1-10 Why similarity is not disagreement
src/log.rs:11-30 A hash chain, a CRC, and a lineage fence
src/ops.rs:14-40 The whole mutation vocabulary in one enum

History

2026-09-16f3176e58… — first reading, at a commit dated 16 September 2026. Screened before opening, from a shallow clone: two files scanned, no auto-run surfaces, one build-time execution point, no unpinned surfaces and one dependency file inside the seven-day cooldown. Nothing was installed, built or run.