Architecture
dent8 is a memory integrity platform, not a memory provider. The platform owns correctness around memory writes and reads: provenance, freshness, contradiction handling, supersession, replay, authority, and explainability.
Product Boundary
Section titled “Product Boundary”The first product boundary is one runtime with three surfaces:
- Memory firewall: validates fact-event writes before they alter projections.
- Versioned memory store: persists immutable events and materialized current state.
- Memory debugger: replays event streams and explains why state looks the way it does.
These surfaces share one event model. A debugger that cannot replay the same events accepted by the firewall is not trustworthy.
A future desktop app is part of the memory debugger/control-plane surface, not a new runtime or storage boundary. It must consume the same receipts and call the same CLI/MCP/daemon/future HTTP write path rather than writing events, provider-native memory, or database rows directly. See ADR 0020.
The integrity semantics behind these surfaces have a formal identity — dent8 is a belief base with paraconsistent contradiction tolerance and an authority-as-entrenchment ordering. See belief-revision.md. The adversary the firewall defends against is in threat-model.md, and how the invariants are checked is in formal-verification.md.
Storage
Section titled “Storage”The durable storage design is the append-only event log, its projection, the edge graph, and a tamper-evident hash chain — expressed against the EventStore / AsyncEventStore traits. Postgres and embedded SQLite are adapters that realize it, not the architecture — both implement the async boundary and share the same firewall and hash chain. The full design (backend-agnostic event log + adapters + canonicalization) lives in storage.md; the original Postgres-first choice is ADR 0001, with the SQLite backend added later as the second adapter that proves the boundary holds. DuckDB and Parquet are an export-only analytical lane that consumes exported event streams, never runtime writes — built as dent8-export / dent8 export (see storage.md).
Rust Workspace
Section titled “Rust Workspace”dent8/ crates/ dent8-core/ # fact-event model, lifecycle state machine, invariants dent8-store/ # store traits (sync EventStore + async AsyncEventStore), replay boundary dent8-store-postgres/ # Postgres migrations and adapter (AsyncEventStore) dent8-store-sqlite/ # embedded SQLite adapter (AsyncEventStore) — the second backend dent8-cli/ # CLI commands for schema, replay, explain, MCP dent8-evals/ # adversarial corpus behind the self-demonstrating `dent8 eval` dent8-export/ # Parquet export for DuckDB analysis (opt-in, `--features export`) crates/dent8-store-postgres/migrations/postgres/ # SQL migrations packaged with the Postgres crate docs/ # architecture, eval strategy, naming, MVP notes evals/ fixtures/ # canonical event streams replay/ # replay scenarios and expected outcomesLater crates should be added only when they own a real boundary:
dent8-policy: write/read policy and approval state machines.dent8-mcp: MCP transport adapter.dent8-http: HTTP API.dent8-debugger: debugger query model before the TypeScript/Tauri desktop UI exists.
Core Model
Section titled “Core Model”The primitive is FactEvent.
Each event belongs to a fact stream identified by fact_id. A fact stream starts with fact.asserted; later events can reinforce, contradict, supersede, expire, retract, retrieve, use the fact in a decision, or record a rejected challenge (fact.challenge_rejected, ADR 0015).
Core fields:
event_idfact_idevent_typesubjectpredicatefact_valueconfidenceauthorityttlprovenanceevidenceobserved_atvalid_fromvalid_torecorded_atcausation_event_idcorrelation_idevent_hash
The first projection state machine is intentionally small:
none -> activeactive -> contestedactive -> supersededactive -> expiredactive -> retractedcontested -> supersededcontested -> expiredcontested -> retractedRetrieval and decision-use events are audit events. They do not change lifecycle state but they matter for debugging stale or unsafe context use. On the CLI, dent8 context --record-retrieval emits fact.retrieved for every fact it packs, and a dent8 capture proposal with "op": "used_in_decision" records fact.used_in_decision. MCP resources/read auto-emits fact.retrieved (purpose mcp:resources/read; opt out with DENT8_MCP_RECORD_RETRIEVAL=0); the MCP server also renders both event kinds in replay/explain (see STATUS.md).
Write Path
Section titled “Write Path”- Normalize input into a candidate fact event.
- Validate required provenance, evidence, authority, TTL, and schema shape.
- Read relevant active/contested projections for the same subject and predicate.
- Detect duplicate, contradiction, or supersession candidates.
- Accept, reject, or require explicit policy approval.
- Append the immutable event in Postgres.
- Update projections in the same transaction.
- Emit invariant results for replay and debugger use.
Read Path
Section titled “Read Path”Reads should return fact state plus integrity metadata:
- lifecycle state
- freshness and TTL
- authority level
- supporting evidence
- contradiction count
- supersession chain
- provenance summary
- replay position
The MCP and CLI surfaces should expose this metadata by default. dent8 should make unsafe or stale memory visible, not hide it behind a plain string context blob.
Implementation status (honest)
Section titled “Implementation status (honest)”The core fold implements lifecycle transitions, terminal immutability,
contradiction-as-contested, canonical-contradiction hard alarms, and
authority-weighted terminal mutation: lower-authority supersession, explicit
expiration, and retraction are rejected
(ADR 0007,
ADR 0011). The CLI/MCP write
surface runs through the same firewall path, with an additional source->authority
ceiling registry at the op_* layer, plus an optional pluggable content-check hook
at that same layer (content-check.md): arbitration never reads
value text, so a deployment composes an external scanner in — dent8 ships no
classifier of its own. The Postgres adapter commits accepted events
transactionally under an advisory-lock-serialized append, and the read surface applies
freshness by flagging stale receipts. Remaining product gaps are operational: signed
identity key distribution/rotation, an operated witness service, richer transports, and
more live Postgres smoke coverage. See STATUS.md, roadmap.md,
and threat-model.md.