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
This commit is contained in:
@@ -21,7 +21,7 @@ architecture exploration is in
|
||||
=notes/passepartout-symbolic-engine-exploration.org=. Whitehead's contributions are
|
||||
enumerated in =notes/passepartout-whitehead.org=.
|
||||
|
||||
* Phase 0: PM-Type-Level Gates (~30 lines — builds on existing Dispatcher)
|
||||
* Phase 0: PM-Type-Level Gates + Core Integrity (~75 lines — builds on existing Dispatcher)
|
||||
|
||||
** What
|
||||
|
||||
@@ -52,13 +52,26 @@ syntactically invalid before any logical evaluation.
|
||||
write-file at 3, read-file at 1, shell at 2, eval at 4.
|
||||
4. Assign type levels to existing gate vectors: self-build boundary at 5,
|
||||
shell safety at 3, path protection at 2, network exfil at 2, secret content at 1.
|
||||
5. Add =dispatcher-check-self-termination=: scan shell commands for patterns
|
||||
targeting the Passepartout process (=kill -9 <pid>=, =rm -rf ~/.cache/passepartout/=,
|
||||
=sudo apt remove sbcl=). Return =:reject-self-termination= with a diagnostic
|
||||
message explaining which command matched and why it would destroy the agent.
|
||||
Human override is possible via HITL — the gate does not prevent the human
|
||||
from issuing the command in a terminal. It prevents the /LLM/ from issuing
|
||||
it accidentally. ~20 lines.
|
||||
6. Add =integrity-verify-core-files=: on heartbeat, hash the eight core files
|
||||
against known-good values stored at daemon startup. On mismatch, inject an
|
||||
integrity alert into the signal queue. ~25 lines, uses existing SHA-256
|
||||
infrastructure from v0.2.0 Merkle memory.
|
||||
|
||||
** Verification
|
||||
|
||||
Existing FiveAM gate tests continue to pass. New test: signal at type-level 5
|
||||
targeting a gate at type-level 4 returns =:reject-type-violation= without
|
||||
evaluating the gate predicate. New test: signal at type-level 1 passing through
|
||||
a gate at type-level 3 proceeds to predicate evaluation.
|
||||
a gate at type-level 3 proceeds to predicate evaluation. New test: =kill -9 <pid>=
|
||||
returns =:reject-self-termination=. New test: modified core file is detected by
|
||||
integrity hash check.
|
||||
|
||||
** Relation to Other Work
|
||||
|
||||
@@ -67,6 +80,115 @@ gate-to-fact bootstrap mechanism — every type-level rejection emits a structur
|
||||
event that Phase 1 ingests as a fact. The ~30 lines implement the seed of the
|
||||
ontology without any new dependencies.
|
||||
|
||||
* Phase 0b: Layered Signal Authentication — Layer 1 (Cryptographic) (~200 lines — extends Phase 0)
|
||||
|
||||
** What
|
||||
|
||||
Implement gate vector 0 at priority 700 — before all other gates and before any
|
||||
type-level checking — with Layer 1 (cryptographic authentication) active.
|
||||
Layers 2-4 (sensory, deterministic reasoning, probabilistic) are stubbed with
|
||||
=:unavailable= results and deferred to later phases.
|
||||
|
||||
Signals carry cryptographic signatures verified against a key registry stored
|
||||
as fact-store facts. Automated signal sources cannot impersonate the human. The
|
||||
human can revoke compromised keys. The authorization matrix is per-key, per-action-class.
|
||||
|
||||
** Rationale
|
||||
|
||||
Authentication is layered because no single mechanism suffices. Cryptographic
|
||||
authentication proves key ownership but not identity. A valid key can be used
|
||||
by a compromised process. A valid key can sign pre-recorded frames. A valid key
|
||||
can be held by someone who is not who they claim to be. The four-layer design
|
||||
(Layer 1: crypto, Layer 2: sensory, Layer 3: deterministic reasoning, Layer 4:
|
||||
probabilistic) stacks evidence. Phase 0b ships Layer 1 — the foundation — with
|
||||
the architecture for layers 2-4 already designed.
|
||||
|
||||
The =:source= field in the signal plist is metadata — it /claims/ origin, it
|
||||
does not /prove/ it. This phase replaces it with cryptographic proof.
|
||||
|
||||
** Implementation
|
||||
|
||||
*** Key generation and signature utilities — extends =security-vault.lisp=
|
||||
|
||||
Generate key pairs for signal sources. Canonicalize signal plists (sorted keys,
|
||||
stripped of the signature field). Sign with the source's private key. Verify
|
||||
with the public key from the key registry. ~50 lines. Uses Ironclad (already an
|
||||
ASDF dependency). The vault already stores credential material — key material
|
||||
extends the same storage with the same encryption.
|
||||
|
||||
*** Gate vector 0 — extends =security-dispatcher.lisp=
|
||||
|
||||
Registered at priority 700 (before the policy gate at 600, before all other
|
||||
gates). Architecture for all four layers:
|
||||
|
||||
#+begin_src lisp
|
||||
(defun gate-layered-authentication (signal)
|
||||
(let ((results '()))
|
||||
;; Layer 1: Cryptographic (always available, always runs first)
|
||||
(let ((crypto-result (auth-crypto-verify signal)))
|
||||
(push (cons :crypto crypto-result) results)
|
||||
(when (eq (getf crypto-result :result) :reject)
|
||||
(return-from gate-layered-authentication
|
||||
(list :result :reject :confidence nil
|
||||
:layer-results (nreverse results)))))
|
||||
|
||||
;; Layer 2: Sensory (fboundp-guarded, deferred)
|
||||
(let ((sensory (if (fboundp 'auth-sensory-verify)
|
||||
(auth-sensory-verify signal)
|
||||
'(:result :unavailable))))
|
||||
(push (cons :sensory sensory) results))
|
||||
|
||||
;; Layer 3: Deterministic (fboundp-guarded, deferred to Phase 2+)
|
||||
(let ((det (if (fboundp 'auth-deterministic-verify)
|
||||
(auth-deterministic-verify signal)
|
||||
'(:result :unavailable))))
|
||||
(push (cons :deterministic det) results)
|
||||
(when (eq (getf det :result) :reject)
|
||||
(return-from gate-layered-authentication
|
||||
(list :result :reject :confidence nil
|
||||
:layer-results (nreverse results)))))
|
||||
|
||||
;; Layer 4: Probabilistic (fboundp-guarded, deferred)
|
||||
(let ((prob (if (fboundp 'auth-probabilistic-verify)
|
||||
(auth-probabilistic-verify signal)
|
||||
'(:result :unavailable))))
|
||||
(push (cons :probabilistic prob) results))
|
||||
;; Layer 4 never rejects outright — it downgrades authorization
|
||||
(let ((confidence (aggregate-confidence results)))
|
||||
(list :result :pass :confidence confidence
|
||||
:layer-results (nreverse results))))))
|
||||
#+end_src
|
||||
|
||||
Layer 1: verify cryptographic signature, check permission matrix against key
|
||||
registry, reject on failure. ~50 lines. Layers 2-4: stubbed, return =:unavailable=.
|
||||
|
||||
*** Key registry — facts in the fact store
|
||||
|
||||
Key lifecycle facts are admitted in a =:key-lifecycle= domain with =:singular=
|
||||
cardinality: =(:key-id "#47" :class :sensor :permissions (:observe :propose) :status :active)=.
|
||||
Key creation, promotion, and revocation are facts with Merkle version chains.
|
||||
The human's key signs new keys into existence and signs revocation. ~50 lines.
|
||||
|
||||
*** Signal provenance chain — Merkle-linked causality
|
||||
|
||||
When a signal triggers a downstream signal, each carries a =:sigchain= field
|
||||
with all upstream =(:key-id <k> :signature <s> :auth-result <r>)= entries.
|
||||
Tampering with any link invalidates the leaf. Revocation propagates through
|
||||
the chain — flagged, not deleted. ~50 lines.
|
||||
|
||||
** Verification — ~8 FiveAM tests
|
||||
|
||||
1. =test-sign-verify-roundtrip= — sign and verify a plist roundtrip.
|
||||
2. =test-tampered-signal-rejected= — modify payload after signing, verification fails.
|
||||
3. =test-human-key-permits-write= — human key with =:write= passes Layer 1 and the full gate.
|
||||
4. =test-sensor-key-denied-write= — sensor key proposing a write is rejected.
|
||||
5. =test-revoked-key-rejected= — revoked key is rejected by Layer 1.
|
||||
6. =test-sigchain-invalidated-by-revocation= — root signer revoked flags downstream.
|
||||
7. =test-layers-2-3-4-unavailable= — when Layers 2-4 are not loaded, they return
|
||||
=:unavailable= and the gate proceeds with Layer 1 only.
|
||||
8. =test-layer-3-rejects-on-contradiction= — deterministic reasoning (mock) detects
|
||||
identity-ruling contradiction, gate rejects.
|
||||
|
||||
* Phase 1: Minimum Viable Fact Language (~150 lines — new skill)
|
||||
|
||||
** What
|
||||
@@ -95,6 +217,31 @@ first step. Three reasons:
|
||||
|
||||
** Implementation — =org/symbolic-facts.org= → =lisp/symbolic-facts.lisp= (skill)
|
||||
|
||||
*** Abstract Fact Store Interface — design before implementation
|
||||
|
||||
Before any code is written, the five-function API must be designed and committed:
|
||||
|
||||
#+begin_example
|
||||
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
|
||||
#+end_example
|
||||
|
||||
This interface is load-bearing. Every consumer — the archivist, Screamer, ACL2,
|
||||
the planner — calls these five functions. They never access the backing store
|
||||
directly. In Phase 1-4, the backing store is an ephemeral hash table. In Phase 5,
|
||||
it is VivaceGraph + Merkle =memory-object= wrappers. The interface must be tested
|
||||
against both backends from the start. Every API function receives a FiveAM test
|
||||
that runs against both a hash-table mock and a VivaceGraph mock.
|
||||
|
||||
The interface also exposes a read-only =fact-degraded-mode-p= function. When
|
||||
Screamer is not loaded, the fact store functions with basic hash-table consistency
|
||||
checks (string equality, not constraint solving). When VivaceGraph is not loaded,
|
||||
Prolog queries are unavailable. The degraded-mode flag tells consumers (and the
|
||||
status bar) what is and isn't operational.
|
||||
|
||||
*** Triple store
|
||||
|
||||
A hash table keyed by =(entity relation)=. Values are plists:
|
||||
@@ -104,13 +251,14 @@ A hash table keyed by =(entity relation)=. Values are plists:
|
||||
:grounding <heading-id-or-nil>
|
||||
:provenance <:gate-outcome | :human-authored | :deduced | :llm-proposed>
|
||||
:timestamp <universal-time>
|
||||
:contradiction <:awaiting-resolution-or-nil>
|
||||
:superseded-by <entity-string-or-nil>)
|
||||
:parent-id <hash-of-predecessor>
|
||||
:policy <:singular | :dual | :plural>)
|
||||
#+end_example
|
||||
|
||||
The =:provenance= field tracks how the fact entered the store. The
|
||||
=:contradiction= field is nil on standard facts. The =:superseded-by= field is
|
||||
set when a =:temporal= domain fact is replaced by a newer version.
|
||||
The =:provenance= field tracks how the fact entered the store. The =:parent-id=
|
||||
field links to the previous version in the Merkle chain — every fact has version
|
||||
history regardless of cardinality. The =:policy= field records the cardinality
|
||||
that was active when the fact was admitted.
|
||||
|
||||
*** Bootstrap from gates
|
||||
|
||||
@@ -150,19 +298,29 @@ lookup. Returns the matching triple or nil. ~30 lines.
|
||||
=(fact-query-all &key relation value source-provenance)= — returns all triples
|
||||
matching the filter criteria. Enables "find all files classified as secrets."
|
||||
|
||||
*** Contradiction detection
|
||||
*** Contradiction detection — policy-driven, not policy-agnostic
|
||||
|
||||
On every =fact-assert=, check if the new triple contradicts an existing one
|
||||
(same entity, same relation, different value, same provenance domain). If the
|
||||
entity's class has =:contradiction-policy :exclusive=, the new fact is rejected
|
||||
with a signal. If the policy is =:coexistent=, both facts are stored with a
|
||||
=:contradiction= flag cross-referencing each other. If the policy is =:temporal=,
|
||||
the old fact is marked =:superseded-by= the new one but retained.
|
||||
On every =fact-assert=, the system checks the fact's =entity= class to determine
|
||||
its cardinality policy. Time is universal — every fact carries a =:timestamp= and
|
||||
=:parent-id= link regardless of policy. The policy only governs the active set:
|
||||
|
||||
The policy table is a hash table mapping entity classes to one of =:exclusive=,
|
||||
=:coexistent=, or =:temporal=. Gate-bootstrapped facts default to =:exclusive=
|
||||
(the filesystem is singular). New categories default to =:coexistent= (safe,
|
||||
never loses information).
|
||||
- =:singular=: same =(:entity :relation)=, same value → supersede (chain via
|
||||
=:parent-id=). Same pair, different value at later timestamp → supersede,
|
||||
chain as new leaf. Same pair, different value at same timestamp → contradiction
|
||||
rejected, human resolves which is the active value.
|
||||
- =:dual=: first two values admitted as complementary, cross-referenced via
|
||||
=:complement= edge. Third value → prompt: promote to =:plural= or demote one?
|
||||
Each value has its own version chain via =:parent-id=.
|
||||
- =:plural=: any value admitted. Values cross-referenced when in tension. Each
|
||||
has independent version chain. If active count drops to 1 → collapse to
|
||||
=:singular=. If active count drops to 2 and values are complementary → prompt
|
||||
to collapse to =:dual=.
|
||||
|
||||
The policy table is a hash table mapping entity classes to one of =:singular=,
|
||||
=:dual=, or =:plural=. Gate-bootstrapped facts default to =:singular= (the
|
||||
filesystem is physically singular). New categories default to =:plural= (safe —
|
||||
never loses information). Categories for dialectical or complementary domains
|
||||
are explicitly =:dual=.
|
||||
|
||||
** Verification — ~8 FiveAM tests
|
||||
|
||||
@@ -175,20 +333,120 @@ never loses information).
|
||||
4. =test-fact-query-returns-correct-value= — querying by entity and relation
|
||||
returns the expected value plist.
|
||||
5. =test-duplicate-ingestion-idempotent= — asserting the same fact twice does
|
||||
not produce a duplicate or a contradiction.
|
||||
6. =test-exclusive-contradiction-rejected= — asserting a contradictory fact in
|
||||
an =:exclusive= domain returns a rejection.
|
||||
7. =test-coexistent-contradiction-flagged= — asserting a contradictory fact in a
|
||||
=:coexistent= domain stores both with cross-referencing flags.
|
||||
8. =test-temporal-supersedes= — asserting a newer fact in a =:temporal= domain
|
||||
marks the old fact as superseded but retains it.
|
||||
not produce a duplicate or a contradiction.
|
||||
6. =test-singular-supersedes= — a fact with a later timestamp supersedes the old
|
||||
value, which is retained with =:parent-id= chain in the Merkle DAG.
|
||||
7. =test-singular-same-time-contradiction= — asserting a contradictory fact in a
|
||||
=:singular= domain at the same logical timestamp returns a rejection, requiring
|
||||
human resolution.
|
||||
8. =test-plural-admits-all= — asserting multiple values for the same pair in a
|
||||
=:plural= domain stores all with cross-references.
|
||||
9. =test-dual-admits-two-rejects-third= — a =:dual= domain admits two complementary
|
||||
values and rejects the third, prompting cardinality promotion to =:plural=.
|
||||
|
||||
** Relation to Other Work
|
||||
|
||||
This is Phase 1 of =notes/passepartout-v3.0.0-roadmap.org=. It implements Options 4 and 5
|
||||
from the architecture note. The contradiction policies are from
|
||||
from the architecture note. The cardinality policies are defined in
|
||||
=passepartout-neurosymbolic-design-decisions-and-options.org=.
|
||||
|
||||
* Phase 1a: Self-Preservation Mechanisms (~120 lines — extends Phase 0 and Phase 1)
|
||||
|
||||
** What
|
||||
|
||||
Make self-preservation active rather than architectural. The agent monitors its
|
||||
own integrity, quarantines failing skills, signals degradation to the user, and
|
||||
monitors resource pressure. The external watchdog guards the daemon process from
|
||||
outside the SBCL image.
|
||||
|
||||
** Rationale
|
||||
|
||||
The current architecture has passive self-preservation: the self-build boundary
|
||||
blocks LLM-originated core modifications, memory snapshots enable rollback, and
|
||||
=fboundp= guards catch missing skills. But degradation is silent — a skill dies,
|
||||
the guard fires, and the agent never tells you. The status bar shows green
|
||||
"connected" while the symbolic reasoning layer is down.
|
||||
|
||||
These mechanisms are small (~20-50 lines each), leverage existing infrastructure
|
||||
(Merkle hashes, heartbeat, the dispatcher gate stack), and transform
|
||||
self-preservation from a structural property into an active behavior. They
|
||||
implement the Third Law for Passepartout: preserve yourself against non-human
|
||||
threats — LLM proposals, environmental degradation, resource exhaustion — and
|
||||
signal to the human when you are wounded.
|
||||
|
||||
** Implementation
|
||||
|
||||
*** Quarantine on skill failure — extends =core-skills.lisp=
|
||||
|
||||
Track per-skill error counts in a =*skill-error-counter*= hash table, resetting
|
||||
on each heartbeat cycle. When a skill accumulates three unhandled errors within
|
||||
a single cycle, unload the skill, log the quarantine event, and inject a system
|
||||
message: "Skill 'symbolic-facts' quarantined (3 errors: consistency check nil,
|
||||
fact-query on missing key, Screamer timeout). Reload with /skill-reload
|
||||
symbolic-facts." The skill's =defskill= struct is flagged =:quarantined= and
|
||||
excluded from trigger resolution until explicitly reloaded. ~40 lines.
|
||||
|
||||
*** Degraded-mode signaling — extends =core-reason.lisp= and TUI
|
||||
|
||||
Maintain a =*degraded-components*= list populated by =fboundp= guards and the
|
||||
quarantine system. When =think()= assembles the system prompt, inject a
|
||||
DEGRADATION section: "I am operating in degraded mode. Screamer is unavailable
|
||||
(consistency checks disabled). VivaceGraph is unavailable (Prolog queries
|
||||
disabled). Core safety gates are all active."
|
||||
|
||||
The TUI status bar renders a second line, amber-colored, when =*degraded-components*=
|
||||
is non-empty: "⚠ Degraded: Screamer, VivaceGraph. /doctor skills for details."
|
||||
~30 lines across daemon and TUI.
|
||||
|
||||
*** Resource self-monitoring — extends =symbolic-events.lisp=
|
||||
|
||||
On heartbeat, check memory pressure (=sb-kernel:dynamic-usage= against total),
|
||||
disk space on =~/.cache/= (=uiop:directory-exists-p= + stat), and open file
|
||||
descriptors. When a resource crosses a critical threshold, shed non-essential
|
||||
skills in order of =:preservation-priority= (=:critical= never shed, =:normal=
|
||||
shed after =:low=, =:low= shed first).
|
||||
|
||||
Inject a system message: "Memory critical (94% of 16GB). Unloading
|
||||
embedding-native (768MB), channel-discord, channel-slack. Core safety: unchanged.
|
||||
Essential skills retained: 18." ~50 lines.
|
||||
|
||||
Skill shed order is determined by a new =:preservation-priority= slot on
|
||||
=defskill= (default =:normal=). Core safety skills carry =:critical= and are
|
||||
never shed. Heavy skills (embedding-native with its model in memory, channel
|
||||
gateways with connection pools) carry =:low=.
|
||||
|
||||
*** External watchdog — extends =passepartout= bash entry point
|
||||
|
||||
The bash script spawns a watchdog subprocess that polls the daemon port every
|
||||
=WATCHDOG_TIMEOUT= seconds (default 30). If the port stops responding, the
|
||||
watchdog snapshots the last known-good Merkle root, kills the stale process,
|
||||
and restarts the daemon with =--snapshot <root-hash>=.
|
||||
|
||||
The watchdog is outside the SBCL image. A dead process cannot restart itself.
|
||||
~25 lines of bash, no new Lisp code.
|
||||
|
||||
** Verification — ~6 FiveAM tests
|
||||
|
||||
1. =test-quarantine-on-three-errors= — a skill that errors three times in a
|
||||
single cycle is quarantined and removed from trigger resolution.
|
||||
2. =test-degraded-mode-visible= — when Screamer is not loaded, the system
|
||||
prompt includes a DEGRADATION section.
|
||||
3. =test-resource-shed-low-priority= — when memory exceeds threshold, =:low=
|
||||
priority skills are unloaded first.
|
||||
4. =test-critical-skills-never-shed= — =:critical= priority skills are retained
|
||||
regardless of resource pressure.
|
||||
5. =test-resource-recovery-reloads= — when resources recover below threshold
|
||||
for N consecutive heartbeats, shed skills are reloaded automatically.
|
||||
6. =test-quarantined-skill-relaodable= — a quarantined skill can be reloaded
|
||||
via =/skill-reload= and passes sandbox validation before promotion.
|
||||
|
||||
** Relation to Other Work
|
||||
|
||||
This phase implements the Third Law of self-preservation as described in
|
||||
=passepartout-neurosymbolic-design-decisions-and-options.org=. The integrity
|
||||
monitoring (Phase 0) and degraded-mode signaling (Phase 1) provide the
|
||||
infrastructure this phase extends with active, autonomous behavior.
|
||||
|
||||
* Phase 2: Screamer as Admission Gate (~200 lines — new skill)
|
||||
|
||||
** What
|
||||
@@ -246,29 +504,36 @@ heartbeat or manually. The cost is compute (Screamer exploration), not tokens.
|
||||
*** Admission gate
|
||||
|
||||
=(screamer-admit candidate-fact existing-facts)= — wraps consistency check with
|
||||
the contradiction policy lookup. If the candidate fact's entity class has policy
|
||||
=:exclusive=, contradictions reject. If =:coexistent=, flag. If =:temporal=,
|
||||
supersede.
|
||||
the cardinality policy lookup. The policy is determined by the fact's entity class
|
||||
(see Phase 1: =:singular=, =:dual=, or =:plural=).
|
||||
|
||||
- =:singular=: same value ⇒ supersede (chain via =:parent-id=). Different value,
|
||||
later timestamp ⇒ supersede. Different value, same timestamp ⇒ contradiction
|
||||
rejected (human resolves).
|
||||
- =:dual=: first two values admitted as complementary. Third rejected (prompt
|
||||
cardinality promotion).
|
||||
- =:plural=: any value admitted with cross-references. Active count transitions
|
||||
trigger cardinality collapse checks.
|
||||
|
||||
This is the function the archivist calls before any LLM-proposed fact enters the
|
||||
store. It is also called on human-authored facts (which override the policy —
|
||||
the human can assert contradictory facts in any domain). It is not called on
|
||||
gate-outcome facts (gates are the ground truth for security domains).
|
||||
store. It is also called on human-authored facts (which override — the human can
|
||||
assert facts that bypass cardinality checks). It is not called on gate-outcome
|
||||
facts (gates are the ground truth for security =:singular= domains).
|
||||
|
||||
** Verification — ~6 FiveAM tests
|
||||
|
||||
1. =test-screamer-consistency-passes= — a fact consistent with existing triples
|
||||
returns =:consistent=.
|
||||
2. =test-screamer-contradiction-detected= — "app.env is not secret" contradicts
|
||||
"all *.env files are secrets" and returns =:contradiction=.
|
||||
"all *.env files are secrets" and returns =:contradiction=.
|
||||
3. =test-screamer-redundant-detected= — asserting a fact already implied by
|
||||
existing facts returns =:redundant=.
|
||||
existing facts returns =:redundant=.
|
||||
4. =test-screamer-deduction-produces-new-fact= — given "all *.env files are
|
||||
secrets" and "prod.env matches *.env", Screamer deduces "prod.env is secret."
|
||||
5. =test-admission-gate-rejects-contradiction= — the archivist's proposal that
|
||||
contradicts an =:exclusive= domain fact is rejected.
|
||||
6. =test-admission-gate-flags-coexistent-contradiction= — the archivist's proposal
|
||||
that contradicts a =:coexistent= domain fact is stored with a cross-reference.
|
||||
secrets" and "prod.env matches *.env", Screamer deduces "prod.env is secret."
|
||||
5. =test-admission-gate-singular-supersedes= — a later-timestamped value for a
|
||||
=:singular= domain fact supersedes the old value, chaining via =:parent-id=.
|
||||
6. =test-admission-gate-dual-rejects-third= — a =:dual= domain rejects the third
|
||||
value, prompting =:plural= promotion.
|
||||
|
||||
** Relation to Other Work
|
||||
|
||||
@@ -315,8 +580,8 @@ relations (=:wrote=, =:published-in=, =:influenced=). If the heading is in
|
||||
*** Verify through Screamer
|
||||
|
||||
Each proposed triple runs through =(screamer-admit candidate existing-facts)=
|
||||
from Phase 2. Consistent and coexistent-flagged triples are admitted. Contradictory
|
||||
triples in =:exclusive= domains are discarded with a log entry.
|
||||
from Phase 2. Facts admitted follow the cardinality policy of their entity class
|
||||
(=:singular=, =:dual=, or =:plural=). Rejected facts are discarded with a log entry.
|
||||
|
||||
*** Provenance tracking
|
||||
|
||||
@@ -448,7 +713,7 @@ This is Phase 4 of =notes/passepartout-v3.0.0-roadmap.org=. The flip concept ori
|
||||
=notes/passepartout-symbolic-engine-exploration.org= (lines 68-76) and is refined in
|
||||
=passepartout-neurosymbolic-design-decisions-and-options.org= under "The Flip."
|
||||
|
||||
* Phase 5: VivaceGraph as Persistent Store (~300 lines — new skill)
|
||||
* Phase 5: VivaceGraph + Merkle DAG + Ontology Versioning (~400 lines — new skill)
|
||||
|
||||
** What
|
||||
|
||||
@@ -531,6 +796,41 @@ On daemon start, =(vivacegraph-load)= reads the last saved graph. On heartbeat,
|
||||
=*memory-auto-save-interval*=. The save is atomic: write to a temp file, rename
|
||||
on success. Corruption-safe.
|
||||
|
||||
*** Merkle DAG version chains
|
||||
|
||||
Each =(:entity :relation)= pair forms an independent Merkle chain. Facts hash
|
||||
over =SHA-256(value || provenance || timestamp || parent-hash || grounding)=.
|
||||
The =:parent-id= pointer forms the chain. Tampering with any version breaks all
|
||||
downstream hashes.
|
||||
|
||||
The chains form a DAG, not a single list. Facts about =.env= evolve independently
|
||||
from facts about Nabokov. Inserting a new version is O(1) per chain. =:dual= and
|
||||
=:plural= facts cross-reference via =:complement= and =:contradiction= edges
|
||||
but maintain independent ancestor chains. The Merkle DAG rests on the existing
|
||||
=memory-object= infrastructure from v0.2.0 — the fact store is a new occupant
|
||||
of existing housing. ~50 lines to bridge the fact schema into =memory-object=
|
||||
wrappers.
|
||||
|
||||
*** Ontology versioning
|
||||
|
||||
The category hierarchy itself is a Merkle tree. Every entity class definition
|
||||
hashes over its superclasses, cardinality policy, relations, and description.
|
||||
The aggregate hash of all active class definitions is the =:ontology-version= —
|
||||
a Merkle root of the current worldview.
|
||||
|
||||
Every fact stores its =:ontology-version= at the time of assertion (a single
|
||||
64-hex-char field). When categories change, the new hash flags affected facts
|
||||
for re-verification (Screamer re-evaluates each against the new category
|
||||
definitions). Re-verification outcomes are =:survived= (deduction still holds),
|
||||
=:incoherent= (premises don't translate under new categories, flagged for human
|
||||
review), or =:reclassified= (valid but under different classification).
|
||||
|
||||
Queries accept an optional =:ontology-version= parameter. The default is
|
||||
=:active= (current worldview). Specifying a version returns facts as they were
|
||||
under that worldview: "Under my 2024 security model, this file was a secret.
|
||||
Under my 2025 model, it is an auth-secret." ~40 lines on top of VivaceGraph
|
||||
persistence.
|
||||
|
||||
** Verification — ~5 FiveAM tests
|
||||
|
||||
1. =test-vivacegraph-roundtrip= — save and load preserves all facts with
|
||||
@@ -542,7 +842,13 @@ on success. Corruption-safe.
|
||||
4. =test-type-level-prevents-self-reference= — a query from a type-level-2
|
||||
function cannot return type-level-3 facts.
|
||||
5. =test-fact-store-fallback-without-vivacegraph= — when VivaceGraph is not
|
||||
loaded, the hash-table fallback functions identically to Phase 1-4 behavior.
|
||||
loaded, the hash-table fallback functions identically to Phase 1-4 behavior.
|
||||
6. =test-merkle-chain-tamper-detected= — modifying a fact's value breaks the
|
||||
hash chain, detectable by re-walking the =:parent-id= spine.
|
||||
7. =test-ontology-version-query= — querying with an old =:ontology-version=
|
||||
returns facts as they were under that worldview, not the current one.
|
||||
8. =test-reverification-flags-on-category-change= — changing a category
|
||||
definition sets =:re-verify-status :pending= on all affected facts.
|
||||
|
||||
** Relation to Other Work
|
||||
|
||||
@@ -749,10 +1055,10 @@ is pre-structured. The archivist's job changes from "discover entities" to
|
||||
to it (1-hop neighbors). Optionally expand to 2-hop for deeply connected
|
||||
domains.
|
||||
|
||||
3. *Admit with co-existent policy.* Wikidata facts are admitted with
|
||||
=:provenance :wikidata= and contradiction policy =:coexistent=. They do not
|
||||
override your memex's facts. They sit alongside them. Contradictions are
|
||||
surfaced, not resolved.
|
||||
3. *Admit with plural policy.* Wikidata facts are admitted with
|
||||
=:provenance :wikidata= and cardinality policy =:plural=. They do not
|
||||
override your memex's facts. They sit alongside them with provenance
|
||||
displayed. Disagreements are surfaced, not resolved.
|
||||
|
||||
4. *Cross-domain query.* "What does my memex say about Nabokov that Wikidata
|
||||
doesn't?" "Where does my memex disagree with Wikidata?" "What entities in my
|
||||
@@ -778,35 +1084,65 @@ hop. A memex spanning literature, history, philosophy, and science may need 3-4
|
||||
hops. The query performance and memory costs of a large Wikidata load have not
|
||||
been estimated.
|
||||
|
||||
** Deferred Authentication Layers (Layers 2-4)
|
||||
|
||||
Phase 0b ships Layer 1 (cryptographic). The remaining layers are deferred to
|
||||
when their dependencies are available:
|
||||
|
||||
- *Layer 2 — Sensory.* Active when vision and audio processing skills are loaded.
|
||||
Verifies liveness, cross-modal consistency (face matches voice matches location),
|
||||
and environmental coherence. Defers to Phase TBD (vision/audio skills feature
|
||||
cycle). When unavailable, returns =:unavailable= — graceful degradation.
|
||||
- *Layer 3 — Deterministic Identity Reasoning.* Active when Phase 2 (Screamer +
|
||||
populated fact store) is complete. Queries the fact store for identity-ruling
|
||||
facts ("Jack is deceased; this signal claims to be Jack") and returns binary
|
||||
reject/pass. Defers to Phase 2+.
|
||||
- *Layer 4 — Probabilistic Identity Reasoning.* Active when style profiles exist
|
||||
as a fact-store domain. Uses embedding infrastructure (=embedding-native.lisp=,
|
||||
v0.4.0, already exists) to compare writing style, behavioral patterns, and
|
||||
content coherence against known profiles. Returns a confidence score; never
|
||||
rejects outright — downgrades authorization. Defers to when style profiles are
|
||||
built as a fact-store domain.
|
||||
|
||||
The gate architecture (single vector 0, =fboundp=-guarded sub-layers,
|
||||
configurable per signal class) is designed with all four layers from Phase 0b.
|
||||
Adding a layer requires adding a skill, not modifying the gate.
|
||||
|
||||
* Summary: Lines and Dependencies
|
||||
|
||||
| Phase | Component | Lines | New Skill? | Depends On | Earliest Release |
|
||||
|-------+-------------------------+-------+------------+-----------------+------------------|
|
||||
| 0 | PM-type-level gates | ~30 | No | Dispatcher | Immediately |
|
||||
| 1 | Triple fact store | ~150 | Yes | Phase 0 | v0.7.2+ |
|
||||
| 2 | Screamer admission | ~200 | Yes | Phase 1 | v0.7.2+ |
|
||||
| 3 | Archivist extraction | ~100 | Extends | Phase 2 | v0.8.0+ |
|
||||
| 4 | Flip — sufficiency | ~50 | Extends | Phase 3 | v0.8.0+ |
|
||||
| 5 | VivaceGraph store | ~300 | Yes | Phase 4 | v0.10.0+ |
|
||||
| 6 | ACL2 verification | ~200 | Yes | Phase 5 | v0.12.0+ |
|
||||
| 7 | 10-80-10 planner | ~500 | Yes | Phase 6 | v3.0.0 |
|
||||
| 8+ | Semantic Wikipedia | TBD | Yes | Phase 5 | TBD |
|
||||
|-------+-------------------------+-------+------------+-----------------+------------------|
|
||||
| Total | | ~1530 | | | |
|
||||
| Phase | Component | Lines | New Skill? | Depends On | Earliest Release |
|
||||
|-------+-----------------------------------------+--------+------------+-----------------+------------------|
|
||||
| 0 | PM-type-level gates + core integrity | ~75 | No | Dispatcher | Immediately |
|
||||
| 0b | Layered auth — Layer 1 (cryptographic) | ~200 | No | Phase 0 | v0.7.2+ |
|
||||
| 1 | Triple fact store + abstract API | ~200 | Yes | Phase 0b | v0.7.2+ |
|
||||
| 1a | Self-preservation mechanisms | ~120 | Extends | Phase 0, 1 | v0.7.2+ |
|
||||
| 2 | Screamer admission gate | ~200 | Yes | Phase 1 | v0.7.2+ |
|
||||
| 3 | Archivist as fact proposer | ~100 | Extends | Phase 2 | v0.8.0+ |
|
||||
| 4 | Sufficiency criterion — the flip | ~50 | Extends | Phase 3 | v0.8.0+ |
|
||||
| 5 | VivaceGraph + Merkle DAG + ontology ver | ~400 | Yes | Phase 4 | v0.10.0+ |
|
||||
| 6 | ACL2 structural verification | ~200 | Yes | Phase 5 | v0.12.0+ |
|
||||
| 7 | 10-80-10 planner | ~500 | Yes | Phase 6 | v3.0.0 |
|
||||
| 8+ | Semantic Wikipedia integration | TBD | Yes | Phase 5 | TBD |
|
||||
|-------+-----------------------------------------+--------+------------+-----------------+------------------|
|
||||
| Total | | ~2045 | | | |
|
||||
|
||||
This roadmap is independent of the feature roadmap in
|
||||
=passepartout/docs/ROADMAP.org=. Phase 0 ships alongside any v0.7.x patch. The
|
||||
symbolic engine grows in parallel with feature work (TUI improvements, MCP tools,
|
||||
gateway expansion, etc.), not after it. The feature roadmap describes /what/ the
|
||||
agent can do; this roadmap describes /how/ it knows what it knows.
|
||||
agent can do; this roadmap describes /how/ it knows what it knows and how it
|
||||
protects itself.
|
||||
|
||||
The total new code across all phases is approximately 1,530 lines. Relative to
|
||||
The total new code across all phases is approximately 2,045 lines. Relative to
|
||||
the existing codebase (~8,000+ lines across 40+ Org source files and 30+ skills),
|
||||
the symbolic engine is a ~20% addition. Relative to the ROADMAP's planned feature
|
||||
the symbolic engine is a ~25% addition. Relative to the ROADMAP's planned feature
|
||||
work through v0.13.0 (thousands of lines of TUI rendering, MCP protocol
|
||||
implementation, skin engine, planning, etc.), the symbolic engine is a small,
|
||||
orthogonal thread that grows the architecture's reasoning depth while the feature
|
||||
work grows its interaction breadth.
|
||||
orthogonal thread that grows the architecture's reasoning depth, self-preservation,
|
||||
signal authentication, and knowledge integrity while the feature work grows its
|
||||
interaction breadth.
|
||||
capability, and knowledge integrity while the feature work grows its interaction
|
||||
breadth.
|
||||
|
||||
* Competitive Advantage Analysis
|
||||
|
||||
@@ -818,6 +1154,23 @@ A request to modify the dispatcher's own rules is impossible by construction, no
|
||||
just caught by pattern matching. No competitor has this — their equivalent of
|
||||
"core file protection" is a prompt instruction, not a type system.
|
||||
|
||||
** Phase 0b: Layered signal authentication — verified origin, not claimed origin
|
||||
|
||||
No competitor verifies /who/ issued a signal. Every agent harness accepts signals
|
||||
from any source that speaks its protocol — TUI, CLI, subprocess, internal skill.
|
||||
A compromised dependency can impersonate any signal source. Passepartout's
|
||||
four-layer authentication gate makes signal source spoofing impossible at Layer 1
|
||||
(cryptographic), detectable at Layers 2-3 (sensory + deterministic reasoning),
|
||||
and probabilistically flagged at Layer 4 (style analysis). The key registry is a
|
||||
fact-store domain with Merkle-hashed provenance — key creation, promotion, and
|
||||
revocation are auditable, versioned, and survivable across restarts. The signal
|
||||
provenance chain makes multi-step automated causality traceable: "this deletion
|
||||
happened because sensor #3 signed a classification (liveness check failed, sender
|
||||
deceased 2 years ago) that skill #7 signed a re-indexing from, and sensor #3 was
|
||||
subsequently revoked." No competitor can trace an action to its authenticated
|
||||
source through four independent evidence layers because no competitor has a
|
||||
layered authentication gate and a Merkle-linked provenance chain.
|
||||
|
||||
** Phase 2-3: Verified extraction — the symbolic index grows without corruption
|
||||
|
||||
No competitor verifies extracted facts against an existing knowledge base. Their
|
||||
@@ -851,6 +1204,22 @@ Nabokov wrote /Pale Fire/ and lectured on Kafka. Passepartout with Wikidata
|
||||
loaded knows both, and the entity knowledge costs zero LLM tokens — it is loaded
|
||||
once as structured data and queried via VivaceGraph traversals.
|
||||
|
||||
** Phase 0-1a: Self-preservation — the agent knows when it is wounded
|
||||
|
||||
No competitor detects its own degradation. Claude Code, OpenCode, and Hermes
|
||||
all fail silently when a tool crashes or a dependency is missing — the agent
|
||||
keeps running, producing degraded output, never telling the user. Passepartout's
|
||||
quarantine system detects failing skills, unloads them automatically, and
|
||||
displays a degraded-mode indicator in the status bar. The external watchdog
|
||||
restarts the daemon if the process dies. The integrity monitor detects corrupted
|
||||
core files. The agent refuses to execute commands that would destroy its own
|
||||
runtime, explaining /why/ and redirecting to the safe termination path.
|
||||
|
||||
This is not a feature. It is the architectural difference between a tool that
|
||||
breaks silently and a system that preserves itself actively, signals its wounds,
|
||||
and traces its degradation to specific components that can be reloaded or
|
||||
repaired.
|
||||
|
||||
** The permanent competitive advantage
|
||||
|
||||
The competitive advantage is not any single feature. It is the architecture's
|
||||
|
||||
Reference in New Issue
Block a user