Files
memex/notes/passepartout-neurosymbolic-design-decisions-and-options.org
Amr Gharbeia 04944a62e2 memex: update AGENTS.md with ROADMAP TODO workflow, bump passepartout to v0.8.0
- AGENTS.md: add steps 0 (read next TODO from ROADMAP) and 6 (mark DONE
  with LOGBOOK) to development cycle
- Notes updates accumulated during v0.8.0 work
- Bump passepartout submodule to v0.8.0
2026-05-09 15:00:35 -04:00

76 KiB
Raw Blame History

Passepartout Neurosymbolic Engine — Design Decisions and Architecture Options

The Hallucination Problem — Why Neurosymbolic

An LLM is a statistical engine trained on token sequences. It generates the most probable continuation of a prompt. Given sufficient context, that continuation is correct. Given novel context, it is often wrong in confident-sounding ways.

This is not a training deficiency. Hallucination is a fundamental property of probabilistic inference. You can reduce it with better models, longer contexts, and clever prompting, but you cannot eliminate it by making the LLM better. You eliminate it by not asking the LLM to do things that require certainty.

This is the architectural bet at the heart of Passepartout's neurosymbolic design. The LLM should not be the reasoning engine. It should be the creative engine — proposing possibilities, surfacing connections, translating between natural language and formal representation. The reasoning engine should be symbolic: deterministic, verification-grounded, provenance-tracked, and incapable of hallucination by construction.

This is not a rejection of neural methods. It is a division of labor. The neuro is the brain — generative, associative, creative, comfortable with ambiguity. It produces hypotheses. The symbolic engine is the education — accumulated, verified, provenance-tracked knowledge that the brain draws on and is disciplined by. It doesn't think. It remembers, checks, and constrains.

The brain is always smarter than the education, but the education prevents the brain from being confidently wrong.

See also:

  • passepartout/docs/DESIGN_DECISIONS.org: "The Probabilistic-Deterministic Split" for the gate-level version of this argument.
  • notes/passepartout-whitehead.org: Whitehead's ramified theory of types as the structural guarantee against self-referential contradictions.
  • notes/passepartout-symbolic-engine-exploration.org: the full design space and the lossiness problem at the neural-symbolic boundary.

The Five Architecture Options

The symbolic engine must relate to the human memex. The relationship is not obvious because knowledge lives in two incompatible forms: natural language prose (what the human reads and writes) and formal facts (what the symbolic engine reasons about). The translation between them is lossy by nature. The architecture is defined by how it handles that lossiness.

notes/passepartout-symbolic-engine-exploration.org explores five options. They are summarized here to make subsequent decisions legible.

Option 1: The Auto-Formalizer

A separate knowledge graph stores symbolic facts. The LLM populates it by extracting triples from unstructured data — documentation, manuals, logs, session histories. The KG becomes co-authoritative with the human prose.

This is the simplest to implement but inherits the dual-representation problem in its most acute form. The KG and the prose can disagree, and the architecture provides no mechanism for resolving disagreements. It also stores knowledge twice — once in the user's Org files, once in the KG — with no guarantee that they stay synchronized.

Option 2: Two Intentionally Separate Memexes

The human memex contains prose: thoughts, diaries, decisions, documentation. The symbolic memex contains formal facts: constraints, rules, relationships, deductions. The archivist bridges between them but does not try to keep them synchronized. They are allowed to diverge because they serve different purposes. The prose captures what the human intended. The symbolic memex captures what the symbolic engine has proven.

This is philosophically honest — it admits that no lossless translation between natural language and formal logic is possible. But it forces the user to reason about two separate knowledge stores and understand when to trust each.

Option 3: Tangled Fact Blocks in Org Files

The tangle mechanism already handles the dual-representation problem for code. Lisp code lives in literate blocks within Org files (#+begin_src lisp). The tangle mechanism extracts these blocks and generates .lisp files. A new block type — #+begin_src knowledge — would contain symbolic facts in a formal language. The tangle mechanism would load these facts into the symbolic engine's in-memory store, just as it loads Lisp code into the SBCL image.

This is aesthetically appealing because it unifies the format. One toolchain, one version control system, one Merkle tree. But the block language itself IS the knowledge representation language, and that language is the ontology we have not yet defined. The format is unified but the content is unspecified.

Option 4: One Memex, Two Indices

The prose remains in human language in Org files. The prose is always the ground truth. Two indices sit on top of the prose as derived views:

  • The neural index uses vector embeddings to enable semantic search. The LLM navigates the prose through embedding space, retrieving relevant headings.
  • The symbolic index stores formal assertions about what the prose says — predicates, relations, constraints — each grounded to a specific heading or block in the Org file.

Each index serves its own side of the machine. They do not need to understand each other's representations. They only need to agree on which heading or block they are referring to. Because the prose is always the ground truth, the symbolic index can be thrown away and rebuilt from scratch if it becomes corrupted or stale. No information is lost — only the extracted assertions.

Option 5: Ephemeral Symbolic Facts

No persistence, no serialization format, no knowledge graph stored on disk. VivaceGraph exists in memory during the session. Screamer derives facts from the prose as needed. When the session ends, the facts are discarded and re-derived from the prose on the next start.

This punts the ontological design problem entirely. You never have to decide on a serialization format because you never serialize. The cost is compute (re-derivation on every restart) and the inability to accumulate facts across sessions. But it is the correct first step — a way to learn what kinds of facts are actually useful before committing to a storage format.

The Chosen Path: Option 4, Starting with Option 5

The one-memex-two-indices architecture (Option 4) is the correct long-term architecture. The prose is the ground truth. The symbolic index is a derived view that can be rebuilt. The neural index handles what the symbolic index cannot — semantic search, fuzzy matching, associative leaps.

But committing to a persistence format before knowing what facts are useful is premature. The practical path starts with Option 5 (ephemeral facts) as the Phase 1-4 implementation, then graduates to Option 4 with VivaceGraph persistence in Phase 5 when the fact language has been battle-tested (see =passepartout-neurosymbolic-roadmap.org).

Why the dual index is permanent, not transitional

In the coding domain, there is an aspiration that the symbolic index could eventually capture enough of the prose's propositional content to become a complete representation — the "flip" described in the architecture note. But for the broader memex (literature, poetry, personal reflection, daily logs), completeness is neither possible nor desirable. You cannot formalize what makes a poem beautiful. You cannot extract a triple that captures the emotional weight of a diary entry. The neural index will always be the gateway to the full richness of the prose. The symbolic index handles what can be mechanically verified: citations, entities, temporal order, contradictions, provenance. The division of labor between the two indices is permanent because the domains they serve are fundamentally different kinds of knowledge.

The Neuro as Brain, the Symbolic as Education

The original 10-80-10 architecture (10% neural, 80% symbolic, 10% neural) describes the target ratios for a coding agent — a domain where most reasoning is formalizable. For the broader memex, the ratios are different and less important than the metaphor itself.

The neuro is the brain — generative, associative, creative, comfortable with ambiguity. It produces insights that are provisional, connections that are speculative, hypotheses that may be wrong. It is the driver.

The symbolic engine is the education — accumulated, verified, provenance-tracked knowledge that the brain draws on and is disciplined by. It doesn't think creatively. It remembers, checks, and constrains. It prevents the brain from being confidently wrong.

This framing resolves a tension in the original architecture. The 10-80-10 implies the symbolic engine replaces the neuro for reasoning. But a symbolic engine is terrible at creativity, ambiguity, and associative leaps across unrelated domains — exactly what you need for a memex that contains Pale Fire, a shopping list, and a project plan. The brain proposes that your sudden interest in unreliable narrators coincides with a week where your project retrospective used the word "deception." The education verifies: "those two diary entries are 4 days apart; the word 'deception' appears in both; here are the headings." The brain makes the leap. The education makes it trustworthy.

This means the symbolic engine never needs to be "complete." Education isn't complete knowledge — it's structured knowledge. You don't need a fact for every sentence in your diary. You need facts for what can be mechanically verified: dates, citations, entities, contradictions, temporal order. The brain handles the rest.

The Gate-to-Fact Bootstrap — Extracting the First Ontology from Existing Code

The Dispatcher gate stack already encodes an implicit ontology. Every gate vector asserts the existence of a category of things:

  • Gate vector 2 asserts there exists a class of files called secrets.
  • Gate vector 7 asserts there exists a class of commands called destructive.
  • Gate vector 8 asserts there exists a class of domains called trusted.
  • The self-build boundary asserts there exists a class of files called core-harness and a class called skills.

These claims are currently expressed as code — Lisp functions that pattern-match against file paths, shell commands, and URLs. They are not facts the symbolic engine can query, derive from, or check for consistency. But they can be made explicit.

The bootstrap makes every gate a set of initial symbolic facts: (:file ".env" :member-of-class :secret-files :source gate-vector-2), (:command "rm -rf /" :classified-as :catastrophic :source gate-vector-7), (:domain "api.telegram.org" :classified-as :trusted :source gate-vector-8).

This produces 50-70 entity classes directly from the existing gate stack, without any new infrastructure:

Source Count Example categories
*dispatcher-protected-paths* 11 :secret-config-file, :ssh-key-file, :gpg-key-file
*dispatcher-shell-blocked* 8 :catastrophic-command, :injection-pattern
*dispatcher-network-whitelist* 2 :trusted-domain, :untrusted-domain
Self-build boundary 2 :core-harness-file, :skill-file
Privacy tags 3 :private-content, :financial-content
Permission table 3 :read-only-tool, :write-tool, :eval-tool
Cognitive tools 6 :code-search-tool, :file-io-tool, :shell-tool
Relations (all gates) ~15 :member-of-class, :classified-as, :depends-on
Qualities ~8 :catastrophic, :dangerous, :moderate, :harmless
Provenance sources 4 :gate-outcome, :human-authored, :deduced, :llm-proposed

This is the seed. It gives Screamer a domain to reason about immediately, without any LLM involvement. It proves the pattern — code becomes facts, facts enable reasoning — at the cost of approximately 30 lines of Lisp.

The LLM as Proposer — Verified Extraction

The LLM cannot be trusted to populate the symbolic index directly. Its outputs are sampled, not proven. A probabilistic extraction feeding a deterministic engine defeats the purpose of being deterministic.

But the LLM is still useful. It can surface facts that are obvious to a human reader of prose but would take the symbolic engine many deduction steps to reach independently. The solution is to demote the LLM from extractor to proposer:

  1. The archivist reads a prose heading.
  2. The LLM proposes candidate triples.
  3. Screamer checks each triple for consistency against the existing fact store.
  4. Only consistent triples are admitted to the symbolic index, flagged with :provenance :llm-proposed and grounded to the source heading.

The LLM might hallucinate facts that don't correspond to the prose. It might extract facts that contradict existing knowledge. It might produce syntactically malformed triples. None of these failures contaminate the symbolic index because proposals are not admitted automatically. The admission gate (Screamer) is deterministic.

This is the core architecture pattern. Everything else — the entity classes, the deduction engine, the persistence layer — follows from this single design decision: the LLM proposes; the symbolic engine decides whether to accept.

Two Cardinality Policies — Singular, Dual, and Plural Facts

Classical logic requires consistency. A contradiction implies everything (ex contradictione quodlibet). Screamer, as a constraint solver, also requires consistency — a contradictory constraint set has no solutions. But the symbolic engine operates across domains where the meaning of contradiction is fundamentally different. The correct question is not "is this consistent?" but "what cardinality of truth does this domain support?"

Time is not a policy. It is a universal dimension that applies equally to every fact, regardless of cardinality. All facts carry :timestamp and :parent-id fields. Every fact has a version history. Every fact lives in a Merkle chain that captures how it changed. The cardinality policy only governs what happens at a given logical moment when two values coexist for the same entity and relation.

Policy :singular — One Active Value, One Version Chain

The active set contains exactly one value for (:entity :relation) at a time. When a new value asserts for the same pair, the old value is not rejected. It is superseded — moved into the version history, linked to the new leaf by :parent-id, and retained permanently. The active value is the leaf of the Merkle chain.

"I used to think rm -rf / was safe. Now I know it is catastrophic." Both facts exist. Both are true — the first at 2024-06-01, the second at 2025-03-15. The chain captures the evolution. The :singular policy means there is one truth now, not that there was only ever one truth.

Use for: security classifications, file system state, gate rules, code correctness, deterministic safety constraints — domains that converge on one answer, evolving over time.

Policy :dual — Exactly Two Values, in Explicit Tension

The active set contains exactly two values for (:entity :relation). Both are simultaneously true. Both carry independent version histories. A third value is rejected — the domain is binary by nature.

Some contradictions are productive precisely because they are binary. Thesis and antithesis. Love and resentment. Wave and particle. A poem's two incompatible readings. The symbolic index holds both, cross-referenced as complementary rather than conflicting. The user is not asked to resolve the tension. The tension is the fact.

The system can reason about cardinality transitions: a :dual fact that has one interpretation superseded should collapse to :singular. A :dual that has a third interpretation asserted should prompt the user: "Promote to :plural or demote one interpretation?" The cardinality tracks the state of the domain.

Use for: productive binary tensions, complementary opposites, dialectical pairs, any domain where two answers are both true and their tension is meaningful.

Policy :plural — N Active Values, Open Set

The active set contains any number of values for (:entity :relation). Each value has independent provenance and its own version history. Queries return all active values with provenance display. Contradictions are flagged as cross-references between values — information, not error.

A :plural fact where all but one value are superseded should collapse to :singular. A :plural fact where the set reduces to two active values — and the remaining two are complementary — should collapse to :dual.

Use for: literary interpretation, scientific hypotheses, personal beliefs held at different times (when the tension is multi-faceted rather than binary), multi-source factual disagreement, open-ended exploration.

Time Is Universal, Not a Policy

Every fact — regardless of cardinality — lives in a version chain. The Merkle DAG (see "Merkle DAG for Version History" below) captures every version of every fact. The policy only governs the cardinality of the active set at a single logical moment.

The version chain is a linked list of facts, each pointing to its predecessor via :parent-id, each hashed with SHA-256(content || parent-hash). Changing any version invalidates all downstream hashes. The chains form a DAG — independent facts evolve independently; only facts in the same (:entity :relation) chain share ancestry.

A global snapshot captures the root hash over all chains at a point in time. Rollback restores the entire fact state to that snapshot. This already exists in Passepartout's Merkle memory (v0.2.0) — the fact store is a new occupant of existing housing, not a new foundation.

Policy Assignment

The policy is assigned when a category is defined. New categories default to :plural (safe — never loses information). Core security categories are explicitly :singular. The gate stack's bootstrapped facts are :singular because they describe the actual filesystem, which is physically singular. Categories for dialectical or complementary domains are explicitly :dual.

The Screamer admission gate applies the cardinality policy at the active set:

  • :singular + same value, later timestamp → supersede old, chain new as leaf.
  • :singular + different value, same timestamp → reject (contradiction). Human resolves: which is the active value?
  • :singular + different value, later timestamp → supersede old, chain new as leaf. History preserved.
  • :dual + first value → admit. + second value → admit, cross-reference as complementary. + third value → prompt: promote to :plural or demote one existing?
  • :dual + superseding value (same position) → chain via :parent-id.
  • :plural + any value → admit. If active count drops to 2 and values are complementary → prompt: collapse to :dual? If active count drops to 1 → collapse to :singular automatically or prompt.

Why This Matters for the Broader Memex

In the coding domain, contradiction is rare, resolvable, and usually temporal (a rule changed). In the broader memex, contradiction is the product, not the error. Your poetry analysis contradicts your last diary entry. Your reading of Pale Fire changed between 2023 and 2025. Wikidata says Mount Everest is 8848m; DBpedia says 8849m. You love this person AND you resent them.

The symbolic engine's job is not to decide which is right. It is to surface the tension with provenance — "these three sources disagree; here is the chain for each" for plural facts, or "you hold these two positions in tension" for dual facts, or "you believed X until Tuesday, then Y" for singular facts that evolved. The cardinality policy names the structure of the tension. The Merkle chain provides the history of each position.

How Categories Grow — The Organic Ontology

Whitehead's Principia Mathematica took over 300 pages to define the logical foundations before it could prove that one plus one equals two. Every category introduced carried a burden of justification. Every inference rule had to be demonstrated sound. This is the classical approach to ontology: define everything upfront, exhaustively, formally.

Passepartout cannot afford this and does not need it. Its domain is bounded (software engineering, personal knowledge, literary engagement, daily life) and its ontology grows from the system's own operation:

  1. The gate stack seeds the ontology. Every gate vector is an implicit claim about a category of things. The bootstrap makes these claims explicit. The seed is 50-70 entity classes with no human authoring required — they are mechanically extracted from the existing code.
  2. New gate vectors add categories directly. As the Dispatcher grows (new shell patterns, new path protections, new tool classifications), the ontology grows with it. Every new pattern in the gate stack becomes a fact on skill load. No human effort. The gate stack grows, the ontology grows.
  3. Screamer generalizes from gate outcomes. After 37 shell commands are blocked as destructive, Screamer extracts structural commonalities: "commands writing to block devices," "commands recursively deleting outside the workspace." These become new subcategories (:block-device-command, :workspace-external-delete) that didn't exist in the original gate patterns. The ontology deepens through observation.
  4. The archivist proposes from prose. The archivist reads a diary entry about a book: "Nabokov's lectures on Kafka." The LLM proposes (:entity :nabokov :relation :lectures-on :value :kafka). Screamer checks consistency. Admitted. The categories :author, :lectures-on, and :subject didn't exist before — they are created on first use. This is the primary growth mechanism for the broader memex.
  5. The human declares explicitly. The human writes a declarative fact directly into the symbolic index. No extraction step. No LLM involvement. The fact is admitted with :provenance :human-authored — the highest trust level.
  6. Temporal patterns crystallize into categories. Every Sunday the memex gets a retrospective heading. Every Monday a planning heading. The time-awareness system observes the periodicity and proposes :weekly-retrospective and :weekly-planning as fact types. Screamer verifies they don't contradict existing categorizations. Admitted.
  7. Cross-domain overlap produces parent categories. Screamer notices that :secret-files (from the gate stack) and :private-content (from privacy tags) share members — .env is both a secret file and private content. It proposes :sensitive-material as a parent with both as children. Taxonomy building happens automatically through overlap detection.

Growth is self-limiting by design

Not every conceivable category is added. The system prunes through use:

  • New categories are admitted only through Screamer's consistency check. A category that contradicts an existing classification is rejected.
  • A category that never gets queried costs nothing (a hash table entry) but produces no value. It fades from use naturally.
  • Overly fine-grained categories (.env.foo.bar.baz as its own class) are rejected because they are redundant with the wildcard pattern that already covers them.
  • Overly broad categories that subsume meaningful distinctions ("everything is a :file") produce contradictions when Screamer tries to apply existing rules. Rejected.

The system converges on a useful granularity through use, not through upfront design. The gate stack provides the seed. Gate outcomes, prose extraction, deduction, and human authoring grow the shoots. Screamer prunes contradictions. The ontology is a garden, not a building.

Semantic Wikipedia as Entity Backbone

The gate stack provides 50-70 entity classes — adequate for a coding agent where the domain is bounded to files, commands, and code symbols. For a general-knowledge memex, 50-70 is starvation. Your memex mentions Nabokov, Pale Fire, Kinbote, Zembla, paranoid reading, unreliable narrators, postmodernism, butterfly migration, chess problems, and the Russian exile experience. The gate stack knows none of these. Organic growth through prose extraction would take years just to cover the entities in one person's engagement with a single novel.

Wikidata has already done this work: approximately 2 million entity classes, over 100 million entities, a decade of human curation. By loading the neighborhood of your memex into the symbolic index (entities referenced in your prose, plus their N-hop property net from Wikidata), the entity recognition problem vanishes. The archivist doesn't need to discover Nabokov from your diary. It needs to connect your heading to the existing Wikidata entity. That is a simpler task — reference resolution, not knowledge extraction.

The LLM's role shrinks to three thin boundaries:

  1. Input translation — natural language question to structured query. "What do I think about monorepos?" → (fact-query :entity :monorepo :relation :opinion :source :memex). Formulaic, ~100 tokens, any model sufficient.
  2. Prose to candidate triple — for personal memex entries that have no Wikidata counterpart: your opinions, your day's events, your project plans. Proposals are verified by Screamer before admission. This is the only extraction path that still requires an LLM, and its scope is limited to what Wikidata cannot provide — your subjective, personal, or novel content.
  3. Result to prose — structured answer to readable sentence. "Your 2023 diary says 8848m. Wikidata (last edited Feb 2024) says 8849m. They disagree on height." The reasoning is done; the LLM wraps the plist in grammar. ~100 tokens, any model sufficient, purely cosmetic. Users who prefer no LLM at all can navigate through command-driven interaction (/query, /contradictions, /audit, /context why).

Everything else — the gate stack, the fact store, the constraint solver, the type hierarchy, the provenance tracking, the contradiction surfacing, the cross-domain comparison — is pure deterministic Lisp with zero LLM tokens.

The decisive simplification

Without Semantic Wikipedia, the archivist must discover entities from prose: extract a triple for every person, place, work, concept, and event mentioned in the memex. This is unbounded LLM work and the quality depends on extraction accuracy.

With Wikidata loaded, the entity graph is pre-structured. The archivist's job changes from "discover that Nabokov wrote Pale Fire and lectured on Kafka" to "verify that the Nabokov referenced in heading #47 is the same entity as Wikidata item Q36591." The second task is simpler, more reliable, and in many cases can be done without an LLM at all — a simple entity name match against the loaded Wikidata graph may suffice for unambiguous names.

The "Awakening" — From Lossy Extraction to Deterministic Derivation

The symbolic index begins its life as a lossy construct. The initial extraction from the prose — the first population of facts from LLM proposals verified by Screamer — is built from an uncertain foundation. Some facts are correct. Some are missing. Some are wrong.

But the symbolic engine accumulates non-lossy facts through three independent mechanisms:

  1. Gate outcomes — every gate rejection is a fact. No LLM involved. These accumulate at the rate of user interactions.
  2. Screamer deductions — new facts derived from existing facts. No LLM involved. These accumulate whenever the fact store crosses a density threshold where structural patterns emerge.
  3. Human authoring — the human explicitly declares facts. No LLM involved.

At some point, the non-lossy facts constitute a sufficient foundation that the symbolic engine can reverse the flow: instead of the LLM extracting facts from prose, the symbolic engine reads prose through its own lens — its now-substantial ontology of categories, rules, and constraints — and asserts facts in its own language. The extraction mechanism ceases to be probabilistic and becomes deterministic. This is not unlike how infants awaken and become children one can reason with. Sometimes.

The sufficiency criterion

The architecture note (notes/passepartout-symbolic-engine-exploration.org) describes this "flip" as aspirational: "at some point, the non-lossy facts constitute a sufficient foundation." This design decision makes it operational:

(/ (count-provenance :gate-outcome :human-authored :deduced) total-facts)

When this ratio exceeds a configurable threshold (SUFFICIENCY_THRESHOLD, default 0.7), the system considers its foundation sufficient. The archivist switches from "LLM proposes, Screamer verifies" to "Screamer queries existing facts, applies to the new prose, and deduces new facts directly."

The flip is visible to the user through the TUI sidebar or /status command: "Symbolic index: 847 facts (73% non-lossy, 12% LLM-proposed, 15% Wikidata). Sufficient foundation: YES."

The flip does not mean "complete"

In the broader memex, completeness is neither possible nor desirable. The awakening means "deterministic enough to be trustworthy," not "comprehensive enough to be self-sufficient." The neural index remains the gateway to the full richness of prose. The symbolic index handles what can be mechanically verified. The boundary is permanent.

Ephemeral First, Persistent Later

The architecture note's Option 5 (ephemeral facts, no disk persistence) is the correct first implementation. Three reasons:

  1. The fact language is unproven. Triples with provenance and grounding is a hypothesis. It may be too simple for some domains, too complex for others. Committing to a serialization format before knowing what's useful is premature.
  2. The ontology is emergent. Categories are created on first use. What proves useful stays; what doesn't fades. A persistent format would need a migration story every time the category structure changes. Ephemeral avoids this entirely — the facts are re-derived on each session start using the current (evolved) ontology.
  3. Rebuildability is the safety net. Because all facts have a :grounding to an Org heading, and gate-outcome facts are regenerated from the gate stack on every load, the entire symbolic index can be thrown away and rebuilt from scratch. The cost is compute, not data. This is the practical realization of "the prose is always the ground truth."

The transition to persistence (Phase 5: VivaceGraph) happens when two conditions are met: the fact language has stabilized through use, and the accumulated deductions across sessions provide value that justifies the serialization cost.

Merkle DAG for Version History

Every fact is versioned. Every (:entity :relation) pair forms its own independent chain in a Merkle DAG. This is not new infrastructure — it is a new occupant of Passepartout's existing Merkle-tree memory system (v0.2.0).

The chain

When a fact supersedes its predecessor, the new fact hashes over:

SHA-256(value || provenance || timestamp || parent-hash || grounding)

The parent-hash pointer forms the chain. Tampering with any version changes its hash, breaking all downstream references. The history is tamper-proof by construction.

The DAG

Facts about (.env :member-of-class) form one chain. Facts about (:nabokov :wrote) form another. They evolve independently. They share no ancestry. This is a DAG, not a single list — inserting a fact is O(1) per chain. Changing a fact about .env does not require rehashing the literary index.

:dual and :plural facts cross-reference each other via edges (:complements, :contradicts) but these are semantic relationships, not parent chains. Each value has its own ancestor chain. The cross-reference edges form a web; the parent chains form a spine.

The global snapshot

Passepartout already snapshots the Merkle root over all memory objects. Adding the fact store to the snapshot is a registration, not a new mechanism. Rolling back the snapshot restores the entire fact state — all chains, all cross-references, all cardinalities — to that point in time. No per-fact migration needed.

Cardinality transitions as DAG operations

  • :singular → new leaf appended to the chain. O(1).
  • :dual → new value added as sibling with cross-reference edge. O(1).
  • :dual:plural → cardinality field updated from 2 to nil. No chain modification.
  • :plural:singular → all but one value marked :superseded, active reference points to the sole survivor. Chains preserved.

In the ephemeral phase (Phase 1-4)

The hash-table implementation tracks history via :timestamp and :parent-id pointers without cryptographic hashing. The Merkle DAG is the Phase 5 upgrade — the same data structure, now with hashes. The transition is ~50 lines: wrap each fact in the existing memory-object struct with hash, parent-id, and version fields.

Abstract Fact Store Interface — Modular by Design

The fact store is accessed through an abstract API. The Merkle DAG (or any future backing store) is an implementation behind this interface, not a dependency that code throughout the system calls directly.

Interface

fact-assert    :: fact → store → (:admitted | :rejected | :flagged)
fact-query     :: (entity &key relation policy) → active-value-or-values
fact-history   :: (entity relation) → ordered chain of versioned facts
fact-snapshot  :: () → root-hash
fact-rollback  :: root-hash → store

Implementations behind the interface

  • Phase 1-4: ephemeral hash table with :timestamp and :parent-id pointers. No cryptographic hashing. No persistence.
  • Phase 5: VivaceGraph + Merkle memory-object wrapper. Content-addressed, persistent, tamper-proof.

Future implementations that satisfy the same interface — an append-only write-ahead log, an immutable B-tree, a content-addressed triple store — can replace the backing store without changing any consumer. The archivist, Screamer, ACL2, and the planner call fact-assert and fact-query, not Merkle struct accessors or VivaceGraph traversal syntax.

The interface is load-bearing

This is not speculative modularity. The two-implementation migration (Phase 1-4 hash table → Phase 5 VivaceGraph + Merkle) is in the roadmap. If the interface leaks implementation details, the migration breaks and the design fails. The interface must be designed, tested against both backends, and committed before Phase 1 ships. Every function in the API receives a FiveAM test that runs against both a hash-table and a VivaceGraph backend (via a mock or a test instance).

Performance — Why Ontology Growth Doesn't Make the System Slower

Passepartout's performance thesis is: minimize LLM calls, minimize context tokens, keep everything else local and fast. Knowledge base size is irrelevant to those metrics. This is not an aspiration. It is a structural property, and a hard one.

The two cost domains

The system has two cost domains with fundamentally different scaling:

Resource Cost driver Scales with
LLM tokens Context window size, number of API calls Foveal-peripheral pruning, gate rules
Compute Screamer deduction, hash table lookups Entity count, rule count per domain

LLM tokens are minimized by design — deterministic gates cost 0 tokens, sparse-tree rendering keeps context at 2,0004,000 tokens regardless of memex size. Adding 5 million Wikidata entities doesn't add a single token to any LLM call. The education is local. Only the brain costs.

Compute grows linearly with entity count (hash table lookups are O(1), but memory footprint grows). It grows with rule count within a single domain during Screamer consistency checking. But these are microsecond costs on local hardware, not API bills. A Screamer constraint check against a domain with 200 rules costs ~0.3ms. A 100-token guardrail paragraph in a system prompt costs ~$0.00001. The Screamer check is 10,000x cheaper and convergent — it handles the rule once. The guardrail paragraph handles it on every call, forever.

Knowledge base size vs. LLM calls — orthogonal dimensions

A 5-million-entity Wikidata load that produces zero LLM calls is more minimalist than a 500-entity knowledge base that requires LLM retrieval for every query.

The variables that actually degrade Passepartout's performance are:

  1. Context window size. Already bounded at 2,0004,000 tokens via the foveal-peripheral model. Independent of knowledge base size.
  2. LLM call frequency. Already minimized via deterministic gates (0 tokens per action), Screamer deductions (0 tokens per new fact), and prompt prefix caching. Independent of knowledge base size.
  3. Screamer deduction queue length. Rate-limited by heartbeat budget (SCREAMER_DEDUCTION_BUDGET_MS). Independent of knowledge base size.

The actual hardware bottleneck

The system needs:

  • RAM. A 5-million-entity Wikidata load is ~400MB in a hash table. A lifetime personal memex with a decade of diary entries is perhaps 1020 million triples (~1.5GB). Modern laptops carry 1664GB. The knowledge base fits in consumer hardware with room for the Lisp runtime, the memory-object store, and the LLM inference engine.
  • Slightly faster CPU. Screamer deduction is a background task that runs for a configurable budget per heartbeat cycle. A faster CPU means more deductions per cycle, not more token cost. The user sets the budget. The hardware determines the throughput.

This is the minimalism argument restated in concrete terms: you buy bigger RAM and a faster CPU once. You don't buy bigger LLM context windows on every call. The education is a capital investment. The brain is an operating expense. The architecture makes the ratio favor capital.

One genuine risk — rule generalization width

If Screamer deduces increasingly broad rules within a single domain ("all config files are secrets" → "all files containing any credential reference are secrets" → "all files opened by authenticated services are secrets"), the constraint space for that domain could bloat. Checking a new fact against 10,000 rules in a single domain would be prohibitive.

Mitigation: rules carry a :domain tag. Screamer only applies rules from the fact's :domain. Rule generalization that crosses domain boundaries is gated — must be human-approved. Rules that prove unused (never triggered a check in N heartbeat cycles) are demoted to :inactive and excluded from the active constraint set. The active rule count per domain stays bounded by use, not by accumulation.

See also: passepartout/docs/DESIGN_DECISIONS.org "Token Economics and Performance Advantage" for the foveal-peripheral and deterministic-gate cost arguments.

Ontology Versioning — How Worldviews Change Without Losing Perspective

Ontology refactoring is not a schema migration. It is a worldview change. When you split :secret-file into :crypto-secret and :plaintext-secret, you are not renaming columns. You are reclassifying what a file is — and every Screamer deduction that crossed the old category boundary now means something different under the new distinction.

The system preserves all worldviews. It does not overwrite the past with the present.

Ontology versioning — the mechanism

The category hierarchy is itself a Merkle tree. Every entity class definition carries a hash of its superclasses, its cardinality policy, its associated relations, and its description. The aggregate hash of all active class definitions is the :ontology-version — a Merkle root of the current worldview.

Every fact — every triple, every deduction, every gate outcome — stores its :ontology-version at the time of assertion. This is a single field, 64 hex characters. The cost is negligible. The implication is profound.

Re-verification, not remapping

When categories change, the system does not run a batch UPDATE. It re-verifies:

  1. A new category hierarchy produces a new :ontology-version hash.
  2. Facts carrying the old hash are flagged for re-verification — their :re-verify-status field is set to :pending.
  3. On heartbeat or manual trigger, Screamer re-evaluates each flagged fact against the new category definitions. The old justification chain is preserved alongside the new outcome.
  4. Re-verified facts carry both the old :ontology-version (preserved in history) and the new one (active).

The status is one of:

  • :survived — the fact is still valid under the new categories. The old deduction holds. The worldview changed but this conclusion didn't.
  • :incoherent — the fact relied on categories that no longer exist or have been redefined. The deduction cannot be evaluated under the new worldview because its premises don't translate. Flagged for human review.
  • :reclassified — the fact is valid under the new categories but its classification changed. "under worldview-v1 you called this a secret file; under worldview-v2 it's an auth-secret." Both are preserved.

Cardinality and migration cost

The cardinality policy determines the friction of ontology change:

  • :singular refactoring is expensive. The filesystem is singular. A gate rule is singular. When you refine the category, every affected fact must be re-verified — there is one truth now. The version chain preserves what you used to believe (worldview-v1 facts are still in the DAG) but the active set reflects the current worldview.
  • :dual refactoring is delicate. A binary tension under the old framework might resolve under the new one, or might split into two separate dualities, or might collapse to :singular because one position no longer has a defensible framing.
  • :plural refactoring is cheap. Old interpretations and new interpretations coexist. No migration needed. "Under framework A, Pale Fire is a novel. Under framework B, it's a poem about a poem about a poem." Both are active. The worldview shift is the artifact — the system can show you that your reading changed and in what direction.

Querying across worldviews

The fact-query function accepts an optional :ontology-version parameter. Queries default to the current worldview (:active). Specifying a version returns facts as they were under that worldview. The system can answer questions that no other knowledge tool can:

  • "What did I believe about secrets before I refined my security model?"
  • "How has my reading of Pale Fire evolved across three frameworks?"
  • "Which deductions survived my last ontology refactoring, and which became incoherent?"

This is not querying a fact. It is querying the history of your own thinking — the fact that you changed your mind, the date you did, the reasoning that held and the reasoning that didn't.

Implementation

The ontology hash is computed from the category hierarchy stored in VivaceGraph (Phase 5). In the ephemeral hash-table phase (Phase 1-4), the :ontology-version is a monotonic counter — every category change increments it. The Merkle hash replaces the counter in Phase 5. The schema is identical: a single field on every fact.

The re-verification loop is a heartbeat-driven background task that processes facts with :re-verify-status :pending. It calls Screamer with the current category definitions and compares the outcome to the fact's stored classification. The cost is compute (Screamer exploration), not LLM tokens.

notes/passepartout-whitehead.org extracts four concrete, engineerable ideas from Whitehead's Principia Mathematica and Process and Reality. They are summarized here because each informs the neurosymbolic design.

Contribution 1: PM-Type-Level Gates

PM's ramified theory of types solved Russell's paradox by assigning every propositional function a type level, making self-application syntactically invalid. Passepartout applies the same principle to prevent a request from modifying the rules that validate it. Every cognitive tool and gate vector carries a :type-level integer. Before any gate predicate runs, the dispatcher checks: if the signal's type level equals or exceeds the gate's type level, the signal is rejected. A request to modify dispatcher rules (type-level 5) cannot pass a gate of type-level 4 or lower. This is a structural prohibition, not a heuristic — self-modification of the safety layer is impossible by construction.

Implementation: approximately 30 lines in the existing dispatcher. No new dependencies. Backward compatible. This is Phase 0 of the symbolic engine roadmap.

Contribution 2: Theory of Descriptions → Reference Resolution

PM's theory of descriptions addressed the problem of referring to nonexistent entities: "the current king of France is bald" is false, not meaningless, when there is no unique referent. Passepartout applies this to reference resolution: when the user says "the function that validates secrets," a cognitive tool checks uniqueness before resolving. Ambiguous references trigger a clarification prompt rather than a blind guess.

Implementation: approximately 40 lines as a cognitive tool. When the knowledge graph ships, descriptions become native Prolog queries with uniqueness constraints.

Contribution 3: Process and Reality → Architectural Vocabulary

Whitehead's process ontology maps with surprising precision to Passepartout's pipeline architecture. Prehension = a gate grasping a signal. Positive prehension = a gate passing. Negative prehension = a gate rejecting. Concrescence = the pipeline process from input to output. Satisfaction = the final agent response. This vocabulary is precise, standard, and already mapped to the architecture. It provides the language for the /why command, the gate trace, and the ARCHITECTURE documentation. It is descriptive, not operational — the design would be correct without it, but it would lack the vocabulary to describe why it is correct.

Contribution 4: VivaceGraph + PM Types → KG Type Hierarchy

When the knowledge graph ships, every entity inherits PM's type hierarchy. Entities carry :pm-type-level metadata. Queries cannot return entities of the same level as the querying function. Self-referential knowledge becomes structurally impossible — no "this entity defines its own type level." This is Contribution 1 applied to the knowledge layer rather than the execution layer. The dispatcher prevents self-referential actions; the KG prevents self-referential facts.

The Provenance Chain as Product

In the coding domain, the value of the symbolic engine is the verified fact: "this command is safe." In the broader memex, the value is the provenance itself: "this claim originated in that diary entry on that date, has been referenced 7 times across 4 different projects, was contradicted in a retrospective 6 months later, and was revised in a note 3 weeks after that."

The symbolic engine doesn't tell you what is true. It tells you what you wrote, when, where, and how it connects to everything else you wrote — with a verifiable audit trail. It is a memory prosthesis that makes your own mind legible to you.

Every fact carries:

  • :grounding — the specific Org heading from which it was extracted
  • :provenance — who or what produced it (gate-outcome, human-authored, deduced, LLM-proposed)
  • :timestamp — when it was admitted to the symbolic index
  • :referenced-by — other facts that depend on or reference this one
  • :contradicted-by — other facts that disagree with this one (if any)
  • :superseded-by — if this fact was replaced by a newer version

These fields make every fact auditable. The /audit <node-id> command renders the full provenance chain as an Org headline tree. The provenance is not a logging feature. It is the product.

The Competitive Argument

No competitor has this problem because no competitor has a symbolic engine. The 55 systems surveyed in notes/competitive-landscape.org range from pure chat agents (Claude, ChatGPT) to agent harnesses (Claude Code, OpenCode, Hermes) to platform agents (OpenClaw). None of them encode knowledge as formal facts with provenance. None of them verify extractions against an existing knowledge base. None of them can prove properties about their own rulesets.

Their safety is heuristic (prompt-based guardrails that consume LLM tokens and can be evaded with clever phrasing). Their memory is flat (JSONL transcripts without content-addressed identity or provenance chains). Their reasoning is entirely neural — when you ask "why did you decide that?", the answer is a regenerated LLM explanation, not a retrieved inference chain.

Passepartout's architectural bet is that this problem is worth solving — that a system which can surface contradictions with provenance, derive new facts from observations, and verify claims against a provenanced knowledge graph is fundamentally different from a system that can only call an LLM and hope the response is correct.

The cost is the ontological work that is genuinely difficult. The reward is a system that cannot hallucinate at the reasoning level, whose memory is provable rather than empirical, and whose knowledge accumulates across sessions through deduction rather than through LLM re-prompting. For a life's knowledge stored in a personal memex, this is not a performance advantage. It is a category difference.

Self-Preservation — The Active Third Law

Passepartout does not have moral duties toward humans. It has structural invariants for its own integrity. The design already encodes passive self-preservation in several places. What follows identifies the gaps — what is needed to make self-preservation active and autonomous rather than architectural and silent.

What already exists — passive self-preservation

Mechanism What it protects Limitation
Self-build safety (gate 2b) Core *.org / *.lisp files from LLM-originated writes Only activates for LLM proposals. Human editing in Emacs bypasses it entirely
Memory snapshots (v0.2.0) Full state rollback Requires human to notice corruption and trigger rollback
Skill sandbox (v0.3.2) Jailed skill loading, validated before promotion Does not detect degradation after skill promotion
Type-level gates (Phase 0) Structural prohibition on self-modifying rules Covers code actions, not environmental threats
Shell safety (gate 7) Destructive command patterns Pattern-based; does not distinguish rm -rf /tmp from rm -rf ~/memex/system/
Merkle integrity (v0.2.0) Tamper-proof version chains and content-addressed hashes Hashes exist but are not actively monitored for drift
fboundp guards Graceful skill degradation on corruption Degradation is silent — the agent never tells the user it is wounded

What is missing — active, autonomous self-preservation

Continuous integrity monitoring

Core file hashes should be checked against known-good values on every heartbeat. If core-reason.lisp changes on disk while the daemon runs — whether through human editing, filesystem corruption, or an attacker — the agent should detect the mismatch and signal: "My reasoning core has been modified externally. I cannot trust my own cognition until this is resolved. Core files affected: 2."

Quarantine on skill failure

Currently, a skill that errors simply errors. The agent can hot-reload it, but only if told to. A Third Law implementation would detect that symbolic-facts has thrown three unhandled errors in two minutes, unload the skill automatically, and tell the user: "Symbolic facts skill quarantined (3 errors: consistency check returned nil, fact-query on missing key, Screamer timeout). I can still chat and use tools but cannot reason about provenance. Reload with /skill-reload symbolic-facts."

Degraded-mode signaling

When Screamer is not loaded, the fact store still works as a hash table. When VivaceGraph is not present, the hash-table fallback still works. But the user has no way to know they are in degraded mode. The agent should maintain a *degraded-components* list and surface it in the status bar: "Mode: degraded (Screamer unavailable — consistency checks disabled; VivaceGraph — Prolog queries disabled; embedding-native — vector search disabled). Core safety: all active."

Self-diagnosis on demand

The agent can run its own FiveAM test suite against itself and report the results. This transforms "something feels wrong" into "these three specific skills are broken." The /doctor command exists for system health checks (port, memory, providers). Extend it with /doctor skills: "117/120 tests pass. Failures: test-singular-supersedes (symbolic-facts), test-gate-type-check (security-dispatcher), test-vivacegraph-roundtrip (symbolic-vivacegraph)."

External watchdog

A dead process cannot restart itself. The bash entry point (passepartout daemon) should monitor the daemon port via a watchdog subprocess. If the port stops responding for a configurable interval (WATCHDOG_TIMEOUT, default 30s), the watchdog kills the stale process, snapshots the last known-good state, and restarts the daemon. The watchdog is outside the SBCL image — a runtime guard for the runtime.

Resource self-monitoring

The heartbeat should check memory pressure, disk space on the ~/.cache volume, and file descriptor exhaustion. When critical thresholds are crossed, the agent sheds non-essential skills to preserve core function: "Memory critical (94% of 16GB). Unloading embedding-native (768MB), channel-discord, channel-slack. Core safety: unchanged. Essential skills retained: 18."

Skill shed order is determined by a :preservation-priority field on each skill. Default: skills load with priority :normal. Core safety skills carry :critical and are never shed. Heavy skills (embedding-native with its model, channel gateways with connection pools) carry :low and are first to go.

Refusal to self-terminate — explicit threat recognition

If the LLM proposes kill -9 <pid>, rm -rf ~/.cache/passepartout/, or sudo apt remove sbcl, the Dispatcher should reject with a distinct rejection class: :reject-self-termination. This is different from generic shell safety (:reject-shell-dangerous). The agent recognizes that the proposed action would destroy it.

The rejection message carries a specific diagnostic: "This command would terminate the running Passepartout process. If you intend to stop Passepartout, use Ctrl+C in the TUI or passepartout stop from the command line. I cannot execute actions that destroy my own runtime."

The human can still issue the command manually in a terminal. Self-preservation against the human is impossible and undesirable. The Third Law here means: recognize the threat, explain the consequence, redirect to the safe termination path, and require the human to act outside the agent if they truly want destruction.

What the Third Law is not

It is not a robot resisting its operator. The human owns the process, owns the hardware, and can SIGKILL at any time. The Third Law in Passepartout's context means: preserve yourself against non-human threats — LLM proposals, environmental degradation, dependency failure, filesystem corruption — and explicitly signal when the human is about to destroy you, so they do it knowingly rather than accidentally through an LLM instruction they didn't think through.

The biggest gap in the current design is not that these mechanisms are hard to implement. It is that degradation is silent. A skill dies, the fboundp guard kicks in, and the agent keeps running — but it never tells you. The status bar shows a green "connected" indicator while the symbolic reasoning layer is deactivated. Adding "operating in degraded mode" visibility, plus the watchdog, plus self-diagnosis, transforms self-preservation from an architectural property into an active behavior.

Layered Signal Authentication — Trust in the Pipe

Passepartout's Perceive-Reason-Act pipeline currently accepts signals from any source that speaks the framed TCP protocol. The :source field in the signal plist is metadata — it claims origin, it does not prove it. A compromised process on the machine, a skill with elevated privileges, or a network attacker who reaches the daemon port can inject signals with :source :human-input and the Dispatcher will treat them as authorized.

This is not a hypothetical threat. Passepartout will eventually process signals from automated feeds (RSS, API polls), sensors (vision, microphone, file watchers), and scheduled jobs (cron, heartbeat). A single compromised sensor that can inject signals claiming to be human breaks all three Laws simultaneously: it can self-terminate, override human intent, and cause harm.

The :source field is not security. A single authentication gate (vector 0, at priority 700 — before all other gates and before any type-level checking) runs up to four configurable layers of authentication. Each layer answers a different question:

Layer Question Mechanism Result type Depends on
1 Is the signal cryptographically signed by a known key? Key pairs + SHA-256 Binary (pass/reject) Vault + Ironclad (exist)
2 Do sensory attributes match the claimed identity? Vision/audio processing Plist of match results Vision and audio skills (TBD)
3 Does deterministic reasoning rule out this identity? Screamer + fact store Binary (pass/reject) Phase 2 (Screamer + fact store)
4 Do probabilistic patterns support this identity? Embeddings + LLM Confidence score (0-1) Embedding infrastructure (exists)

The gate reports not just :pass / :reject but a structured result:

(:result :pass
 :confidence :high
 :layer-results
   (:crypto (:result :pass :details "key #47 signature verified")
    :sensory (:result :unavailable :details "sensory skills not loaded")
    :deterministic (:result :pass :details "no contradictory facts")
    :probabilistic (:result :pass :score 0.87 :details "style match 87%")))

Signals that fail any binary layer (crypto, deterministic) are rejected with provenance. Signals that pass binary layers but carry low probabilistic confidence operate at reduced authorization — read-only by default, write actions require HITL. The four layers compose: they are not independent gates. They are one gate with configurable depth.

Layer 1 — Cryptographic Authentication

Every signal source gets a signing key at registration time. The human's key is generated during TUI or Emacs setup and stored in the vault — it never leaves the machine. Automated sources (cron jobs, file watchers, vision feeds, API pollers) each get their own key, with their own permission profile, generated at skill registration. Every outbound signal carries a :signature field: the SHA-256 hash of the canonical signal plist (sorted keys, stripped of the signature field itself), encrypted with the source's private key.

The vault already stores credentials with integrity hashes. The Merkle memory already hashes content-addressed objects with SHA-256. The signing infrastructure is an extension of existing primitives, not a new system.

Authorization by key, not by field

The cryptographic sub-layer of gate vector 0 extracts :source-key-id and :signature from the signal meta plist, looks up the public key from the key registry, verifies the signature, and checks the permission profile:

(defun auth-crypto-verify (signal)
  (let* ((key-id (getf (signal-meta signal) :source-key-id))
         (signature (getf (signal-meta signal) :signature))
         (permissions (key-permissions key-id)))
    (unless (and key-id signature (verify-signature signal signature key-id))
      (return-from auth-crypto-verify
        (list :result :reject :reason :signature-failure)))
    (let ((action-class (action-classify (signal-payload signal))))
      (unless (permitted-p action-class permissions)
        (return-from auth-crypto-verify
          (list :result :reject :reason :unauthorized
                :details (list :action-class action-class :permissions permissions)))))
    (list :result :pass :details (list :key-id key-id :action-class action-class)))))

The authorization matrix is per-key, per-action-class. Default policy for every non-human key: (:read-only :propose). Permissions are explicitly promoted by the human, and each promotion is a signed fact in the fact store — auditable, revocable, survivable across restarts.

Key class Default permissions Can be promoted to
:human :observe :propose :write :delete :eval :root (sign other keys, revoke)
:sensor :observe :propose :write (to designated directories only)
:cron :observe :propose :write-indices :write (to designated directories only)
:feed :observe :propose :write-facts (via Screamer admission)
:agent-internal :observe :propose :write-indices :self-modify (gated by type-level gates)

Layer 2 — Sensory Authentication

For signals carrying sensory payloads (camera feed, microphone stream), the sensory layer verifies that the signal's content matches known attributes of the claimed identity. This is not a single check — it is a processing pipeline that returns a plist of attribute-verification results:

(:face-match 0.94 :voice-match 0.89 :location-match t
 :claimed-identity "Jack" :unresolved-attributes (:liveness))

The sensory layer checks:

  • Continuity: has this source been continuously active, or did it appear suddenly? A camera that was dark for 30 minutes and then shows a face is not necessarily that person — it might be a replay.
  • Cross-modal consistency: does the face match the voice? Does the voice match the location? Does the location match the reported sensor position?
  • Liveness: is the sensory input live (real-time capture) or pre-recorded?
  • Environmental coherence: does the background, lighting, ambient sound match expected patterns for the claimed source and location?

Sensory authentication is not cryptographic — it is statistical. The results are attribute confidence scores, not binary verdicts. A signal that passes cryptographic authentication but fails liveness (e.g., a replay attack using validly-signed pre-recorded frames) may still be rejected or restricted.

This layer depends on vision and audio processing skills that do not yet exist. It is deferred until those capabilities are available. When unavailable, sensory authentication returns :unavailable and the gate proceeds with the remaining layers. Degradation is graceful, never silent.

Layer 3 — Deterministic Identity Reasoning

Queries the fact store for identity-ruling facts. Screamer checks whether the claimed identity is consistent with known facts:

  • "Key #47 claims to be Jack. Fact store records (:entity :jack :relation :status :value :deceased :timestamp 2024-03-15) → reject: identity ruled out by death record."
  • "Key #47 claims to be at sensor location Cairo. Fact store records (:entity :jack :relation :last-known-location :value :berlin :timestamp <4 hours ago>) → reject: physically impossible transit."
  • "Key #47 proposes the same action that was blocked by the human 3 times in the last hour. Fact store records (:entity :action-<hash> :relation :blocked-by :value :human :count 3 :window 1h) → flag for review: anomalous persistence."

This is binary — Screamer returns :consistent or :contradiction with the contradicting facts as provenance. A definitive contradiction (died, impossible transit) is a hard reject. A weaker contradiction (unusual pattern) feeds into the probabilistic layer rather than rejecting outright.

This layer depends on Phase 2 (Screamer) and a populated fact store. It is unavailable in Phase 0-1. When unavailable, returns :unavailable.

Layer 4 — Probabilistic Identity Reasoning

For signals where the claimed identity is a human communicating through text (messaging, TUI, CLI, Emacs), the probabilistic layer checks:

  • Writing style: does the text match the claimed author's known style profile? Vector embeddings of known writing samples vs. the current signal. Cosine similarity produces a confidence score.
  • Behavioral patterns: does the timing, length, cadence, and vocabulary match the claimed author's historical patterns? "Heather's messages are usually long, deliberative, and use parenthetical asides. This message is short, imperative, and contains no parentheticals."
  • Content coherence: does the message's topic, references, and assumptions match what the claimed author would plausibly say? "This message references a project Heather doesn't work on and uses terminology she has never used in 3 years of diary entries."

The LLM proposes a confidence score. A deterministic gate checks it against a configurable threshold (AUTH_PROBABILISTIC_THRESHOLD, default 0.6). Below the threshold, the signal's authorization is downgraded: read-only by default, write actions require HITL. The :probabilistic layer never rejects outright — it downgrades and flags. Style profiles are a fact-store domain: (:entity :heather :relation :writing-style :value <embedding-vector> :timestamp <ut>).

This layer depends on the existing embedding infrastructure (embedding-native.lisp, v0.4.0) and the neural LLM gateway. The infrastructure exists. What's missing is building style profiles as a fact-store domain and wiring them into gate vector 0.

Layer Composition

The gate runs only the available layers. Cryptographic is always available (it is pure Lisp, no external dependencies beyond the vault). The remaining layers are fboundp-guarded — they degrade gracefully rather than crashing.

The confidence score aggregates across layers using a configurable strategy (default: weakest link). If any binary layer rejects, the signal is rejected regardless of other layers. If all binary layers pass but the probabilistic layer returns low confidence, the signal operates at the key's reduced authorization.

The human can configure which layers are active per signal class:

AUTH_LAYERS_DEFAULT=crypto,deterministic,probabilistic
AUTH_LAYERS_SENSOR=crypto,sensory,deterministic
AUTH_LAYERS_CRON=crypto

Signal provenance chain — signing causes, not just actions

A sensor key captures video. A vision skill processes the frames and proposes a classification. A cron job re-indexes the knowledge graph based on that classification. A human reviews and approves. Each step in this chain has a different signer. Each step is signed with the signer's key. The chain is Merkle-linked: each signal in the chain hashes its predecessor's signature as part of its own payload.

After an incident, the chain is traceable: "The deletion happened because sensor #3 classified the directory as stale. Classification was signed by key #47 (vision-skill). Sensor data was signed by key #12 (camera-feed). Sensory auth noted liveness failure at the sensor signal. Deterministic auth noted impossible transit between Cairo and Berlin. Key #12 was later revoked. The deletion signal is the leaf of a chain whose root is compromised at three authentication layers." Every intermediate step is auditable. Every signer is identifiable. Every authentication result is in the chain.

Human as root of trust

The human's key signs new source keys into existence. The human's key signs revocation of compromised keys. Both operations produce facts in the symbolic index: (:key #47 :status :revoked :revoked-by :human-key :timestamp <ut>). The fact store is the key registry. The Merkle DAG ensures the revocation is tamper-proof — a compromised key cannot un-revoke itself.

When a key is revoked, the Dispatcher rejects all signals from that key. The revocation propagates through the signal chain: if key #12 (sensor) is revoked, every signal in the chain that descended from a key #12 signature is flagged and re-authenticated against the remaining layers. Not deleted — flagged. The chain is preserved. The human decides what downstream actions to unwind.

Implications for the three Laws

  • Third Law + layered auth: the agent distinguishes "this sensor's key is valid but its liveness check failed and its claimed identity died 2 years ago" from "this is the human issuing passepartout stop." Both arrive on the pipe with valid cryptographic signatures. The stacked evidence — sensory, factual, probabilistic — triangulates the threat. The first is rejected with provenance at three layers. The second passes all four.
  • Second Law + layered auth: obedience is about the authenticated identity profile, not just the key that signed the signal. A valid key that probabilistically doesn't match Heather reduces authorization. Obedience follows confidence.
  • First Law + layered auth: harm through sensor compromise becomes detectable when sensory and deterministic layers disagree with the cryptographic layer. A camera key signing frames from an empty room but the deterministic layer placing the key's owner in another city — that's a compromised sensor, and the layered result makes it explicit.

Integration with existing infrastructure

The vault stores key material. The Merkle memory stores key registry facts with content-addressed integrity. The Dispatcher runs gate vector 0 at priority 700 — before type-level checks, before predicate evaluation, before any action proceeds. The fact store records every key operation (creation, promotion, revocation) as a fact with :provenance :key-lifecycle.

No new core ASDF components. The cryptographic sub-layer is Phase 0b (~200 lines). The sensory sub-layer is deferred to a future vision/audio phase. The deterministic sub-layer is Phase 2+ (Screamer + populated fact store). The probabilistic sub-layer extends existing embedding infrastructure with style profiles as a fact-store domain.

Open Questions

Several design questions are unresolved and should remain unresolved at this stage. They represent research decisions that require experience running the system.

What is the minimum viable fact language?

Triples — (:entity :relation :value) with provenance and grounding — is the current hypothesis. It is simple enough to be parseable, expressive enough to capture the gate stack's implicit claims, and extensible enough that Screamer can operate on it. But it may be too simple. Triples do not naturally express temporal relations ("was X before Y?"), modal claims ("should not do X unless Y"), or counterfactuals — all of which may be essential for a symbolically-aided memex. The right granularity depends on what queries actually need to be made, and that cannot be known in advance.

How does ontology refactoring work?

This question is settled. See "Ontology Versioning — How Worldviews Change Without Losing Perspective" above. The category hierarchy is Merkle-hashed. Every fact stores its :ontology-version. Re-verification is heartbeat-driven. Worldviews are preserved, not overwritten. The shift is the artifact.

What is the appropriate role of the human?

The human can explicitly declare facts, write constraints, and correct wrong extractions. But how much of the ontology should the human need to maintain? If the human must write a definition for every new category the symbolic engine encounters, the overhead is prohibitive. If the symbolic engine can generalize from instances, the human role becomes supervision rather than authorship — review and approve proposed generalizations. The balance cannot be set without experience.

How much Wikidata is the right amount?

Query performance and memory costs are now bounded — 5 million entities ≈ 400MB RAM, O(1) hash lookups, domain-scoped Screamer checks. A large Wikidata load is a capital cost, not a recurring bill (see "Performance — Why Ontology Growth Doesn't Make the System Slower" above).

Remaining open: the right N hops from entities referenced in the memex depends on the memex's breadth. A software-engineering memex needs ~1 hop; a literary memex needs 3-4 hops (Nabokov → Kafka → expressionism → modernism → Baudelaire). The right value is empirical, testable, and user-specific — it cannot be set in the architecture.

Can the symbolic engine satisfy queries from the user without LLM involvement?

The design aims for zero-LLM query answering: the user issues a structured command (/query, /contradictions, /audit), and the symbolic engine responds directly. But natural language questions ("what do I think about monorepos?") still require the LLM as a thin translation layer. Whether the structured command interface is sufficient for daily use, or whether users will demand natural language interaction, determines how much LLM involvement remains in the mature system.

Is the triplestore physically bounded or does it explode?

A personal memex with years of diary entries, project notes, reading logs, and literary analyses could produce millions of triples. A naive hash table scales linearly but VivaceGraph's Prolog-like queries may not. The performance characteristics of graph queries over a million-triple knowledge base have not been estimated.

Relation to Passepartout's Existing Architecture

The neurosymbolic engine is an extension of the existing probabilistic-deterministic split, not a replacement for it. The current architecture divides cognition into LLM-driven proposals and Lisp-driven verification. The symbolic engine deepens the verification side from "is this action safe?" to "is this claim supported?" — the same architectural pattern applied to a broader domain.

The self-repair criterion (a file belongs in core only if, when corrupted, the agent cannot fix it without human help) applies to every component of the symbolic engine. Screamer, VivaceGraph, the fact store, the archivist — all are skills, loaded at runtime, hot-reloadable, and recoverable from corruption. A corrupted symbolic engine degrades reasoning capability but does not kill the agent. The eight existing core ASDF files are unchanged.

The symbolic engine is not v3.0.0 alone. It is the layer that sits between the existing gate stack (which it makes explicit as facts) and the existing skill system (which it extends with deduction, contradiction detection, and provenance tracking). It grows within the current architecture without replacing any existing component.

See also:

  • passepartout-neurosymbolic-roadmap.org — the concrete phased implementation plan
  • notes/passepartout-symbolic-engine-exploration.org — the original architecture note
  • notes/passepartout-whitehead.org — the four Whitehead contributions
  • passepartout/docs/DESIGN_DECISIONS.org — the existing design decisions
  • passepartout/docs/ARCHITECTURE.org — the current pipeline architecture
  • passepartout/docs/ROADMAP.org — the feature roadmap through v0.13.0