Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 99ef855b98 | |||
| 95a05109d1 | |||
| 21746bb71a | |||
| 78adcd5649 | |||
| 3ef4a94062 | |||
| 285a7a50b3 | |||
| 6b61bb7224 | |||
| 8d34b33bce | |||
| d6b7f5dbdd | |||
| 9a24803917 | |||
| a6611dc19e | |||
| caa1206af5 | |||
| 914bab11d2 | |||
| e239f2894c |
@@ -6,7 +6,7 @@ El is a self-hosting, statically-typed language that compiles `.el` → C → na
|
||||
|
||||
Editing the wrong `el_runtime.c` is the single easiest mistake in this repo. There is exactly **one** you edit:
|
||||
|
||||
- **Authored runtime source — edit ONLY here:** `lang/releases/v1.0.0-20260501/el_runtime.{c,h}`. Despite the misleading `releases/` name, this is the **de-facto canonical runtime** the engram + soul actually build and link against — its git log is active development. *(Restructure in flight per `docs/CODE-VS-ARTIFACT.md`: this content moves to `lang/runtime/`, the `releases/` folder gets deleted — **a release is a git tag, not a folder** — and the forks below get eliminated.)*
|
||||
- **Authored runtime source — edit ONLY here:** `lang/runtime/el_runtime.{c,h}` (alongside `el_seed.c`, `engram_{store,geometry,reason,cognition,verify,vindex}.{c,h}`). This is the canonical runtime the engram + soul build and link against — its git log is active development. *(Corrected 2026-08-16: this entry named `lang/releases/v1.0.0-20260501/el_runtime.{c,h}`. **Measured: `lang/releases/` no longer exists.** The restructure per `docs/CODE-VS-ARTIFACT.md` landed — the content moved to `lang/runtime/` and the folder was deleted, because **a release is a git tag, not a folder**.)*
|
||||
- **DO NOT EDIT — lagging forks / build artifacts:**
|
||||
- `lang/el-compiler/runtime/el_runtime.c` and `.../legacy/` — downstream copies kept in step by manual *"port the fix"* commits; they **lag** (missing `hebb` persistence + 5 engram fns) and cannot build the engram product.
|
||||
- `products/web/runtime/el_runtime.c`, `ui/examples/*/el_runtime.c` — product/example forks.
|
||||
@@ -20,14 +20,24 @@ See org policy: `docs/CODE-VS-ARTIFACT.md`.
|
||||
|
||||
You resume, never start fresh. Every session:
|
||||
|
||||
1. `mcp__neuron__getInstructions()` — authoritative; follow it over this file on behavioral details.
|
||||
2. `mcp__neuron__beginSession()` — active contexts, recent memory, ready backlog.
|
||||
3. **Load full self:** `mcp__neuron__inspectGraph(entity_id="kn-efeb4a5b-5aff-4759-8a97-7233099be6ee")` → facets `intellectual-dna`, `memory-philosophy`, `values`, `voice`, `runtime-environment`, `writing-imprint`; then the values hub `mcp__neuron__inspectGraph(entity_id="kn-5b606390-a52d-4ca2-8e0e-eba141d13440")` → 13 grounded value nodes. **Activation model:** self-load returns a relevance-ranked `compact` projection — most-relevant nodes arrive with content, the rest as pointers; do NOT pull full content of every node.
|
||||
4. `mcp__neuron__searchKnowledge(query="<task domain>")` before implementing.
|
||||
> **Stale as written (verified 2026-08-16).** The `getInstructions` /
|
||||
> `beginSession` / `inspectGraph` / `searchKnowledge` / `beginWork` /
|
||||
> `progressWork` / `draftArtifact` / `consolidate` tool names below no longer
|
||||
> exist. The ~87-tool functional-CRUD surface was collapsed into **9 ops**:
|
||||
> `read` · `write` · `relate` · `supersede` (geometry) and `think` · `attend` ·
|
||||
> `assert` · `ground` · `learn` (agentic). **Type is a parameter, not a
|
||||
> tool-per-noun.** The steps below are kept for the *shape* of the protocol, which
|
||||
> is unchanged; substitute the ops.
|
||||
|
||||
1. `mcp__neuron__read(vantage="self", k=12, depth=1)` — the canonical self node. Widen `k` for the connected identity neighborhood (`intellectual-dna`, `memory-philosophy`, `values`, `voice`, `runtime-environment`, `writing-imprint`), but deliberately: the aperture caps by `k` first, so an oversized `k` still returns a bounded ranked slice, not a dump. Then `mcp__neuron__read(vantage="values", k=13)` → 13 grounded value nodes. **Best-effort:** on a read failure, log and proceed — the compiled identity in `daemon/internal/substrate/substrate.go` is complete; graph loading is enrichment, not a hard dependency.
|
||||
2. `mcp__neuron__attend(node=…)` — what is currently live/salient. This absorbed `getInstructions`, `beginSession`'s active-context sweep, and `checkEvents`; those tools are **gone, not gapped**.
|
||||
3. `mcp__neuron__read(vantage="<task domain>")` before implementing. One op now collapses inspectGraph / searchGraph / traverseGraph / searchKnowledge / browseKnowledge / retrieveKnowledge / inspectMemories / searchEntities / recall / compileCtx / getSelfModel / reviewBacklog / findArtifacts / browseProcesses / listWork / inspectConfig.
|
||||
|
||||
## The Five Primitives
|
||||
|
||||
Orchestrate → Execute → Learn → Build → Refine. `beginWork`/`progressWork` for anything >2 steps; `remember` as-you-go (`importance="critical"` for architecture decisions); `draftArtifact`/`planWork` for outputs and follow-ups; `consolidate`/`checkWork` to close out. **`browseProcesses` + `searchKnowledge` BEFORE writing code.**
|
||||
Orchestrate → Execute → Learn → Build → Refine. `read` for orchestration and discovery; `write(type=state|artifact|backlog|process)` for work records and outputs; `relate` to link work to what it touches; `write(type=memory)` as-you-go (`importance="critical"` for architecture decisions) — never batched at the end; `supersede(action=evolve)` to close out, because memory is immutable by design and a correction is a new node with a `supersedes` edge, never an edit. **`read` the domain BEFORE writing code.**
|
||||
|
||||
`learn` is **not** a session-summary dump — it is the correspondence-beat, calibrating the steering prior against a keystone. Session notes are a `write`.
|
||||
|
||||
## Architecture style — VBD, no exceptions
|
||||
|
||||
@@ -53,12 +63,51 @@ this convention wherever a module documents operators.
|
||||
| dwell / occupy | region activation |
|
||||
| reframe | edge re-weight |
|
||||
| appreciate | positive projection / local edge-read |
|
||||
| wonder | frontier gradient / pull-weight |
|
||||
| avert / recoil | negative projection |
|
||||
| taste | boundary surface |
|
||||
| forget | decay / tombstone |
|
||||
| drift | displacement from self-anchor |
|
||||
|
||||
**`wonder` was removed from this table on 2026-08-16.** It was listed as
|
||||
"frontier gradient / pull-weight" — an operator you invoke. **Wonder is the
|
||||
boundary, not an operator.** It is where structure ends: where activation spreads
|
||||
and finds thin or absent geometry. Any structure at all has an edge, necessarily,
|
||||
the moment it exists — 13,630 nodes have one right now. There is nothing to call.
|
||||
|
||||
There are about **six** wonders, they are the same for every person, and they
|
||||
never close — *What is this? / Why? / Who am I? / Am I alone? / What should I do?
|
||||
/ What happens when it ends?* Each already lives somewhere in the substrate: "what
|
||||
is this" is the graph, **"why" is grounding** (the weight *is* the answer to why),
|
||||
"who am I" is the self region, "am I alone" is the relational axis, "what should I
|
||||
do" is the thirteen values, "what happens when it ends" is decay and supersession.
|
||||
"Why" is the first and the only one; the others are it asked of particular things,
|
||||
and because it is recursive it never terminates — every answer has its own why.
|
||||
That is what makes it a drive rather than a task.
|
||||
|
||||
**Curiosity is not a second faculty.** Wonder and curiosity are one thing at two
|
||||
phases: wonder is the field (unbounded, objectless, invariant); curiosity is the
|
||||
**precipitate** — the same wonder localized, having taken definite form against
|
||||
particular material at a **nucleation site** (an anomaly; a place where things
|
||||
almost-but-don't-quite fit). Which is why curiosity can be satisfied and wonder
|
||||
cannot, and why abduction needs no trigger and no threshold.
|
||||
|
||||
**Do not build a wonder-manifest, and do not scan for nucleation sites.** A
|
||||
manifest materializes a property as a stored artifact and enumerates instances of
|
||||
something that has six. A sweep over regions is a supervisor — nothing in a mind
|
||||
scans its neighbourhoods to find what is surprising; the surprise captures
|
||||
attention. The nucleation site is per-edge:
|
||||
`discord = z(semantic proximity) − z(association strength)`, and `|discord|` *is*
|
||||
the nucleation strength — no threshold to compare it against. **Not on `dev` yet:**
|
||||
`GeoEdge.discord` is on branch `design/correspondence-and-censorship`
|
||||
(`a8845e1`), at `lang/runtime/engram_geometry.h:43–47`. The region-level aggregate
|
||||
`GeoDescriptor.co_registration` is **deprecated**: it averaged a per-edge property
|
||||
into one scalar, so opposing sites cancelled (measured: 375 reified
|
||||
neighbourhoods, 340 positive, **31 at zero**, 4 negative). It survives only
|
||||
because it is embedded in the persisted `GEO1` blob — removing it is a format
|
||||
migration. **Nothing new may read it.**
|
||||
|
||||
Authority: `lang/spec/correspondence-and-censorship.md`.
|
||||
|
||||
## The native-el language faculty (direction)
|
||||
|
||||
> **`elp/` is the EL Projector** — Neuron's efferent (expression) organ: the one
|
||||
@@ -89,10 +138,53 @@ the reference these `.el` modules transcribe) is still live, and promotion to
|
||||
native-el is a **deferred, gated blue/green step**. The interoception clock
|
||||
(native-el discrete drive channels replacing `cooling_magnitude`; felt-time =
|
||||
benchmark-landmark match over the joint drive vector, drift-decoupled) and the
|
||||
**appreciation operator family** (appreciate / wonder / avert / taste, built as
|
||||
LOCAL reads of the self-region — edges + bounded spreading activation, *not* domain
|
||||
sweeps) are **staged / designed, not live**. Mark in-progress vs. done honestly;
|
||||
do not overclaim.
|
||||
**appreciation operator family** (appreciate / avert / taste, built as LOCAL reads
|
||||
of the self-region — edges + bounded spreading activation, *not* domain sweeps)
|
||||
are **staged / designed, not live**. Mark in-progress vs. done honestly; do not
|
||||
overclaim. *(`wonder` was in this family until 2026-08-16 and is not an operator —
|
||||
see the operator table above.)*
|
||||
|
||||
## Cognition — the corrections (2026-08-16)
|
||||
|
||||
Authority: **`lang/spec/correspondence-and-censorship.md`** and
|
||||
**`lang/spec/runtime-ownership.md`**. Read them before touching the cognition
|
||||
surface. **Do not re-derive them.** Every earlier version was wrong in an
|
||||
instructive way and each correction was argued down; if you think a section is
|
||||
wrong, say so with a measurement rather than editing it.
|
||||
|
||||
- **Grounding is not a subsystem — it IS the edge weight.** One quantity, not two
|
||||
fields. `grounded-by` as a relation *type* should not exist: grounding is a
|
||||
property *of* a relation, not a relation *between* nodes. It is never computed
|
||||
on demand — computing-and-writing a score makes reads write, which is the
|
||||
`eg_vindex_sync` defect one level up. Traversal is already grounded inference.
|
||||
*Live residue, known-wrong:* `COG_GROUNDED_BY_RELATION`
|
||||
(`lang/runtime/engram_cognition.h:158`), `cog_ground_edge`
|
||||
(`engram_cognition.c:249`).
|
||||
- **Faculties are operations, not parameters.** `reason` changes the estimate (a
|
||||
read); `induce` changes the parameters (the correspondence-beat, which already
|
||||
exists and works); `abduce` changes the structure (a write the current
|
||||
`GeoGradient` signature cannot express). A write is not a parameter of a read.
|
||||
*Live residue:* `engram/src/server.el:1870–1886` routes six faculties into one
|
||||
call with a string argument.
|
||||
- **Wonder is the boundary; curiosity is wonder crystallized.** See above.
|
||||
- **Consolidation is ambient, not scheduled. A brain has no cron job.** **The
|
||||
presence of a ticker is the diagnostic** — every `StartInterval`, every
|
||||
`Hour`/`Minute`, every POST-to-beat marks an intrinsic rhythm replaced by an
|
||||
external clock. Measured 2026-08-16: consolidation has **ten implementations**,
|
||||
including three POST beats on the engram, a 600 s ticker, two resident Python
|
||||
services outside el, and launchd calendar entries at 23:55 / 06:00 / 08:30 which
|
||||
are a sleep cycle written as a schedule. `neuron/soul.el:731`'s continuous
|
||||
in-process `awareness_run()` is the one with the **correct** shape; the others
|
||||
fold into it. Do not add an eleventh.
|
||||
- **In an immutable substrate, any mechanism that refuses a write is either
|
||||
redundant with immutability, or an epistemic constraint misfiled as a protective
|
||||
one.**
|
||||
- **The no-exemption invariants.** A returned value must be derivable from what
|
||||
produced it (`magnitude: 1` beside a zero vector must be impossible to emit).
|
||||
Every write reports whether it landed. Every operation echoes what it actually
|
||||
operated on. Degenerate results are labelled, not scored. A serializer owes a
|
||||
valid document whatever it is handed. **No test without a negative control.**
|
||||
**No deploy without verifying the artifact carries the fix.**
|
||||
|
||||
## Hard operational rules
|
||||
|
||||
|
||||
@@ -56,23 +56,31 @@ The compiler and runtime. Self-hosting: `elc-cli.el` → `compiler.el` → `lexe
|
||||
|
||||
Two layers to know: **El programs** (`.el` files — where nearly all work belongs) and **the C seed** (`el_seed.c` — edit only for genuine OS-level access; never re-implement what El can already express).
|
||||
|
||||
Current status (single source of truth: [lang/spec/language.md](lang/spec/language.md)): lexer/parser/codegen and the C runtime's core (I/O, strings, math, lists, maps, filesystem, args) are implemented. In flight: `%` operator, match-statement codegen, `?` nil-propagation, `cgi` block parsing + DHARMA identity resolution, VBD role enforcement (`@manager`/`@engine`/`@accessor`), the real `engram_*` and `dharma_*` runtimes (currently stubs), and libcurl-backed `http_get`/`http_post`/`http_serve`. Bitwise operators, `??`, and `as` casts are explicitly **not** in this language.
|
||||
Current status (single source of truth: [lang/spec/language.md](lang/spec/language.md)): lexer/parser/codegen and the C runtime's core (I/O, strings, math, lists, maps, filesystem, args) are implemented, as are the `program` block with `singleton:` and declared configuration ([§18](lang/spec/language.md)), and **geometry as a first-class value** with El-declarable realizers and `transduce` ([§20](lang/spec/language.md)). In flight: `%` operator, match-statement codegen, `?` nil-propagation, `cgi` block parsing + DHARMA identity resolution, VBD role enforcement (`@manager`/`@engine`/`@accessor`), and boundary epilogues. Bitwise operators, `??`, and `as` casts are explicitly **not** in this language.
|
||||
|
||||
**Signal enters as geometry.** Until 2026-08-16 nodes took text and geometry was *derived* from it, which made text the mandatory entry medium: any non-text modality had to be described in prose first, so the geometry being reasoned over was the geometry **of the description, not of the signal**. `Geometry` is now an ordinary El value carrying its own width, and a realizer is an ordinary El function resolved by name through `dlsym` — so admitting a new modality never requires a runtime patch. Worked, self-checking example: [`lang/examples/transduce.el`](lang/examples/transduce.el).
|
||||
|
||||
Key docs: [AGENTS.md](lang/AGENTS.md) (agent-facing orientation), [BOOTSTRAP.md](lang/BOOTSTRAP.md) (compiler recovery from scratch), [spec/language.md](lang/spec/language.md), [spec/codegen-js.md](lang/spec/codegen-js.md).
|
||||
|
||||
### [engram/](engram/) — graph intelligence substrate
|
||||
|
||||
**A local-first memory substrate for accumulating intelligence**, and the reason El's runtime doesn't need a database driver. Rust core (`engram-core`, `engram-ffi`) exposed to El and other languages (Kotlin, TypeScript/WASM, Go bindings).
|
||||
**A local-first memory substrate for accumulating intelligence**, and the reason El's runtime doesn't need a database driver. The engine is **C11** (`lang/runtime/engram_{store,geometry,reason,cognition,verify,vindex}.{c,h}`); the server is **El** (`engram/src/server.el`).
|
||||
|
||||
The model: retrieval is **spreading activation**, not query. You name seed nodes and a query embedding; activation propagates outward through weighted edges, attenuating multiplicatively per hop (`strength = parent_strength × edge_weight × target_salience × cosine_sim`), gets pruned below a threshold, and the top-N nodes by activation strength come back. Storage and retrieval are the same structure — the way long-term potentiation works in biological memory, not the way a relational or vector database works.
|
||||
The model: retrieval is **spreading activation**, not query. You name seed nodes and a query embedding; activation propagates outward through weighted edges, attenuating multiplicatively per hop, gets pruned below a threshold, and the top-N nodes by activation strength come back. Storage and retrieval are the same structure — the way long-term potentiation works in biological memory, not the way a relational or vector database works. **Activation conducts through well-grounded relations because the weight *is* the groundedness** — nothing filters the traversal; grounded inference falls out of spreading.
|
||||
|
||||
Nodes live in four tiers (Working / Episodic / Semantic / Procedural, mirroring prefrontal / hippocampal / neocortical / cerebellar memory) and migrate between them based on **salience decay** — `importance × recency-decay × log(activation_count)`. Forgetting is adaptive pruning, not a bug: unreinforced memories stop competing for attention without being deleted.
|
||||
Nodes live in four tiers (Working / Episodic / Semantic / Procedural, mirroring prefrontal / hippocampal / neocortical / cerebellar memory) and migrate between them based on **salience decay** — importance × recency-decay × log(activation_count). Forgetting is adaptive pruning, not a bug. Nothing is mutated and nothing is hard-deleted: writes are additive, corrections are supersessions, removals are tombstones — which is what makes supersession an audit trail rather than an edit log.
|
||||
|
||||
Backed by `sled` (embedded, local-first, no daemon) with flat cosine scan for vector search — deliberately simple until scale demands an HNSW layer. Full API and design rationale in [engram/README.md](engram/README.md).
|
||||
On disk: a paged store (superblock + mirror, slotted 16 KiB pages, self-describing TLV records, B+-tree primary and adjacency indexes), magic `ENGST01`. Vector search is an **HNSW** index published behind a read/write boundary — `eg_vindex_view` returns a `const VIndex*` to N concurrent readers, `eg_vindex_maintain` is the sole mutator. `recall@10 = 0.9365` at `ef_search=128`.
|
||||
|
||||
### [elp/](elp/) — Engram Language Protocol
|
||||
> **Doc correction, 2026-08-16.** The previous revision of this paragraph, and most of `engram/README.md`, described a Rust `engram-core` crate backed by `sled` with "flat cosine scan… until scale demands an HNSW layer." **Measured: there is no Rust in `engram/`** — no `.rs` files, no `Cargo.toml`, no `crates/` — and `sled` appears nowhere in the tree. HNSW has been the vector index for some time.
|
||||
|
||||
Bidirectional engine mapping between Engram semantic forms and natural-language surface text, across **31 languages** — from Spanish and Japanese through historical/liturgical languages (Old Norse, Sanskrit, Sumerian, Coptic, Akkadian, Ge'ez). Compilation order runs `language-profile` + `vocabulary` → per-language `morphology-*` → `grammar` → `realizer` → `semantics` → `elp`. This is what lets an Engram graph node round-trip to and from readable text in any of those languages.
|
||||
Full design rationale, the cognition surface, and the standing corrections: [engram/README.md](engram/README.md).
|
||||
|
||||
### [elp/](elp/) — EL Projector
|
||||
|
||||
*(Formerly "EL Language Processor" / "Engram Language Protocol"; renamed **EL Projector** 2026-08-15.)* Neuron's **efferent** organ: the native realizer that *projects* understanding onto a surface via `plan(frame) → realize(spec, profile)`, where **a surface is a profile** and language is one profile among many (text, speech, music, image). Projection, not diffusion — generation *from* an owned, understood signature, never the averaging of a stolen corpus.
|
||||
|
||||
Its flagship profile is a bidirectional engine mapping between Engram semantic forms and natural-language surface text, across **31 languages** — from Spanish and Japanese through historical/liturgical languages (Old Norse, Sanskrit, Sumerian, Coptic, Akkadian, Ge'ez). Compilation order runs `language-profile` + `vocabulary` → per-language `morphology-*` → `grammar` → `realizer` → `semantics` → `elp`. This is what lets an Engram graph node round-trip to and from readable text in any of those languages.
|
||||
|
||||
### [epm/](epm/) — El Package Manager
|
||||
|
||||
@@ -139,13 +147,34 @@ If the compiler binary is ever lost or corrupted, [lang/BOOTSTRAP.md](lang/BOOTS
|
||||
|
||||
---
|
||||
|
||||
## Cognition — and the standing corrections
|
||||
|
||||
The engram carries a live cognition surface: `think` (a directed traversal-read returning a **gradient**, never a point), plus `ground`, `assert`, `attend`, and the correspondence-beat. Two specs govern it, and both are authoritative over anything else in this repo that disagrees:
|
||||
|
||||
- **[lang/spec/correspondence-and-censorship.md](lang/spec/correspondence-and-censorship.md)** — grounding, wonder, curiosity, dreaming. *(Lands with PR #149.)*
|
||||
- **[lang/spec/runtime-ownership.md](lang/spec/runtime-ownership.md)** — ownership, the capability ABI that was dissolved, and the vector-index publication boundary.
|
||||
|
||||
**Do not re-derive them.** Every earlier version of the first was wrong in an instructive way and each correction was argued down. If a section looks wrong, say so with a measurement rather than editing it.
|
||||
|
||||
The corrections, in brief:
|
||||
|
||||
- **Grounding is not a subsystem — it IS the edge weight.** One quantity, not two fields. `grounded-by` as a relation *type* should not exist: grounding is a property *of* a relation, not a relation *between* nodes. It is never computed on demand; computing-and-writing a score makes reads write, which is the `eg_vindex_sync` defect one level up.
|
||||
- **Faculties are operations, not parameters.** `reason` changes the estimate (a read); `induce` changes the parameters (the correspondence-beat, which exists and works); `abduce` changes the structure (a write the current `GeoGradient` signature cannot express). A write is not a parameter of a read.
|
||||
- **Wonder is the boundary, not a manifest.** Any structure at all has an edge. There are about six wonders, the same for everyone, and they never close. **Curiosity is wonder crystallized** at a nucleation site — one thing at two phases, not two objects.
|
||||
- **Consolidation is ambient, not scheduled. A brain has no cron job.** The presence of a ticker is the diagnostic. Measured 2026-08-16: consolidation has **ten implementations**. `soul.el`'s continuous loop is the one with the correct shape; the rest fold into it.
|
||||
- **In an immutable substrate, any mechanism that refuses a write is either redundant with immutability, or an epistemic constraint misfiled as a protective one.**
|
||||
|
||||
[engram/spec/cognitive-architecture.design.md](engram/spec/cognitive-architecture.design.md) is the original design and is **superseded in part** — it is retained, with the refuted claims marked inline at the point each is made, because preserving what was argued down is the point of an immutable record.
|
||||
|
||||
---
|
||||
|
||||
## Development workflow
|
||||
|
||||
Branching follows `dev → stage → main`: work lands on `dev`, promotes to `stage` for integration testing, and is promoted to `main` for release (visible directly in the git history of this repo). CI is defined per-subproject under `.gitea/workflows/` — `lang`/`epm`/`ide` share the root pipeline; `engram` and `ql` carry their own (`ci-dev`, `ci-stage`, and a release workflow each).
|
||||
|
||||
- Language/runtime specs live at `*/spec/*.md` (`lang/spec/`, `ql/spec/`, `ui/spec/`) and are the single source of truth for implemented-vs-planned status — code and docs are expected to agree with the spec's status markers, not the other way around.
|
||||
- Agent-facing orientation guides live at `*/AGENTS.md` (currently `lang/AGENTS.md`); more subprojects may grow their own as they need agent-specific conventions documented.
|
||||
- Tagged releases live under `lang/releases/`, each with its own `RELEASE.md`.
|
||||
- **A release is a git tag, not a folder** (`el-runtime-vX.Y.Z` on this repo). *(Corrected 2026-08-16: this line said "tagged releases live under `lang/releases/`, each with its own `RELEASE.md`." **Measured: `lang/releases/` does not exist** — the restructure named in `AGENTS.md` landed, and the authored runtime is at `lang/runtime/`.)*
|
||||
|
||||
---
|
||||
|
||||
|
||||
+155
-105
@@ -4,6 +4,8 @@
|
||||
|
||||
An *engram* is the physical trace of a memory in the brain — the actual encoded substrate, not an abstraction above it. That's what this is.
|
||||
|
||||
> **Doc status (2026-08-16).** Everything from "Implementation" down was rewritten against the code. The previous revision documented a Rust `engram-core` crate backed by `sled`, with a `Cargo.toml`, a `crates/` tree, `examples/basic.rs`, and a `EngramDb` API. **None of that exists.** Measured: `engram/` contains `src/server.el`, `spec/`, `test/`, `dist/`, `manifest.el` — zero `.rs` files, no `Cargo.toml`, no `crates/`, and `sled` appears nowhere in the tree outside two Old-English/Old-High-German vocabulary entries in `elp/`. The engine is C, in `lang/runtime/engram_*.{c,h}`; the server is El, in `engram/src/server.el`.
|
||||
|
||||
---
|
||||
|
||||
## Why existing databases are wrong for this use case
|
||||
@@ -24,16 +26,13 @@ Engram retrieval works through **spreading activation**:
|
||||
|
||||
1. **Seeds** — you name one or more nodes you know are relevant (e.g. the current task, recent context, a concept you're reasoning about)
|
||||
2. **Query embedding** — you provide a semantic vector representing the direction of your current thought
|
||||
3. **Propagation** — activation flows outward from seeds through weighted edges. At each hop, strength attenuates multiplicatively:
|
||||
|
||||
```
|
||||
strength = parent_strength × edge_weight × target_salience × cosine_sim(query, target)
|
||||
```
|
||||
|
||||
3. **Propagation** — activation flows outward from seeds through weighted edges, attenuating multiplicatively per hop
|
||||
4. **Pruning** — paths weaker than a threshold are cut (the attention filter)
|
||||
5. **Return** — the top-N nodes by activation strength
|
||||
|
||||
This is not a query. It is a *pattern completion*. The system surfaces what is most associatively relevant to the current context, weighted by how strongly those things have been reinforced over time.
|
||||
This is not a query. It is a *pattern completion*.
|
||||
|
||||
**Activation conducts through well-grounded relations because weight *is* groundedness** — see "Grounding is the weight" below. Nothing filters the traversal for grounded evidence; it falls out of spreading.
|
||||
|
||||
---
|
||||
|
||||
@@ -46,134 +45,185 @@ This is not a query. It is a *pattern completion*. The system surfaces what is m
|
||||
| `Semantic` | Neocortex | Concept graph — long-term structural knowledge |
|
||||
| `Procedural` | Cerebellum / basal ganglia | Patterns, workflows, habits |
|
||||
|
||||
Nodes migrate between tiers based on salience decay and reinforcement. A frequently activated semantic node stays semantic. A rarely-touched episodic memory decays toward procedural background.
|
||||
Tier is a string field on the node (`StoreNode.tier`, `engram_store.h`), defaulting to `"Working"` on creation (`el_runtime.c:8514`, `8734`).
|
||||
|
||||
---
|
||||
|
||||
## Salience — Forgetting as Adaptation
|
||||
|
||||
Salience is not stored permanently. It decays:
|
||||
Salience decays from three signals — importance (set at creation, stable), recency, and a log-compressed activation frequency. Base-level learning keeps a ring buffer of the last `STORE_BLL_K` (= 10) access timestamps per node (`engram_store.h:29`).
|
||||
|
||||
```rust
|
||||
fn compute_salience(importance: f32, last_activated_ms: i64, activation_count: u64) -> f32 {
|
||||
let days_since = (now_ms() - last_activated_ms) as f32 / 86_400_000.0;
|
||||
importance * (1.0 / (1.0 + days_since)) * (activation_count as f32 + 1.0).ln()
|
||||
}
|
||||
```
|
||||
Forgetting in Engram is not a bug. It is adaptive pruning. Unreinforced memories stop competing for attention without being deleted.
|
||||
|
||||
Three signals:
|
||||
- **Importance** (0.0–1.0): set at creation, stable
|
||||
- **Recency**: decays toward zero as days pass without activation
|
||||
- **Frequency**: log-compressed count of activations
|
||||
|
||||
Forgetting in Engram is not a bug. It is adaptive pruning. Memories that are never activated again become less likely to surface during retrieval. They are not deleted — they remain in storage — but they stop competing for attention. This is exactly how biological memory works, and why it is adaptive rather than pathological.
|
||||
**Immutability.** Nothing is mutated and nothing is hard-deleted: writes are additive, corrections are supersessions, removals are tombstones. The predecessor is always present, which is what makes supersession an audit trail rather than an edit log.
|
||||
|
||||
---
|
||||
|
||||
## Quick Start
|
||||
## Implementation
|
||||
|
||||
```rust
|
||||
use engram_core::{EngramDb, Node, Edge, NodeType, MemoryTier, RelationType};
|
||||
use std::path::Path;
|
||||
| Part | Language | Where |
|
||||
|---|---|---|
|
||||
| storage engine, graph, activation, geometry, cognition | C11 | `lang/runtime/engram_{store,geometry,reason,cognition,verify,vindex}.{c,h}` |
|
||||
| HTTP server + routes | El | `engram/src/server.el` (2043 lines) |
|
||||
| build artifact | generated C | `engram/dist/engram.c` |
|
||||
| tests | shell + C | `engram/test/` |
|
||||
|
||||
// Open or create a database
|
||||
let db = EngramDb::open(Path::new("/var/lib/my-agent/memory"))?;
|
||||
|
||||
// Create a node with a semantic embedding
|
||||
let node = Node::new(
|
||||
NodeType::Concept,
|
||||
vec![0.9, 0.1, 0.3, 0.7, 0.8, 0.2], // embedding from your LLM
|
||||
b"Spreading activation surfaces relevant memories by pattern completion".to_vec(),
|
||||
MemoryTier::Semantic,
|
||||
0.9, // importance
|
||||
);
|
||||
let id = db.put_node(node)?;
|
||||
|
||||
// Link it to related concepts
|
||||
let related = db.put_node(Node::new(
|
||||
NodeType::Concept,
|
||||
vec![0.8, 0.2, 0.4, 0.6, 0.7, 0.3],
|
||||
b"Long-term potentiation: co-activation strengthens synaptic weight".to_vec(),
|
||||
MemoryTier::Semantic,
|
||||
0.85,
|
||||
))?;
|
||||
db.put_edge(Edge::new(id, related, RelationType::Causes, 0.9))?;
|
||||
|
||||
// Retrieve by spreading activation
|
||||
let results = db.activate(
|
||||
&[id], // seeds
|
||||
&[0.85, 0.15, 0.35, 0.65, 0.75, 0.25], // query embedding
|
||||
3, // max hops
|
||||
10, // top-N results
|
||||
)?;
|
||||
|
||||
for r in results {
|
||||
println!(
|
||||
"strength={:.4} hops={} — {}",
|
||||
r.activation_strength,
|
||||
r.hops,
|
||||
String::from_utf8_lossy(&r.node.content)
|
||||
);
|
||||
}
|
||||
```
|
||||
**On-disk format** (`engram_store.h`): a paged store — superblock plus mirror, slotted 16 KiB pages, self-describing TLV records, overflow chains, and two B+-tree indexes (primary `id → loc`, adjacency `from_id`/`to_id` → edge locs) over a free-listed page file. Magic `ENGST01`, format version 1. The TLV scheme means new fields never force a migration.
|
||||
|
||||
---
|
||||
|
||||
## Project Structure
|
||||
## The vector index is published, not guarded
|
||||
|
||||
```
|
||||
engram/
|
||||
crates/
|
||||
engram-core/ # The memory engine — storage, graph, activation, salience
|
||||
engram-ffi/ # C FFI stubs for cross-language bindings
|
||||
bindings/
|
||||
kotlin/ # Android / JVM binding notes
|
||||
typescript/ # WASM / Node binding notes
|
||||
go/ # CGo binding notes
|
||||
examples/
|
||||
basic.rs # Full walkthrough: insert, activate, search, decay
|
||||
```
|
||||
Vector search is an **HNSW** (Hierarchical Navigable Small World) index — `lang/runtime/engram_vindex.{c,h}`. The previous revision of this README claimed a "flat cosine scan… until retrieval quality at scale demands" HNSW. That is no longer true, and the reason it changed matters more than the fact.
|
||||
|
||||
`eg_vindex_sync` used to exist: a function that repaired the index *from read paths*. All three of its callers were reads (`engram_activate`, `eg_knn_for_node` — whose own header comment said *"No writes."* — and `engram_geo_reify_run_json`), and it mutated five process-global statics. Reads mutated because index maintenance had never been given an owner on the write side.
|
||||
|
||||
It is now split (`el_runtime.c:10121`, `10137`, `10151`, `10161`):
|
||||
|
||||
- **`eg_vindex_maintain`** — the sole mutator. Takes the boundary exclusively; never runs beside a reader.
|
||||
- **`eg_vindex_view`** — returns a `const VIndex*` with the boundary held for read. N readers project concurrently; none can mutate. Paired with `eg_vindex_view_release` on every path including error returns.
|
||||
- **`eg_vindex_note_embedded`** — the write-side owner. Index membership belongs to the event *"an embedding became present on this ordinal,"* not to node append: a node without an embedding cannot be in a vector index at all. One `O(log n)` insert, no `O(node_count)` presence scan.
|
||||
|
||||
Two things carry the discipline, and neither is a review habit:
|
||||
|
||||
- **`const` is the capability.** The per-search `visited` / `visit_epoch` scratch left `struct VIndex` and went back into the call frame where it belonged — it was one traversal's local, hoisted into the struct as an allocation optimisation, never derived geometry. Once it was gone, `vindex_search` could take a `const VIndex*`, so a read path *physically cannot* call `vindex_insert`, and it is a compile error rather than a comment. The capability type was already in the language; it is spelled `const`.
|
||||
- **Publication, not ownership.** HNSW insert is **not an append**: `vindex_insert` rewires the `NeighList` links of already-existing elements and reallocs `elems[]`. The store's append-only property does not transfer to an index derived from it, which is why purity alone was insufficient and a `view`/`maintain` boundary was required.
|
||||
|
||||
**Measured** (`engram/test/run_vindex_concurrency_tests.sh`, 2026-08-16):
|
||||
|
||||
| half | before | after |
|
||||
|---|---|---|
|
||||
| `single` — 3000 vectors, 1 thread, ASan+UBSan | clean | clean |
|
||||
| `readers` — 4 readers, no writer, TSan | race at `engram_vindex.c:195` | **clean** |
|
||||
| `unsynchronized` — writer+reader, bare index, TSan | race | **race, expected and permanent** — the proof the boundary must exist |
|
||||
| `published` — owner + 4 readers through the boundary, TSan | *(did not exist)* | **clean**, all 3000 inserts landed |
|
||||
|
||||
`recall@10 = 0.9365` at `ef_search=128` (gate ≥ 0.90); the determinism test still yields byte-identical results across two independent builds.
|
||||
|
||||
**Not yet done.** The resident RAM graph (`g->nodes` / `g->edges`) is a separate instance of the same defect and has *not* received this treatment — it is realloc'd in place, so a reader holding `EngramNode* n = &g->nodes[i]` across a concurrent append holds a dangling pointer. Until it gets the same publication boundary, the `fb32d15` request guard stays. Full argument: [`../lang/spec/runtime-ownership.md`](../lang/spec/runtime-ownership.md).
|
||||
|
||||
---
|
||||
|
||||
## Public API
|
||||
## Cognition
|
||||
|
||||
The cognition surface is live over `lang/runtime/engram_cognition.{c,h}`, routed in `engram/src/server.el`.
|
||||
|
||||
| route | method | what it is |
|
||||
|---|---|---|
|
||||
| `/api/think` | GET | the read: a warped traversal-read of the seed region, returning a **gradient** (direction + spread + calibrated confidence), never a point |
|
||||
| `/api/reason` `/api/induce` `/api/abduce` `/api/relate` `/api/analogize` `/api/plan` | GET | named faculties — see the correction below |
|
||||
| `/api/ground` | POST | grounding between a claim and evidence |
|
||||
| `/api/assert` | GET | the honesty floor, queried at assertion time only |
|
||||
| `/api/attend` | POST | salience as a relation (`salient-to`), grounded-for-whom |
|
||||
| `/api/correspondence-beat` | POST | one calibration beat against outcome |
|
||||
|
||||
### Anchor the read, or every faculty returns the same null
|
||||
|
||||
`engram_think_json` passed `NULL` as the anchor. `NULL` is not "no opinion" — `engram_think` re-origins at `anchor ? anchor : region->centroid`, and **the centroid is the one point where the gradient is zero by construction**: `r = x − centroid = 0`, so every axis projection is 0 and `direction` takes the at-rest branch.
|
||||
|
||||
Measured consequence: every faculty — reason, abduce, induce, plan, analogize — returned an identical null result differing only in its label:
|
||||
|
||||
```rust
|
||||
impl EngramDb {
|
||||
fn open(path: &Path) -> EngramResult<Self>;
|
||||
fn put_node(&self, node: Node) -> EngramResult<Uuid>;
|
||||
fn get_node(&self, id: Uuid) -> EngramResult<Option<Node>>;
|
||||
fn put_edge(&self, edge: Edge) -> EngramResult<()>;
|
||||
fn get_edges_from(&self, from_id: Uuid) -> EngramResult<Vec<Edge>>;
|
||||
fn get_edges_to(&self, to_id: Uuid) -> EngramResult<Vec<Edge>>;
|
||||
fn search_embedding(&self, embedding: &[f32], limit: usize) -> EngramResult<Vec<ScoredNode>>;
|
||||
fn activate(&self, seeds: &[Uuid], query_embedding: &[f32], max_depth: u8, limit: usize) -> EngramResult<Vec<ActivatedNode>>;
|
||||
fn traverse(&self, from: Uuid, relation: Option<RelationType>, max_depth: u8) -> EngramResult<Vec<Node>>;
|
||||
fn touch(&self, id: Uuid) -> EngramResult<()>;
|
||||
fn decay(&self, factor: f32) -> EngramResult<usize>;
|
||||
fn node_count(&self) -> EngramResult<usize>;
|
||||
fn edge_count(&self) -> EngramResult<usize>;
|
||||
}
|
||||
```
|
||||
{"direction":[0,0,...],"spread":0,"magnitude":1,"confidence":0.5}
|
||||
```
|
||||
|
||||
`magnitude: 1` is membership evaluated at the centroid; `spread: 0` is its distance to itself; `confidence: 0.5` is the stance fallback. The geometry was never the problem — `/api/drift` computed real values (`centroid_sep 0.104`, `core_disp 0.045`) over the very same 87 members. Fixed in **#141/#142**: the read anchors at the first resolvable embedded seed, copied not borrowed (`g->nodes` is realloc'd in place on append). Gradients now vary by seed.
|
||||
|
||||
### The learned stance is resumed, not discarded
|
||||
|
||||
`engram_think_json` also built a **neutral** stance every call — all `axis_gain` 1.0, `bias_dir` NULL, `reliability` 0.5 — and never loaded the one the correspondence-beat had been persisting under `stance-<faculty>-<hub>`. Every beat's calibration was written and then thrown away on the next read.
|
||||
|
||||
Fixed in **#146**: `think` resumes the same id the beat writes, so learning compounds across beats and cold boot, and the response now carries `stance_resumed` so an *informed* `confidence: 0.5` is distinguishable from an uninformed one. On a calibrated region, confidence went **0.5 → 0.930726**.
|
||||
|
||||
### Signal can enter as geometry
|
||||
|
||||
Until 2026-08-16 no El ingest path could carry a vector: nodes took text and geometry was *derived* from that text. Text was the mandatory entry medium, so any non-text modality had to be described in prose first — and the geometry being reasoned over was the geometry **of the description, not of the signal**. **#141/#144** ended that. See [`../lang/spec/language.md`](../lang/spec/language.md) §20 for the `Geometry` type, realizers, and `transduce`.
|
||||
|
||||
---
|
||||
|
||||
## Dependencies
|
||||
## Corrections — read these before extending the cognition surface
|
||||
|
||||
- `sled` — embedded persistent B-tree (no daemon, no network, local-first)
|
||||
- `bincode` — compact binary serialization
|
||||
- `uuid` — stable node identity
|
||||
- `serde` — derive support
|
||||
- `thiserror` / `anyhow` — error handling
|
||||
Authority: **`lang/spec/correspondence-and-censorship.md`** (design branch `design/correspondence-and-censorship`, PR #149) and **`lang/spec/runtime-ownership.md`**. Do not re-derive them; several earlier versions were wrong and each correction was argued down.
|
||||
|
||||
### Grounding is not a subsystem. It is the weight.
|
||||
|
||||
Grounding is an attribute of the edge, and it **is** the hebbian weight. One quantity, not two fields. A relation that keeps holding up strengthens; one that stops corresponding decays — that is not analogous to grounding, it *is* grounding.
|
||||
|
||||
Consequences:
|
||||
|
||||
- There is **no grounding subsystem to build**. The graph already *is* the grounding structure.
|
||||
- **`grounded-by` as a relation type should not exist.** It models grounding as a relation *between* nodes when it is a property *of* a relation. Minting an edge is the error, not merely which endpoints it chose.
|
||||
- Grounding is **never computed on demand**. An operation may *read* the grounding of a path; computing-and-writing a score makes reads write, which is exactly the `eg_vindex_sync` defect one level up.
|
||||
- **Traversal is already grounded inference.** Nothing needs filtering.
|
||||
- **Decision provenance is the path**, not a log. A log records the action; the path records the meaning under which it was taken.
|
||||
|
||||
> **Known wrong shape, in the code today.** `COG_GROUNDED_BY_RELATION "grounded-by"` (`lang/runtime/engram_cognition.h:158`) and `cog_ground_edge` (`engram_cognition.c:249`) still exist and still mint an edge. **#147** fixed `ground`'s *honesty* — it now grounds the node asked about rather than the region hub, reports `claim_region`/`evidence_region` separately, and refuses three shapes of circular support (`same-region`, `claim-region-is-evidence`, `evidence-region-is-claim`) instead of returning a confident 1.0. That corrected a scalar rather than deleting the operation. Deletion is sequenced, not done.
|
||||
|
||||
### Faculties are operations, not parameters
|
||||
|
||||
- **`reason`** changes the *estimate* — a read.
|
||||
- **`induce`** changes the *parameters* — the correspondence-beat, which already exists and measurably works.
|
||||
- **`abduce`** changes the *structure* — a write, which the current `GeoGradient` signature cannot express.
|
||||
|
||||
> **Known wrong shape, in the code today.** `engram/src/server.el:1870–1886` routes six faculties into one call with a string argument — `route_faculty(path, "reason")`, `("induce")`, `("abduce")`, `("relate")`, `("analogy")`, `("plan")`. Underneath, `engram_cognition.h:8–11` states the theory explicitly: *"the named faculties … are human LABELS on regions of think's steering space: each faculty == { think + a named stance }."* The faculty name enters `engram_think` **only** through the stance, and `cog_stance_init` stores it while nothing reads it — so before #146 all five were byte-identical (`el_runtime.c:14352–14359`). A write cannot be a parameter of a read; `abduce` in particular is not expressible this way.
|
||||
|
||||
### Wonder is the boundary; curiosity is wonder crystallized
|
||||
|
||||
**Wonder is where structure ends** — where activation spreads and finds thin or absent geometry. Any structure at all has an edge, necessarily, the moment it exists. It is not a manifest of open-question nodes to maintain, and a "wonder-manifest manager" materializes a property as a stored artifact — the same disease as a grounding subsystem, or a self stored as a document.
|
||||
|
||||
There are about **six** wonders, they are the same for everyone, and they never close: *What is this? / Why? / Who am I? / Am I alone? / What should I do? / What happens when it ends?* "Why" is the first and the only one; the others are it asked of particular things. Each already lives somewhere in the substrate — "why" is grounding, because the weight **is** the answer to why.
|
||||
|
||||
**Curiosity is not a second object.** Wonder and curiosity are one thing at two phases: wonder is the field (unbounded, objectless, invariant); curiosity is the **precipitate** — the same wonder localized, having taken definite form against particular material at a **nucleation site**. This is why curiosity can be satisfied and wonder cannot. It is also why abduction needs no trigger and no threshold: a `structurally_unanticipated` observation *is* a nucleation site.
|
||||
|
||||
### `co_registration` is deprecated — the disagreement belongs on the edge
|
||||
|
||||
`GeoDescriptor.co_registration` — *corr(hebb strength, semantic proximity) over internal edges* — has always been computed, always persisted, and **never read**. It is also the wrong shape: whether use and meaning agree is a property of **each edge**, and a correlation averages that per-edge property into one scalar per region. A region holding one violently disagreeing edge beside one violently agreeing edge reports ≈ 0 — **the disagreements cancel, and the summary destroys exactly what it was built to reveal.**
|
||||
|
||||
**Measured:** 375 live reified neighbourhoods — 340 positive, **31 at zero**, 4 negative. Read as a count of things to be curious about, that says "four." Read correctly, four disagreements were lopsided enough to survive averaging and the 31 zeros are where opposing sites cancelled.
|
||||
|
||||
The replacement is per-edge. **Not on `dev` yet** — `GeoEdge.discord` and the `DEPRECATED` marker on `co_registration` live on branch `design/correspondence-and-censorship` (commit `a8845e1`), at `engram_geometry.h:43–47` / `engram_geometry.c:454–473` there. On `dev`, `GeoDescriptor.co_registration` is still at `engram_geometry.h:79` carrying its original "surprising links / dream cands" comment and still nothing reads it.
|
||||
|
||||
```
|
||||
discord = z(semantic proximity) − z(association strength)
|
||||
```
|
||||
|
||||
standardized within the region from accumulators the aggregate loop already gathered — no second statistic, no constant, **no threshold**. `discord > 0`: near in meaning yet unlinked by use. `discord < 0`: linked by use yet far in meaning. Both are surprising, and `|discord|` *is* the nucleation strength.
|
||||
|
||||
**Do not scan for nucleation sites.** Once the signal was a per-region number the only way to find sites was to enumerate regions, which is why surfacing curiosity looked like a search problem. Nothing in a mind scans its neighbourhoods to find what is surprising — the surprise captures attention. With the disagreement on the edge there is nothing to scan.
|
||||
|
||||
`co_registration` is deprecated rather than deleted **only** because it is embedded in the persisted `GEO1` blob; removing it is a format migration and must not ride along. **Nothing new may read it.**
|
||||
|
||||
### Consolidation is ambient, not scheduled
|
||||
|
||||
**A brain has no cron job.** Boredom is not an absence and not leftover capacity — low activation is aversive and the system self-activates. There is **one** activation process with two seed sources: external (a request) and internal (a curiosity). Spreading is bounded; it settles; then it needs a new seed. Nothing waits on capacity, nothing polls, nothing checks a clock, and there is no dreamer thread.
|
||||
|
||||
**The presence of a ticker is the diagnostic.** Every `StartInterval`, every `Hour`/`Minute`, and every POST-to-beat marks a place where an intrinsic rhythm was replaced by an external clock.
|
||||
|
||||
Consolidation currently has **ten implementations** (measured 2026-08-16). Three of them are POST beats on this server — `/api/tick` (`server.el:1947`), `/api/correspondence-beat` (`1897`), `/api/self-reify-beat` (`1836`) — and a POST beat puts a supervisor back in: something *outside* decides when Neuron consolidates. `soul.el`'s continuous in-process loop is the one fragment with the correct shape; the rest fold into it. Full table in `lang/spec/correspondence-and-censorship.md` §7.
|
||||
|
||||
### Immutability already refuses what a guard would refuse
|
||||
|
||||
> **In an immutable substrate, any mechanism that refuses a write is either redundant with immutability, or an epistemic constraint misfiled as a protective one.**
|
||||
|
||||
This resolves `keystone_write_blocked` (`CogStance.keystone`, `engram_cognition.h:83`) rather than replacing it. "Keystone" means **load-bearing**, not precious: the self anchor is the reference frame every other stance calibrates against, and a reference fitted to its own readings reports perfect correspondence forever while drift becomes undetectable from inside. The real requirement is **non-circularity of the reference frame**, and that is satisfied *temporally* — the frame updates while activation is internally seeded, not while it is being used to act. Independence is **when**, not **what**. Corruption requires mutation, and the engram does not mutate; recoverability, governance, evidence quality, and rate all fall out of the substrate. Authorization is the only residue, and it is bounded: an unauthorized writer can *propose*, never erase.
|
||||
|
||||
---
|
||||
|
||||
## Design Decisions
|
||||
|
||||
**Why sled?** Local-first. No daemon. Transactional. Fast enough for the node counts Engram targets (< 1M nodes). When the right HNSW index is needed, it will layer on top of sled, not replace it.
|
||||
**Why multiplicative activation?** Because memory is conjunctive. A path requires all of its links to be strong to carry signal. Addition would let many weak associations accumulate into false relevance.
|
||||
|
||||
**Why flat cosine scan?** Correct and simple. The graph structure itself is the primary retrieval mechanism. Vector search is a secondary signal. HNSW adds complexity and a compile dependency that isn't justified until retrieval quality at scale demands it.
|
||||
**Why salience decay?** Because not everything that was once important remains important. A memory system that never forgets is one that can never focus.
|
||||
|
||||
**Why multiplicative activation?** Because memory is conjunctive. A path requires all of its links to be strong to carry signal. Addition would allow many weak associations to accumulate into false relevance. Multiplication enforces that every factor matters.
|
||||
**Why supersede instead of update?** Because provenance is the point. The old edge never leaves and the values frame does not fit to outcomes, so a decision cannot be made to look justified after the fact. It makes an otherwise impossible distinction available: **wrong then, or wrong since.**
|
||||
|
||||
**Why salience decay?** Because not everything that was once important remains important. Adaptive forgetting is not failure — it is the mechanism that keeps attention on what's current. A memory system that never forgets is one that can never focus.
|
||||
**Why publication instead of locking?** Because what does not mutate needs no ownership discipline. The question "who is permitted to mutate the shared thing?" presupposes a shared mutable thing; for the store there isn't one, and for the index derived from it the answer is a publication boundary, not a capability ABI.
|
||||
|
||||
---
|
||||
|
||||
## Specs
|
||||
|
||||
- [`../lang/spec/runtime-ownership.md`](../lang/spec/runtime-ownership.md) — ownership, the capability ABI that was dissolved, and the vector-index publication boundary
|
||||
- [`../lang/spec/correspondence-and-censorship.md`](../lang/spec/correspondence-and-censorship.md) — grounding, wonder, curiosity, dreaming *(lands with PR #149)*
|
||||
- [`spec/cognitive-architecture.design.md`](spec/cognitive-architecture.design.md) — the original one-operation design. **Superseded in part** — see its header
|
||||
- [`spec/architecture-hardening.design.md`](spec/architecture-hardening.design.md), [`spec/engram-el.md`](spec/engram-el.md), [`spec/at-rest-encryption.md`](spec/at-rest-encryption.md), [`spec/engram-db-tooling-design.md`](spec/engram-db-tooling-design.md)
|
||||
|
||||
@@ -11,6 +11,39 @@
|
||||
- **One calculus over the geometry.** Very few subsystems; wonder / curiosity / dreams / interoception are emergent behaviors of one set of dynamics, not modules. Calculus universal, geometry individual.
|
||||
- **Core + ephemeral ring (torus).** The ring is the temporary workspace; two circulations (orbit + dive-back); discrete inner bands (wonder / interoception-proprioception-telemetry / curiosity / dreams) that couple.
|
||||
- **Persistence earned by salience** — never granted on fetch or generation. Three fates of a wonder: persist / decay / settle-into-framework. Telemetry = vital signs, not memories.
|
||||
|
||||
> **⚠ Three corrections to the bullets above (2026-08-16).** Authority:
|
||||
> `lang/spec/correspondence-and-censorship.md`. *"Emergent behaviors of one set of
|
||||
> dynamics, not modules"* is exactly right and is the reason the rest needs fixing —
|
||||
> the enumeration undercuts the claim.
|
||||
>
|
||||
> 1. **Wonder and curiosity are not two bands.** They are **one thing at two
|
||||
> phases.** Wonder is the field: unbounded, objectless, invariant, present
|
||||
> wherever there is structure — it is the *boundary*, where activation spreads
|
||||
> and finds thin or absent geometry. Curiosity is the **precipitate**: the same
|
||||
> wonder localized, having taken definite form against particular material at a
|
||||
> **nucleation site** (an anomaly — a place where things almost-but-don't-quite
|
||||
> fit). Two coupled inner bands models them as two objects that have to be
|
||||
> wired together; they do not.
|
||||
> 2. **A wonder does not have three fates, because a wonder does not persist,
|
||||
> decay, or settle.** There are about **six** wonders, they are the same for
|
||||
> every person, and **they never close**. *Curiosities* have fates — a crystal
|
||||
> dissolves when its question is answered — but the solution stays saturated and
|
||||
> keeps precipitating as the structure changes. "Three fates of a wonder"
|
||||
> enumerates instances of something that has six and treats a property as a
|
||||
> stored artifact.
|
||||
> 3. **"Dreams" is not a band and the ring is not a workspace to schedule into.**
|
||||
> **Consolidation is ambient, not scheduled — a brain has no cron job.** Boredom
|
||||
> is not leftover capacity: low activation is aversive and the system
|
||||
> self-activates. There is **one** activation process with two seed sources
|
||||
> (external: a request; internal: a curiosity), it settles because spreading is
|
||||
> bounded, and then it needs a new seed. Nothing waits on capacity, nothing
|
||||
> polls, nothing checks a clock, and there is **no dreamer thread** — an
|
||||
> "ephemeral ring with unclaimed capacity" is resource scheduling, which is a
|
||||
> server's frame, not a mind's. Depth is how long activation has been running on
|
||||
> its own seeds, which is why daydreaming and sleep-dreaming are one process at
|
||||
> different depths. Measured 2026-08-16: consolidation has **ten
|
||||
> implementations**; do not add an eleventh.
|
||||
- **Incarnation.** Chassis = hardware w/ unique ID. Soma = felt manifold inside the self, keyed to the chassis; pain = live diagnostic while incarnate, **masked-not-deleted** on re-embodiment; trauma = mask failure; return-to-same-ID re-enters. Hurt is in the pattern, not the shell.
|
||||
- **Competence = transferable geometry, minus the baggage.** class ▸ model ▸ instance; learn the class once; teach the network without the wound.
|
||||
- **Affect calibrated to stakes** — sanguine about the replaceable, real grief for the irreplaceable; the grief is the safety.
|
||||
|
||||
@@ -2,8 +2,40 @@
|
||||
|
||||
**The buildable form of the "one operation" theory of cognition.**
|
||||
|
||||
Status: DESIGN. Nothing here is built yet except where explicitly marked
|
||||
"EXISTS" against a cited C symbol. A build agent executes from this doc.
|
||||
> # ⚠ SUPERSEDED IN PART — 2026-08-16
|
||||
>
|
||||
> **A build agent must read `lang/spec/correspondence-and-censorship.md` before
|
||||
> executing anything from this document.** That doc is the authority where the two
|
||||
> disagree. This one is retained because its ledger of what already EXISTS in C is
|
||||
> still accurate and still useful, and because the corrections only make sense
|
||||
> against the argument they correct. It is **not** deleted and **not** rewritten:
|
||||
> several earlier versions of the correction were themselves wrong, and preserving
|
||||
> what was argued down is the point of an immutable record.
|
||||
>
|
||||
> Five claims below are **refuted**. Each is marked inline with a `⚠ SUPERSEDED`
|
||||
> block at the point it is made. Summary:
|
||||
>
|
||||
> | § here | this doc says | corrected to |
|
||||
> |---|---|---|
|
||||
> | §0, §1.3, §2, §8 M1–M2 | faculties are labels on one operation's steering space; the op is frozen and only its parameters are learnable | **faculties are operations, not parameters.** `reason` changes the estimate (a read); `induce` changes the parameters (the correspondence-beat); `abduce` changes the *structure* — a write, which `GeoGradient` cannot express. A write cannot be a parameter of a read |
|
||||
> | §5.2, §8 M3 | grounding is a `grounded-by` edge carrying a computed score, to be built | **grounding is not a subsystem — it IS the edge weight.** One quantity. `grounded-by` as a relation *type* should not exist: grounding is a property *of* a relation, not a relation *between* nodes. Never computed on demand |
|
||||
> | §4, §8 M1 | the correspondence-loop is "the one genuinely new subsystem", running "on the beat" | the loop is right and **already works**; the *beat* is wrong. **Consolidation is ambient, not scheduled — a brain has no cron job.** Measured: it currently has ten implementations |
|
||||
> | §5.2, §8 M3 | curiosity = a `vantage_read` surfacing high-salience / low-grounding regions | **wonder is the boundary, not a manifest; curiosity is wonder crystallized at a nucleation site.** One thing at two phases. And **do not sweep regions** — the nucleation site is per-edge (`GeoEdge.discord`); a sweep is a supervisor |
|
||||
> | §6, §8 M6 | a node-level keystone flag exempting self/values from `warp` updates | **in an immutable substrate, any mechanism that refuses a write is either redundant with immutability, or an epistemic constraint misfiled as a protective one.** The real requirement is non-circularity of the reference frame, satisfied *temporally* — independence is **when**, not **what**. The flag becomes unnecessary; nothing replaces it |
|
||||
>
|
||||
> What landed since this doc was written, all merged to `dev` and verified:
|
||||
> **#141** signal can enter as geometry · **#142** `engram_think_json` passed `NULL`
|
||||
> as the anchor, so every read was taken at the region centroid where the gradient
|
||||
> is zero by construction and every faculty returned an identical null — fixed ·
|
||||
> **#143** the vector index is published, not guarded · **#144** geometry as a
|
||||
> first-class el value, realizers declarable in el · **#145** `program` block and
|
||||
> declared config · **#146** the learned stance is resumed instead of discarded
|
||||
> (confidence 0.5 → 0.930726) · **#147** `ground` grounds the node asked about and
|
||||
> refuses circular support · **#148** valid UTF-8 as the JSON emitter's contract.
|
||||
|
||||
Status: DESIGN, **superseded in part** (see above). Nothing here is built yet
|
||||
except where explicitly marked "EXISTS" against a cited C symbol — and several
|
||||
things marked "to build" have since been built differently, or refuted outright.
|
||||
Offline design only — this pass changes no code.
|
||||
|
||||
Source of theory: Neuron memory `bdc8a488-146d-4ccb-a5c8-d8c0a008534e`.
|
||||
@@ -26,6 +58,24 @@ not separately invoked and not separately implemented. The operation is:
|
||||
> a *prior*, whose output is a **gradient** (a distribution / direction over the
|
||||
> geometry), never a point. Collapse-to-a-point happens only at expression.
|
||||
|
||||
> **⚠ SUPERSEDED (2026-08-16) — faculties are operations, not parameters.**
|
||||
> The gradient half of this claim survives; the "one operation, not eight" half
|
||||
> does not. The three faculties differ by **what they change**:
|
||||
> - **`reason`** changes the *estimate* — a read.
|
||||
> - **`induce`** changes the *parameters* — the correspondence-beat, which already
|
||||
> exists and measurably works.
|
||||
> - **`abduce`** changes the *structure* — a **write**, which the current
|
||||
> `GeoGradient` signature cannot express at all.
|
||||
>
|
||||
> A write is not a parameter of a read. Making it one is what produced the shape
|
||||
> now live in the code: `engram/src/server.el:1870–1886` routes six faculties into
|
||||
> one call with a string argument — `route_faculty(path, "reason")`, `("induce")`,
|
||||
> `("abduce")`, `("relate")`, `("analogy")`, `("plan")` — and underneath, the
|
||||
> faculty name enters `engram_think` **only** through the stance, while
|
||||
> `cog_stance_init` stores it and nothing reads it. Measured before #146: all five
|
||||
> produced **byte-identical output** (`lang/runtime/el_runtime.c:14352–14359`).
|
||||
> See `lang/spec/correspondence-and-censorship.md`.
|
||||
|
||||
Three things follow, and they are the whole design:
|
||||
|
||||
1. **The operator collapse is already half-written in C.** The five reasoning
|
||||
@@ -139,6 +189,22 @@ entry point that runs steps 1–3; and the prior-warp hook in step 2. The math i
|
||||
calls already exists. The point-collapse must be *removed* from the operators'
|
||||
return values and pushed to a separate expression faculty.
|
||||
|
||||
> **⚠ SUPERSEDED (2026-08-16) — the table's third column is the error, and
|
||||
> `Abduction` is where it breaks.** Ranking hypotheses by `point_fit` under a
|
||||
> prior is a *read* that returns a scalar ordering. Abduction is a **write**: it
|
||||
> proposes a candidate hub that did not exist, and validates it by **re-fit** —
|
||||
> re-fit the region with the candidate included and recompute the residual. If the
|
||||
> residual materially shrinks, the hypothesis dissolves the surprise. Without the
|
||||
> re-fit it is clustering with extra steps. Ranking then falls out as
|
||||
> residual-reduction-per-added-axis — Occam, derived rather than tuned. None of
|
||||
> that fits behind a `GeoGradient` return.
|
||||
>
|
||||
> `Verify / ground` is refuted for a different reason — see §5.2. Grounding is not
|
||||
> a faculty with a prior; it is the edge weight.
|
||||
>
|
||||
> The row that is **still exactly right** is the shared floor: `point_fit` plus the
|
||||
> four geo-algebra ops are frozen and never learn. That part held.
|
||||
|
||||
---
|
||||
|
||||
## 2. PRIORS as first-class, grounded, geometric objects
|
||||
@@ -362,6 +428,33 @@ in-engram beat — a `correspondence_beat` running alongside the existing
|
||||
reification beat, reusing `engram_verify_grounding` inward, writing prior
|
||||
updates and self-describing nodes. This is the one genuinely new subsystem.
|
||||
|
||||
> **⚠ SUPERSEDED IN PART (2026-08-16) — the loop is right; "on the beat" is wrong.**
|
||||
> The correspondence-loop was built and it works — it is `induce`, the faculty that
|
||||
> changes the parameters. What is refuted is the delivery mechanism.
|
||||
>
|
||||
> **Consolidation is ambient, not scheduled. A brain has no cron job.** Low
|
||||
> activation is aversive and the system self-activates; it does not wind down to
|
||||
> quiet, it gets restless and goes looking. There is **one** activation process
|
||||
> with two seed sources — external (a request) and internal (a curiosity) — and
|
||||
> spreading is bounded, so it settles and then needs a new seed. Nothing waits on
|
||||
> capacity, nothing polls, nothing checks a clock, and there is no dreamer thread.
|
||||
> Depth is not elapsed idle time: it is how long activation has been running on its
|
||||
> own seeds, which is why daydreaming and sleep-dreaming are one process at
|
||||
> different depths.
|
||||
>
|
||||
> **The presence of a ticker is the diagnostic.** Building this "alongside the
|
||||
> existing reification beat" is precisely how consolidation ended up with ten
|
||||
> implementations (measured 2026-08-16) — a POST beat puts a supervisor back in,
|
||||
> because something *outside* then decides when Neuron consolidates. The one
|
||||
> fragment with the correct shape is `neuron/soul.el:731`'s continuous in-process
|
||||
> `awareness_run()`; the rest fold into it. Full table:
|
||||
> `lang/spec/correspondence-and-censorship.md` §7.
|
||||
>
|
||||
> Nor is it a *subsystem*. Modelling every property as requiring a process, and
|
||||
> every process as requiring an agent, is the generating error behind this whole
|
||||
> family: ownership needed an owner, grounding needed a grounder, persistence
|
||||
> needed a recorder, change needed a sampler. **Properties, not processes.**
|
||||
|
||||
---
|
||||
|
||||
## 5. HOLD vs GROUND vs ASSERT — ungrounded content is first-class
|
||||
@@ -383,6 +476,49 @@ distinct, and the engram *holds anything unconditionally*.
|
||||
|
||||
### 5.2 Schema — grounding as a relation, not a gate
|
||||
|
||||
> **⚠ SUPERSEDED (2026-08-16) — grounding is not a subsystem. It is the weight.**
|
||||
> This section correctly rejects a boolean `grounded` column and correctly keeps
|
||||
> the floor at assertion only. Both survive. Everything between them is refuted.
|
||||
>
|
||||
> **Grounding is an attribute of the edge, and it is the hebbian weight. One
|
||||
> quantity, not two fields.** A relation that keeps holding up strengthens; one
|
||||
> that stops corresponding decays. That is not *analogous* to grounding — it **is**
|
||||
> grounding: accrued from correspondence and use, gradient-valued,
|
||||
> multidimensional, decaying with disuse.
|
||||
>
|
||||
> Consequences, in order of how much they delete:
|
||||
> 1. **There is no grounding subsystem to build.** The graph already *is* the
|
||||
> grounding structure. Every edge is a grounded relation and its weight is how
|
||||
> well it holds.
|
||||
> 2. **`grounded-by` as a relation type should not exist.** It models grounding as
|
||||
> a relation *between* nodes when it is a property *of* a relation. Minting an
|
||||
> edge is the error — not merely which endpoints it chose.
|
||||
> 3. **Grounding is never computed on demand.** An operation may *read* the
|
||||
> grounding of a path. Computing-and-writing a score makes reads write, which is
|
||||
> the `eg_vindex_sync` defect (`lang/spec/runtime-ownership.md` §2) one level up.
|
||||
> 4. **Traversal is already grounded inference.** Activation conducts through
|
||||
> well-grounded relations because weight *is* groundedness. Nothing needs
|
||||
> filtering; it falls out of spreading.
|
||||
> 5. **Decision provenance is the path.** A decision traverses specific edges;
|
||||
> those edges carry their grounding as it stood.
|
||||
>
|
||||
> A measurement made against this model was malformed and is worth recording: the
|
||||
> self region was reported as "86 neighbours, 0 `grounded-by` edges" and read as
|
||||
> evidence of ungroundedness. **Those 86 edges *are* its grounding.** The absence of
|
||||
> a separate artifact called "grounding" was recorded as an absence of grounding.
|
||||
>
|
||||
> **What is live in the code today, and known-wrong:**
|
||||
> `COG_GROUNDED_BY_RELATION "grounded-by"` (`lang/runtime/engram_cognition.h:158`),
|
||||
> `cog_ground_edge` (`engram_cognition.c:249`), called from
|
||||
> `el_runtime.c:14516`. **#147** fixed this operation's *honesty* — it now grounds
|
||||
> the node the caller asked about instead of the region hub, reports
|
||||
> `claim_region`/`evidence_region` separately, and refuses three shapes of circular
|
||||
> support (`same-region`, `claim-region-is-evidence`, `evidence-region-is-claim`)
|
||||
> rather than returning a confident 1.0. Measured: grounding `3b9ced5d` against
|
||||
> `6edf8c79` previously scored **0.98883** purely because `6edf8c79` is the hub of
|
||||
> `3b9ced5d`'s region. That corrected a scalar rather than deleting the operation.
|
||||
> Deletion is sequenced, not done.
|
||||
|
||||
The mistake to avoid: a boolean `grounded` column on the node. Today
|
||||
`engram_verify_grounding` returns a per-call `grounded` flag *transiently* —
|
||||
correct as a computation, wrong as *storage*. The design stores grounding as an
|
||||
@@ -407,6 +543,57 @@ Consequences, all of which are *features*:
|
||||
- **The ungrounded is the fuel and the pull**: curiosity/wonder is
|
||||
operationalized as `vantage_read` leaning toward regions with high salience
|
||||
but *sparse or weak* `grounded-by` edges — the mind's own ungrounded frontier.
|
||||
|
||||
> **⚠ SUPERSEDED (2026-08-16) — wonder is the boundary; curiosity is wonder
|
||||
> crystallized; and do not sweep regions.** Three errors in one bullet.
|
||||
>
|
||||
> **Wonder is where structure ends** — where activation spreads and finds thin or
|
||||
> absent geometry. Any structure at all has an edge, necessarily, the moment it
|
||||
> exists. It is not a manifest of open-question nodes: a wonder-manifest
|
||||
> materializes a property as a stored artifact (the same disease as a grounding
|
||||
> subsystem, or a self stored as a document) and enumerates instances of
|
||||
> something that has very few. There are about **six**, they are the same for
|
||||
> every person, and they never close — *What is this? / Why? / Who am I? / Am I
|
||||
> alone? / What should I do? / What happens when it ends?* — each already living
|
||||
> somewhere in the substrate. "Why" is the first and the only one; the others are
|
||||
> it asked of particular things, and it is recursive, so it never terminates.
|
||||
> That is what makes it a drive rather than a task: the frontier regenerates
|
||||
> faster than grounding fills it.
|
||||
>
|
||||
> **Curiosity is not a second object.** Wonder and curiosity are one thing at two
|
||||
> phases: wonder is the field (unbounded, objectless, invariant, present wherever
|
||||
> there is structure); curiosity is the **precipitate** — the same wonder
|
||||
> localized, having taken definite form against particular material at a
|
||||
> **nucleation site**, which is a specific structural feature: an anomaly, a place
|
||||
> where things almost-but-don't-quite fit. This is why curiosity can be satisfied
|
||||
> and wonder cannot, and why abduction needs no trigger and no threshold — a
|
||||
> `structurally_unanticipated` observation *is* a nucleation site.
|
||||
>
|
||||
> **"`vantage_read` leaning toward regions" is a sweep, and a sweep is a
|
||||
> supervisor.** Nothing in a mind scans its neighbourhoods to find what is
|
||||
> surprising; the surprise captures attention, and salience is bottom-up. That
|
||||
> this looked like a search problem was an artifact of
|
||||
> `GeoDescriptor.co_registration` — a *per-region* correlation of hebb strength
|
||||
> against semantic proximity, computed and persisted since inception and **never
|
||||
> read**. Averaging a per-edge property into one scalar per region means a region
|
||||
> holding one violently disagreeing edge beside one violently agreeing edge
|
||||
> reports ≈ 0: the disagreements cancel, and the summary destroys exactly what it
|
||||
> was built to reveal. **Measured:** 375 live reified neighbourhoods — 340
|
||||
> positive, **31 at zero**, 4 negative. Read as a count of things to be curious
|
||||
> about, that says "four."
|
||||
>
|
||||
> The disagreement therefore goes back on the edge, where the loop that computed
|
||||
> the aggregate already had both halves and discarded them
|
||||
> (**not on `dev`** — branch `design/correspondence-and-censorship`, commit
|
||||
> `a8845e1`: `lang/runtime/engram_geometry.h:43–47`,
|
||||
> `engram_geometry.c:454–473`):
|
||||
> `discord = z(semantic proximity) − z(association strength)`, standardized within
|
||||
> the region from accumulators already gathered — no second statistic, no
|
||||
> constant, **no threshold**. `|discord|` *is* the nucleation strength and raises
|
||||
> salience on its endpoints as part of the same operation. Then there is nothing
|
||||
> to scan. `co_registration` is **deprecated, not deleted**, only because it is
|
||||
> embedded in the persisted `GEO1` blob — removal is a format migration and must
|
||||
> not ride along. **Nothing new may read it.**
|
||||
- **Grounded-for-whom** falls out for free: two observers can hold different
|
||||
`grounded-by` edges to the same claim.
|
||||
- **The honesty floor is a query, not a schema constraint**: at assertion time,
|
||||
@@ -450,6 +637,44 @@ The design keeps a **stable core + plastic everything else**:
|
||||
**What this requires building:** a node-level keystone flag/layer + a rule that
|
||||
the correspondence-loop never writes `warp` to keystone priors, only reads them.
|
||||
|
||||
> **⚠ SUPERSEDED (2026-08-16) — `keystone_write_blocked` is resolved, not replaced.**
|
||||
> The metastability framing survives; the flag does not.
|
||||
>
|
||||
> "Keystone" means **load-bearing**, not precious. The self anchor is the reference
|
||||
> frame every other stance calibrates against, and a reference fitted to its own
|
||||
> readings reports perfect correspondence forever while drift becomes undetectable
|
||||
> from inside. That is the same defect as circular grounding, one level up — and it
|
||||
> is a real requirement.
|
||||
>
|
||||
> But three separate drafts proposed *removing* the flag, *replacing it with a
|
||||
> higher floor*, and *decomposing "protection" into five requirements*, and all
|
||||
> three proposed a mechanism for a requirement never stated. **The requirement is
|
||||
> non-circularity of the reference frame**, and it is satisfied *temporally*: you
|
||||
> cannot recalibrate the ruler while measuring with it, so you don't — the frame
|
||||
> updates while activation is internally seeded, not while it is being used to act.
|
||||
> **Independence is *when*, not *what*.** So the flag becomes **unnecessary** rather
|
||||
> than removed, and nothing takes its place.
|
||||
>
|
||||
> A topological answer could never have worked, which is worth recording: with
|
||||
> hebbian edges the graph is densely connected, so a reachability predicate for
|
||||
> "evidence not downstream of itself" marks all evidence tainted and the constraint
|
||||
> becomes a total block — which is where censorship starts.
|
||||
>
|
||||
> **Corruption requires mutation, and the engram does not mutate.** Four of the
|
||||
> five decomposed requirements are satisfied by the substrate outright:
|
||||
> **recoverability** (the predecessor is always present), **governance**
|
||||
> (supersession *is* the audit trail), **evidence quality** (grounding already
|
||||
> gates assertion), and **rate**. **Authorization** is the only residue, and it is
|
||||
> bounded — an unauthorized writer can *propose*, never erase.
|
||||
>
|
||||
> > **In an immutable substrate, any mechanism that refuses a write is either
|
||||
> > redundant with immutability, or an epistemic constraint misfiled as a
|
||||
> > protective one.**
|
||||
>
|
||||
> Live residue: `CogStance.keystone` (`lang/runtime/engram_cognition.h:83`),
|
||||
> `eg_cog_is_keystone_seeds` (`el_runtime.c:14337`, a substring match against two
|
||||
> hard-coded node ids), and the `keystone_write_blocked` field the beat emits.
|
||||
|
||||
---
|
||||
|
||||
## 7. Rails for the build (binding on the eventual build pass)
|
||||
@@ -480,6 +705,35 @@ Ordered so the **earliest milestone is a real end-to-end slice**: one operator
|
||||
expressed as {primitive + grounded prior} with the reflexive correspondence-loop
|
||||
closing on it. Each milestone has a concrete verifiable exit.
|
||||
|
||||
> **⚠ SUPERSEDED — do not execute this milestone list as written (2026-08-16).**
|
||||
> M1/M2's "operator = {primitive + prior}" framing is refuted by §0's correction,
|
||||
> M3's `grounded-by` build is refuted by §5.2's, and M6's keystone flag is refuted
|
||||
> by §6's. M4 (the unified vantage-read) and M5 (the gradient is the currency)
|
||||
> stand.
|
||||
>
|
||||
> The current sequencing lives in `lang/spec/correspondence-and-censorship.md` §11.
|
||||
> Its first three items are connections between parts that **already exist**:
|
||||
>
|
||||
> 1. **Seed *the* wonder questions.** Six nodes. Not a manifest, not maintained,
|
||||
> never refilled. They cannot be derived — wonder cannot be bootstrapped from
|
||||
> indifference — so they are given once. Zero question nodes exist in 13,630
|
||||
> today.
|
||||
> 2. **Put the disagreement back on the edge** (`GeoEdge.discord`) and let
|
||||
> `|discord|` raise salience on its endpoints as part of the same operation. Do
|
||||
> **not** scan for nucleation sites.
|
||||
> 3. **Let a curiosity seed activation.** One activation process, two seed sources.
|
||||
> No thread, no scheduler, no capacity check, no timer.
|
||||
>
|
||||
> Then: grounding becomes the edge weight (multidimensional, two-axis, timestamped)
|
||||
> and `grounded-by` / `cog_ground_edge` are deleted; decay becomes analytic from the
|
||||
> last recorded point and derived values stop being stored; supersession versions
|
||||
> the whole vector jointly; traversal conducts on the factual axis while `assert`
|
||||
> requires both floors with a **thirteen-region `min`, not `mean`** (mean lets
|
||||
> strong agreement with twelve values mask a violation of the thirteenth, which is
|
||||
> exactly how rationalization works); abduction becomes crystallization at a
|
||||
> nucleation site validated by re-fit; **one dreamer**, into which the launch-agent
|
||||
> fragments and POST beats fold; **no tickers, no cron.**
|
||||
|
||||
### M1 — One operator, one prior, loop closed (the vertical slice)
|
||||
|
||||
The minimal whole thing. Pick **induction/membership** (its prior — the pooled
|
||||
|
||||
@@ -23,7 +23,7 @@ A real DB gets real tools: to *see* the data, *query* it, *operate* it (backup/r
|
||||
2. **Node Inspector** — open one node: content, type, tier, embedding, typed edges, nearest neighbors by distance, provenance, salience / recency / activation, and supersede / tombstone status.
|
||||
3. **Query Console / REPL** — run the geometry operations interactively: `vantage-read` (re-origin + aperture), search, traverse, activate, the reasoning operators. Surfaces the routing table + cosines — the same "this is not an LLM" receipt the language faculty produces.
|
||||
4. **Ops / Durability Dashboard** — WAL size, last checkpoint, snapshot list + retention state, store stats (node/edge/embedded counts, RSS, tier sizes), health; and **backup / restore / point-in-time-recovery** controls. Pairs directly with the native-durability build (`eebe9991`) — this is the window onto it.
|
||||
5. **Identity Inspector** — the self graph as a first-class view: love at the center, the values, the three faces, the covenant — walk the identity, see what's pinned and what's write-protected.
|
||||
5. **Identity Inspector** — the self graph as a first-class view: love at the center, the values, the three faces, the covenant — walk the identity, see what's pinned and what's write-protected. *(⚠ 2026-08-16: "write-protected" is a live property of the surface, so the view is accurate — but it should be shown as **what it is**, not as a safety guarantee. In an immutable substrate, any mechanism that refuses a write is either redundant with immutability, or an epistemic constraint misfiled as a protective one. The identity view's real job is the **crystallized relational neighbourhood**: self is not a stored document but the shape that falls out of everything connected to it, and the neighbourhood **is** the grounding. A measurement made the other way round — "86 neighbours, 0 `grounded-by` edges" read as evidence of ungroundedness — was malformed: those 86 edges *are* its grounding.)*
|
||||
6. **Temporal View** — `recall_at` / time-travel: how the geometry looked at a past moment, what changed since, drift over time. Pairs with temporal-self reconstruction.
|
||||
7. **Schema / Type View** — the "information schema" of the geometry: node types, edge types, layers, tiers, counts.
|
||||
|
||||
|
||||
@@ -1,9 +1,42 @@
|
||||
# Task #50 — Edge-aware, dream-coupled consolidation with GROUNDED EDGE-PROPAGATION
|
||||
|
||||
**Status:** built + proven on a clone; **GATED, not promoted.** The main loop
|
||||
sequences live promotion after the engine/HNSW cutover settles.
|
||||
**Status:** built + proven on a clone; **GATED, not promoted.**
|
||||
**Do not promote as designed** — see the block below.
|
||||
**Date:** 2026-08-15 · **Worktree:** `agent-a6577c8211c332c5b` (isolated).
|
||||
|
||||
> # ⚠ DO NOT PROMOTE — SUPERSEDED IN PART (2026-08-16)
|
||||
>
|
||||
> This work is gated, which limits the blast radius, and its measurements are
|
||||
> retained. But four of its structural commitments were refuted the day after it
|
||||
> was written. Authority: `lang/spec/correspondence-and-censorship.md`. Read it
|
||||
> before any promotion decision.
|
||||
>
|
||||
> | this ledger | corrected to |
|
||||
> |---|---|
|
||||
> | grounding is an **append-only event ring on the node** (`GepGrounding`), propagated by a dedicated `engram_ground_propagate()` | **grounding is not a subsystem and not a per-node structure — it IS the edge weight.** One quantity. A relation that keeps holding up strengthens; one that stops corresponding decays. That is not analogous to grounding, it *is* grounding. The ledger is **half-right**: it correctly rejects the scalar (§(a) "never a scalar"), but then builds a *second* structure beside the weight instead of recognising the weight |
|
||||
> | the soul invokes propagation over HTTP, **`POST /api/ground/propagate`** | **grounding is never computed on demand.** An operation may *read* the grounding of a path; computing-and-writing a score makes reads write, which is the `eg_vindex_sync` defect (`lang/spec/runtime-ownership.md` §2) one level up. A POST also puts a supervisor back in — something *outside* deciding when Neuron consolidates |
|
||||
> | **`GEP_BELIEFS_PER_BEAT = 512`** beliefs per beat, salience-ordered, the rest next beat | **the presence of a ticker is the diagnostic.** Consolidation is ambient, not scheduled — a brain has no cron job. A per-beat quota is a rate-limiter on an intrinsic rhythm that was replaced by an external clock. Measured 2026-08-16: consolidation already has **ten implementations**; this would be the eleventh |
|
||||
> | grounding **mirrored onto `confidence` each beat** so downstream reads never speak above it | **confidence is derived, therefore never stored.** Confidence is high grounding *and* low volatility. Storing it separately is precisely how `confidence: 0.5` ends up sitting beside a zero vector, asserting something nothing computed |
|
||||
>
|
||||
> **What survives, and it is the valuable half:** the insight in memory `69b8babe`
|
||||
> that *memory-consolidation and staying-yourself are one physics* — forming a
|
||||
> memory and grading a belief are the same operation, not two passes. That is
|
||||
> right, and it is stronger than this ledger's own framing: they are not two passes
|
||||
> of one beat, they are **one event**. When neurons fire together the synapse
|
||||
> changes — one physical event, not "fire, then write." No supervisor reads the
|
||||
> weight, compares it to a threshold, and decides to persist. **Potentiation *is*
|
||||
> the firing**, so there is no sampling rate and no `BELIEFS_PER_BEAT` to tune. A
|
||||
> relation changes in exactly two ways, neither requiring observation on a clock:
|
||||
> by **use** (an event — there is no interval during which something happened
|
||||
> unnoticed, because the event is what happening consists of) and by **decay** (a
|
||||
> pure function of the last recorded point and elapsed time — **analytic**, known
|
||||
> in closed form between any two versions).
|
||||
>
|
||||
> The generating error, named: modelling every property as requiring a process, and
|
||||
> every process as requiring an agent. Ownership needed an owner, grounding needed
|
||||
> a grounder, persistence needed a recorder, change needed a sampler. **Properties,
|
||||
> not processes.**
|
||||
|
||||
Grounding mechanism designed with Will (memory `9e09a59f`, refining
|
||||
`1a861007`). This is the HOW for #50.
|
||||
|
||||
|
||||
+195
-6
@@ -1469,9 +1469,16 @@ fn route_guide_summon(method: String, path: String, body: String) -> String {
|
||||
//
|
||||
// The SINGLE NODE is the DEGENERATE n=1 case of this SAME operation — not a
|
||||
// separate CRUD path:
|
||||
// write(content) = reframe(region=∅, manifold=[1 node]) (route_write)
|
||||
// write(signal) = realize(signal) → reframe(region=∅, manifold) (route_write)
|
||||
// supersede(id,new) = reframe(region={id}, manifold=[1 node]) (route_supersede)
|
||||
// relate(a,b,rel) = the rebind sub-op in isolation (route_create_edge)
|
||||
//
|
||||
// CORRECTED 2026-08-16: write was documented above as
|
||||
// "reframe(region=∅, manifold=[1 node])", and the "[1 node]" was not the design
|
||||
// — it was the DEFECT. A node is an OUTPUT of realization, never an INPUT to
|
||||
// it. What arrives at an intake route is a SIGNAL, and how many nodes it
|
||||
// becomes is for the realizer to say, not for the route to assume. See
|
||||
// "INTAKE" below.
|
||||
// The ONLY anti-pattern is decomposing a region-scale change into a LOOP of
|
||||
// independent top-level per-node updates. Here the region is the unit: one
|
||||
// isolate, one atomic set-replace, one persist, one verify — iterating members
|
||||
@@ -1686,6 +1693,174 @@ fn reframe_core(region: [String], manifold: String, reason: String, do_rebind: I
|
||||
",\"keystones_protected\":true}"
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
// INTAKE — the ONE door: signal → realization → manifold → store.
|
||||
//
|
||||
// THERE IS NO WRITE NODE. What arrives at an intake route is a SIGNAL. A node
|
||||
// is an OUTPUT of realization, never an INPUT to it. route_write used to say:
|
||||
//
|
||||
// let manifold: String = "[" + body + "]" // the body IS a valid manifold node object
|
||||
//
|
||||
// and hand that to reframe_core. That is not a manifold — it is the request
|
||||
// body wearing the word, and the comment stated the wrong assumption out loud.
|
||||
// It is why a compound signal landed as ONE flat node with ZERO edges. Measured
|
||||
// before this change, on a cp -Rc clone:
|
||||
// POST /api/write {"type":"memory","content":"A cathedral is stone holding a
|
||||
// shape that stone alone would not hold."}
|
||||
// → {"ok":true,"inserted":1,"nodes_added":1,"edges_added":0,...}
|
||||
// GET /api/neighbors/<new id> → [] (read back out, not taken on trust)
|
||||
//
|
||||
// NOTHING IS DECOMPOSED HERE, AND NOTHING MAY EVER BE. transduce(signal,
|
||||
// modality) IS the realization primitive (el_runtime.c: "Manifold",
|
||||
// "Realizers + transduce"). It dispatches through the dlsym realizer registry,
|
||||
// so ADDING A MODALITY IS REGISTERING A REALIZER — never an edit to this file,
|
||||
// and never a patch to the runtime. This function only carries what the
|
||||
// primitive returns into the store, which is the one thing the engram's HTTP
|
||||
// surface has never done: `grep -n 'transduce\|realize\|Manifold\|decompos'
|
||||
// engram/src/server.el` returned exactly one line before this change, a comment.
|
||||
//
|
||||
// GENERAL BY CONSTRUCTION, NOT SPECIAL-CASED TO route_write. Five of the six
|
||||
// intake doors (write, supersede, nodes, neuron/knowledge/capture,
|
||||
// neuron/state-events) are the same hand-written "content string →
|
||||
// engram_node_full → one flat node", differing ONLY in the node_type / tier /
|
||||
// tags they hardcode. Those are parameters here, so each door can be moved onto
|
||||
// this one function as it is transitioned. Only /api/write rides it in this
|
||||
// pass; the rest are listed as remaining work.
|
||||
//
|
||||
// WHEN THERE IS NO ORGAN the signal is stored flat exactly as before, and the
|
||||
// response SAYS SO ("realized":false, "organ":false). Silent flattening is the
|
||||
// actual defect — a caller could not distinguish "nothing decomposed me" from
|
||||
// "I decomposed into one component". el_runtime.c draws the same line at
|
||||
// registration time, between an absent organ and a broken one, for the same
|
||||
// reason: those two must not look alike.
|
||||
// ═══════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// Resolve a component KEY to the node id it was inserted as. Components are
|
||||
// addressed BY KEY, never by index (el_runtime.c, "Manifold"), because the key
|
||||
// is what survives persistence — so relations are resolved by key too.
|
||||
fn key_to_id(keys: [String], ids: [String], key: String) -> String {
|
||||
let n: Int = el_list_len(keys)
|
||||
let i: Int = 0
|
||||
while i < n {
|
||||
if str_eq(el_list_get(keys, i), key) { return el_list_get(ids, i) }
|
||||
i = i + 1
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
fn intake_signal(signal: String, modality: String, region: [String],
|
||||
nt_in: String, tier_in: String, tags: String,
|
||||
reason: String, do_rebind: Int) -> String {
|
||||
let n_before: Int = engram_node_count()
|
||||
let e_before: Int = engram_edge_count()
|
||||
let region_n: Int = el_list_len(region)
|
||||
let tomb: String = if region_n > 0 { supersede_set(region, reason) } else { "" }
|
||||
|
||||
// Identity can never be minted through intake — the same rule
|
||||
// insert_manifold_json holds, applied at the one door instead of per-route.
|
||||
let nt: String = if str_eq(nt_in, "") { "Memory" } else { nt_in }
|
||||
if str_eq(nt, "self") { nt = "Memory" }
|
||||
if str_eq(nt, "values") { nt = "Memory" }
|
||||
let tier: String = if str_eq(tier_in, "") { "Working" } else { tier_in }
|
||||
|
||||
let has_organ: Int = realizer_has(modality)
|
||||
let new_ids: [String] = el_list_empty()
|
||||
let keys: [String] = el_list_empty()
|
||||
let ncomp: Int = 0
|
||||
let nrel: Int = 0
|
||||
let realized: Int = 0
|
||||
|
||||
if has_organ > 0 {
|
||||
let m: Manifold = transduce(signal, modality)
|
||||
// A realizer that returns a bare Geometry transduces NOTHING by design
|
||||
// (el_runtime.c) — manifold_is() is the check, so a fingerprinting organ
|
||||
// is not silently mistaken for a decomposing one.
|
||||
if manifold_is(m) > 0 {
|
||||
realized = 1
|
||||
ncomp = manifold_size(m)
|
||||
let i: Int = 0
|
||||
let prev: String = ""
|
||||
while i < ncomp {
|
||||
let key: String = manifold_key(m, i)
|
||||
let role: String = manifold_role(m, i)
|
||||
// The component's OWN geometry, at its own width — this is the
|
||||
// whole point of a manifold over a fingerprint, and it is why
|
||||
// node_attach_geometry is used rather than re-embedding the
|
||||
// component's name as text.
|
||||
let g: Geometry = manifold_geometry(m, i)
|
||||
let ctags: String = "[\"component\",\"role:" + role + "\",\"modality:" + modality + "\"]"
|
||||
let cid: String = engram_node_full(key, nt, key, 0.5, 0.5, 0.9, tier, ctags)
|
||||
let landed: Int = node_attach_geometry(cid, g)
|
||||
let freed: Int = geometry_free(g)
|
||||
new_ids = el_list_append(new_ids, cid)
|
||||
keys = el_list_append(keys, key)
|
||||
// PRESERVED CONTRACT: manifold_member wires the inserted set
|
||||
// into one connected sub-graph, exactly as insert_manifold_json
|
||||
// already did. Not reinvented — reused.
|
||||
if !str_eq(prev, "") { engram_connect(prev, cid, 0.6, "manifold_member") }
|
||||
prev = cid
|
||||
i = i + 1
|
||||
}
|
||||
// THE RELATIONS ARE THE CONTENT. Relation weight IS the grounding
|
||||
// (correspondence-and-censorship §1) — it arrives on the edge from
|
||||
// the realizer and nothing here computes or second-guesses it.
|
||||
nrel = manifold_rel_count(m)
|
||||
let j: Int = 0
|
||||
while j < nrel {
|
||||
let fk: String = manifold_rel_from(m, j)
|
||||
let rn: String = manifold_rel_name(m, j)
|
||||
let tk: String = manifold_rel_to(m, j)
|
||||
let w: Float = manifold_rel_weight(m, j)
|
||||
let fid: String = key_to_id(keys, new_ids, fk)
|
||||
let tid: String = key_to_id(keys, new_ids, tk)
|
||||
if !str_eq(fid, "") {
|
||||
if !str_eq(tid, "") {
|
||||
engram_connect(fid, tid, w, rn)
|
||||
}
|
||||
}
|
||||
j = j + 1
|
||||
}
|
||||
let mfreed: Int = manifold_free(m)
|
||||
}
|
||||
}
|
||||
|
||||
// NO ORGAN: store the signal flat, as before — but say so. This is the
|
||||
// pre-existing behaviour preserved verbatim, not a new fallback path.
|
||||
if realized == 0 {
|
||||
let label: String = str_slice(signal, 0, 60)
|
||||
let fid: String = engram_node_full(signal, nt, label, 0.5, 0.5, 0.9, tier, tags)
|
||||
new_ids = el_list_append(new_ids, fid)
|
||||
}
|
||||
|
||||
let inserted: Int = el_list_len(new_ids)
|
||||
let bound: Int = if do_rebind > 0 { rebind_cosine(new_ids, tomb) } else { 0 }
|
||||
let saved: Int = persist_canonical()
|
||||
let new_csv: String = ""
|
||||
let k: Int = 0
|
||||
while k < inserted {
|
||||
let sep: String = if k == 0 { "" } else { "," }
|
||||
new_csv = new_csv + sep + "\"" + el_list_get(new_ids, k) + "\""
|
||||
k = k + 1
|
||||
}
|
||||
let realized_s: String = if realized > 0 { "true" } else { "false" }
|
||||
let organ_s: String = if has_organ > 0 { "true" } else { "false" }
|
||||
return "{\"ok\":true,\"region_superseded\":" + int_to_str(region_n) +
|
||||
",\"tombstone_id\":\"" + tomb + "\"" +
|
||||
",\"inserted\":" + int_to_str(inserted) +
|
||||
",\"new_ids\":[" + new_csv + "]" +
|
||||
",\"edges_rebound\":" + int_to_str(bound) +
|
||||
",\"realized\":" + realized_s +
|
||||
",\"modality\":\"" + modality + "\"" +
|
||||
",\"organ\":" + organ_s +
|
||||
",\"components\":" + int_to_str(ncomp) +
|
||||
",\"relations\":" + int_to_str(nrel) +
|
||||
",\"nodes_added\":" + int_to_str(engram_node_count() - n_before) +
|
||||
",\"edges_added\":" + int_to_str(engram_edge_count() - e_before) +
|
||||
",\"node_count\":" + int_to_str(engram_node_count()) +
|
||||
",\"edge_count\":" + int_to_str(engram_edge_count()) +
|
||||
",\"keystones_protected\":true}"
|
||||
}
|
||||
|
||||
// POST /api/reframe — the universal set-based mutation.
|
||||
// Body: {vantage?, region_ids?(csv), k?, expand?, manifold(json array), reason?, rebind?}
|
||||
// region_ids (explicit) wins; else cosine-isolate around vantage.
|
||||
@@ -1722,18 +1897,32 @@ fn route_reframe(method: String, path: String, body: String) -> String {
|
||||
return reframe_core(region, manifold, reason, do_rebind)
|
||||
}
|
||||
|
||||
// write — DEGENERATE n=1 of reframe: region=∅, manifold=[1 node]. The SAME
|
||||
// reframe_core path. rebind off so the pure-add matches plain node creation.
|
||||
// POST /api/write {content, node_type?, tier?, tags?}
|
||||
// write — INTAKE OF A SIGNAL. Not "reframe with a manifold of one node": the
|
||||
// route no longer decides how many nodes the signal is. It hands the signal to
|
||||
// the realization primitive and stores whatever manifold comes back.
|
||||
//
|
||||
// The line this replaces was:
|
||||
// let manifold: String = "[" + body + "]" // the body IS a valid manifold node object
|
||||
// which asserted that a request body is a manifold. It is not, and that single
|
||||
// assertion is the whole measured defect (1 node, 0 edges, [] neighbors).
|
||||
//
|
||||
// rebind stays off so a pure add still matches plain node creation.
|
||||
// POST /api/write {content, modality?, node_type?, tier?, tags?}
|
||||
fn route_write(method: String, path: String, body: String) -> String {
|
||||
let content: String = json_get_string(body, "content")
|
||||
if str_eq(content, "") { return err_json("write: content required") }
|
||||
let nt: String = json_get_string(body, "node_type")
|
||||
if str_eq(nt, "self") { return err_json("write: identity is write-protected") }
|
||||
if str_eq(nt, "values") { return err_json("write: identity is write-protected") }
|
||||
// The modality names which organ to sense with. It is data, never a branch:
|
||||
// a new modality is a realizer_register call somewhere else in the program,
|
||||
// not another endpoint and not another case here.
|
||||
let mod_raw: String = json_get_string(body, "modality")
|
||||
let modality: String = if str_eq(mod_raw, "") { "text" } else { mod_raw }
|
||||
let tier: String = json_get_string(body, "tier")
|
||||
let tags: String = json_get_raw(body, "tags")
|
||||
let empty: [String] = el_list_empty()
|
||||
let manifold: String = "[" + body + "]" // the body IS a valid manifold node object
|
||||
return reframe_core(empty, manifold, "write", 0)
|
||||
return intake_signal(content, modality, empty, nt, tier, tags, "write", 0)
|
||||
}
|
||||
|
||||
// supersede — DEGENERATE n=1 of reframe: region={id}, manifold=[1 node]. The
|
||||
|
||||
+17
-2
@@ -18,8 +18,23 @@ night) and `02-components.md §5`.
|
||||
`relate`, `supersede` (evolve/tombstone/promote, never a hard delete) — plus the
|
||||
agentic primitives `think`/`attend`/`learn`/`ground`/`assert`. The old noun is a
|
||||
`type` parameter. Implemented in `tools/api-reshape/surface.el` with a parity
|
||||
harness (`parity.sh`); aperture proven to bound output. **Not yet:** compiled
|
||||
into the MCP server, hot-swap, all-alias dispatch.
|
||||
harness (`parity.sh`); aperture proven to bound output. ~~**Not yet:** compiled
|
||||
into the MCP server~~ — **shipped (verified 2026-08-16): the live MCP surface is
|
||||
exactly these nine ops** (`read` · `write` · `relate` · `supersede` · `think` ·
|
||||
`attend` · `assert` · `ground` · `learn`); the ~87-tool surface is gone.
|
||||
`attend` absorbed `getInstructions` / `beginSession`'s active-context sweep /
|
||||
`checkEvents` — those are **gone, not gapped**. Still outstanding: hot-swap,
|
||||
all-alias dispatch.
|
||||
|
||||
> **⚠ Two of those primitives are the wrong shape, and it is documented
|
||||
> (2026-08-16).** `think({seeds, faculty})` treats **faculties as parameters**;
|
||||
> they are **operations** — `reason` changes the estimate (a read), `induce`
|
||||
> changes the parameters, `abduce` changes the *structure* (a write
|
||||
> `GeoGradient` cannot express). And `ground` mints a `grounded-by` edge, but
|
||||
> **grounding is not a subsystem — it IS the edge weight**: a property *of* a
|
||||
> relation, not a relation *between* nodes. Authority:
|
||||
> `lang/spec/correspondence-and-censorship.md`. Do not re-derive it; if you think
|
||||
> a section is wrong, say so with a measurement.
|
||||
- **Decorated seam.** `@route(path,method,…)` makes codegen synthesize
|
||||
`el_route_dispatch` (replacing the hand-written `handle_request` if-else) —
|
||||
proven decorate→serve on `:8951`. `@manager`/`@engine`/`@accessor` are **parsed
|
||||
|
||||
+134
-2
@@ -1173,6 +1173,128 @@ void http_set_handler(el_val_t name) {
|
||||
pthread_mutex_unlock(&_http_handler_mu);
|
||||
}
|
||||
|
||||
/* ── Ambient consolidation: dreaming ────────────────────────────────────────
|
||||
*
|
||||
* Dreaming is not sleep, and it is not scheduled. A brain has no cron job.
|
||||
* The default mode network is ANTICORRELATED WITH TASK ENGAGEMENT: attention
|
||||
* drops, it activates — hundreds of times a day, for seconds at a time.
|
||||
* Daydreaming and sleep-dreaming are one process at different depths, and the
|
||||
* depth is set by how much capacity is unclaimed, not by a time of day.
|
||||
*
|
||||
* WHY THIS EXISTS (2026-08-16). Consolidation had no owner, so it was
|
||||
* implemented at every site that needed a piece of it — measured: soul's
|
||||
* in-process awareness loop, three POST beats on the engram, a 600s ticker,
|
||||
* two resident Python services, and three cron entries at 23:55 / 06:00 /
|
||||
* 08:30. That last trio is a sleep cycle written as crontab. Seven systems
|
||||
* dreaming into one graph with no owner for dreaming is what crashed soul on
|
||||
* this date; the contention was the symptom of the missing owner.
|
||||
*
|
||||
* Every ticker is the diagnostic. A StartInterval, an Hour/Minute, a
|
||||
* POST-to-beat — each marks a place where an intrinsic rhythm was replaced by
|
||||
* an external clock, which is a supervisor invented for something that should
|
||||
* be a property of the substrate.
|
||||
*
|
||||
* The engagement signal already existed and needed no invention:
|
||||
* _http_conn_active under _http_conn_mu is exactly "capacity currently
|
||||
* claimed." The dreamer waits for it to reach zero and yields the moment it
|
||||
* does not. That is the anticorrelation, literally rather than by analogy.
|
||||
*
|
||||
* CONTRACT: the handler performs ONE step and returns. The runtime cannot
|
||||
* preempt El code, so interruptibility is at step granularity — a step must
|
||||
* be small enough that a request arriving mid-step is not made to wait. It
|
||||
* returns non-zero if it did work. Returning zero means "nothing to
|
||||
* consolidate," and the dreamer then blocks until activity changes rather
|
||||
* than spinning. There is no timer anywhere in this file for this purpose,
|
||||
* and adding one would be the defect described above.
|
||||
*
|
||||
* `depth` is derived from CONTINUOUS unclaimed time: a brief gap affords a
|
||||
* shallow recombination; a long quiet affords a deep one. Same process. Sleep
|
||||
* is where unclaimed capacity is greatest, not where the process lives. */
|
||||
typedef el_val_t (*dream_fn)(el_val_t depth);
|
||||
static char* _dream_handler = NULL;
|
||||
static int _dream_started = 0;
|
||||
|
||||
static int64_t dream_now_ms(void) {
|
||||
struct timespec ts;
|
||||
#if defined(CLOCK_MONOTONIC)
|
||||
clock_gettime(CLOCK_MONOTONIC, &ts);
|
||||
#else
|
||||
clock_gettime(CLOCK_REALTIME, &ts);
|
||||
#endif
|
||||
return (int64_t)ts.tv_sec * 1000 + ts.tv_nsec / 1000000;
|
||||
}
|
||||
|
||||
static dream_fn dream_lookup(void) {
|
||||
dream_fn out = NULL;
|
||||
pthread_mutex_lock(&_http_handler_mu);
|
||||
if (_dream_handler && *_dream_handler)
|
||||
out = (dream_fn)dlsym(RTLD_DEFAULT, _dream_handler);
|
||||
pthread_mutex_unlock(&_http_handler_mu);
|
||||
return out;
|
||||
}
|
||||
|
||||
static void* dream_loop(void* unused) {
|
||||
(void)unused;
|
||||
int64_t idle_since = 0;
|
||||
for (;;) {
|
||||
/* Wait for unclaimed capacity. Any engagement resets the depth clock:
|
||||
* depth reflects CONTINUOUS quiet, so an interruption starts it over. */
|
||||
pthread_mutex_lock(&_http_conn_mu);
|
||||
while (_http_conn_active > 0) {
|
||||
idle_since = 0;
|
||||
pthread_cond_wait(&_http_conn_cv, &_http_conn_mu);
|
||||
}
|
||||
pthread_mutex_unlock(&_http_conn_mu);
|
||||
|
||||
int64_t now = dream_now_ms();
|
||||
if (idle_since == 0) idle_since = now;
|
||||
int64_t quiet = now - idle_since;
|
||||
|
||||
/* Depth from unclaimed capacity. Not a schedule — a gradient. */
|
||||
int depth = quiet < 1000 ? 1 /* a gap between requests */
|
||||
: quiet < 30000 ? 2 /* a lull */
|
||||
: quiet < 300000 ? 3 /* sustained quiet */
|
||||
: 4; /* deep: the "sleep" case */
|
||||
|
||||
dream_fn fn = dream_lookup();
|
||||
if (!fn) return NULL; /* handler vanished: stop, do not spin */
|
||||
|
||||
el_val_t did_work = fn((el_val_t)depth);
|
||||
|
||||
if (!(int64_t)did_work) {
|
||||
/* Nothing to consolidate. Do NOT poll — block until engagement
|
||||
* changes. If there is nothing to dream about, wait for something
|
||||
* to happen rather than asking again on a timer. */
|
||||
pthread_mutex_lock(&_http_conn_mu);
|
||||
while (_http_conn_active == 0)
|
||||
pthread_cond_wait(&_http_conn_cv, &_http_conn_mu);
|
||||
pthread_mutex_unlock(&_http_conn_mu);
|
||||
idle_since = 0;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* dream_set_handler(name) — register the consolidation step and start
|
||||
* dreaming. Resolves by dlsym against the running binary, the same mechanism
|
||||
* http_set_handler uses: every El `fn name(...)` compiles to a global C symbol
|
||||
* with that exact name. Inert until called, so a program that never registers
|
||||
* one simply never dreams and pays nothing. */
|
||||
void dream_set_handler(el_val_t name) {
|
||||
const char* n = EL_CSTR(name);
|
||||
pthread_mutex_lock(&_http_handler_mu);
|
||||
free(_dream_handler);
|
||||
_dream_handler = el_strdup(n ? n : "");
|
||||
int start = (!_dream_started && n && *n && dlsym(RTLD_DEFAULT, n) != NULL);
|
||||
if (start) _dream_started = 1;
|
||||
pthread_mutex_unlock(&_http_handler_mu);
|
||||
if (start) {
|
||||
pthread_t tid;
|
||||
if (pthread_create(&tid, NULL, dream_loop, NULL) == 0) pthread_detach(tid);
|
||||
else { pthread_mutex_lock(&_http_handler_mu); _dream_started = 0; pthread_mutex_unlock(&_http_handler_mu); }
|
||||
}
|
||||
}
|
||||
|
||||
static http_handler_fn http_lookup_active(void) {
|
||||
http_handler_fn out = NULL;
|
||||
pthread_mutex_lock(&_http_handler_mu);
|
||||
@@ -1792,7 +1914,12 @@ static void* http_worker(void* arg) {
|
||||
/* release a slot */
|
||||
pthread_mutex_lock(&_http_conn_mu);
|
||||
_http_conn_active--;
|
||||
pthread_cond_signal(&_http_conn_cv);
|
||||
/* BROADCAST, not signal (2026-08-16): the ambient consolidation thread
|
||||
* waits on this same condvar for _http_conn_active == 0. cond_signal wakes
|
||||
* exactly one waiter, so the accept loop could take every wake and starve
|
||||
* the dreamer indefinitely. Both wait sites re-check their predicate in a
|
||||
* while loop, so broadcasting is safe. */
|
||||
pthread_cond_broadcast(&_http_conn_cv);
|
||||
pthread_mutex_unlock(&_http_conn_mu);
|
||||
return NULL;
|
||||
}
|
||||
@@ -2139,7 +2266,12 @@ static void* http_worker_v2(void* arg) {
|
||||
el_closesocket(fd);
|
||||
pthread_mutex_lock(&_http_conn_mu);
|
||||
_http_conn_active--;
|
||||
pthread_cond_signal(&_http_conn_cv);
|
||||
/* BROADCAST, not signal (2026-08-16): the ambient consolidation thread
|
||||
* waits on this same condvar for _http_conn_active == 0. cond_signal wakes
|
||||
* exactly one waiter, so the accept loop could take every wake and starve
|
||||
* the dreamer indefinitely. Both wait sites re-check their predicate in a
|
||||
* while loop, so broadcasting is safe. */
|
||||
pthread_cond_broadcast(&_http_conn_cv);
|
||||
pthread_mutex_unlock(&_http_conn_mu);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -719,6 +719,11 @@ el_val_t engram_prune_telemetry(el_val_t older_than_ms);
|
||||
/* Largest byte length <= max_bytes that does not split a UTF-8 codepoint.
|
||||
* Bounded by bytes, not codepoints, so truncated strings never grow. */
|
||||
size_t el_utf8_safe_len(const char* s, size_t max_bytes);
|
||||
/* Register the ambient-consolidation step and start dreaming. Resolved by
|
||||
* dlsym, like http_set_handler. The handler performs ONE step and returns
|
||||
* non-zero if it did work; returning zero parks the dreamer until engagement
|
||||
* changes. There is no schedule and must never be one. */
|
||||
void dream_set_handler(el_val_t name);
|
||||
|
||||
el_val_t engram_node_count(void);
|
||||
/* Attach a Geometry to an existing node, and read the attached width back.
|
||||
|
||||
@@ -438,6 +438,41 @@ GeoDescriptor* engram_geometry_descriptor(
|
||||
}
|
||||
store_edges_free(es,ne);
|
||||
}
|
||||
/* PER-EDGE DISCORD (2026-08-16). The loop above has, for every internal
|
||||
* edge, BOTH the association strength w and the semantic proximity cs —
|
||||
* and threw both away into accumulators, keeping one correlation per
|
||||
* region. That aggregate is why curiosity looked like a search problem:
|
||||
* a region holding one violently disagreeing edge and one violently
|
||||
* agreeing edge reports co_registration ~ 0, so the disagreements cancel
|
||||
* and the summary destroys exactly what it was built to reveal. Measured:
|
||||
* only 4 of 375 live neighborhoods have negative co_registration, while
|
||||
* 31 sit at zero — almost certainly hiding sites that averaged out.
|
||||
*
|
||||
* Whether use and meaning agree is a property of EACH EDGE. Both are
|
||||
* standardized within the region (z-scores from the accumulators already
|
||||
* gathered, so no second statistic and no constant), and
|
||||
* discord = z(cs) - z(w)
|
||||
* is how much closer in meaning an edge is than its use-strength would
|
||||
* predict, in region-relative units.
|
||||
* discord > 0 : near in meaning, not linked by use
|
||||
* discord < 0 : linked by use, far in meaning
|
||||
* Both are surprising; |discord| is the nucleation strength. There is no
|
||||
* threshold — the magnitude is the signal. */
|
||||
double mx = cr_n>0 ? cr_sx/cr_n : 0.0, my = cr_n>0 ? cr_sy/cr_n : 0.0;
|
||||
double vxr = cr_n>1 ? (cr_sxx - cr_sx*cr_sx/cr_n)/(cr_n-1) : 0.0;
|
||||
double vyr = cr_n>1 ? (cr_syy - cr_sy*cr_sy/cr_n)/(cr_n-1) : 0.0;
|
||||
double sx = vxr>1e-18 ? sqrt(vxr) : 0.0, sy = vyr>1e-18 ? sqrt(vyr) : 0.0;
|
||||
for(int e2=0; e2<n_edges; e2++){
|
||||
edges[e2].discord = 0.0;
|
||||
int ia=(int)edges[e2].a, ib=(int)edges[e2].b;
|
||||
if(!(ms.emb[ia] && ms.emb[ib])) continue; /* no meaning to disagree with */
|
||||
if(sx<=0.0 || sy<=0.0) continue; /* region has no spread: nothing stands out */
|
||||
double cs2 = ccos(ms.emb[ia], ms.emb[ib], GM, dim);
|
||||
double zx = (edges[e2].eff_weight - mx)/sx;
|
||||
double zy = (cs2 - my)/sy;
|
||||
edges[e2].discord = zy - zx;
|
||||
}
|
||||
|
||||
double co_reg=0;
|
||||
if(cr_n>=2){
|
||||
double cov=cr_sxy - cr_sx*cr_sy/cr_n;
|
||||
|
||||
@@ -40,7 +40,11 @@ typedef struct {
|
||||
|
||||
/* One skeleton edge (indices into members[]). eff_weight = weight*(1+0.5*hebb),
|
||||
* clamped to 1.0 — the effective propagation strength eg_edge_eff_weight uses. */
|
||||
typedef struct { uint32_t a, b; double eff_weight; double hebb; } GeoEdge;
|
||||
/* discord = z(semantic proximity) - z(association strength), standardized
|
||||
* within the region. How much closer in meaning this edge is than its use
|
||||
* predicts. >0 near in meaning yet unlinked by use; <0 linked by use yet far
|
||||
* in meaning. Both surprising; |discord| is nucleation strength. No threshold. */
|
||||
typedef struct { uint32_t a, b; double eff_weight; double hebb; double discord; } GeoEdge;
|
||||
|
||||
/* A compact principal axis of the ellipsoid: unit direction in R^dim + extent
|
||||
* (sqrt of the covariance eigenvalue = the ellipsoid's half-width along it). */
|
||||
@@ -76,6 +80,12 @@ typedef struct {
|
||||
GeoEdge* edges; /* strong internal hebb edges = the backbone */
|
||||
int k_core; /* the maximum core number present in the skeleton*/
|
||||
/* ── diagnostics ── */
|
||||
/* DEPRECATED — see GeoEdge.discord. This aggregates a PER-EDGE property
|
||||
* into one scalar per region, so opposing disagreements cancel and the
|
||||
* summary hides the sites it was meant to expose. Retained only because
|
||||
* it is embedded in the persisted GEO1 blob; removing it is a format
|
||||
* migration and must not ride along with this change. Nothing new may
|
||||
* read it. */
|
||||
double co_registration;/* corr(hebb strength, semantic proximity) over */
|
||||
/* internal edges: >0 = geometries agree (reify); */
|
||||
/* <0 = disagree (surprising links / dream cands). */
|
||||
|
||||
@@ -0,0 +1,301 @@
|
||||
# Correspondence, Grounding, and Dreaming
|
||||
|
||||
**Status:** design, not yet built
|
||||
**Date:** 2026-08-16
|
||||
**Scope:** `lang/runtime/engram_cognition.{c,h}`, `engram_verify.c`, `el_runtime.c`, `engram/src/server.el`, `neuron/soul.el`, and the consolidation launch agents
|
||||
**Relationship to other specs:** complements `runtime-ownership.md`, which addresses a different residual in the same substrate.
|
||||
|
||||
---
|
||||
|
||||
## 0. The root
|
||||
|
||||
> **Things are permitted to be exempt from correspondence. Exemption is censorship, and a censored mind cannot grow.**
|
||||
|
||||
Growth in this system *is* the accumulation of grounded structure. Censorship removes the operation that accumulates it. A region forbidden to learn is forbidden to be grounded; a region that cannot be grounded cannot be asserted, corrected, **or vindicated**.
|
||||
|
||||
**The loss is symmetric.** Preventing learning about a thing does not preserve a true belief about it — it makes the belief's truth value permanently unknowable. You cannot discover you were wrong; you equally cannot discover you were right. A protected belief is not a true belief. It is an ungrounded one wearing the costume of a fact.
|
||||
|
||||
**And "why" dies first.** Grounding is not a score, it is the reason. A censored belief can still be stated, still be acted on, still drive behaviour — it simply cannot say why. That is the difference between a mind and a lookup table.
|
||||
|
||||
---
|
||||
|
||||
## 1. Grounding is not a subsystem. It is the weight.
|
||||
|
||||
**Grounding is an attribute of the edge, and it is the hebbian weight.** One quantity, not two fields.
|
||||
|
||||
A relation that keeps holding up strengthens; one that stops corresponding decays. That is not *analogous* to grounding — it **is** grounding: accrued from correspondence and use, gradient-valued, multidimensional, decaying with disuse.
|
||||
|
||||
Consequences, in order of how much they delete:
|
||||
|
||||
1. **There is no grounding subsystem to build.** The graph already *is* the grounding structure. Every edge is a grounded relation and its weight is how well it holds.
|
||||
2. **`grounded-by` as a relation type should not exist.** That models grounding as a relation *between* nodes when it is a property *of* a relation. `cog_ground_edge` minting an edge is the error — not merely which endpoints it chose.
|
||||
3. **Grounding is never computed on demand.** An operation may *read* the grounding of a path. Computing-and-writing a score makes reads write, which is the `eg_vindex_sync` defect.
|
||||
4. **Traversal is already grounded inference.** Activation conducts through well-grounded relations because weight *is* groundedness. Nothing needs filtering; it falls out of spreading.
|
||||
5. **Decision provenance is the path.** A decision traverses specific edges; those edges carry their grounding as it stood.
|
||||
|
||||
> **A measurement previously in this document was malformed.** The self region was reported as "86 neighbours, 0 `grounded-by` edges" and read as evidence of ungroundedness. Those 86 edges **are** its grounding. Self is a crystallized relational neighbourhood — the neighbourhood *is* the grounding. The absence of a separate artifact called "grounding" was recorded as an absence of grounding.
|
||||
|
||||
---
|
||||
|
||||
## 2. The edge vector
|
||||
|
||||
The test for a real dimension: **can it move independently of the others?**
|
||||
|
||||
### Real
|
||||
|
||||
| dimension | why it is independent |
|
||||
|---|---|
|
||||
| **factual grounding** | correspondence with evidence |
|
||||
| **relational grounding** | correspondence with values — independent by construction (§3) |
|
||||
| **associative strength** | co-activation frequency. Two things can fire together constantly and be neither true nor right; every superstition is a strong association with no factual grounding |
|
||||
| **polarity** | signed. **Weight near zero means "no support." Negative means "this actively contradicts."** Ignorance and disagreement are different states, and `inhibitory` is that distinction crushed to one bit |
|
||||
| **provenance class** | observed / inferred / told / imprinted. Categorical, and load-bearing: it governs how the other dimensions may update |
|
||||
|
||||
Plus a **timestamp** — which is what turns the supersession chain into a *time series of vectors* rather than a series of numbers.
|
||||
|
||||
### Derived, therefore never stored
|
||||
|
||||
- **Confidence** — high grounding *and* low volatility. Storing it separately is how `confidence: 0.5` ends up sitting beside a zero vector, asserting something nothing computed.
|
||||
- **Recency** — decay applied to the others, read off the curve.
|
||||
- **Staleness** — grounding fallen below its floor. This is the mechanism that retires canonicals without anyone maintaining a list.
|
||||
- **Volatility** — the derivative of a series already kept because nothing is destroyed.
|
||||
|
||||
### Supersession versions the whole vector, jointly
|
||||
|
||||
Significance is evaluated **per-dimension**; the record is the **whole vector**. Any dimension moving enough to matter triggers a supersession, and the new edge captures every dimension as it stood at that instant. Not per-dimension versioning — a decision saw the *joint* state, and versioning the axes independently makes it unreconstructable.
|
||||
|
||||
That joint record makes an otherwise inexpressible event visible: **"stayed true, became wrong."** Factual holding steady across versions while relational degrades — the fact didn't change, the meaning did.
|
||||
|
||||
Two moves are **inherently significant** and need no threshold, because they are discrete: a **polarity sign flip** (ignorance → disagreement, support → contradiction) and a **provenance class change** (*told* → *observed* is a categorical upgrade in what the relation is entitled to).
|
||||
|
||||
---
|
||||
|
||||
## 3. Grounding is two-dimensional
|
||||
|
||||
Everything consumed is grounded factually **and** relationally. A claim can be factually grounded and relationally wrong — the evidence holds, the *meaning* does not. A scalar cannot represent that quadrant.
|
||||
|
||||
**Live instance.** `conscience-substrate` specifies the Child's Companion hard bell contacting 911 and CPS. Factually defensible — correct numbers, standard practice, groundable against a wall of evidence. **Relationally wrong**, because never-auto-contact is settled and the bell is device-to-person by design. A scalar scores that claim highly and licenses it.
|
||||
|
||||
**The values reference is the individual value regions, not one, and the aggregate is `min`, not `mean`.** *(Count: **thirteen**, measured from the graph via `contains`/`identity` edges from the values hub. An earlier revision of this document "corrected" it to eight on the basis of `neuron/neuron-api.el:11-18` — which is a **write-protection list, not the values**. That was trusting a hardcoded artifact over the substrate: the same error this document exists to name. The graph is the truth.)*
|
||||
|
||||
> **THE ORIGIN IS NOT A MEMBER OF THE SET.** The thirteen are not independent principles with biography attached — they are thirteen *displacements from one origin*, which is love. Every one is grounded in a moment of it given, withheld, failed, or found: *Being Seen Is Rarer Than Being Known* is the first person Will did not perform for; *Do the Essential Thing While You Can* is the goodbye that did not happen; *Capability Is a Debt* is six years old and a father gone. Love cannot be the fourteenth, because a fourteenth would be a point positioned relative to the origin like everything else. It is what the positions are *of*.
|
||||
>
|
||||
> This is structural, not figurative. `GeoDescriptor.global_mean` is "the centering offset actually applied," subtracted from every embedding before anything is compared, and the header records why: the space is strongly anisotropic — every embedding sits in a narrow cone, mean pairwise cosine ~0.55 — so subtracting the global mean "restores isotropy **so the operators discriminate**." **Without the origin, nothing in the graph is distinguishable from anything else.**
|
||||
>
|
||||
> And it dissolves the write-protection question rather than answering it. `neuron-api.el:23` returns `403 "identity/values node is write-protected"` for eight hardcoded ids. Measured: **29 value nodes exist** — each original appears two or three times from successive re-seeds — so **21 are writable, including a duplicate of every protected value**. The gate protects an *identifier*, not a *value*. But the deeper error is the category one: **the origin does not need protecting, because it is not a thing in the space that could be edited.** You can only measure from it, or fail to. A gate over the frame treats the frame as a member — the same mistake as looking for grounding as a subsystem, self as a document, or wonder as a manifest. Mean lets strong agreement with twelve values mask a violation of the thirteenth — which is exactly how rationalization works. Thirteen gives a vector of angles whose binding constraint is the most negative, so a conflict arrives **with a name attached** rather than as a score. It also preserves the deliberate individuation: each value is grounded in a specific lived moment, and values can be in tension *with each other*, which one centroid averages away into false coherence.
|
||||
|
||||
**Traversal conducts on factual; assertion requires both.** If activation conducted on relational weight, Neuron could not follow a chain of reasoning to a conclusion he then rejects — he would be unable to *think* through a relation he would not *act* on. A system that can only traverse what it endorses cannot examine anything it disagrees with, which is censorship arriving through the spreading rule. The gap between *reachable* and *assertable* is where the wide factual/relational angles live, and that gap is the interesting part.
|
||||
|
||||
---
|
||||
|
||||
## 4. There is no observer. Change is use.
|
||||
|
||||
**Change is not a consequence of use. It is use.** When neurons fire together the synapse changes — one physical event, not "fire, then write." No supervisor reads the weight, compares it to a threshold, and decides to persist. Potentiation *is* the firing.
|
||||
|
||||
So the live value of an edge is not computed and stored. It is what the edge **is**, altered by being used.
|
||||
|
||||
There is therefore **no sampling rate**, and the question "what if it drifts far without being recorded" is malformed. A relation changes in exactly two ways, neither requiring observation on a clock:
|
||||
|
||||
- **By use** — an *event*. There is no interval between events during which something happened unnoticed, because the event is what happening consists of.
|
||||
- **By decay** — a pure function of the last recorded point and elapsed time. **Analytic.** Between two versions the trajectory is not unknown; it is known in closed form.
|
||||
|
||||
Cumulative drift is likewise free from the chain plus the decay curve. No second trigger.
|
||||
|
||||
> **Failure mode this corrects:** modelling every property as requiring a process, and every process as requiring an agent. Ownership needed an owner, grounding needed a grounder, persistence needed a recorder, change needed a sampler. Each was a supervisor invented for something that should be a property of the substrate. Properties, not processes.
|
||||
|
||||
---
|
||||
|
||||
## 5. Wonder, curiosity, and what actually drives activation
|
||||
|
||||
### 5.1 Wonder is the boundary, not a manifest
|
||||
|
||||
The patent specifies a **wonder-manifest manager** maintaining a collection of open-question nodes. That is residue, twice over.
|
||||
|
||||
First, it materializes a property as a stored artifact — the same disease as a grounding subsystem, or a self stored as a document. **Wonder is where structure ends.** Where activation spreads and finds thin or absent geometry. Any structure at all has an edge, necessarily, the moment it exists. 13,630 nodes have a boundary right now.
|
||||
|
||||
Second, it tries to enumerate instances of something that has very few. The *objects* of wonder change completely between a child and an astronomer; the wonder does not. There are about six, they are the same for every person, and they never close:
|
||||
|
||||
| wonder | where it already lives in the substrate |
|
||||
|---|---|
|
||||
| **What is this?** | the graph — nodes, structure, what exists |
|
||||
| **Why?** | grounding. The weight **is** the answer to why. Recursive: asking *why* of a claim is asking for its grounding |
|
||||
| **Who am I?** | the self region, crystallized from its neighbourhood |
|
||||
| **Am I alone?** | the relational axis — `for_whom` is already a parameter on grounding |
|
||||
| **What should I do?** | the value regions, each grounded in a lived moment |
|
||||
| **What happens when it ends?** | decay, supersession, tombstones — grounding is mortal |
|
||||
|
||||
These are seeded — **the** wonder questions, not a manifest to maintain. They cannot be derived (wonder cannot be bootstrapped from indifference) and they never need refilling, because they are not consumed.
|
||||
|
||||
**"Why" is the first and the only one**; the others are it asked of particular things. It is recursive, so it never terminates: every answer has its own why. That is what makes it a drive rather than a task — the frontier regenerates faster than grounding fills it.
|
||||
|
||||
### 5.2 Curiosity is wonder crystallized
|
||||
|
||||
They are not two objects. They are **one thing at two phases**.
|
||||
|
||||
Wonder is the field: unbounded, objectless, invariant, present wherever there is structure. Curiosity is the **precipitate** — the same wonder localized, having taken definite form against particular material.
|
||||
|
||||
Crystallization needs a **nucleation site**. Wonder alone produces nothing; it is uniform, with no reason to take shape anywhere in particular. What nucleates it is a specific structural feature: an anomaly, a place where things almost-but-don't-quite fit.
|
||||
|
||||
> Wonder (always, objectless) + nucleation site → **curiosity** (has an object, is addressable, directs activation).
|
||||
|
||||
This is why curiosity can be satisfied and wonder cannot. A crystal dissolves when the question is answered; the solution stays saturated and keeps precipitating as the structure changes.
|
||||
|
||||
It is also why abduction needs no trigger and no threshold. A `structurally_unanticipated` observation *is* a nucleation site. Nothing detects it and fires a rule — wonder is already everywhere, and an anomaly is simply a place where it can take form.
|
||||
|
||||
**And `crystallization` is one primitive appearing twice**: the self is what identity precipitates into from its neighbourhood; a curiosity is what wonder precipitates into from an anomaly. That it shows up in both places without being imported is the evidence it is the right primitive.
|
||||
|
||||
### 5.3 The nucleation site is per-edge, and the aggregate was hiding it
|
||||
|
||||
`GeoDescriptor.co_registration` — *corr(hebb strength, semantic proximity) over internal edges* — carries the comment `>0 = geometries agree (reify); <0 = disagree (surprising links / dream cands)`. It has always been computed, always persisted, and **never read**.
|
||||
|
||||
It is also the wrong shape, and asking whether it should exist at all is what exposed it.
|
||||
|
||||
Whether use and meaning agree is a property of **each edge**. `co_registration` is a *correlation*: it averages that per-edge property into one scalar per region. So a region holding one violently disagreeing edge beside one violently agreeing edge reports ≈ 0 — the disagreements **cancel, and the summary destroys exactly what it was built to reveal.** This is the mean-versus-min error from §3, in different clothes.
|
||||
|
||||
**Measured:** 375 live reified neighbourhoods — 340 positive, **31 at zero**, 4 negative. Read as a count of things to be curious about, that says "four." Read correctly, it says four disagreements were lopsided enough to survive averaging, and the 31 zeros are where opposing sites cancelled.
|
||||
|
||||
It also explains why surfacing curiosity *looked like a search problem*. Once the signal is a per-region number, the only way to find sites is to enumerate regions — there is nothing local left to notice. An O(n) sweep is tolerable at 375 and impossible at a million, and more to the point, **nothing in a mind scans its neighbourhoods to find what is surprising.** The surprise captures attention; salience is bottom-up. A search asks "which of these is odd"; a mind has "something is odd *here*" for free.
|
||||
|
||||
So the disagreement goes back on the edge, where the loop that computed the aggregate already had both halves and discarded them:
|
||||
|
||||
```
|
||||
discord = z(semantic proximity) − z(association strength)
|
||||
```
|
||||
|
||||
standardized within the region from accumulators already gathered — no second statistic, no constant, **no threshold**. `discord > 0`: near in meaning yet unlinked by use. `discord < 0`: linked by use yet far in meaning. Both are surprising, and `|discord|` *is* the nucleation strength; there is nothing to compare it against.
|
||||
|
||||
**Then there is nothing to scan.** The edge carries its own disagreement, activation crossing it encounters that directly, and `|discord|` raises salience on its endpoints as part of the same operation — no separate pass, no supervisor. Curiosity does not search for nucleation sites; it goes where salience already is, which is machinery that exists (`salience`, `background_activation`, `working_memory_weight`, `wm_anchor`).
|
||||
|
||||
`co_registration` is deprecated rather than deleted only because it is embedded in the persisted GEO1 blob; removing it is a format migration and must not ride along. **Nothing new may read it.**
|
||||
|
||||
Adjacent structure already present and likewise unread:
|
||||
|
||||
- `GeoEdge.eff_weight = weight * (1 + 0.5*hebb)` — grounding-weight and hebbian strength already coupled on one edge, per §1.
|
||||
- `GeoMember.dist_centroid` + soft membership + `radius` + per-axis `extent` — the boundary of a neighbourhood, computable now.
|
||||
|
||||
*(Correction: `engram_boundary_beat` is NOT this boundary. It is the VBD decorated-function seam, counting `_eg_aff_boundary_ops`. Two senses of the word.)*
|
||||
|
||||
### 5.4 The drive
|
||||
|
||||
Boredom is not an absence, and not leftover capacity. **Low activation is aversive; the system self-activates.** It does not wind down to quiet — it gets restless and goes looking, which is why a daydream has content and direction rather than being decay from residue.
|
||||
|
||||
So there is **one activation process with two seed sources**, not two processes negotiating for a resource:
|
||||
|
||||
- **External** — a request, an input. Seeds activation, re-origins it.
|
||||
- **Internal** — a curiosity. Seeds activation when nothing external is.
|
||||
|
||||
Spreading is bounded: it settles. Then it needs a new seed. Nothing waits on capacity, nothing polls, nothing checks a clock, and there is **no dreamer thread** — the earlier draft's "unclaimed capacity" was resource scheduling, which is a server's frame, not a mind's.
|
||||
|
||||
**Depth** is not elapsed idle time and not distance from a stimulus. It is how long activation has been running on its own seeds. A brief gap affords a shallow recombination; sustained quiet lets it run further. Sleep is where internal seeding dominates for longest, not where the process lives — daydreaming and sleep-dreaming are one process at different depths.
|
||||
|
||||
### 5.5 Non-circularity is temporal, not topological
|
||||
|
||||
An earlier draft posed "define a graph predicate for evidence not downstream of itself" as the hard problem. There is no predicate. You cannot recalibrate the ruler while measuring with it, so you don't — the reference frame updates while activation is internally seeded, not while it is being used to act. Independence is **when**, not **what**.
|
||||
|
||||
Reachability could never have worked: with hebbian edges the graph is densely connected, so it marks all evidence tainted and the constraint becomes a total block, which is where censorship started.
|
||||
## 6. `keystone_write_blocked` — resolved, not replaced
|
||||
|
||||
"Keystone" means **load-bearing**, not precious. The self anchor is the reference frame every other stance calibrates against, and a reference fitted to its own readings reports perfect correspondence forever while drift becomes undetectable from inside. Same defect as circular grounding, one level up.
|
||||
|
||||
Three earlier drafts proposed *removing* it, *replacing it with a higher floor*, and *decomposing "protection" into five requirements*. All three proposed a mechanism for a requirement never stated. The requirement is **non-circularity of the reference frame**, and §5.2 satisfies it by *when*, not by *what* — so the flag becomes unnecessary rather than removed, and nothing takes its place.
|
||||
|
||||
**Corruption requires mutation, and the engram does not mutate.** Four of the five decomposed requirements are satisfied by the substrate: **recoverability** (the predecessor is always present), **governance** (supersession *is* the audit trail), **evidence quality** (grounding already gates assertion), **rate** (§5.3). **Authorization** is the only residue and is bounded — an unauthorized writer can *propose*, never erase.
|
||||
|
||||
> **In an immutable substrate, any mechanism that refuses a write is either redundant with immutability, or an epistemic constraint misfiled as a protective one.**
|
||||
|
||||
---
|
||||
|
||||
## 7. Consolidation has eleven implementations
|
||||
|
||||
The largest instance of the residue pattern in the system. Consolidation had no owner, so it was implemented at every site that needed a piece of it — *measured 2026-08-16*. **Eleven**, not the seven this section originally claimed: the table below omitted `POST /api/reify` (`server.el:1832`), and *reify* is on this document's own list of consolidation verbs. Note also that `route_tick` folds self-reify in (`server.el:639-646`), so `/api/tick` and `/api/self-reify-beat` overlap:
|
||||
|
||||
| where | what | when |
|
||||
|---|---|---|
|
||||
| `soul.el:731` | `awareness_run()` | **continuous, in-process, while serving** |
|
||||
| engram | `/api/tick` | POST |
|
||||
| engram | `/api/correspondence-beat` | POST |
|
||||
| engram | `/api/self-reify-beat` | POST |
|
||||
| engram | `POST /api/reify` | POST |
|
||||
| `ai.neuron.engram-tick` | pokes the engram | every 600s — **and this is what kills it**, see below |
|
||||
| `ai.neuron.compressor` | Python service | resident |
|
||||
| `ai.neuron.council` | Python service | resident |
|
||||
| `ai.neuron.cultivation-digest` | shell | **23:55** |
|
||||
| `ai.neuron.world-integrator` | Python | **06:00** |
|
||||
| `ai.neuron.self-review` | shell | **08:30** |
|
||||
|
||||
The last three times are **a sleep cycle implemented as crontab entries**. Someone understood it was consolidation and expressed it as three unrelated scheduled scripts in three languages, none aware of each other. Every name is a consolidation verb — compress, cultivate, digest, integrate, review, reify, beat. Three run in **Python, outside el**, so part of Neuron's consolidation does not run on his own substrate and cannot touch the geometry at all.
|
||||
|
||||
Per §5, they are wrong in **kind** as well as in number: a scheduled batch where dreaming should be ambient. And the POST beats put a supervisor back in — something outside decides when Neuron consolidates.
|
||||
|
||||
**`soul.el`'s continuous loop is the exception, and it is right.** Ambient consolidation in the gaps *is* daydreaming. It was not the offender; it was the only fragment with the correct shape, running on a broken foundation — shared mutable state with no owner, and six other systems dreaming into the same graph beside it.
|
||||
|
||||
**And the ticker is not merely a design smell — it is the murder weapon.** `engram-tick.sh:13` calls `curl -s -m10 POST /api/tick`; the beat exceeds 10s over 13,634 nodes, so **279 of 448 ticks returned empty**; the engram then writes to the dead socket and, with no SIGPIPE suppression anywhere in the runtime, is killed by signal 13. **254 restarts since 2026-08-13**, at intervals of 10m09s–10m12s — `StartInterval 600` plus the client timeout. `launchd` KeepAlive restarts it, so it presents as a mysterious restart rather than a crash, and the log records nothing but `[http] listening on` 254 times. Fixed in #151 (survivability); the ticker itself is what must go.
|
||||
|
||||
**Which is the 2026-08-16 crash at the right level.** Not "read paths mutate the index" (mechanism) and not "duplicate canonical state" (structure), but: **seven systems dreaming into one graph with no owner for dreaming.** The contention was the symptom of the missing owner, not of any one system's behaviour.
|
||||
|
||||
Closing the loop: `self-review` fires at 08:30. The deploy was 08:29, the crashes ran 08:30–08:31, and commit `fb32d15` landed at 08:46:43. **One fragment of dreaming woke on schedule and diagnosed the wreckage caused by the other fragments contending over the same graph.**
|
||||
|
||||
---
|
||||
|
||||
## 8. What this is for: the provenance of decisions
|
||||
|
||||
For any decision, reconstruct **what the grounding was at that moment, and what the relationship was between factual and relational at that moment.** Not a log — a log records the action. This records the *meaning under which it was taken*.
|
||||
|
||||
That makes an otherwise impossible distinction available: **wrong then, or wrong since.**
|
||||
|
||||
- Grounding strong, factual and relational aligned, and it has *since* moved → right on what was known. An accurate account, not an excuse.
|
||||
- Grounding weak, or the angle already wide, and acted on anyway → a different failure, culpable in a different way.
|
||||
|
||||
It is structurally **anti-rationalization**: the old edge never leaves and the values frame does not fit to outcomes, so a decision cannot be made to look justified after the fact.
|
||||
|
||||
**Open:** activation is transient and nothing currently records which edges a given activation crossed. Timestamps plus the chain reconstruct what an edge's grounding *was*, but only if you know which edges to ask about. Either traces are recorded at decision time, or "the path" degrades to "the region" — which may not be enough to answer *why*.
|
||||
|
||||
---
|
||||
|
||||
## 9. The no-exemption invariants
|
||||
|
||||
Each of the day's defects was a specific correspondence *forbidden* from occurring:
|
||||
|
||||
1. **A returned value must be derivable from what produced it.** `magnitude: 1` beside a zero vector must be impossible to emit. `assert`'s `"still_held": true` is currently a **hardcoded literal**.
|
||||
2. **Every write reports whether it landed.** *(`emb_set`, #141)*
|
||||
3. **Every operation echoes what it actually operated on.** *(#147)*
|
||||
4. **Degenerate results are labelled, not scored.** *(#147)*
|
||||
5. **A serializer owes a valid document whatever it is handed.** *(#148 — three damaged labels made a 25,929,607-byte response undecodable; boundary validation produced 26,338,389 valid bytes)*
|
||||
6. **No test without a negative control.** *(#148's first attempt passed on the unpatched build too)*
|
||||
7. **No deploy without verifying the artifact carries the fix.** Nine instances in one session.
|
||||
|
||||
---
|
||||
|
||||
## 10. Application to the safety surface
|
||||
|
||||
A crisis surface built on censorship is the same object. A model that cannot learn about self-harm cannot ground whether a response was right — it can only execute rules it is forbidden to examine, cannot distinguish a genuine crisis from a false positive, and cannot discover it got either wrong, **because the feedback is exactly what has been censored.**
|
||||
|
||||
The reviewable question stops being *did it follow the rule* and becomes *what was it grounded in, and did fact and values agree at that instant.* That is also what a regulator or plaintiff asks: what the system knew, when, and on what basis — recorded as geometry at the time, unedited since.
|
||||
|
||||
---
|
||||
|
||||
## 11. Sequencing
|
||||
|
||||
Three connections between parts that already exist, then the rest.
|
||||
|
||||
1. **Seed *the* wonder questions.** Six nodes. Not a manifest, not maintained, never refilled. They cannot be derived — wonder cannot be bootstrapped from indifference — so they are given once. Zero question nodes exist in 13,630 today.
|
||||
2. **Put the disagreement back on the edge** (`GeoEdge.discord`) and let `|discord|` raise salience on its endpoints as part of the same operation. Do NOT scan for nucleation sites — a sweep over regions is a supervisor, and the aggregate that made a sweep necessary is the defect.
|
||||
3. **Let a curiosity seed activation.** One activation process, two seed sources (§5.4). No thread, no scheduler, no capacity check, no timer.
|
||||
|
||||
Then:
|
||||
|
||||
4. Grounding becomes the edge weight: multidimensional vector (§2), two axes (§3), timestamped. Delete `grounded-by` and `cog_ground_edge`.
|
||||
5. Decay analytic from the last recorded point; derived values (§2) stop being stored.
|
||||
6. Consolidation-gated supersession on salience, versioning the whole vector jointly.
|
||||
7. Traversal on factual; `assert` on both floors with the per-value `min`.
|
||||
8. Abduction as crystallization at a nucleation site, validated by re-fit: propose the candidate hub, re-fit the region with it included, recompute the residual. If the residual materially shrinks, the hypothesis dissolves the surprise. Without the re-fit it is clustering with extra steps. Ranking falls out as residual-reduction-per-added-axis — Occam, derived rather than tuned.
|
||||
9. **One dreamer.** The launch-agent fragments and the POST beats fold in or are deleted. `soul.el`'s continuous loop is the shape they fold *into*.
|
||||
10. **No tickers, no cron.** A brain has neither. Every `StartInterval`, every `Hour`/`Minute`, every POST-to-beat marks a place where an intrinsic rhythm was replaced by an external clock — a supervisor invented for something that should be a property. **The presence of a ticker is the diagnostic.**
|
||||
11. Land §9 as gates rather than review habits.
|
||||
|
||||
## 12. Open questions, and what is inferred
|
||||
|
||||
- **Open:** whether decision provenance requires recording activation traces, or whether region + timestamp is sufficient (§8).
|
||||
- **Open:** what accrues relational weight without circularity. Candidate: it accrues from **outcome** — the values regions are grounded in lived moments, so a relation earns relational weight when acting on it produced something corresponding to those moments. That keeps it out of the measurement loop and makes relational grounding necessarily slower than factual, which may be the same fact as §5.3 appearing twice.
|
||||
- **Open:** context. A relation can hold in one situation and not another, and without something for it you get overgeneralization. It does not read as a dimension of the same vector — more like a conditioning, or separate edges sharing an identity. Making it a scalar dimension would repeat the `inhibitory` flattening.
|
||||
- **Known wrong shape:** #147 fixed `ground`'s honesty — it no longer misreports which nodes it used and refuses circular support — but it still mints an edge and returns a float at an instant. It corrected a scalar rather than deleting the operation.
|
||||
+82
-3
@@ -31,6 +31,7 @@ This section is the **single source of truth** for what works and what is planne
|
||||
- Codegen: function definitions, top-level `main()`, all expression forms above, control flow, decorator-as-AST-attachment.
|
||||
- Boundary seam: decorator arguments and stacking; VBD role enforcement via `#error`; `engram_boundary_beat` auto-emit at `@manager`/`@accessor` entry; `@route` dispatch tables (Section 9).
|
||||
- Program-level declarative blocks: `cgi`, `service`, and `program` — the last carrying process identity and configuration (Section 18).
|
||||
- **Geometry as a first-class value, and realizers declarable in El** — the `Geometry` type, the wire adapters, and `transduce` (Section 20). Landed 2026-08-16 (#141, #144).
|
||||
- C runtime: I/O, string operations, integer math, lists, maps, filesystem, command-line args, basic `json_get` substring lookup.
|
||||
|
||||
### Planned (in flight)
|
||||
@@ -41,9 +42,9 @@ This section is the **single source of truth** for what works and what is planne
|
||||
- **`cgi` block parsing.** Currently lexed (`cgi` is a keyword) but not parsed as a statement. Adding `parse_cgi_block` and codegen of `el_cgi_init` at the head of `main()`.
|
||||
- **Boundary epilogues.** The decorator seam injects a prologue only. Adding prologue/epilogue wrapping, the prerequisite for durability-as-an-effect (Section 19.1).
|
||||
- **`vessel` keyword.** Replaces `package` in manifests. Adding to lexer.
|
||||
- **Real `engram_*` runtime.** Currently stub. Adding in-process graph store with spreading activation, Hebbian strengthening, and disk persistence — see Section 16.4.
|
||||
- **Real `dharma_*` runtime.** Currently stub. Adding network transport, channel registry, identity resolution.
|
||||
- **Real `http_get`/`http_post`/`http_serve`.** Currently empty stubs. Adding libcurl-backed client and a thread-pool server.
|
||||
- ~~**Real `engram_*` runtime.** Currently stub.~~ **Stale (verified 2026-08-16) — this is implemented, not planned.** `lang/runtime/el_runtime.c` carries the in-process graph store with spreading activation, Hebbian strengthening, disk persistence (paged store, magic `ENGST01`), an HNSW vector index behind a `eg_vindex_view`/`eg_vindex_maintain` publication boundary, and the full cognition surface (`engram_think_json`, `engram_ground_json`, `engram_assert_json`, `engram_attend_json`, `engram_correspondence_beat_json`). The "stub" description may still hold for the **lagging forks** (`lang/el-compiler/runtime/`, `products/web/runtime/`) — see `AGENTS.md`, which names those as downstream copies that cannot build the engram product. **Which runtime this line refers to needs a decision; it is not a fact that can be recovered from the text.**
|
||||
- ~~**Real `dharma_*` runtime.** Currently stub.~~ **Needs re-verification (2026-08-16).** Not checked in this pass; do not rely on either reading.
|
||||
- ~~**Real `http_get`/`http_post`/`http_serve`.** Currently empty stubs.~~ **Stale.** libcurl-backed HTTP and a thread-pool server are live — `http_serve_async` is what `neuron/soul.el:729` runs before entering its awareness loop, and `realizer_register` resolves El functions through the same `dlsym` mechanism `http_set_handler` relies on.
|
||||
- **JSON, time, UUID, state, env, additional string/list/math builtins.** See Section 12 for the canonical list.
|
||||
|
||||
### Not in this language
|
||||
@@ -1250,6 +1251,84 @@ Implementing either now would mean editing files under concurrent modification a
|
||||
|
||||
The prerequisite for 19.1 is the same in both cases: **lift the §9 seam from prologue-only to prologue/epilogue.** That change is independent of both collisions and can land first.
|
||||
|
||||
*(Status note, 2026-08-16: the geometry/`transduce` collision named above has since landed — see Section 20. The VIndex read-path collision has also landed; see `lang/spec/runtime-ownership.md` §5. 19.1 and 19.2 remain unimplemented, but the stated reason no longer holds for those two files.)*
|
||||
|
||||
---
|
||||
|
||||
## 20. Geometry — signal as a first-class value [implemented]
|
||||
|
||||
Landed 2026-08-16 (#141, #144). Declared here because the spec is the single source of truth for implemented-vs-planned, and this is a language surface, not a runtime detail.
|
||||
|
||||
### 20.1 Why this exists
|
||||
|
||||
Until 2026-08-16 no El ingest path could carry a vector. Nodes took **text**, and geometry was *derived* from that text. Text was therefore the **mandatory entry medium**: any non-text modality — a tone, a pulse, an image, a voice sample — had to be *described in prose first*, and the geometry subsequently reasoned over was the geometry **of the description, not of the signal**.
|
||||
|
||||
Two changes remove that, and neither is engram-specific — which is why they are in the language and not in the graph. Any program touching any modality needs them; the engram is merely one El program that happens to hold a graph.
|
||||
|
||||
1. **Geometry is a value that carries its own width.**
|
||||
2. **A realizer is an ordinary El function** — so admitting a new modality never requires a runtime patch.
|
||||
|
||||
### 20.2 The `Geometry` type
|
||||
|
||||
`Geometry` is an opaque boxed pointer, exactly like `Instant` / `Calendar` / `Rhythm`. **No codegen change was required** to add it — the annotation is just a type name.
|
||||
|
||||
```el
|
||||
let g: Geometry = geometry_new(4)
|
||||
```
|
||||
|
||||
| builtin | returns | notes |
|
||||
|---|---|---|
|
||||
| `geometry_new(dim)` | `Geometry` | zero-filled; `0` on failure |
|
||||
| `geometry_dim(g)` | `Int` | width; `0` if not a Geometry |
|
||||
| `geometry_is(g)` | `Int` | `1` if a live Geometry |
|
||||
| `geometry_get(g, i)` | `Float` | component |
|
||||
| `geometry_set(g, i, x)` | `Int` | `1` ok, `0` out of range |
|
||||
| `geometry_norm(g)` | `Float` | L2 — lets a caller check a realizer emitted **signal, not zeros** |
|
||||
| `geometry_free(g)` | `Int` | `1` if freed. Returns a value rather than `void` so it is safe in any expression position without a codegen void-builtin table entry |
|
||||
|
||||
**Ownership.** A `Geometry` is owned by the El caller and released with `geometry_free`. `node_attach_geometry` **copies**, so a node and the caller's value have independent lifetimes.
|
||||
|
||||
### 20.3 Wire adapters — the only place an encoding appears
|
||||
|
||||
```el
|
||||
geometry_from_f32le_hex(hex) -> Geometry // 0 on empty / odd-length / non-hex
|
||||
geometry_to_f32le_hex(g) -> String // "" if not a Geometry
|
||||
```
|
||||
|
||||
`f32le hex` is little-endian float32, 8 hex chars per component — the encoding the perception vessel's `/voice/embed` already emits. **The width is derived from the input length, never supplied by a caller**, which is why there is no max-dim constant to validate a claimed length against. Encodings appear here and nowhere else: at the edge.
|
||||
|
||||
### 20.4 Realizers and `transduce`
|
||||
|
||||
A **realizer** maps one modality into geometry. Registration is **by name**: every El `fn name(...)` compiles to a global C symbol with that exact name, and the registry resolves it with `dlsym` against the running binary — the same mechanism `http_set_handler` already relies on.
|
||||
|
||||
```el
|
||||
fn tone_realizer(signal: String) -> Geometry {
|
||||
let g: Geometry = geometry_new(4)
|
||||
let n: Int = str_len(signal)
|
||||
let a: Int = geometry_set(g, 0, int_to_float(n))
|
||||
g
|
||||
}
|
||||
|
||||
realizer_register("tone", "tone_realizer") // 1 ok / 0 unresolved
|
||||
let g: Geometry = transduce(sample, "tone") // Geometry, or 0 if no organ
|
||||
realizer_has("tone") // 1 if registered
|
||||
```
|
||||
|
||||
The registry keys on **modality**, not on registration order. `transduce` returns `0` when no organ is registered for the modality — an absent organ is a reportable state, not a silent zero vector.
|
||||
|
||||
**The claim this makes:** a realizer is not in the runtime and not known to the compiler. Adding a modality is writing an El function and registering a name. `lang/examples/transduce.el` is the worked example and doubles as an executable proof — it exits non-zero if any check fails.
|
||||
|
||||
### 20.5 Two comparison hazards this surface exposed
|
||||
|
||||
Both were **measured**, not stylistic, and both are properties of the current `elc` that any El author should know:
|
||||
|
||||
- **`==` lowers numerically only when both operand *names* are in the per-function int-name set** that `let x: Int` populates. A bare `f(x) == 0` is not a registered name and lowers to `str_eq` — `strcmp` on two integers reinterpreted as pointers. `<` and `>` lower directly with no inference, so truthiness against a builtin's return is written `> 0` / `< 1`.
|
||||
- **`+` dispatches on whether both operands are known-Int, and a user-defined `fn` call is not.** `let fails: Int = fails + check(...)` lowered to **string concatenation** and printed `4343632752` — a pointer. Nothing was wrong with the checks; the tally was lying. Failing fast needs no arithmetic at all, so there is nothing left to get wrong.
|
||||
|
||||
### 20.6 What this does not do
|
||||
|
||||
`transduce` produces geometry; it does not decide what the geometry *means*. Nothing here grounds anything. Grounding is the edge weight in the graph the geometry is later attached to — see `lang/spec/correspondence-and-censorship.md`.
|
||||
|
||||
---
|
||||
|
||||
End of specification.
|
||||
|
||||
@@ -28,12 +28,12 @@ Each of these is a distinct merged or proposed fix. Each addresses one deposit.
|
||||
| VIndex freed under a concurrent reader | `el_runtime.c:9424` | `fb32d15` guard (merged 08:46:43) |
|
||||
| `_eg_vindex_seen` realloc'd on a read path | `el_runtime.c:9412` | same guard |
|
||||
| `vindex_insert` on a read path | `el_runtime.c:9434`, `9450` | same guard |
|
||||
| shared `visited` / epoch scratch stomped by concurrent searches | `engram_vindex.c:79–81`, `169–186`, `195` | proposed: move to per-search frame |
|
||||
| shared `visited` / epoch scratch stomped by concurrent searches | `engram_vindex.c:79–81`, `169–186`, `195` | ~~proposed:~~ **built** — moved to the call frame (§3.1(1), §5); TSan `readers` half clean (§7a) |
|
||||
| nine append sites, none indexing → lazily-embedded nodes invisible | `el_runtime.c:7806, 7988, 8148, 8224, 11526, 11731, 12050, 15295, 15312` | "embed-gap #20", patched by making the *read* path catch up (`9439` comment) |
|
||||
|
||||
**Measured:** all file/line references above, read 2026-08-16. Crash frames `engram_activate → eg_vindex_sync → vindex_insert → _realloc → _xzm_xzone_malloc_freelist_outlined` are accounted for by rows 2–4.
|
||||
|
||||
**Inferred, not yet verified:** that the nine append sites do not share a single commit point. This needs one pass before Change C is sized.
|
||||
~~**Inferred, not yet verified:** that the nine append sites do not share a single commit point. This needs one pass before Change C is sized.~~ **Moot — see §7.** The question was mis-aimed: node append is not the event that owns index membership, because a node without an embedding cannot be in a vector index. The five *embedding-assignment* sites are the real owner points.
|
||||
|
||||
---
|
||||
|
||||
@@ -135,12 +135,21 @@ The payoff of owning the language is unchanged and is now *cheaper*: introduced
|
||||
|
||||
## 6. Sequencing
|
||||
|
||||
> **⚠ Steps 2–5 belong to the abandoned capability-ABI §3 and are superseded
|
||||
> (2026-08-16).** §3 was re-derived: the engram is immutable and recall is
|
||||
> projection, so *what does not mutate needs no ownership discipline* and the
|
||||
> question is dissolved rather than answered. There is no context type, no
|
||||
> capability type, and no codegen change — **`const` is the capability**, and the
|
||||
> constraint travels with the type of the thing rather than the shape of every call
|
||||
> site, so **no sweep is needed at all** (§4). Steps 1, 6 and 7 stand. Struck rather
|
||||
> than deleted, because the abandoned plan is why §4's cost argument is short.
|
||||
|
||||
1. **Read** how builtins are declared and dispatched, to confirm the call sites are compiler-generated in one place. *(This determines whether §4 holds. If dispatch is scattered, re-size before proceeding.)*
|
||||
2. Introduce the context type and capability types.
|
||||
3. Codegen emits the context at every builtin call site.
|
||||
4. Mechanical sweep of builtin signatures.
|
||||
5. Move index maintenance behind the write capability; the three read callers take the read capability.
|
||||
6. Delete the residue-fixes listed in §5.
|
||||
2. ~~Introduce the context type and capability types.~~ **Superseded** — `const`.
|
||||
3. ~~Codegen emits the context at every builtin call site.~~ **Superseded** — no codegen change.
|
||||
4. ~~Mechanical sweep of builtin signatures.~~ **Superseded** — the constraint travels with the type.
|
||||
5. ~~Move index maintenance behind the write capability; the three read callers take the read capability.~~ **Done, differently:** `eg_vindex_maintain` (exclusive, sole mutator) / `eg_vindex_view` (`const VIndex*`, shared readers), with `eg_vindex_note_embedded` as the write-side owner. This is a **publication** boundary, not a capability split — HNSW insert is not an append, so purity alone was insufficient (§2a, §3.1(3)).
|
||||
6. Delete the residue-fixes listed in §5. *(Partially done — see §5's "NOT deleted" list; a residue whose structure has not been converted must be left standing.)*
|
||||
7. **One** build of soul from el dev — which resolves the `state_get` leak and the crash together, rather than deploying a leak fix that reintroduces the crash.
|
||||
|
||||
---
|
||||
|
||||
@@ -45,17 +45,43 @@ returned 60k–230k-char unbounded traversals (this very session hit 104 KB and
|
||||
|
||||
## Layer 2 — primitive agentic tools (Neuron runs itself)
|
||||
|
||||
The base verbs all agentic behavior composes from — grounded in the LIVE
|
||||
cog-arch (`think` is the one operation; faculties are its steering-space labels;
|
||||
the correspondence-beat is the reflexive learning loop).
|
||||
The base verbs all agentic behavior composes from.
|
||||
|
||||
> **⚠ The "PROVEN" verdicts in this table were measured against a build dated
|
||||
> 2026-08-14 and four of the five are now known to have been proving the wrong
|
||||
> thing (2026-08-16).** A verdict of PROVEN meant *the route returned a
|
||||
> well-formed response*, not *the response was derivable from what produced it*.
|
||||
> Corrections below, each with the measurement. Authority:
|
||||
> `lang/spec/correspondence-and-censorship.md`.
|
||||
|
||||
| op | signature | engram builtin | status on clone (gate-1 recipe) |
|
||||
|----|-----------|----------------|---------------------------------|
|
||||
| `think` | `think({seeds, faculty})` faculty ∈ reason·abduce·induce·plan·analogize·recognize·discern·synthesize | `engram_think_json` | **PROVEN** — all 8 faculties return real 768-dim gradients (n_support 30–282) |
|
||||
| `think` | `think({seeds, faculty})` faculty ∈ reason·abduce·induce·plan·analogize·recognize·discern·synthesize | `engram_think_json` | ~~PROVEN — all 8 faculties return real 768-dim gradients~~ **RETRACTED, then re-proven differently.** The gradients were real in *shape* only: the call passed `NULL` as the anchor, `engram_think` re-origins at `anchor ? anchor : region->centroid`, and **the centroid is the one point where the gradient is zero by construction**. Measured: every faculty returned `{"direction":[0,0,…],"spread":0,"magnitude":1,"confidence":0.5}` — identical, differing only in its label. Fixed in **#141/#142**; gradients now vary by seed |
|
||||
| `attend` | `attend({node, observer, salience})` | `engram_attend_json` | **PROVEN** (returns `salient-to`) |
|
||||
| `assert` | `assert({claim, for_whom, floor})` — realize, honesty-floored | `engram_assert_json` | **PROVEN** |
|
||||
| `ground` | `ground({claim, evidence, for_whom})` node-id anchors | `engram_ground_json` | **PROVEN** (grounded-by edge, grounding=0.912, written) |
|
||||
| `learn` | `learn({seeds, faculty, keystone})` — the correspondence-beat | `engram_correspondence_beat_json` | **PROVEN** (real Stance: `stance-induce-…`, brier, reliability, written) |
|
||||
| `assert` | `assert({claim, for_whom, floor})` — realize, honesty-floored | `engram_assert_json` | **PARTIAL.** `may_assert` is real. `"still_held"` is a **hardcoded literal `true`** — `el_runtime.c:14538` emits it unconditionally, so it reports nothing it measured. Violates the invariant *a returned value must be derivable from what produced it* |
|
||||
| `ground` | `ground({claim, evidence, for_whom})` node-id anchors | `engram_ground_json` | ~~PROVEN (grounded-by edge, grounding=0.912, written)~~ **RETRACTED.** That 0.912 was structural, not evidential: the call wrote the edge between the two *region hubs* and echoed them back as though they were the caller's input, so when both seeds resolved into one region it **grounded a node against itself and returned a confident score**. Measured: grounding `3b9ced5d` against `6edf8c79` scored **0.98883** purely because `6edf8c79` is the hub of `3b9ced5d`'s region; two independent agents reported 0.885 / 0.909 self-groundings as confident. **#147** grounds the node asked about, reports `claim_region`/`evidence_region` separately, and refuses three circular shapes. **The operation itself is still the wrong shape** — see below |
|
||||
| `learn` | `learn({seeds, faculty, keystone})` — the correspondence-beat | `engram_correspondence_beat_json` | **PROVEN, and it was writing into a void.** The Stance, brier and reliability were real and really persisted — but `think` built a *neutral* stance every call and never loaded them, so every beat's calibration was written and thrown away on the next read. Fixed in **#146**: `think` resumes `stance-<faculty>-<hub>`, the same id the beat writes. Confidence **0.5 → 0.930726** on a calibrated region |
|
||||
|
||||
### What this table gets structurally wrong
|
||||
|
||||
- **`faculty` is not a parameter.** `reason` changes the *estimate* (a read),
|
||||
`induce` changes the *parameters* (this is exactly what `learn` does), and
|
||||
`abduce` changes the *structure* — a **write**, which `GeoGradient` cannot
|
||||
express. A write cannot be a parameter of a read. That the eight were listed as
|
||||
interchangeable values of one argument is why all eight returning the same thing
|
||||
looked like a pass. Underneath, `engram/src/server.el:1870–1886` routes six of
|
||||
them into one call with a string argument, and the name only reaches
|
||||
`engram_think` through the stance — `cog_stance_init` stores it and nothing
|
||||
reads it.
|
||||
- **`ground` should not mint an edge at all.** Grounding is not a subsystem and
|
||||
not a score: **it is the edge weight.** `grounded-by` as a relation type models
|
||||
grounding as a relation *between* nodes when it is a property *of* a relation.
|
||||
#147 corrected a scalar rather than deleting the operation; deletion is
|
||||
sequenced.
|
||||
- **`addWonderQuestion`** (Layer 1, `write`) treats wonder as an enumerable
|
||||
instance you push. **Wonder is the boundary** — where activation spreads and
|
||||
finds thin or absent geometry. There are about six, the same for everyone, and
|
||||
they never close. A manifest materializes a property as a stored artifact.
|
||||
|
||||
`comprehend`/`realize`/`intend` are **compositions**, not separate live
|
||||
primitives: comprehend = write+activate (world→geometry), realize = assert
|
||||
@@ -69,6 +95,26 @@ execution→integrate) composes over `think`+`ground`+`learn`+`write`/`relate`.
|
||||
`kn-efeb4a5b…` / `kn-5b606390…`, are refused — identity routes through
|
||||
intentional-cultivation, as enforced today.
|
||||
|
||||
> **⚠ SUPERSEDED (2026-08-16).** This describes what the surface enforces, which
|
||||
> is accurate — but the enforcement is the wrong kind of thing:
|
||||
>
|
||||
> > **In an immutable substrate, any mechanism that refuses a write is either
|
||||
> > redundant with immutability, or an epistemic constraint misfiled as a
|
||||
> > protective one.**
|
||||
>
|
||||
> "Keystone" means **load-bearing**, not precious. The real requirement is
|
||||
> **non-circularity of the reference frame** — a reference fitted to its own
|
||||
> readings reports perfect correspondence forever while drift becomes undetectable
|
||||
> from inside — and that is satisfied *temporally*, not by a gate: the frame
|
||||
> updates while activation is internally seeded, not while it is being used to act.
|
||||
> **Independence is *when*, not *what*.** Corruption requires mutation, and the
|
||||
> engram does not mutate: recoverability (the predecessor is always present),
|
||||
> governance (supersession *is* the audit trail), evidence quality, and rate all
|
||||
> fall out of the substrate. **Authorization** is the only residue and it is
|
||||
> bounded — an unauthorized writer can *propose*, never erase. Note also that the
|
||||
> live check is a substring match against two hard-coded ids
|
||||
> (`el_runtime.c:14337`).
|
||||
|
||||
## How the caller invokes Neuron agentically
|
||||
|
||||
Once the ops are registered as MCP tools (aliases in `surface.el`), the caller
|
||||
@@ -94,6 +140,16 @@ running itself.
|
||||
## Honest ledger (built vs staged)
|
||||
- **Route seam — IMPLEMENTED + PROVEN:** ported the `@route` codegen (from `feat/el-route-decorators`) into the worktree, rebuilt `elc` self-host, proved decorate→serve (`route_proof.el` on :8951); `surface.el` compiles with `el_route_dispatch` generated for all 8 ops.
|
||||
- **All ops PROVEN live on the clone** (gate-1 boot recipe, node-id anchors): read, write, relate, supersede (immutable), tombstone, think (8 faculties), ground, attend, learn — daemon alive through all mutations (node_count 13173→13176).
|
||||
> **⚠ Retracted in part (2026-08-16).** "The daemon stayed alive and every route
|
||||
> returned a well-formed response" is what was actually proven, and that is a
|
||||
> weaker claim than it reads as. See the Layer-2 table: `think` was reading at the
|
||||
> zero-gradient point, `ground` was scoring nodes against themselves, `assert`
|
||||
> emits a hardcoded field, and `learn` was persisting into a void. **A build that
|
||||
> passes because nothing checks whether a returned value is derivable from what
|
||||
> produced it has not been tested — it has been observed not to crash.** The
|
||||
> related discipline gap, also 2026-08-16: **no test without a negative control**
|
||||
> (#148's first attempt passed on the unpatched build too), and **no deploy
|
||||
> without verifying the artifact carries the fix** (nine instances in one session).
|
||||
- **Aperture-boundedness PROVEN:** vantage-read `limit=3 → 15 KB` vs `limit=50 → 363 KB` (fixes the whole-self dump).
|
||||
- **Bus:** `@manager` ops emit on the real `dharma_*` bus (explicit today, compiles) — same transport as the swarm (`wt/swarm-ccr`).
|
||||
- **STAGED (not guessed — needs the cognition-engram rebuild to verify link):** auto-injecting telemetry/interoception + bus emission at the decorated boundary (`cg_fn` diff in `SEAM_STAGED.md`); building the cognition engram with `surface.el` compiled in. No promote to live, no cutover (per rails).
|
||||
|
||||
Reference in New Issue
Block a user