Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| caa1206af5 | |||
| 914bab11d2 | |||
| e239f2894c | |||
| 4a57b4faa8 | |||
| 0ee82d9e91 | |||
| 9526bda507 | |||
| 0389bf9363 | |||
| fe820928b0 | |||
| 385c18442d | |||
| cace6a5ebf | |||
| 7a1501d097 | |||
| d41645388a | |||
| 8a307dfd42 | |||
| 616815b2ab | |||
| 1a8a966cb3 | |||
| 1f70b9fa18 | |||
| 317466e8f7 | |||
| eb3e6d7c1f | |||
| 88e3008735 | |||
| 3fcc36c2f1 |
@@ -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.
|
||||
|
||||
|
||||
+104
-17
@@ -296,6 +296,24 @@ fn persist_bulk() -> Int {
|
||||
return persist_canonical()
|
||||
}
|
||||
|
||||
// COMPILER LANDMINE, measured 2026-08-16 — do not inline this back into the
|
||||
// caller. elc lowers `a == b` to numeric comparison only when both operand
|
||||
// NAMES are in the per-function int-name set, which `let x: Int` populates.
|
||||
// That registration does NOT propagate into a nested if-expression block: the
|
||||
// first cut of the geometry-ingest path wrote `let claimed: Int = ...` and
|
||||
// `let got: Int = ...` inside the else-arm and `claimed == got` came out of
|
||||
// codegen as `str_eq(claimed, got)` — strcmp on two integers reinterpreted as
|
||||
// pointers, i.e. a segfault on the first geometry-bearing request. Read back
|
||||
// out of the generated C, not guessed. Function PARAMETERS annotated `: Int`
|
||||
// do register reliably (verified: `if (claimed == actual)`), so the comparison
|
||||
// lives in a function of its own. Note also the explicit `return`s — a trailing
|
||||
// if-EXPRESSION at a function tail emits as a statement and the function
|
||||
// returns 0 regardless, which is the same probe's second finding.
|
||||
fn width_agrees(claimed: Int, actual: Int) -> Int {
|
||||
if claimed == actual { return 1 }
|
||||
return 0
|
||||
}
|
||||
|
||||
// INCOMPLETE-ROUTE FIX (2026-07-24 self-review): this route silently dropped
|
||||
// label, importance, tier, and tags — engram_node() defaults label to content
|
||||
// and importance to 0.5, so every node created over HTTP lost its metadata.
|
||||
@@ -337,26 +355,44 @@ fn route_create_node(method: String, path: String, body: String) -> String {
|
||||
salience, importance, confidence,
|
||||
tier, tags
|
||||
)
|
||||
// GEOMETRY INGEST (2026-08-16 self-review): this route accepted an "emb"
|
||||
// field, returned 200 with a fresh id, and stored NOTHING — engram_node_full
|
||||
// has no vector parameter, so the caller's geometry was silently discarded
|
||||
// and the node came back emb_dim=None / embedded:false. Measured live while
|
||||
// trying to admit a voice signal. The consequence was structural, not
|
||||
// cosmetic: text was the only entry medium, so any non-text modality had to
|
||||
// be DESCRIBED in prose and what we then reasoned over was the geometry of
|
||||
// the description, not of the signal.
|
||||
// GEOMETRY INGEST — geometry-valued end to end (2026-08-16).
|
||||
//
|
||||
// "emb" is little-endian float32 hex (dim*8 chars) — the encoding the
|
||||
// perception vessel's /voice/embed already emits, so a realizer's output
|
||||
// moves in with no float-array round trip. "dim" defaults to the vector's
|
||||
// implied width. Off-dimension vectors are stored but not inserted into the
|
||||
// resident index (its build loop filters on emb_dim), so a modality vector
|
||||
// is durable and addressable without perturbing the canonical index.
|
||||
// The defect this route originally had: it accepted an "emb" field,
|
||||
// returned 200 with a fresh id, and stored NOTHING, because engram_node_full
|
||||
// has no vector parameter. The consequence was structural, not cosmetic —
|
||||
// text was the only entry medium, so any non-text modality had to be
|
||||
// DESCRIBED in prose, and what we then reasoned over was the geometry of the
|
||||
// description, not of the signal.
|
||||
//
|
||||
// #141 fixed the drop but marshalled the vector as a hex STRING through
|
||||
// engram_node_set_emb, which put text back as the TRANSPORT medium one layer
|
||||
// below the problem being fixed. This is that correction: hex is decoded
|
||||
// exactly ONCE, here at the edge, into a first-class Geometry, and every
|
||||
// step below this line moves geometry rather than text. An encoding at the
|
||||
// boundary is what an encoding is for.
|
||||
//
|
||||
// The WIRE is deliberately unchanged — "emb" is still little-endian float32
|
||||
// hex (8 chars per component), the encoding the perception vessel's
|
||||
// /voice/embed already emits — because production clients speak it. What
|
||||
// changed is underneath it.
|
||||
//
|
||||
// "dim" is now treated as an ASSERTION about the vector the caller sent, not
|
||||
// as the source of its width: a Geometry carries its own width. A stated dim
|
||||
// that disagrees is a REJECTED ingest, not a silent reinterpretation. Omitting
|
||||
// "dim" is fine and means "trust the vector", which is the honest default.
|
||||
//
|
||||
// Off-dimension vectors remain stored but not inserted into the resident HNSW
|
||||
// index (its build loop filters on emb_dim), so a 64-dim voice geometry is
|
||||
// durable and addressable without perturbing the 768-dim canonical index.
|
||||
let emb_hex: String = json_get_string(body, "emb")
|
||||
let emb_set: Int = if str_eq(emb_hex, "") { 0 } else {
|
||||
let g: Geometry = geometry_from_f32le_hex(emb_hex)
|
||||
let got: Int = geometry_dim(g)
|
||||
let dim_raw: String = json_get_raw(body, "dim")
|
||||
let dim: Int = if str_eq(dim_raw, "") { str_len(emb_hex) / 8 } else { json_get_int(body, "dim") }
|
||||
engram_node_set_emb(id, emb_hex, dim)
|
||||
let claimed: Int = if str_eq(dim_raw, "") { got } else { json_get_int(body, "dim") }
|
||||
let landed: Int = if width_agrees(claimed, got) > 0 { node_attach_geometry(id, g) } else { 0 }
|
||||
let freed: Int = geometry_free(g)
|
||||
landed
|
||||
}
|
||||
let saved: Int = persist_node(id)
|
||||
// ORPHAN PREVENTION (ENGRAM_AUTOCONNECT): connect the fresh node to its
|
||||
@@ -989,6 +1025,22 @@ fn route_similarity(method: String, path: String, body: String) -> String {
|
||||
// nothing on request. NOTE: the offline reify WRITER (engram_geo_reify_store) is
|
||||
// currently unwired, so on the live store the resident index is empty and the
|
||||
// list returns [] until reification runs — see the cutover report.
|
||||
// route_scan_emb — GET /api/nodes/emb?limit=&offset= — read the raw geometry.
|
||||
//
|
||||
// engram_scan_nodes_emb_json has existed as a builtin with NO ROUTE, so the
|
||||
// embeddings — the actual positions every distance, angle, membership and
|
||||
// grounding is computed from — were unreadable from outside the process. You
|
||||
// cannot verify a coordinate system you cannot see, and every claim about the
|
||||
// frame (isotropy, centering, what the origin is) was therefore unfalsifiable
|
||||
// from the API. Read-only.
|
||||
fn route_scan_emb(method: String, path: String, body: String) -> String {
|
||||
let l_raw: String = query_param(path, "limit")
|
||||
let o_raw: String = query_param(path, "offset")
|
||||
let l: Int = if str_eq(l_raw, "") { 200 } else { str_to_int(l_raw) }
|
||||
let o: Int = if str_eq(o_raw, "") { 0 } else { str_to_int(o_raw) }
|
||||
return engram_scan_nodes_emb_json(l, o)
|
||||
}
|
||||
|
||||
fn route_neighborhoods(method: String, path: String, body: String) -> String {
|
||||
engram_geo_reify_list_json()
|
||||
}
|
||||
@@ -1082,6 +1134,11 @@ fn route_faculty(path: String, faculty: String) -> String {
|
||||
fn route_boundary_proof(method: String, path: String, body: String) -> String {
|
||||
return "{\"op\":\"boundary_proof\",\"body_instrumentation\":\"none\",\"seam\":\"@manager -> engram_boundary_beat auto-injected\"}"
|
||||
}
|
||||
// ── GROUNDING: an attribute of the RELATION, and the relation's weight is a
|
||||
// VECTOR (factual, relational, associative, polarity, provenance, timestamp).
|
||||
// /api/ground READS it — it never writes. /api/ground/record is the write,
|
||||
// named as one, and it consolidates only on a consequential + salient move.
|
||||
// /api/ground/trajectory reads the supersession chain as a time series.
|
||||
fn route_ground(method: String, path: String, body: String) -> String {
|
||||
let claim: String = json_get_string(body, "claim")
|
||||
let evidence: String = json_get_string(body, "evidence")
|
||||
@@ -1090,12 +1147,31 @@ fn route_ground(method: String, path: String, body: String) -> String {
|
||||
if str_eq(evidence, "") { return err_json("missing evidence") }
|
||||
return engram_ground_json(claim, evidence, for_whom)
|
||||
}
|
||||
fn route_ground_record(method: String, path: String, body: String) -> String {
|
||||
let claim: String = json_get_string(body, "claim")
|
||||
let evidence: String = json_get_string(body, "evidence")
|
||||
let provenance: String = json_get_string(body, "provenance")
|
||||
let floor: String = json_get_string(body, "floor")
|
||||
if str_eq(claim, "") { return err_json("missing claim") }
|
||||
if str_eq(evidence, "") { return err_json("missing evidence") }
|
||||
return engram_ground_record_json(claim, evidence, provenance, floor)
|
||||
}
|
||||
fn route_ground_trajectory(method: String, path: String, body: String) -> String {
|
||||
let claim: String = query_param(path, "claim")
|
||||
let evidence: String = query_param(path, "evidence")
|
||||
if str_eq(claim, "") { return err_json("missing claim") }
|
||||
if str_eq(evidence, "") { return err_json("missing evidence") }
|
||||
return engram_ground_trajectory_json(claim, evidence)
|
||||
}
|
||||
fn route_assert(method: String, path: String, body: String) -> String {
|
||||
let claim: String = query_param(path, "claim")
|
||||
if str_eq(claim, "") { return err_json("missing claim") }
|
||||
let for_whom: String = query_param(path, "for_whom")
|
||||
let floor: String = query_param(path, "floor")
|
||||
return engram_assert_json(claim, for_whom, floor)
|
||||
// Both floors. A well-evidenced claim does not earn the right to be asserted
|
||||
// regardless of whether it means the right thing. rel_floor defaults to floor.
|
||||
let rel_floor: String = query_param(path, "rel_floor")
|
||||
return engram_assert_json(claim, for_whom, floor, rel_floor)
|
||||
}
|
||||
fn route_attend(method: String, path: String, body: String) -> String {
|
||||
let node: String = json_get_string(body, "node")
|
||||
@@ -1760,6 +1836,9 @@ fn handle_request(method: String, path: String, body: String) -> String {
|
||||
if str_eq(method, "GET") && (str_eq(clean, "/api/edges") || str_eq(clean, "/edges")) {
|
||||
return route_scan_edges(method, path, body)
|
||||
}
|
||||
if str_eq(method, "GET") && (str_eq(clean, "/api/nodes/emb") || str_eq(clean, "/nodes/emb")) {
|
||||
return route_scan_emb(method, path, body)
|
||||
}
|
||||
if str_eq(method, "GET") && str_starts_with(clean, "/api/nodes/") {
|
||||
return route_get_node(method, path, body)
|
||||
}
|
||||
@@ -1849,6 +1928,14 @@ fn handle_request(method: String, path: String, body: String) -> String {
|
||||
if str_eq(method, "GET") && str_starts_with(clean, "/api/plan") {
|
||||
return route_faculty(path, "plan")
|
||||
}
|
||||
// Order matters: the more specific paths must be tested before the /api/ground
|
||||
// prefix match below, which would otherwise swallow them.
|
||||
if str_eq(method, "POST") && str_starts_with(clean, "/api/ground/record") {
|
||||
return route_ground_record(method, path, body)
|
||||
}
|
||||
if str_eq(method, "GET") && str_starts_with(clean, "/api/ground/trajectory") {
|
||||
return route_ground_trajectory(method, path, body)
|
||||
}
|
||||
if str_eq(method, "POST") && str_starts_with(clean, "/api/ground") {
|
||||
return route_ground(method, path, body)
|
||||
}
|
||||
|
||||
Executable
+40
@@ -0,0 +1,40 @@
|
||||
#!/bin/sh
|
||||
# Build + RUN the §7 GROUNDING-VECTOR tests (engram_cognition.c): the one decay
|
||||
# model, the consequence gate, and the stored/derived split. Closed-form
|
||||
# constructed cases — no server, no store, no network. Pure C11 (stdlib + libm).
|
||||
# Standalone — NOT folded through elc. Two passes:
|
||||
# 1. PERF — optimised (-O2, no sanitizer): the functional gate.
|
||||
# 2. SAFETY — ASan + UBSan on the same suite.
|
||||
#
|
||||
# NEGATIVE CONTROL (invariant §8.6 — no test without one). Every symbol this
|
||||
# suite exercises (cog_decay_factor, cog_grounding_significant,
|
||||
# cog_significance_inherent, CogGrounding, CogProvClass) is introduced by the
|
||||
# change under test, so the suite does not COMPILE against the pre-change source.
|
||||
# To reproduce:
|
||||
# git show origin/dev:lang/runtime/engram_cognition.h > /tmp/pre/engram_cognition.h
|
||||
# git show origin/dev:lang/runtime/engram_cognition.c > /tmp/pre/engram_cognition.c
|
||||
# cc -I/tmp/pre engram/test/test_grounding_vector.c /tmp/pre/engram_cognition.c ...
|
||||
# => error: unknown type name 'CogGrounding'; no binary produced.
|
||||
set -e
|
||||
HERE=$(cd "$(dirname "$0")" && pwd)
|
||||
RT="$HERE/../../lang/runtime"
|
||||
CC=${CC:-cc}
|
||||
SRC="$HERE/test_grounding_vector.c $RT/engram_cognition.c $RT/engram_reason.c $RT/engram_geometry.c $RT/engram_store.c $RT/engram_vindex.c"
|
||||
WARN="-std=c11 -Wall -Wextra"
|
||||
# engram_store.c declares emit_log as a WEAK symbol and null-checks it, which is
|
||||
# how a test links the store without the EL runtime. Darwin's ld does not resolve
|
||||
# an undefined weak symbol at static-link time, so it must be allowed explicitly.
|
||||
# (The pre-existing runners in this directory — run_verify_tests.sh among them —
|
||||
# do not do this and therefore fail to link on macOS. Unrelated to this change.)
|
||||
LDX=""
|
||||
[ "$(uname -s)" = "Darwin" ] && LDX="-Wl,-U,_emit_log"
|
||||
TMP=$(mktemp -d)
|
||||
|
||||
echo "### PASS 1: PERF (optimised, un-sanitised) — functional gate"
|
||||
$CC $WARN -O2 -I"$RT" $SRC -lm -lpthread $LDX -o "$TMP/perf"
|
||||
"$TMP/perf"
|
||||
|
||||
echo
|
||||
echo "### PASS 2: SAFETY (ASan/UBSan)"
|
||||
$CC $WARN -O1 -g -fsanitize=address,undefined -fno-omit-frame-pointer -I"$RT" $SRC -lm -lpthread $LDX -o "$TMP/safe"
|
||||
ASAN_OPTIONS=${ASAN_OPTIONS:-detect_leaks=0} UBSAN_OPTIONS=halt_on_error=1 "$TMP/safe"
|
||||
@@ -0,0 +1,176 @@
|
||||
/* test_grounding_vector.c — deterministic tests for §7: the one decay model, the
|
||||
* consequence gate, and the stored/derived split. Links engram_cognition.c
|
||||
* directly; no server, no store, no network. See run_grounding_vector_tests.sh.
|
||||
*
|
||||
* NEGATIVE CONTROL (invariant §8.6). Every symbol exercised here —
|
||||
* cog_decay_factor, cog_grounding_significant, cog_significance_inherent,
|
||||
* CogGrounding, CogProvClass — is introduced by the change under test, so this
|
||||
* suite does not COMPILE against the pre-change source, let alone pass. The
|
||||
* runner documents the exact reproduction.
|
||||
*/
|
||||
#include "engram_cognition.h"
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <stdlib.h>
|
||||
#include <math.h>
|
||||
|
||||
static int fails = 0;
|
||||
static void ok(int cond, const char* what) {
|
||||
printf(" %-62s %s\n", what, cond ? "PASS" : "*** FAIL ***");
|
||||
if (!cond) fails++;
|
||||
}
|
||||
|
||||
/* The decay formula exactly as el_runtime.c carried it before the move, so the
|
||||
* refactor can be shown to be bit-identical rather than merely similar. */
|
||||
static double old_engram_temporal_decay(long long age_ms, long long activation_count,
|
||||
double temporal_decay_rate) {
|
||||
if (age_ms <= 0) return 1.0;
|
||||
double lambda = (temporal_decay_rate > 0.0) ? temporal_decay_rate : 0.693147;
|
||||
double age_hours = (double)age_ms / 3600000.0;
|
||||
double t_half = 168.0 * (1.0 + log(1.0 + (double)activation_count));
|
||||
double factor = exp(-lambda * age_hours / t_half);
|
||||
if (factor < 0.25) factor = 0.25;
|
||||
return factor;
|
||||
}
|
||||
|
||||
static CogGrounding base(void) {
|
||||
CogGrounding g; memset(&g, 0, sizeof g);
|
||||
g.present = 1;
|
||||
g.factual = 0.60; g.relational = 0.60;
|
||||
g.factual_now = 0.60; g.relational_now = 0.60;
|
||||
g.associative = 0.1; g.polarity = 1.0;
|
||||
g.prov = COG_PROV_TOLD;
|
||||
g.fac_proj = 1.0; g.rel_proj = 1.0;
|
||||
g.cos_angle = 0.9; g.agreement = 1;
|
||||
g.ts = 1000; g.seq = 1; g.reinforcements = 3;
|
||||
return g;
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
const double F = 0.5, R = 0.5;
|
||||
|
||||
printf("\n== 1. DECAY IS THE ONE MODEL, AND IT IS BIT-IDENTICAL TO WHAT IT REPLACED ==\n");
|
||||
{
|
||||
long long ages[] = {0, 3600000LL, 86400000LL, 7*86400000LL, 30*86400000LL, 365*86400000LL};
|
||||
int allsame = 1;
|
||||
for (int i = 0; i < 6; i++)
|
||||
for (int ac = 0; ac < 4; ac++) {
|
||||
long long acs[] = {0, 1, 10, 1000};
|
||||
double a = cog_decay_factor(ages[i], (double)acs[ac], 0.0);
|
||||
double b = old_engram_temporal_decay(ages[i], acs[ac], 0.0);
|
||||
if (a != b) allsame = 0;
|
||||
}
|
||||
ok(allsame, "cog_decay_factor == the pre-move engram_temporal_decay (24 pts)");
|
||||
ok(cog_decay_factor(0, 0, 0.0) == 1.0, "age 0 -> no decay");
|
||||
}
|
||||
printf("\n DECAY OVER ELAPSED TIME (reinforcements = 0, default rate):\n");
|
||||
printf(" %10s %10s\n", "elapsed", "decay");
|
||||
{
|
||||
struct { const char* label; long long ms; } pts[] = {
|
||||
{"0", 0LL},
|
||||
{"1 hour", 3600000LL},
|
||||
{"1 day", 86400000LL},
|
||||
{"3 days", 3LL*86400000LL},
|
||||
{"7 days", 7LL*86400000LL},
|
||||
{"14 days", 14LL*86400000LL},
|
||||
{"30 days", 30LL*86400000LL},
|
||||
{"90 days", 90LL*86400000LL},
|
||||
};
|
||||
double prev = 2.0; int monotone = 1;
|
||||
for (unsigned i = 0; i < sizeof pts / sizeof pts[0]; i++) {
|
||||
double d = cog_decay_factor(pts[i].ms, 0, 0.0);
|
||||
printf(" %10s %10.6f\n", pts[i].label, d);
|
||||
if (d > prev) monotone = 0;
|
||||
prev = d;
|
||||
}
|
||||
ok(monotone, "decay is monotone non-increasing in elapsed time");
|
||||
ok(fabs(cog_decay_factor(7LL*86400000LL, 0, 0.0) - 0.5) < 1e-6,
|
||||
"7 days at zero reinforcements == exactly one half-life (0.5)");
|
||||
ok(cog_decay_factor(7LL*86400000LL, 100, 0.0) > cog_decay_factor(7LL*86400000LL, 0, 0.0),
|
||||
"reinforcement slows ageing (Lindy term)");
|
||||
ok(cog_decay_factor(3650LL*86400000LL, 0, 0.0) == 0.25,
|
||||
"floor is a preference not a cliff: bottoms out at 0.25");
|
||||
}
|
||||
|
||||
printf("\n== 2. CONSEQUENCE GATE: EVERY TRIGGER, AND NO EPSILON ANYWHERE ==\n");
|
||||
{
|
||||
CogGrounding p = base(), n = base();
|
||||
ok(cog_grounding_significant(&p, &n, F, R) == COG_SIG_NONE,
|
||||
"identical vectors -> NONE (a re-read must not consolidate)");
|
||||
|
||||
n = base(); n.factual = 0.9999; n.factual_now = 0.9999;
|
||||
ok(cog_grounding_significant(&p, &n, F, R) == COG_SIG_NONE,
|
||||
"factual 0.60 -> 0.9999 without crossing the floor -> NONE");
|
||||
|
||||
n = base(); n.relational = 0.5001; n.relational_now = 0.5001;
|
||||
ok(cog_grounding_significant(&p, &n, F, R) == COG_SIG_NONE,
|
||||
"relational 0.60 -> 0.5001, still above floor -> NONE");
|
||||
|
||||
n = base(); n.factual_now = 0.4999;
|
||||
ok(cog_grounding_significant(&p, &n, F, R) == COG_SIG_FACTUAL_FLOOR,
|
||||
"a 0.1001 drop that CROSSES the floor -> FACTUAL_FLOOR");
|
||||
|
||||
n = base(); n.relational_now = 0.4999;
|
||||
ok(cog_grounding_significant(&p, &n, F, R) == COG_SIG_RELATIONAL_FLOOR,
|
||||
"relational crossing its floor -> RELATIONAL_FLOOR");
|
||||
|
||||
n = base(); n.cos_angle = -0.05; n.agreement = -1;
|
||||
ok(cog_grounding_significant(&p, &n, F, R) == COG_SIG_AGREEMENT_FLIP,
|
||||
"agreement +1 -> -1 -> AGREEMENT_FLIP");
|
||||
|
||||
n = base(); n.fac_proj = -0.2;
|
||||
ok(cog_grounding_significant(&p, &n, F, R) == COG_SIG_DIRECTION_REVERSAL,
|
||||
"factual gradient reverses -> DIRECTION_REVERSAL");
|
||||
|
||||
n = base(); n.rel_proj = -0.2;
|
||||
ok(cog_grounding_significant(&p, &n, F, R) == COG_SIG_DIRECTION_REVERSAL,
|
||||
"relational gradient reverses -> DIRECTION_REVERSAL");
|
||||
|
||||
n = base(); n.polarity = -1.0;
|
||||
ok(cog_grounding_significant(&p, &n, F, R) == COG_SIG_POLARITY_FLIP,
|
||||
"support -> contradiction -> POLARITY_FLIP (inherent)");
|
||||
|
||||
n = base(); n.polarity = 0.0;
|
||||
ok(cog_grounding_significant(&p, &n, F, R) == COG_SIG_POLARITY_FLIP,
|
||||
"support -> ignorance (zero) -> POLARITY_FLIP: not the same state");
|
||||
|
||||
n = base(); n.prov = COG_PROV_OBSERVED;
|
||||
ok(cog_grounding_significant(&p, &n, F, R) == COG_SIG_PROVENANCE_CHANGE,
|
||||
"told -> observed -> PROVENANCE_CHANGE (inherent)");
|
||||
|
||||
CogGrounding fresh; memset(&fresh, 0, sizeof fresh);
|
||||
ok(cog_grounding_significant(&fresh, &n, F, R) == COG_SIG_FIRST_RECORD,
|
||||
"no prior version -> FIRST_RECORD");
|
||||
}
|
||||
|
||||
printf("\n== 3. INHERENT MOVES BYPASS THE SALIENCE GATE ==\n");
|
||||
ok(cog_significance_inherent(COG_SIG_POLARITY_FLIP), "polarity flip is inherent");
|
||||
ok(cog_significance_inherent(COG_SIG_PROVENANCE_CHANGE), "provenance change is inherent");
|
||||
ok(cog_significance_inherent(COG_SIG_FIRST_RECORD), "first record is inherent");
|
||||
ok(!cog_significance_inherent(COG_SIG_FACTUAL_FLOOR), "a floor crossing is NOT inherent");
|
||||
ok(!cog_significance_inherent(COG_SIG_NONE), "NONE is not inherent");
|
||||
|
||||
printf("\n== 4. THE STORED/DERIVED SPLIT: DERIVED VALUES ARE NEVER SERIALIZED ==\n");
|
||||
{
|
||||
CogGrounding g = base();
|
||||
g.decay = 0.3333; g.factual_now = 0.1234; g.relational_now = 0.2345;
|
||||
g.associative_now = 0.4567; g.age_ms = 999999; g.stale = 1;
|
||||
char* m = cog_grounding_metadata("pre-existing=keepme", &g);
|
||||
ok(m != NULL, "serializer returns a document");
|
||||
ok(m && strstr(m, "pre-existing=keepme"), "pre-existing edge metadata preserved verbatim");
|
||||
ok(m && strstr(m, "GRD1"), "GRD1 magic present");
|
||||
ok(m && !strstr(m, "0.3333"), "decay is NOT stored");
|
||||
ok(m && !strstr(m, "0.1234"), "factual_now is NOT stored");
|
||||
ok(m && !strstr(m, "0.2345"), "relational_now is NOT stored");
|
||||
ok(m && !strstr(m, "0.4567"), "associative_now is NOT stored");
|
||||
ok(m && !strstr(m, "999999"), "age is NOT stored");
|
||||
ok(m && strstr(m, "told"), "provenance class IS stored");
|
||||
ok(m && strstr(m, "0.6"), "the factual/relational dimensions ARE stored");
|
||||
if (m) { printf("\n --- serialized GRD1 block ---\n%s -----------------------------\n", m); }
|
||||
free(m);
|
||||
}
|
||||
|
||||
printf("\n%s (%d failure%s)\n\n", fails ? "SOME TESTS FAILED" : "ALL TESTS PASSED",
|
||||
fails, fails == 1 ? "" : "s");
|
||||
return fails ? 1 : 0;
|
||||
}
|
||||
+27
-12
@@ -13,7 +13,7 @@
|
||||
// relations add edges. Every node enters with PROVENANCE + grounding-level
|
||||
// + stewardship class from the moment of entry.
|
||||
//
|
||||
// transduce() is THE single mechanism — one function, polymorphic, with no
|
||||
// transduce_manifold() is THE single mechanism — one function, polymorphic, with no
|
||||
// content-type branch inside it. It does not ask whether a payload is
|
||||
// prose, structured data, or raw/opaque bytes (audio, or anything else);
|
||||
// it runs one boundary-scan-with-fixed-window-fallback chunking algorithm
|
||||
@@ -401,10 +401,25 @@ fn head80(s: String) -> String {
|
||||
// truncates at the first embedded NUL, which is routine in real binary
|
||||
// bytes) is a MECHANICAL fidelity concern that belongs to whatever produced
|
||||
// `source` (see ingest_file's file_source_string below) — not a
|
||||
// content-type judgment made in here. transduce() never learns whether a
|
||||
// content-type judgment made in here. transduce_manifold() never learns whether a
|
||||
// chunk is plain text or a base64-encoded raw-byte window; every chunk is
|
||||
// handled identically either way.
|
||||
fn transduce(nodes: [String], edges: [String], source: String,
|
||||
// RENAMED transduce -> transduce_manifold (2026-08-16). Two reasons, and the
|
||||
// first is not the interesting one:
|
||||
//
|
||||
// 1. Mechanical: `transduce` is now a LANGUAGE primitive in el_runtime.h
|
||||
// (transduce(signal, modality) -> Geometry). Every El `fn name(...)`
|
||||
// compiles to a global C symbol with that exact name, so keeping this
|
||||
// name here is a hard `conflicting types for 'transduce'` compile error
|
||||
// the moment ingest.c links el_runtime.c. Measured, not anticipated.
|
||||
//
|
||||
// 2. Actual: this function was never signal->geometry. It chunks already-
|
||||
// extracted content and PACKS it into a node+edge manifold — a real
|
||||
// operation, but one layer up, and it had taken the name that belongs to
|
||||
// the primitive underneath it. `transduce` is where a signal becomes
|
||||
// geometry; `transduce_manifold` is where extracted content becomes
|
||||
// structure. Nothing about this function's behaviour changed.
|
||||
fn transduce_manifold(nodes: [String], edges: [String], source: String,
|
||||
prov: String, ground: String, steward: String,
|
||||
root_lid: String, root_title: String) -> [String] {
|
||||
let tagbase: String = "prov:" + prov + " ground:" + ground + " steward:" + steward
|
||||
@@ -531,8 +546,8 @@ fn default_steward() -> String {
|
||||
// trustworthy verbatim. When they don't (silent truncation happened),
|
||||
// rebuild the payload as base64-encoded fixed-size windows read directly
|
||||
// off disk (fs_read_b64_chunk — binary-safe in C), joined with the same
|
||||
// "\n\n" boundary marker transduce()'s generic scan already looks for, so
|
||||
// transduce() sees one ordinary boundary-delimited payload and runs its one
|
||||
// "\n\n" boundary marker transduce_manifold()'s generic scan already looks for, so
|
||||
// transduce_manifold() sees one ordinary boundary-delimited payload and runs its one
|
||||
// algorithm on it exactly as it would on prose — it never learns that a
|
||||
// fidelity problem occurred upstream, let alone why.
|
||||
fn file_source_string(path: String, text: String, real_size: Int) -> String {
|
||||
@@ -541,7 +556,7 @@ fn file_source_string(path: String, text: String, real_size: Int) -> String {
|
||||
// 3072 raw bytes -> 4096 base64 chars (3 divides evenly into base64's
|
||||
// 3-byte/4-char ratio); keeps each resulting node's content a clean,
|
||||
// bounded, low-kilobytes unit, same order of magnitude as the fixed
|
||||
// fallback window in transduce() itself.
|
||||
// fallback window in transduce_manifold() itself.
|
||||
let win: Int = 3072
|
||||
let out: String = ""
|
||||
let off: Int = 0
|
||||
@@ -561,7 +576,7 @@ fn file_source_string(path: String, text: String, real_size: Int) -> String {
|
||||
}
|
||||
|
||||
// ingest one file -> report JSON. Uniform for every file regardless of
|
||||
// extension or content — transduce() decides nothing about content-type, so
|
||||
// extension or content — transduce_manifold() decides nothing about content-type, so
|
||||
// neither does this function; it only decides whether the raw bytes made it
|
||||
// through the read intact (file_source_string), which is a fidelity
|
||||
// question, not a format one.
|
||||
@@ -573,14 +588,14 @@ fn ingest_file(path: String) -> String {
|
||||
return "{\"error\":\"empty or unreadable\",\"path\":" + j_q(path) + "}"
|
||||
}
|
||||
let prov: String = "file:" + path
|
||||
let packed: [String] = transduce(el_list_empty(), el_list_empty(),
|
||||
let packed: [String] = transduce_manifold(el_list_empty(), el_list_empty(),
|
||||
source, prov, default_ground(), default_steward(),
|
||||
"doc:" + basename(path), basename(path))
|
||||
return merge_packed(packed)
|
||||
}
|
||||
|
||||
// ingest a directory: walk one level, ingest every file found, aggregate.
|
||||
// No extension filter — transduce() handles any payload uniformly now, so
|
||||
// No extension filter — transduce_manifold() handles any payload uniformly now, so
|
||||
// there is no content-type gate at the directory boundary either.
|
||||
fn ingest_dir(path: String) -> String {
|
||||
let entries: [String] = fs_list(path)
|
||||
@@ -615,7 +630,7 @@ fn ingest_dir(path: String) -> String {
|
||||
fn ingest_url(url: String) -> String {
|
||||
let body: String = http_get(url)
|
||||
if str_eq(body, "") { return "{\"error\":\"empty fetch\",\"url\":" + j_q(url) + "}" }
|
||||
let packed: [String] = transduce(el_list_empty(), el_list_empty(),
|
||||
let packed: [String] = transduce_manifold(el_list_empty(), el_list_empty(),
|
||||
body, "url:" + url, "extracted", "public-web",
|
||||
"url:" + url, url)
|
||||
return merge_packed(packed)
|
||||
@@ -630,7 +645,7 @@ fn ingest_llm(query: String) -> String {
|
||||
let resp: String = http_post_json("http://127.0.0.1:11434/api/generate", body)
|
||||
let answer: String = json_get_string(resp, "response")
|
||||
if str_eq(answer, "") { return "{\"error\":\"no model response\"}" }
|
||||
let packed: [String] = transduce(el_list_empty(), el_list_empty(),
|
||||
let packed: [String] = transduce_manifold(el_list_empty(), el_list_empty(),
|
||||
answer, "llm:" + model + ":" + query, "candidate-provisional", "guide-provisional",
|
||||
"llm:" + query, "guide answer: " + query)
|
||||
return merge_packed(packed)
|
||||
@@ -682,7 +697,7 @@ fn ingest_stream(path: String) -> String {
|
||||
// It is NOT a content-type flag: it says nothing about what's inside the
|
||||
// bytes once fetched, and none of the five ingest_* functions it selects
|
||||
// among interpret their payload differently by content shape anymore —
|
||||
// they all hand off to the single, format-agnostic transduce(). The old
|
||||
// they all hand off to the single, format-agnostic transduce_manifold(). The old
|
||||
// "structured" value (a caller-declared alias for "file", used only to hint
|
||||
// the now-removed JSON-vs-prose branch) is gone along with that branch.
|
||||
let kind: String = env("INGEST_KIND")
|
||||
|
||||
+28
-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
|
||||
@@ -73,6 +88,17 @@ When you add a C builtin (verbatim-emit recipe — the El name is emitted as the
|
||||
2. Add a `__`-prefixed thin wrapper in `el_seed.c` and declare it in `el_seed.h`.
|
||||
3. Add the name to `builtin_arity` in `el-compiler/src/codegen.el` — add **both** the plain and `__`-prefixed spellings.
|
||||
4. Rebuild the elc binary (see below) and confirm the self-host fixpoint is byte-identical.
|
||||
5. **Prove it with a NEGATIVE CONTROL.** Show the test FAILING on a build without your change, then passing with it. A test that has never been seen to fail has proven nothing.
|
||||
|
||||
> **Step 5 is not optional, and step 4 does not cover it.** The fixpoint proves the *compiler reproduces itself*. It says nothing whatsoever about whether your builtin works. A recipe ending at "byte-identical" reads as complete while having verified nothing about the thing just added — which is why this file, until 2026-08-16, produced builtins with no tests at all.
|
||||
>
|
||||
> Measured cost of the omission (2026-08-16): `engram_node_set_emb`, `engram_curiosity_json` and `dream_set_handler` were all added in one session with zero tests. Separately, a UTF-8 fix was written, tested, and **the test passed on the unpatched build too** — the defect was elsewhere entirely, and only building the pre-fix binary exposed it. Without a negative control that fix would have merged as verified.
|
||||
>
|
||||
> Two shapes that pass while proving nothing, both hit the same day:
|
||||
> - A test that never exercises your change (the route supplied a default that bypassed the code under test).
|
||||
> - An induction that loses a race. `curl --max-time` on a large response left *both* builds alive; only `SO_LINGER 0` — a genuine RST, so the peer is provably gone — reproduced the failure. Six of ten attempts is not a control.
|
||||
>
|
||||
> Before every probe, confirm **your** process bound the port (`lsof -nP -iTCP:<port>`, match the PID). A stale instance answering on the port has silently produced false results here more than once, and `pkill -f` does not reliably match an argv like `./engram`.
|
||||
|
||||
Worked example: the `engram_assert_json` (op_assert seam) and `engram_node_full_in`/`engram_connect_in` (purview write-side) primitives added 2026-08-15 follow exactly this recipe.
|
||||
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
// transduce.el — geometry as a first-class El value, and a realizer written
|
||||
// in El. Runnable: this is the worked example for the transduce surface, and
|
||||
// it doubles as an executable proof because it checks every claim it makes.
|
||||
//
|
||||
// elc lang/examples/transduce.el > transduce.c
|
||||
// cc -std=c11 -O2 -I lang/runtime -o transduce transduce.c \
|
||||
// lang/runtime/el_runtime.c lang/runtime/el_seed.c \
|
||||
// lang/runtime/engram_*.c -lcurl -lpthread -lm
|
||||
// ./transduce # exits 0 only if every check passes
|
||||
//
|
||||
// (A `test "..."` form of the same checks lives in
|
||||
// lang/tests/native/test_transduce.el, for when the native harness is
|
||||
// repaired — the shipped elc currently emits calls to __el_reg_count and
|
||||
// friends without emitting their definitions, which breaks every native test
|
||||
// equally, test_math.el included. Verified 2026-08-16, unrelated to this work.)
|
||||
//
|
||||
// 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 the
|
||||
// mandatory entry medium, so any non-text modality had to be DESCRIBED in
|
||||
// prose first and the geometry we reasoned over was the geometry OF THE
|
||||
// DESCRIPTION, not of the signal. Two things fix that, and both are shown
|
||||
// below: geometry is a VALUE that carries its own width, and a REALIZER is an
|
||||
// ordinary El function — so admitting a new modality never requires a runtime
|
||||
// patch.
|
||||
//
|
||||
// COMPARISON DISCIPLINE (measured, not stylistic): elc lowers `a == b`
|
||||
// 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 as pointers. `<` and `>`
|
||||
// lower directly with no inference, so truthiness is written `> 0` / `< 1`.
|
||||
|
||||
// ── A realizer, written entirely in El ──────────────────────────────────────
|
||||
// Not in the runtime. Not known to the compiler. Registered by NAME and
|
||||
// dispatched to through transduce(). That is the whole claim.
|
||||
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))
|
||||
let b: Int = geometry_set(g, 1, int_to_float(n * 2))
|
||||
let c: Int = geometry_set(g, 2, int_to_float(n * 3))
|
||||
let d: Int = geometry_set(g, 3, int_to_float(n * 4))
|
||||
g
|
||||
}
|
||||
|
||||
// A second modality, to show the registry keys on modality rather than just
|
||||
// returning whatever was registered last.
|
||||
fn pulse_realizer(signal: String) -> Geometry {
|
||||
let g: Geometry = geometry_new(2)
|
||||
let a: Int = geometry_set(g, 0, 1.0)
|
||||
let b: Int = geometry_set(g, 1, 0.0)
|
||||
g
|
||||
}
|
||||
|
||||
// A deliberately BROKEN realizer: returns something that is not a Geometry.
|
||||
fn bogus_realizer(signal: String) -> Geometry {
|
||||
return 12345
|
||||
}
|
||||
|
||||
// Fails FAST rather than accumulating a count, for a measured reason: a first
|
||||
// cut wrote `let fails: Int = fails + check(...)` and `+` lowered to STRING
|
||||
// CONCAT, because elc dispatches `+` on whether both operands are known-Int and
|
||||
// a user-defined fn call is not — so the counter printed 4343632752, a pointer.
|
||||
// Nothing was wrong with the checks; the tally was lying. Exiting at the first
|
||||
// failure needs no arithmetic at all, so there is nothing left to get wrong.
|
||||
fn check(ok: Int, label: String) -> Int {
|
||||
if ok > 0 {
|
||||
println(" ok " + label)
|
||||
return 0
|
||||
}
|
||||
println(" FAIL " + label)
|
||||
exit(1)
|
||||
return 1
|
||||
}
|
||||
|
||||
fn near(a: Float, b: Float) -> Int {
|
||||
let d: Float = a - b
|
||||
if d > 0.001 { return 0 }
|
||||
if d < -0.001 { return 0 }
|
||||
return 1
|
||||
}
|
||||
|
||||
fn eq_int(a: Int, b: Int) -> Int {
|
||||
if a == b { return 1 }
|
||||
return 0
|
||||
}
|
||||
|
||||
fn main() -> Void {
|
||||
println("geometry is a value that carries its own width")
|
||||
let g8: Geometry = geometry_new(8)
|
||||
let _c: Int = check(geometry_is(g8), "geometry_new returns a live Geometry")
|
||||
let d8: Int = geometry_dim(g8)
|
||||
let _c: Int = check(eq_int(d8, 8), "a Geometry carries its own width (8)")
|
||||
let _c: Int = check(geometry_free(g8), "geometry_free reports what it did")
|
||||
|
||||
println("nonsense is refused — with no arbitrary max-dim bound")
|
||||
// #141 needed `dim <= 8192` only to bound an allocation sized from a
|
||||
// caller's CLAIM about a string's length. A value that carries its own
|
||||
// width has nothing left to validate.
|
||||
let z: Geometry = geometry_new(0)
|
||||
let zi: Int = geometry_is(z)
|
||||
let _c: Int = check(1 - zi, "dim 0 is not a geometry")
|
||||
let ng: Geometry = geometry_new(-4)
|
||||
let ngi: Int = geometry_is(ng)
|
||||
let _c: Int = check(1 - ngi, "negative dim is not a geometry")
|
||||
let nd: Int = geometry_dim(0)
|
||||
let _c: Int = check(1 - nd, "geometry_dim of a non-geometry is 0, not a crash")
|
||||
let nf: Int = geometry_free(0)
|
||||
let _c: Int = check(1 - nf, "geometry_free of a non-geometry is a no-op")
|
||||
|
||||
println("components round-trip, and out-of-range is refused")
|
||||
let g3: Geometry = geometry_new(3)
|
||||
let s0: Int = geometry_set(g3, 0, 1.5)
|
||||
let s1: Int = geometry_set(g3, 1, -2.5)
|
||||
let _c: Int = check(s0, "set in range succeeds")
|
||||
let oob: Int = geometry_set(g3, 3, 9.0)
|
||||
let _c: Int = check(1 - oob, "set out of range is refused, not silently dropped")
|
||||
let _c: Int = check(near(geometry_get(g3, 0), 1.5), "component 0 round-trips")
|
||||
let _c: Int = check(near(geometry_get(g3, 1), -2.5), "component 1 round-trips (negative)")
|
||||
let ff3: Int = geometry_free(g3)
|
||||
|
||||
println("hex is an EDGE adapter, and derives its own width")
|
||||
// little-endian float32: 1.0 = 0000803f, 2.0 = 00000040
|
||||
let gh: Geometry = geometry_from_f32le_hex("0000803f00000040")
|
||||
let _c: Int = check(geometry_is(gh), "valid hex decodes to a Geometry")
|
||||
let dh: Int = geometry_dim(gh)
|
||||
let _c: Int = check(eq_int(dh, 2), "width DERIVED from input, never supplied")
|
||||
let _c: Int = check(near(geometry_get(gh, 0), 1.0), "first component decoded")
|
||||
let _c: Int = check(near(geometry_get(gh, 1), 2.0), "second component decoded")
|
||||
let back: String = geometry_to_f32le_hex(gh)
|
||||
let _c: Int = check(str_eq(back, "0000803f00000040"), "hex round-trips exactly")
|
||||
let ffh: Int = geometry_free(gh)
|
||||
|
||||
println("malformed hex is refused")
|
||||
let he: Geometry = geometry_from_f32le_hex("")
|
||||
let hei: Int = geometry_is(he)
|
||||
let _c: Int = check(1 - hei, "empty hex is not a geometry")
|
||||
let hr: Geometry = geometry_from_f32le_hex("0000803f0000")
|
||||
let hri: Int = geometry_is(hr)
|
||||
let _c: Int = check(1 - hri, "length not a multiple of 8 is refused")
|
||||
let hn: Geometry = geometry_from_f32le_hex("zzzzzzzz")
|
||||
let hni: Int = geometry_is(hn)
|
||||
let _c: Int = check(1 - hni, "non-hex characters are refused")
|
||||
|
||||
println("a realizer declared in El is a first-class realizer")
|
||||
let reg: Int = realizer_register("tone", "tone_realizer")
|
||||
let _c: Int = check(reg, "an El fn registers as a realizer BY NAME")
|
||||
let _c: Int = check(realizer_has("tone"), "the modality now has an organ")
|
||||
let gt: Geometry = transduce("aaa", "tone")
|
||||
let _c: Int = check(geometry_is(gt), "transduce returns real geometry")
|
||||
let dt: Int = geometry_dim(gt)
|
||||
let _c: Int = check(eq_int(dt, 4), "the El realizer determined the width, not the runtime")
|
||||
// str_len("aaa") == 3, so component 0 must be 3.0 — proof the signal
|
||||
// actually reached the El function rather than a stub answering for it.
|
||||
let _c: Int = check(near(geometry_get(gt, 0), 3.0), "the signal REACHED the El realizer")
|
||||
let fft: Int = geometry_free(gt)
|
||||
|
||||
println("distinct signals transduce to distinct geometry")
|
||||
let g1: Geometry = transduce("aa", "tone")
|
||||
let g2: Geometry = transduce("aaaaa", "tone")
|
||||
let a1: Float = geometry_get(g1, 0)
|
||||
let a2: Float = geometry_get(g2, 0)
|
||||
// 5 - 2 = 3. If transduction were a stub these would be equal.
|
||||
let _c: Int = check(near(a2 - a1, 3.0), "different signals produce different geometry")
|
||||
let ff1: Int = geometry_free(g1)
|
||||
let ff2: Int = geometry_free(g2)
|
||||
|
||||
println("the registry keys on modality")
|
||||
let r2: Int = realizer_register("pulse", "pulse_realizer")
|
||||
let _c: Int = check(r2, "a second modality registers independently")
|
||||
let mt: Geometry = transduce("aaa", "tone")
|
||||
let mp: Geometry = transduce("aaa", "pulse")
|
||||
let mdt: Int = geometry_dim(mt)
|
||||
let mdp: Int = geometry_dim(mp)
|
||||
let _c: Int = check(eq_int(mdt, 4), "tone still routes to its own realizer")
|
||||
let _c: Int = check(eq_int(mdp, 2), "pulse routes to a different realizer")
|
||||
let ffm1: Int = geometry_free(mt)
|
||||
let ffm2: Int = geometry_free(mp)
|
||||
|
||||
println("no organ is reported as no organ")
|
||||
// A modality with no realizer must transduce to NOTHING. It must never
|
||||
// fall back to embedding a description of the signal and calling that
|
||||
// perception — that silent substitution is the defect this all exists to end.
|
||||
let eh: Int = realizer_has("echolocation")
|
||||
let _c: Int = check(1 - eh, "unregistered modality has no organ")
|
||||
let ge: Geometry = transduce("anything", "echolocation")
|
||||
let gei: Int = geometry_is(ge)
|
||||
let _c: Int = check(1 - gei, "no realizer means NO geometry, not fake geometry")
|
||||
|
||||
println("an unresolvable realizer name fails at WIRING time")
|
||||
let bad: Int = realizer_register("ghost", "no_such_function_anywhere")
|
||||
let _c: Int = check(1 - bad, "unresolvable realizer name is a registration failure")
|
||||
let gh2: Int = realizer_has("ghost")
|
||||
let _c: Int = check(1 - gh2, "and nothing gets registered")
|
||||
|
||||
println("a realizer returning non-geometry transduces nothing")
|
||||
let rb: Int = realizer_register("bogus", "bogus_realizer")
|
||||
let _c: Int = check(rb, "the symbol resolves, so registration succeeds")
|
||||
let gb: Geometry = transduce("x", "bogus")
|
||||
let gbi: Int = geometry_is(gb)
|
||||
let _c: Int = check(1 - gbi, "contract enforced at the boundary: nothing handed back")
|
||||
|
||||
println("norm lets a caller check a realizer emitted signal, not zeros")
|
||||
let gn: Geometry = geometry_new(2)
|
||||
let _c: Int = check(near(geometry_norm(gn), 0.0), "a fresh geometry is zero — norm says so")
|
||||
let n0: Int = geometry_set(gn, 0, 3.0)
|
||||
let n1: Int = geometry_set(gn, 1, 4.0)
|
||||
let _c: Int = check(near(geometry_norm(gn), 5.0), "3-4-5: norm is 5")
|
||||
let ffn: Int = geometry_free(gn)
|
||||
|
||||
// Reaching here means nothing called exit(1) along the way.
|
||||
println("")
|
||||
println("all checks passed")
|
||||
}
|
||||
+1133
-125
File diff suppressed because it is too large
Load Diff
@@ -586,6 +586,60 @@ void el_runtime_dharma_event_arrive(const char* event_type,
|
||||
const char* payload,
|
||||
const char* source);
|
||||
|
||||
/* ── Geometry: signal as a first-class El value ──────────────────────────────
|
||||
*
|
||||
* A Geometry is an opaque, magic-tagged heap value carried in an el_val_t —
|
||||
* the same discipline as List/Map. It holds a width and a float32 payload,
|
||||
* and it is the medium a non-text modality enters in. Declared HERE, above
|
||||
* the engram block, because transduction is a LANGUAGE concern: every El
|
||||
* program touching any modality needs it, and the engram is merely one El
|
||||
* program that happens to hold a graph. See el_runtime.c ("Geometry: signal
|
||||
* as a first-class el value") for the full rationale.
|
||||
*
|
||||
* El-side type annotation is simply `Geometry` — an opaque boxed pointer,
|
||||
* exactly like Instant / Calendar / Rhythm. No codegen change is required.
|
||||
*
|
||||
* 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. */
|
||||
|
||||
el_val_t geometry_new(el_val_t dim); /* zero-filled; 0 on failure */
|
||||
el_val_t geometry_dim(el_val_t g); /* width, 0 if not a Geometry */
|
||||
el_val_t geometry_is(el_val_t g); /* 1 if a live Geometry */
|
||||
el_val_t geometry_get(el_val_t g, el_val_t i); /* Float component */
|
||||
el_val_t geometry_set(el_val_t g, el_val_t i, el_val_t x); /* 1 ok / 0 out of range */
|
||||
el_val_t geometry_norm(el_val_t g); /* Float L2 — lets a caller
|
||||
* check a realizer emitted
|
||||
* signal, not zeros */
|
||||
el_val_t geometry_free(el_val_t g); /* 1 if freed, 0 if not a Geometry.
|
||||
* Returns a value (not void) so it
|
||||
* is safe in any El expression
|
||||
* position without a codegen
|
||||
* void-builtin table entry. */
|
||||
|
||||
/* Wire ADAPTERS — the only place an encoding appears, and only at the edge.
|
||||
* `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 here to validate a claimed length against. */
|
||||
el_val_t geometry_from_f32le_hex(el_val_t hex); /* 0 on empty/odd-length/non-hex */
|
||||
el_val_t geometry_to_f32le_hex(el_val_t g); /* "" if not a Geometry */
|
||||
|
||||
/* ── Realizers + transduce ───────────────────────────────────────────────────
|
||||
* A REALIZER maps one modality into geometry. Registration is by NAME, so a
|
||||
* new modality never requires a runtime patch: 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.
|
||||
*
|
||||
* fn tone_realizer(signal: String) -> Geometry { ... }
|
||||
* realizer_register("tone", "tone_realizer")
|
||||
* let g: Geometry = transduce(sample, "tone")
|
||||
*/
|
||||
el_val_t realizer_register(el_val_t modality, el_val_t fn_name); /* 1 ok / 0 unresolved */
|
||||
el_val_t realizer_has(el_val_t modality); /* 1 if a realizer is registered */
|
||||
el_val_t transduce(el_val_t signal, el_val_t modality); /* Geometry, or 0 if no organ */
|
||||
|
||||
/* ── Engram local graph primitives ───────────────────────────────────────────
|
||||
* Operate on the CGI's local Engram knowledge graph.
|
||||
* `engram_activate` queries the local graph only; `dharma_activate` is
|
||||
@@ -612,11 +666,27 @@ el_val_t engram_get_node(el_val_t id);
|
||||
void engram_strengthen(el_val_t node_id);
|
||||
void engram_forget(el_val_t node_id);
|
||||
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);
|
||||
|
||||
el_val_t engram_node_count(void);
|
||||
/* Attach geometry to an existing node. `hex` is little-endian float32,
|
||||
* exactly dim*8 hex chars — the encoding realizers already emit. Lets a
|
||||
* non-text modality enter as geometry instead of being described in prose
|
||||
* and embedded as its description. Returns 1 on success, 0 otherwise. */
|
||||
/* Attach a Geometry to an existing node, and read the attached width back.
|
||||
* Named for the operation, not the store: a node acquires geometry. This is
|
||||
* the geometry-valued ingest path — nothing about it is hex, and nothing
|
||||
* about it assumes the caller's vector matches the canonical text-embedding
|
||||
* width. node_geometry_dim exists so an attach is VERIFIED by reading it
|
||||
* back rather than by trusting a success return. */
|
||||
el_val_t node_attach_geometry(el_val_t node_id, el_val_t g); /* 1 ok / 0 otherwise */
|
||||
el_val_t node_geometry_dim(el_val_t node_id); /* width, 0 if none */
|
||||
|
||||
/* DEPRECATED (shipped in #141, superseded 2026-08-16). Equivalent to
|
||||
* geometry_from_f32le_hex + node_attach_geometry, and now implemented as
|
||||
* exactly that. Kept only so anything built against the #141 runtime keeps
|
||||
* linking; `dim` is accepted but treated as an assertion about the vector's
|
||||
* width rather than as its source. New code should not call this — a hex
|
||||
* string is a wire encoding, not a way to move geometry between two pieces
|
||||
* of El. Returns 1 on success, 0 otherwise. */
|
||||
el_val_t engram_node_set_emb(el_val_t id, el_val_t hex, el_val_t dim);
|
||||
el_val_t engram_search(el_val_t query, el_val_t limit);
|
||||
el_val_t engram_scan_nodes(el_val_t limit, el_val_t offset);
|
||||
@@ -662,8 +732,13 @@ el_val_t engram_geo_analogy_json(el_val_t a_seeds, el_val_t b_seeds);
|
||||
el_val_t engram_reason_analogy_json(el_val_t a_seeds, el_val_t b_seeds, el_val_t c_seeds);
|
||||
/* COGNITION (2026-08-14): THE ONE OPERATION + grounding, surfaced live. */
|
||||
el_val_t engram_think_json(el_val_t seeds, el_val_t faculty);
|
||||
/* GROUNDING (2026-08-16): grounding is an attribute of the RELATION and it IS the
|
||||
* hebbian weight. ground reads; ground_record writes; trajectory reads the chain. */
|
||||
el_val_t engram_ground_json(el_val_t claim, el_val_t evidence, el_val_t for_whom);
|
||||
el_val_t engram_assert_json(el_val_t claim_id, el_val_t for_whom, el_val_t floor);
|
||||
el_val_t engram_ground_record_json(el_val_t claim, el_val_t evidence,
|
||||
el_val_t provenance, el_val_t floor);
|
||||
el_val_t engram_ground_trajectory_json(el_val_t claim, el_val_t evidence);
|
||||
el_val_t engram_assert_json(el_val_t claim_id, el_val_t for_whom, el_val_t floor, el_val_t rel_floor);
|
||||
el_val_t engram_attend_json(el_val_t node_id, el_val_t observer, el_val_t salience);
|
||||
el_val_t engram_correspondence_beat_json(el_val_t seeds, el_val_t faculty, el_val_t keystone);
|
||||
el_val_t engram_consolidate_permanence(el_val_t node_id);
|
||||
|
||||
+372
-29
@@ -246,14 +246,6 @@ static int put_edge(EngramPagedStore* s, const char* id, const char* from, const
|
||||
e.metadata = (char*)meta;
|
||||
return store_put_edge(s, &e);
|
||||
}
|
||||
int cog_ground_edge(EngramPagedStore* s, const char* claim_id,
|
||||
const char* evidence_id, double grounding, const char* for_whom) {
|
||||
if (!s || !claim_id || !evidence_id) return -1;
|
||||
char id[512], meta[256];
|
||||
snprintf(id, sizeof id, "gb-%s-%s-%s", claim_id, evidence_id, for_whom ? for_whom : "global");
|
||||
snprintf(meta, sizeof meta, "for_whom=%s", for_whom ? for_whom : "-");
|
||||
return put_edge(s, id, claim_id, evidence_id, COG_GROUNDED_BY_RELATION, grounding, meta);
|
||||
}
|
||||
int cog_salient_edge(EngramPagedStore* s, const char* node_id,
|
||||
const char* observer_id, double salience) {
|
||||
if (!s || !node_id || !observer_id) return -1;
|
||||
@@ -261,35 +253,386 @@ int cog_salient_edge(EngramPagedStore* s, const char* node_id,
|
||||
snprintf(id, sizeof id, "st-%s-%s", node_id, observer_id);
|
||||
return put_edge(s, id, node_id, observer_id, COG_SALIENT_TO_RELATION, salience, NULL);
|
||||
}
|
||||
int cog_assert_gate(EngramPagedStore* s, const char* claim_id,
|
||||
const char* for_whom, double floor) {
|
||||
if (!s || !claim_id) return -1;
|
||||
if (!(floor > 0)) floor = 0.5;
|
||||
StoreEdge* edges = NULL; size_t n = 0;
|
||||
if (store_get_edges_from(s, claim_id, &edges, &n) < 0) return -1;
|
||||
double best = 0.0; int found = 0;
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
if (!edges[i].relation || strcmp(edges[i].relation, COG_GROUNDED_BY_RELATION) != 0) continue;
|
||||
/* grounded-for-whom: match observer if requested; global (for_whom=-) always counts */
|
||||
int match = 1;
|
||||
if (for_whom && edges[i].metadata) {
|
||||
const char* fw = strstr(edges[i].metadata, "for_whom=");
|
||||
if (fw) { fw += 9; if (strcmp(fw, for_whom) != 0 && strcmp(fw, "-") != 0) match = 0; }
|
||||
}
|
||||
if (match) { found = 1; if (edges[i].weight > best) best = edges[i].weight; }
|
||||
}
|
||||
store_edges_free(edges, n);
|
||||
if (!found) return 0; /* ungrounded => refuse assertion (still held) */
|
||||
return (best >= floor) ? 1 : 0;
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
* §7 GROUNDING IS THE EDGE'S WEIGHT, AND THE WEIGHT IS A VECTOR.
|
||||
* See engram_cognition.h §7 for the model and for the measurements the two
|
||||
* design decisions (thirteen regions, min aggregate) rest on.
|
||||
* ═══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/* ── The one decay model. Moved here verbatim from el_runtime.c's
|
||||
* engram_temporal_decay so nodes and edges share a single implementation and a
|
||||
* single set of constants; engram_temporal_decay now delegates. Bit-identical
|
||||
* for nodes: reinforcements := activation_count, lambda_override :=
|
||||
* temporal_decay_rate.
|
||||
*
|
||||
* This is what makes decay ANALYTIC rather than sampled: between two recorded
|
||||
* versions the trajectory is not unknown, it is known in closed form from the
|
||||
* last point and elapsed time. Store the point, read the curve. */
|
||||
double cog_decay_factor(int64_t age_ms, double reinforcements, double lambda_override) {
|
||||
if (age_ms <= 0) return 1.0;
|
||||
double lambda = (lambda_override > 0.0) ? lambda_override : COG_DECAY_LAMBDA;
|
||||
double age_hours = (double)age_ms / 3600000.0;
|
||||
if (reinforcements < 0) reinforcements = 0;
|
||||
double t_half = COG_T_HALF_HOURS * (1.0 + log(1.0 + reinforcements));
|
||||
double factor = exp(-lambda * age_hours / t_half);
|
||||
if (factor < COG_DECAY_FLOOR) factor = COG_DECAY_FLOOR;
|
||||
return factor;
|
||||
}
|
||||
|
||||
const char* cog_prov_name(CogProvClass p) {
|
||||
switch (p) {
|
||||
case COG_PROV_OBSERVED: return "observed";
|
||||
case COG_PROV_INFERRED: return "inferred";
|
||||
case COG_PROV_TOLD: return "told";
|
||||
case COG_PROV_IMPRINTED: return "imprinted";
|
||||
default: return "unset";
|
||||
}
|
||||
}
|
||||
CogProvClass cog_prov_parse(const char* s) {
|
||||
if (!s) return COG_PROV_UNSET;
|
||||
if (!strcmp(s, "observed")) return COG_PROV_OBSERVED;
|
||||
if (!strcmp(s, "inferred")) return COG_PROV_INFERRED;
|
||||
if (!strcmp(s, "told")) return COG_PROV_TOLD;
|
||||
if (!strcmp(s, "imprinted")) return COG_PROV_IMPRINTED;
|
||||
return COG_PROV_UNSET;
|
||||
}
|
||||
|
||||
/* Locate the GRD1 block in an edge's metadata. It is always the tail; anything
|
||||
* ahead of it is the edge's pre-existing metadata, preserved verbatim. */
|
||||
static const char* cog_grd_find(const char* meta) {
|
||||
if (!meta) return NULL;
|
||||
size_t ml = strlen(COG_GROUNDING_META_MAGIC);
|
||||
if (strncmp(meta, COG_GROUNDING_META_MAGIC, ml) == 0) return meta;
|
||||
const char* p = meta;
|
||||
while ((p = strstr(p, COG_GROUNDING_META_MAGIC)) != NULL) {
|
||||
if (p > meta && p[-1] == '\n') return p;
|
||||
p += ml;
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int cog_grounding_parse(const StoreEdge* e, int64_t now_ms, CogGrounding* out) {
|
||||
if (!e || !out) return -1;
|
||||
memset(out, 0, sizeof *out);
|
||||
|
||||
/* Two dimensions exist on every edge whether or not grounding has ever been
|
||||
* established, because they ARE existing substrate rather than new fields:
|
||||
* associative — the accrued hebb, with its existing dynamics;
|
||||
* polarity — the signed authored weight. `inhibitory` is precisely this
|
||||
* distinction crushed to one bit, so it is the seed sign. */
|
||||
out->associative = e->hebb;
|
||||
out->polarity = e->inhibitory ? -e->weight : e->weight;
|
||||
out->prov = COG_PROV_UNSET;
|
||||
out->ts = e->last_fired > 0 ? e->last_fired : e->updated_at;
|
||||
|
||||
const char* blk = cog_grd_find(e->metadata);
|
||||
if (blk) {
|
||||
out->present = 1;
|
||||
char* copy = dupstr(blk);
|
||||
if (!copy) return -1;
|
||||
for (char* line = strtok(copy, "\n"); line; line = strtok(NULL, "\n")) {
|
||||
if (line[0] == '\0') continue;
|
||||
char tag = line[0];
|
||||
const char* rest = line + 1; while (*rest == ' ') rest++;
|
||||
if (tag == 'w') { /* the four numeric dimensions */
|
||||
double v[4] = {0,0,0,0}; parse_floats(rest, v, 4);
|
||||
out->factual = v[0]; out->relational = v[1];
|
||||
out->associative = v[2]; out->polarity = v[3];
|
||||
} else if (tag == 'k') { /* provenance class */
|
||||
out->prov = cog_prov_parse(rest);
|
||||
} else if (tag == 't') { /* timestamp + seq + reinforcements */
|
||||
double v[3] = {0,0,0}; parse_floats(rest, v, 3);
|
||||
out->ts = (int64_t)v[0]; out->seq = (int64_t)v[1]; out->reinforcements = v[2];
|
||||
} else if (tag == 'd') {
|
||||
double v[3] = {0,0,0}; parse_floats(rest, v, 3);
|
||||
out->fac_proj = v[0]; out->rel_proj = v[1]; out->cos_angle = v[2];
|
||||
} else if (tag == 'v') {
|
||||
snprintf(out->binding_value, sizeof out->binding_value, "%s", rest);
|
||||
} else if (tag == 'c') {
|
||||
double v[2] = {0,0}; parse_floats(rest, v, 2);
|
||||
out->floor_at_record = v[0]; out->rel_floor_at_record = v[1];
|
||||
} else if (tag == 'p') {
|
||||
snprintf(out->prev_edge, sizeof out->prev_edge, "%s", rest);
|
||||
}
|
||||
}
|
||||
free(copy);
|
||||
}
|
||||
out->agreement = (out->cos_angle > 0) ? 1 : (out->cos_angle < 0 ? -1 : 0);
|
||||
|
||||
/* ── DERIVED. Nothing below this line is ever serialized. Recency, decay and
|
||||
* staleness are read off the curve; storing them is how a number ends up
|
||||
* asserting something nothing computed (§8.1 / spec §2). */
|
||||
out->age_ms = (out->ts > 0 && now_ms > out->ts) ? (now_ms - out->ts) : 0;
|
||||
out->decay = cog_decay_factor(out->age_ms, out->reinforcements, 0.0);
|
||||
out->factual_now = out->factual * out->decay;
|
||||
out->relational_now = out->relational * out->decay;
|
||||
out->associative_now = out->associative * out->decay;
|
||||
out->stale = (out->present && out->floor_at_record > 0 &&
|
||||
out->factual_now < out->floor_at_record) ? 1 : 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
char* cog_grounding_metadata(const char* base_meta, const CogGrounding* g) {
|
||||
if (!g) return NULL;
|
||||
size_t keep = 0;
|
||||
if (base_meta) {
|
||||
const char* blk = cog_grd_find(base_meta);
|
||||
keep = blk ? (size_t)(blk - base_meta) : strlen(base_meta);
|
||||
while (keep > 0 && base_meta[keep - 1] == '\n') keep--;
|
||||
}
|
||||
size_t cap = keep + 1024;
|
||||
char* buf = malloc(cap); if (!buf) return NULL;
|
||||
size_t o = 0;
|
||||
if (keep) { memcpy(buf, base_meta, keep); o = keep; buf[o++] = '\n'; }
|
||||
o += (size_t)snprintf(buf + o, cap - o, "%s\n", COG_GROUNDING_META_MAGIC);
|
||||
/* STORED ONLY. factual / relational / associative / polarity / provenance /
|
||||
* timestamp — plus the joint state a decision saw. No confidence, no
|
||||
* recency, no staleness, no volatility: those are read off the curve. */
|
||||
o += (size_t)snprintf(buf + o, cap - o, "w %.9g %.9g %.9g %.9g\n",
|
||||
g->factual, g->relational, g->associative, g->polarity);
|
||||
o += (size_t)snprintf(buf + o, cap - o, "k %s\n", cog_prov_name(g->prov));
|
||||
o += (size_t)snprintf(buf + o, cap - o, "t %lld %lld %.9g\n",
|
||||
(long long)g->ts, (long long)g->seq, g->reinforcements);
|
||||
o += (size_t)snprintf(buf + o, cap - o, "d %.9g %.9g %.9g\n",
|
||||
g->fac_proj, g->rel_proj, g->cos_angle);
|
||||
o += (size_t)snprintf(buf + o, cap - o, "v %s\n", g->binding_value[0] ? g->binding_value : "-");
|
||||
o += (size_t)snprintf(buf + o, cap - o, "c %.9g %.9g\n", g->floor_at_record, g->rel_floor_at_record);
|
||||
if (g->prev_edge[0]) o += (size_t)snprintf(buf + o, cap - o, "p %s\n", g->prev_edge);
|
||||
(void)o;
|
||||
return buf;
|
||||
}
|
||||
|
||||
/* ── Consequence, not epsilon. Every test is a floor crossing or a sign change,
|
||||
* both exact. Ordered so the two INHERENT (discrete) moves are reported in
|
||||
* preference to the graded ones, because they bypass the salience gate. */
|
||||
CogSignificance cog_grounding_significant(const CogGrounding* prev,
|
||||
const CogGrounding* now,
|
||||
double floor, double rel_floor) {
|
||||
if (!now) return COG_SIG_NONE;
|
||||
if (!prev || !prev->present) return COG_SIG_FIRST_RECORD;
|
||||
|
||||
/* INHERENT 1 — polarity sign flip. Ignorance and disagreement are different
|
||||
* states, and support → contradiction is a change of state rather than a
|
||||
* drift, so no threshold applies. Comparing signs, with zero its own class. */
|
||||
{
|
||||
int sp = prev->polarity > 0 ? 1 : (prev->polarity < 0 ? -1 : 0);
|
||||
int sn = now->polarity > 0 ? 1 : (now->polarity < 0 ? -1 : 0);
|
||||
if (sp != sn) return COG_SIG_POLARITY_FLIP;
|
||||
}
|
||||
/* INHERENT 2 — provenance class change. told → observed is a categorical
|
||||
* upgrade in what the relation is entitled to, not a movement along an axis. */
|
||||
if (prev->prov != now->prov) return COG_SIG_PROVENANCE_CHANGE;
|
||||
|
||||
/* Crossing an assert floor — the move changes whether this relation can be
|
||||
* spoken. Compared on the DECAYED values, because that is what the gate reads. */
|
||||
if ((prev->factual_now >= floor) != (now->factual_now >= floor)) return COG_SIG_FACTUAL_FLOOR;
|
||||
if ((prev->relational_now >= rel_floor) != (now->relational_now >= rel_floor)) return COG_SIG_RELATIONAL_FLOOR;
|
||||
|
||||
/* Flipping factual/relational agreement — the relation stops being "true and
|
||||
* meaningful" and becomes "true and misapplied", or the reverse. This is the
|
||||
* 911/CPS contradiction as a measured event rather than a reviewable one. */
|
||||
if (prev->agreement != now->agreement) return COG_SIG_AGREEMENT_FLIP;
|
||||
|
||||
/* A gradient reversing — the evidence stopped pulling the claim toward it and
|
||||
* began pushing it away, or the same on the values axis. */
|
||||
if ((prev->fac_proj > 0) != (now->fac_proj > 0)) return COG_SIG_DIRECTION_REVERSAL;
|
||||
if ((prev->rel_proj > 0) != (now->rel_proj > 0)) return COG_SIG_DIRECTION_REVERSAL;
|
||||
|
||||
return COG_SIG_NONE;
|
||||
}
|
||||
|
||||
int cog_significance_inherent(CogSignificance s) {
|
||||
return (s == COG_SIG_FIRST_RECORD || s == COG_SIG_POLARITY_FLIP ||
|
||||
s == COG_SIG_PROVENANCE_CHANGE) ? 1 : 0;
|
||||
}
|
||||
|
||||
const char* cog_significance_name(CogSignificance s) {
|
||||
switch (s) {
|
||||
case COG_SIG_FIRST_RECORD: return "first-record";
|
||||
case COG_SIG_POLARITY_FLIP: return "polarity-sign-flip";
|
||||
case COG_SIG_PROVENANCE_CHANGE: return "provenance-class-change";
|
||||
case COG_SIG_FACTUAL_FLOOR: return "factual-floor-crossed";
|
||||
case COG_SIG_RELATIONAL_FLOOR: return "relational-floor-crossed";
|
||||
case COG_SIG_AGREEMENT_FLIP: return "agreement-sign-flip";
|
||||
case COG_SIG_DIRECTION_REVERSAL: return "gradient-direction-reversal";
|
||||
default: return "none";
|
||||
}
|
||||
}
|
||||
|
||||
/* ── Recording: a NEW edge record. The predecessor is never touched. ────────── */
|
||||
int cog_grounding_record(EngramPagedStore* s, const StoreEdge* base,
|
||||
const CogGrounding* g, char* out_id, size_t out_id_cap) {
|
||||
if (!s || !base || !base->id || !g) return -1;
|
||||
char root[192];
|
||||
snprintf(root, sizeof root, "%s", base->id);
|
||||
char* hash = strchr(root, '#'); if (hash) *hash = '\0';
|
||||
|
||||
int seq = (int)g->seq + 1;
|
||||
char vid[224];
|
||||
snprintf(vid, sizeof vid, "%s#%d", root, seq);
|
||||
|
||||
CogGrounding rec = *g;
|
||||
rec.seq = seq;
|
||||
snprintf(rec.prev_edge, sizeof rec.prev_edge, "%s", base->id);
|
||||
|
||||
char* meta = cog_grounding_metadata(base->metadata, &rec);
|
||||
if (!meta) return -1;
|
||||
|
||||
StoreEdge e; memset(&e, 0, sizeof e);
|
||||
e.id = vid; e.from_id = base->from_id; e.to_id = base->to_id;
|
||||
e.relation = base->relation; e.metadata = meta;
|
||||
/* The vector IS the weight, so the scalar fields carry their dimensions:
|
||||
* `weight` the magnitude of polarity, `inhibitory` its sign, `hebb` the
|
||||
* associative strength. Nothing here is a second copy of a derived value. */
|
||||
e.weight = rec.polarity < 0 ? -rec.polarity : rec.polarity;
|
||||
e.inhibitory = rec.polarity < 0 ? 1 : 0;
|
||||
e.hebb = rec.associative;
|
||||
e.confidence = base->confidence;
|
||||
e.created_at = base->created_at;
|
||||
e.updated_at = rec.ts;
|
||||
e.last_fired = rec.ts;
|
||||
e.layer_id = base->layer_id;
|
||||
int rc = store_put_edge(s, &e);
|
||||
free(meta);
|
||||
if (rc != 0) return -1;
|
||||
if (out_id && out_id_cap) snprintf(out_id, out_id_cap, "%s", vid);
|
||||
return seq;
|
||||
}
|
||||
|
||||
int cog_grounding_head(EngramPagedStore* s, const char* base_id,
|
||||
StoreEdge* out, int max_versions) {
|
||||
if (!s || !base_id || !out) return -1;
|
||||
if (max_versions <= 0) max_versions = 64;
|
||||
char root[192]; snprintf(root, sizeof root, "%s", base_id);
|
||||
char* hash = strchr(root, '#'); if (hash) *hash = '\0';
|
||||
|
||||
StoreEdge cur; memset(&cur, 0, sizeof cur);
|
||||
if (store_get_edge(s, root, &cur) != 1) return -1;
|
||||
int found = 0;
|
||||
for (int v = 1; v <= max_versions; v++) {
|
||||
char vid[224]; snprintf(vid, sizeof vid, "%s#%d", root, v);
|
||||
StoreEdge nx;
|
||||
if (store_get_edge(s, vid, &nx) != 1) break;
|
||||
store_edge_free(&cur); cur = nx; found = v;
|
||||
}
|
||||
*out = cur;
|
||||
return found;
|
||||
}
|
||||
|
||||
/* ── VOLATILITY AND DRIFT: derived from the chain, stored nowhere. The series
|
||||
* exists only because nothing was destroyed, which is the whole return on
|
||||
* immutability — a derivative for free. */
|
||||
int cog_grounding_trajectory(EngramPagedStore* s, const char* base_id,
|
||||
int64_t now_ms, CogTrajectory* out) {
|
||||
if (!s || !base_id || !out) return -1;
|
||||
memset(out, 0, sizeof *out);
|
||||
char root[192]; snprintf(root, sizeof root, "%s", base_id);
|
||||
char* hash = strchr(root, '#'); if (hash) *hash = '\0';
|
||||
|
||||
double pf = 0, pr = 0, f0 = 0, r0 = 0, fN = 0, rN = 0;
|
||||
double sum_df = 0, sum_dr = 0;
|
||||
int n = 0;
|
||||
for (int v = 0; v <= 64; v++) {
|
||||
char vid[224];
|
||||
if (v == 0) snprintf(vid, sizeof vid, "%s", root);
|
||||
else snprintf(vid, sizeof vid, "%s#%d", root, v);
|
||||
StoreEdge e;
|
||||
if (store_get_edge(s, vid, &e) != 1) { if (v) break; else continue; }
|
||||
CogGrounding g;
|
||||
if (cog_grounding_parse(&e, now_ms, &g) == 0) {
|
||||
if (n == 0) { f0 = g.factual; r0 = g.relational; }
|
||||
else { sum_df += fabs(g.factual - pf); sum_dr += fabs(g.relational - pr); }
|
||||
pf = g.factual; pr = g.relational; fN = pf; rN = pr;
|
||||
n++;
|
||||
}
|
||||
store_edge_free(&e);
|
||||
}
|
||||
out->n_versions = n;
|
||||
if (n > 1) {
|
||||
out->factual_volatility = sum_df / (double)(n - 1);
|
||||
out->relational_volatility = sum_dr / (double)(n - 1);
|
||||
}
|
||||
out->factual_drift = fN - f0;
|
||||
out->relational_drift = rN - r0;
|
||||
/* "STAYED TRUE, BECAME WRONG" — the event the joint record makes visible and
|
||||
* that per-dimension versioning would have destroyed: the fact held while
|
||||
* the meaning degraded. Expressed as signs, so there is no tolerance here
|
||||
* either: factual did not fall, relational did. */
|
||||
out->stayed_true_became_wrong =
|
||||
(n > 1 && out->factual_drift >= 0 && out->relational_drift < 0) ? 1 : 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* ── Assertion gates on BOTH floors. Traversal is untouched: activation still
|
||||
* conducts on the factual/associative side, so a relation can remain thinkable
|
||||
* while ceasing to be assertable. That gap is where the wide angles live. ──── */
|
||||
int cog_assert_two_axis(EngramPagedStore* s, const char* claim_id,
|
||||
double floor, double rel_floor, int64_t now_ms,
|
||||
CogAssertion* out) {
|
||||
if (!s || !claim_id || !out) return -1;
|
||||
memset(out, 0, sizeof *out);
|
||||
if (!(floor > 0)) floor = 0.5;
|
||||
if (!(rel_floor > 0)) rel_floor = floor;
|
||||
|
||||
/* still_held is DERIVED, not a literal (§8.1). Holding is unconditional —
|
||||
* the store gates nothing — so the question the field actually answers is
|
||||
* whether the content is present and live. */
|
||||
StoreNode n;
|
||||
if (store_get_node(s, claim_id, &n) == 1) { out->still_held = !n.tombstoned; store_node_free(&n); }
|
||||
else out->still_held = 0;
|
||||
|
||||
double best = -1.0;
|
||||
for (int dir = 0; dir < 2; dir++) {
|
||||
StoreEdge* edges = NULL; size_t ne = 0;
|
||||
int rc = dir == 0 ? store_get_edges_from(s, claim_id, &edges, &ne)
|
||||
: store_get_edges_to (s, claim_id, &edges, &ne);
|
||||
if (rc < 0) continue;
|
||||
for (size_t i = 0; i < ne; i++) {
|
||||
if (edges[i].tombstoned) continue;
|
||||
CogGrounding g;
|
||||
if (cog_grounding_parse(&edges[i], now_ms, &g) != 0) continue;
|
||||
out->n_edges++;
|
||||
out->found = 1;
|
||||
if (g.factual_now > best) {
|
||||
best = g.factual_now;
|
||||
out->factual = g.factual_now;
|
||||
out->relational = g.relational_now; /* the SAME edge, not a max */
|
||||
out->polarity = g.polarity;
|
||||
out->cos_angle = g.cos_angle;
|
||||
out->agreement = g.agreement;
|
||||
out->prov = g.prov;
|
||||
out->relational_established = g.present;
|
||||
snprintf(out->best_edge, sizeof out->best_edge, "%s", edges[i].id ? edges[i].id : "");
|
||||
snprintf(out->binding_value, sizeof out->binding_value, "%s", g.binding_value);
|
||||
}
|
||||
}
|
||||
store_edges_free(edges, ne);
|
||||
}
|
||||
/* BOTH floors, and an unestablished relational axis does NOT pass by default
|
||||
* — defaulting it to passing is the exemption §0 forbids. A negative polarity
|
||||
* is a relation that actively contradicts and can never license assertion. */
|
||||
out->may_assert = (out->found && out->relational_established &&
|
||||
out->polarity > 0 &&
|
||||
out->factual >= floor && out->relational >= rel_floor) ? 1 : 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
/* ═══════════════════════════════════════════════ THE CORRESPONDENCE-LOOP ═════ */
|
||||
int engram_correspondence_beat(const GeoDescriptor* region, const float* anchor,
|
||||
double outcome_y, CogStance* stance,
|
||||
int learn, double max_step, CogBeatResult* out) {
|
||||
if (!region || !stance || !out) return -1;
|
||||
memset(out, 0, sizeof *out);
|
||||
if (stance->keystone) { learn = 0; out->wrote_keystone = 1; } /* §6: never write a keystone */
|
||||
/* 2026-08-16: the keystone block is GONE. It refused to learn about the
|
||||
* reference frame, which does not make it a good reference — it makes it
|
||||
* unexaminable, trading circular calibration for an ungroundable one (spec
|
||||
* §2). Measured cost of the block: on the keystone region the beat reported
|
||||
* 0.00% brier reduction over n_trials 0 — it never ran, so nothing about the
|
||||
* self was ever calibrated OR falsifiable. What replaces it is a provenance
|
||||
* constraint, not a permission: cog_grounding_downstream refuses evidence
|
||||
* that is downstream of the region being calibrated, for every region alike.
|
||||
* `wrote_keystone` is retained as a reporting field only and is always 0. */
|
||||
|
||||
GeoGradient g;
|
||||
if (engram_think(region, anchor, stance, &g) != 0) return -1; /* PREDICTION */
|
||||
|
||||
+269
-17
@@ -23,7 +23,7 @@
|
||||
* and a region, and grounded-for-whom.
|
||||
*
|
||||
* PURE + (mostly) READ-ONLY, stdlib + libm only. think() and the warp are pure
|
||||
* over their inputs. Persistence (Stance <-> StoreNode, grounded-by edges) is the
|
||||
* over their inputs. Persistence (Stance <-> StoreNode, edge grounding vectors) is the
|
||||
* only part that touches the store, and it is additive / supersede / tombstone —
|
||||
* never mutate-in-place, never delete. It NEVER touches the live daemon: all
|
||||
* offline against a scratch store, per the design's rails.
|
||||
@@ -152,30 +152,25 @@ int engram_express(const GeoGradient* g, const float* anchor, float* out_point);
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
* §5 HOLD vs GROUND vs ASSERT. Holding is unconditional (the store gates nothing).
|
||||
* Grounding is a RELATION — a "grounded-by" edge, probabilistic, grounded-for-whom.
|
||||
* The honesty floor is checked only at ASSERTION.
|
||||
* Grounding is an ATTRIBUTE OF a relation — carried on the edge itself, as a
|
||||
* vector (§7). The honesty floor is checked only at ASSERTION, on both axes.
|
||||
* ═══════════════════════════════════════════════════════════════════════════ */
|
||||
#define COG_GROUNDED_BY_RELATION "grounded-by"
|
||||
/* DELETED 2026-08-16: COG_GROUNDED_BY_RELATION and cog_ground_edge.
|
||||
*
|
||||
* A "grounded-by" edge models grounding as a relation BETWEEN two nodes. It is a
|
||||
* property OF a relation — and it is that relation's weight. Minting a new edge
|
||||
* to carry a score was the error; #147 corrected which endpoints the edge landed
|
||||
* on and left the wrong idea standing. There is nothing to ground a claim
|
||||
* "against" that is not already an edge, and if no edge exists the honest answer
|
||||
* is that the two are not related — not a freshly minted one scoring 0.98.
|
||||
* See §7 for what replaced it. */
|
||||
#define COG_SALIENT_TO_RELATION "salient-to"
|
||||
|
||||
/* Write a grounded-by edge (additive). weight = grounding ∈(0,1] from the verifier;
|
||||
* for_whom recorded in edge metadata (grounding is relational). Never a node flag. */
|
||||
int cog_ground_edge(EngramPagedStore* s, const char* claim_id,
|
||||
const char* evidence_id, double grounding, const char* for_whom);
|
||||
|
||||
/* Write/refresh a salient-to edge: salience is RELATIONAL (grounded-for-whom),
|
||||
* carried on the edge to the observer — not baked into the node scalar (§2.1). */
|
||||
int cog_salient_edge(EngramPagedStore* s, const char* node_id,
|
||||
const char* observer_id, double salience);
|
||||
|
||||
/* The honesty floor — a QUERY at assertion time, NOT a schema constraint. Reads the
|
||||
* claim's stored grounded-by edges (for the given observer) and returns:
|
||||
* 1 = may assert (best grounding >= floor),
|
||||
* 0 = REFUSE assertion (holds unconditionally; only asserting is gated),
|
||||
* <0 = error. The content remains held either way. */
|
||||
int cog_assert_gate(EngramPagedStore* s, const char* claim_id,
|
||||
const char* for_whom, double floor);
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
* §4 THE REFLEXIVE CORRESPONDENCE-LOOP — the learning engine. think scores its
|
||||
* OWN gradient against outcome, refines the stance on the error, and (optionally)
|
||||
@@ -209,8 +204,265 @@ int engram_correspondence_beat(const GeoDescriptor* region, const float* anchor,
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
* §6 METASTABILITY. Keystones (self/values) are read-mostly: the loop reads but
|
||||
* never writes them. Mark by stance flag or by a keystone-id set the loop consults.
|
||||
*
|
||||
* SUPERSEDED BY §7's PROVENANCE CONSTRAINT (2026-08-16). The keystone flag is a
|
||||
* PERMISSION: it asks who the target is, not where the evidence came from. That
|
||||
* is censorship, and it costs the ability to ever ground the self (spec
|
||||
* correspondence-and-censorship.md §0/§2). The constraint that actually protects
|
||||
* a reference frame is cog_grounding_downstream: a region may not be calibrated
|
||||
* by evidence downstream of itself. These declarations remain only so existing
|
||||
* call sites keep compiling; nothing in the grounding path consults them.
|
||||
* ═══════════════════════════════════════════════════════════════════════════ */
|
||||
typedef struct { const char** ids; int n; } CogKeystoneSet;
|
||||
int cog_is_keystone(const CogKeystoneSet* ks, const CogStance* s);
|
||||
|
||||
/* ═══════════════════════════════════════════════════════════════════════════
|
||||
* §7 GROUNDING IS THE EDGE'S WEIGHT, AND THE WEIGHT IS A VECTOR
|
||||
* (2026-08-16; spec correspondence-and-censorship.md §2–§6 @ 2b7e4ba.)
|
||||
*
|
||||
* THE MODEL. Grounding is not a subsystem, a score, or a relation BETWEEN nodes.
|
||||
* It is an attribute OF a relation. The graph already IS the grounding structure:
|
||||
* every edge is a grounded relation, and what that relation is worth is carried
|
||||
* on the edge itself. Three things follow, and each DELETES rather than adds:
|
||||
*
|
||||
* 1. `grounded-by` as a relation type does not exist, and cog_ground_edge is
|
||||
* gone. Minting an edge to hold a score models grounding as a relation
|
||||
* between nodes when it is a property of a relation. #147 corrected which
|
||||
* endpoints that edge landed on and left the wrong idea standing.
|
||||
* 2. There is no observer, and no sampling rate. Change is not a consequence of
|
||||
* use — it IS use, the way potentiation is the firing rather than something
|
||||
* that reads the firing and writes a weight. So no supervisor compares a
|
||||
* value to a threshold and decides to persist.
|
||||
* 3. Between two recorded versions the trajectory is not unknown. Decay is a
|
||||
* pure function of the last recorded point and elapsed time, so it is
|
||||
* ANALYTIC: store the point, read the curve.
|
||||
*
|
||||
* WHAT IS *NOT* HERE, DELIBERATELY. An earlier draft of the spec posed "a graph
|
||||
* predicate for evidence downstream of itself" as the hard problem, and this file
|
||||
* briefly contained one. It is withdrawn. Non-circularity is TEMPORAL, not
|
||||
* topological: you cannot recalibrate the ruler while measuring with it, so you
|
||||
* do it when you are not using the frame to act. Reachability could never have
|
||||
* worked — measured on the live store, reachability from the self region over
|
||||
* all relations reaches 89.2% of the graph (10,580 of 11,861 nodes) and 16.0%
|
||||
* over hebbian/semantic relations alone, so the predicate marks essentially all
|
||||
* evidence tainted and the constraint degenerates into the total block that
|
||||
* censorship started as. Nothing replaces it here; the independence is a fact
|
||||
* about engagement, owned by the dreamer, not a fact about the graph.
|
||||
*
|
||||
* ═══════════════════════════════════════════════════════════════════════════
|
||||
* §7.1 THE VECTOR
|
||||
*
|
||||
* The test for a real dimension is whether it can move independently of the
|
||||
* others. Five can, and each maps onto substrate that already exists:
|
||||
*
|
||||
* factual correspondence with evidence. [GRD1]
|
||||
* relational correspondence with values — min over THIRTEEN
|
||||
* value regions, carrying the binding value's NAME. [GRD1]
|
||||
* associative co-activation frequency. This is the edge's `hebb`
|
||||
* field with its existing dynamics — NOT a new one.
|
||||
* Independent by construction: every superstition is
|
||||
* a strong association with no factual grounding.
|
||||
* polarity SIGNED. Near zero means "no support"; NEGATIVE means
|
||||
* "this actively contradicts". The edge's `inhibitory`
|
||||
* bit is exactly this distinction crushed to one bit,
|
||||
* and is carried forward as the seed value. [GRD1]
|
||||
* provenance observed / inferred / told / imprinted. Categorical,
|
||||
* and load-bearing: it governs what the relation is
|
||||
* entitled to. [GRD1]
|
||||
*
|
||||
* 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), recency (decay read off the curve), staleness (grounding fallen
|
||||
* below its floor), volatility (the derivative of a series nothing destroyed).
|
||||
* Storing confidence separately is how `confidence: 0.5` ends up sitting beside
|
||||
* a zero direction vector, asserting something nothing computed. Every field in
|
||||
* CogGrounding below is marked STORED or DERIVED, and the serializer writes
|
||||
* only the STORED ones.
|
||||
*
|
||||
* THE VALUES REFERENCE IS THIRTEEN REGIONS AND THE AGGREGATE IS MIN.
|
||||
* Measured on the live store: the values root kn-5b606390 `contains` exactly 13
|
||||
* value nodes; pairwise centroid cosine among their regions is min 0.1525,
|
||||
* mean 0.5199, median 0.5282, max 0.9278 — they demonstrably do not form one
|
||||
* region. Against a single union region the individual values sit at cosine
|
||||
* 0.38..0.89, with constraints-as-freedom at 0.3812 and change-is-the-signal at
|
||||
* 0.4677, so a union centroid under-represents precisely the values a claim is
|
||||
* most likely to be measured against. MIN rather than MEAN because a mean lets
|
||||
* strong agreement with twelve values mask a violation of the thirteenth, which
|
||||
* is the mechanism of rationalization; min yields a binding constraint with a
|
||||
* NAME attached rather than a score.
|
||||
*
|
||||
* 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 — 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.
|
||||
* ═══════════════════════════════════════════════════════════════════════════ */
|
||||
|
||||
/* ── The one decay model (moved here from el_runtime.c so that node decay and
|
||||
* edge-grounding decay are a single implementation with a single set of
|
||||
* constants, rather than a model and a parallel copy of it). Half-life scales
|
||||
* with how established the thing is: T_eff = T_HALF · (1 + ln(1 + reinforcements)).
|
||||
* The floor is a preference, not a cliff — max penalty for age alone is 4x.
|
||||
* `lambda_override` > 0 replaces the default rate; 0 means use the default. */
|
||||
#define COG_T_HALF_HOURS 168.0
|
||||
#define COG_DECAY_LAMBDA 0.693147
|
||||
#define COG_DECAY_FLOOR 0.25
|
||||
double cog_decay_factor(int64_t age_ms, double reinforcements, double lambda_override);
|
||||
|
||||
/* The compact vector block carried in the edge's own metadata. Line schema, same
|
||||
* precedent as STNC1 / GEO1. Metadata the edge already carried is preserved
|
||||
* verbatim ahead of the magic line. */
|
||||
#define COG_GROUNDING_META_MAGIC "GRD1"
|
||||
|
||||
/* Provenance class — categorical, and it governs what the relation is entitled
|
||||
* to. A change of class is inherently significant and needs no threshold,
|
||||
* because told → observed is a categorical upgrade, not a drift. */
|
||||
typedef enum {
|
||||
COG_PROV_UNSET = 0,
|
||||
COG_PROV_OBSERVED = 1,
|
||||
COG_PROV_INFERRED = 2,
|
||||
COG_PROV_TOLD = 3,
|
||||
COG_PROV_IMPRINTED = 4
|
||||
} CogProvClass;
|
||||
const char* cog_prov_name(CogProvClass p);
|
||||
CogProvClass cog_prov_parse(const char* s);
|
||||
|
||||
typedef struct {
|
||||
int present; /* 1 iff the edge carries a GRD1 block */
|
||||
|
||||
/* ── STORED: the vector, as it stood at `ts` ─────────────────────────────── */
|
||||
double factual; /* correspondence with evidence */
|
||||
double relational; /* min over the thirteen value regions */
|
||||
double associative; /* co-activation frequency — mirrors edge->hebb */
|
||||
double polarity; /* SIGNED support; <0 = actively contradicts */
|
||||
CogProvClass prov; /* observed / inferred / told / imprinted */
|
||||
int64_t ts; /* when this version was recorded (ms) */
|
||||
int64_t seq; /* supersession sequence number */
|
||||
double reinforcements; /* uses folded into this version */
|
||||
char binding_value[128]; /* the argmin value — the conflict's NAME */
|
||||
/* the two gradients as frame-independent signed projections, plus the angle
|
||||
* between them in full R^dim. These are part of the JOINT STATE a decision
|
||||
* saw, not a convenience: near +1 evidence and values push the same way; at
|
||||
* or below 0 the relation is factually supported and relationally wrong. */
|
||||
double fac_proj, rel_proj, cos_angle;
|
||||
int agreement; /* sign(cos_angle): +1 / 0 / −1 */
|
||||
double floor_at_record, rel_floor_at_record;
|
||||
char prev_edge[192]; /* the version this superseded ("" if first) */
|
||||
|
||||
/* ── DERIVED at read time. NEVER serialized. ─────────────────────────────── */
|
||||
int64_t age_ms; /* recency: now − ts */
|
||||
double decay; /* cog_decay_factor over that age */
|
||||
double factual_now; /* factual · decay */
|
||||
double relational_now;
|
||||
double associative_now;
|
||||
int stale; /* grounding fallen below its floor */
|
||||
} CogGrounding;
|
||||
|
||||
/* Read an edge's vector as of `now_ms`. Pure — never writes. An edge with no
|
||||
* GRD1 block still has an associative strength (its accrued hebb) and a polarity
|
||||
* (its signed authored weight); `present` says whether the grounding dimensions
|
||||
* have ever been established, and an unestablished dimension is reported as such
|
||||
* rather than defaulted to a passing value. */
|
||||
int cog_grounding_parse(const StoreEdge* e, int64_t now_ms, CogGrounding* out);
|
||||
|
||||
/* Serialize the STORED half of the vector, preserving pre-existing non-GRD1
|
||||
* metadata. Returns an owned string. Derived fields are not written. */
|
||||
char* cog_grounding_metadata(const char* base_meta, const CogGrounding* g);
|
||||
|
||||
/* ── §7.2 CONSOLIDATION-GATED SUPERSESSION ──────────────────────────────────
|
||||
*
|
||||
* Supersession is not recording — it is CONSOLIDATION, gated by salience, which
|
||||
* is why you remember the argument and not the commute. Significance is
|
||||
* evaluated PER-DIMENSION but the record is the WHOLE VECTOR: any dimension
|
||||
* moving enough to matter triggers a supersession, and the new version captures
|
||||
* every dimension as it stood at that instant. Versioning axes independently
|
||||
* would make the joint state unreconstructable, and the joint state is the point
|
||||
* — it is what makes "stayed true, became wrong" visible as an event (factual
|
||||
* holding steady across versions while relational degrades).
|
||||
*
|
||||
* There is deliberately no epsilon in this enum or in the function that computes
|
||||
* it. Every test is a floor crossing or a sign change, both exact. Two of them
|
||||
* are INHERENTLY significant because they are discrete state changes rather than
|
||||
* drift, and those bypass the salience gate entirely. */
|
||||
typedef enum {
|
||||
COG_SIG_NONE = 0, /* nothing decision-relevant moved — DO NOT RECORD */
|
||||
COG_SIG_FIRST_RECORD = 1, /* no prior version exists */
|
||||
COG_SIG_POLARITY_FLIP = 2, /* INHERENT: support ↔ contradiction, or ignorance
|
||||
* ↔ either. A discrete change of state. */
|
||||
COG_SIG_PROVENANCE_CHANGE = 3, /* INHERENT: told → observed is a categorical
|
||||
* upgrade in what the relation is entitled to. */
|
||||
COG_SIG_FACTUAL_FLOOR = 4, /* crossed the assert floor, factual axis */
|
||||
COG_SIG_RELATIONAL_FLOOR = 5, /* crossed the assert floor, relational axis */
|
||||
COG_SIG_AGREEMENT_FLIP = 6, /* factual/relational agreement changed sign */
|
||||
COG_SIG_DIRECTION_REVERSAL = 7 /* a gradient reversed direction */
|
||||
} CogSignificance;
|
||||
|
||||
CogSignificance cog_grounding_significant(const CogGrounding* prev,
|
||||
const CogGrounding* now,
|
||||
double floor, double rel_floor);
|
||||
const char* cog_significance_name(CogSignificance s);
|
||||
/* 1 iff this reason is a discrete state change that consolidates regardless of
|
||||
* salience (polarity flip, provenance change, first record). */
|
||||
int cog_significance_inherent(CogSignificance s);
|
||||
|
||||
/* ── §7.3 RECORDING: supersession of the EDGE, never an overwrite ────────────
|
||||
* Writes version seq+1 as a NEW edge record with the same endpoints and relation
|
||||
* and id "<root>#<seq+1>", carrying a GRD1 `p` pointer to its predecessor. The
|
||||
* predecessor is never touched. The chain IS the trajectory: not only what the
|
||||
* grounding is but which way it has been moving and how fast — a derivative
|
||||
* obtained for free from immutability, because the points were never destroyed.
|
||||
* Returns the version written (>=1), or <0 on error. */
|
||||
int cog_grounding_record(EngramPagedStore* s, const StoreEdge* base,
|
||||
const CogGrounding* g, char* out_id, size_t out_id_cap);
|
||||
|
||||
/* Walk forward from a base edge id to its newest recorded version. Point reads
|
||||
* only; consolidation is gated, so the chain is short. Returns the highest
|
||||
* version found (0 = the base record is the only one). */
|
||||
int cog_grounding_head(EngramPagedStore* s, const char* base_id,
|
||||
StoreEdge* out, int max_versions);
|
||||
|
||||
/* VOLATILITY — derived, never stored: the mean absolute per-version change of a
|
||||
* dimension across the recorded chain. Feeds the equally-derived `confidence`
|
||||
* (high grounding AND low volatility), which is likewise never stored. */
|
||||
typedef struct {
|
||||
int n_versions;
|
||||
double factual_volatility;
|
||||
double relational_volatility;
|
||||
double factual_drift; /* signed: newest − oldest */
|
||||
double relational_drift;
|
||||
int stayed_true_became_wrong; /* factual steady while relational degraded */
|
||||
} CogTrajectory;
|
||||
int cog_grounding_trajectory(EngramPagedStore* s, const char* base_id,
|
||||
int64_t now_ms, CogTrajectory* out);
|
||||
|
||||
/* ── §7.4 ASSERTION GATES ON BOTH FLOORS ────────────────────────────────────
|
||||
* A well-evidenced claim must not earn the right to be asserted regardless of
|
||||
* whether it means the right thing. `may_assert` requires the decayed factual
|
||||
* grounding to clear `floor` AND the decayed relational grounding to clear
|
||||
* `rel_floor`. A relation whose relational axis has never been established does
|
||||
* not pass by default — it is reported unestablished and refused, because
|
||||
* defaulting it to passing is exactly the exemption §0 forbids. Traversal is
|
||||
* untouched: activation still conducts on the factual/associative side, so a
|
||||
* relation can remain thinkable while ceasing to be assertable. */
|
||||
typedef struct {
|
||||
int may_assert;
|
||||
int found; /* any relation at all on this claim */
|
||||
int relational_established;
|
||||
int still_held; /* DERIVED: node present and not tombstoned */
|
||||
double factual; /* best decayed factual grounding */
|
||||
double relational; /* the SAME edge's relational axis, not a max */
|
||||
double polarity;
|
||||
double cos_angle;
|
||||
int agreement;
|
||||
CogProvClass prov;
|
||||
char best_edge[192];
|
||||
char binding_value[128];
|
||||
int n_edges;
|
||||
} CogAssertion;
|
||||
int cog_assert_two_axis(EngramPagedStore* s, const char* claim_id,
|
||||
double floor, double rel_floor, int64_t now_ms,
|
||||
CogAssertion* out);
|
||||
|
||||
#endif /* ENGRAM_COGNITION_H */
|
||||
|
||||
+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.
|
||||
|
||||
---
|
||||
|
||||
@@ -0,0 +1,234 @@
|
||||
import "../../runtime/eltest.el"
|
||||
// test_transduce.el — geometry as a first-class El value, and realizers
|
||||
// declared in El rather than patched into the runtime.
|
||||
//
|
||||
// WHAT IS ACTUALLY UNDER TEST. 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, so any non-text modality had to be
|
||||
// DESCRIBED in prose first and the geometry we reasoned over was the geometry
|
||||
// OF THE DESCRIPTION, not of the signal. The fix has two halves, and this file
|
||||
// exercises both:
|
||||
//
|
||||
// 1. Geometry is a VALUE — it carries its own width, so nothing has to
|
||||
// assert a width against a string's length.
|
||||
// 2. A REALIZER is an ordinary El function. `tone_realizer` below is not in
|
||||
// the runtime, is not known to the compiler, and is not special in any
|
||||
// way; it is registered BY NAME and dispatched to through transduce().
|
||||
// That is the load-bearing claim: adding a modality must not require a
|
||||
// runtime patch, or nothing has actually moved into the language.
|
||||
//
|
||||
// COMPARISON DISCIPLINE IN THIS FILE (measured 2026-08-16, not stylistic):
|
||||
// elc lowers `a == b` to a NUMERIC comparison only when both operand names are
|
||||
// in the per-function int-name set, which `let x: Int` populates. A bare call
|
||||
// like `geometry_is(g) == 0` is not a registered name, so it lowers to
|
||||
// `str_eq(...)` — strcmp on two integers reinterpreted as pointers. `<` and `>`
|
||||
// lower directly via binop_to_c with no type inference at all, so truthiness is
|
||||
// written `> 0` / `< 1` here, and any exact `==` is done on a value first bound
|
||||
// through `let x: Int`.
|
||||
|
||||
// ── A realizer, written entirely in El ──────────────────────────────────────
|
||||
// Maps a "tone" signal into a 4-component geometry. Deliberately trivial —
|
||||
// what is being proven is that an El function can BE a realizer, not that
|
||||
// this is good acoustics. The one real property it has: distinct signals
|
||||
// produce distinct geometry, so the test can tell transduction from a stub.
|
||||
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))
|
||||
let b: Int = geometry_set(g, 1, int_to_float(n * 2))
|
||||
let c: Int = geometry_set(g, 2, int_to_float(n * 3))
|
||||
let d: Int = geometry_set(g, 3, int_to_float(n * 4))
|
||||
g
|
||||
}
|
||||
|
||||
// A second realizer for a different modality, to prove the registry keys on
|
||||
// modality and does not just hand back "the last thing registered".
|
||||
fn pulse_realizer(signal: String) -> Geometry {
|
||||
let g: Geometry = geometry_new(2)
|
||||
let a: Int = geometry_set(g, 0, 1.0)
|
||||
let b: Int = geometry_set(g, 1, 0.0)
|
||||
g
|
||||
}
|
||||
|
||||
// A deliberately BROKEN realizer: it returns something that is not a Geometry.
|
||||
// transduce() must not hand this back to a caller as if it were one.
|
||||
fn bogus_realizer(signal: String) -> Geometry {
|
||||
return 12345
|
||||
}
|
||||
|
||||
test "geometry-is-a-value-with-its-own-width" {
|
||||
let g: Geometry = geometry_new(8)
|
||||
let live: Int = geometry_is(g)
|
||||
assert live > 0, "geometry_new returns a live Geometry"
|
||||
let d: Int = geometry_dim(g)
|
||||
assert d == 8, "a Geometry carries its own width"
|
||||
let freed: Int = geometry_free(g)
|
||||
assert freed > 0, "geometry_free reports what it did"
|
||||
}
|
||||
|
||||
test "geometry-rejects-nonsense-without-an-arbitrary-bound" {
|
||||
// dim <= 0 is not a width. Note there is deliberately no MAX dim here:
|
||||
// #141 needed `dim <= 8192` only to bound an allocation sized from a
|
||||
// caller's claim about a string. A value that carries its own width has
|
||||
// nothing left to validate, so the only failure left is allocation.
|
||||
let zero: Geometry = geometry_new(0)
|
||||
let z: Int = geometry_is(zero)
|
||||
assert z < 1, "dim 0 is not a geometry"
|
||||
let neg: Geometry = geometry_new(-4)
|
||||
let n: Int = geometry_is(neg)
|
||||
assert n < 1, "negative dim is not a geometry"
|
||||
// Accessors must be total: a non-geometry is 0-width, never a crash.
|
||||
let nd: Int = geometry_dim(0)
|
||||
assert nd < 1, "geometry_dim of a non-geometry is 0"
|
||||
let ni: Int = geometry_is(0)
|
||||
assert ni < 1, "geometry_is of a non-geometry is 0"
|
||||
let nf: Int = geometry_free(0)
|
||||
assert nf < 1, "geometry_free of a non-geometry is a no-op"
|
||||
}
|
||||
|
||||
test "geometry-components-round-trip" {
|
||||
let g: Geometry = geometry_new(3)
|
||||
let s0: Int = geometry_set(g, 0, 1.5)
|
||||
let s1: Int = geometry_set(g, 1, -2.5)
|
||||
assert s0 > 0, "set in range succeeds"
|
||||
let oob: Int = geometry_set(g, 3, 9.0)
|
||||
assert oob < 1, "set out of range is refused, not silently dropped"
|
||||
let v0: Float = geometry_get(g, 0)
|
||||
let d0: Float = v0 - 1.5
|
||||
assert d0 < 0.001, "component 0 round-trips"
|
||||
assert d0 > -0.001, "component 0 round-trips"
|
||||
let v1: Float = geometry_get(g, 1)
|
||||
let d1: Float = v1 + 2.5
|
||||
assert d1 < 0.001, "component 1 round-trips (negative)"
|
||||
assert d1 > -0.001, "component 1 round-trips (negative)"
|
||||
let freed: Int = geometry_free(g)
|
||||
}
|
||||
|
||||
test "hex-is-an-edge-adapter-and-derives-its-own-width" {
|
||||
// 2 components, little-endian float32: 1.0 = 0000803f, 2.0 = 00000040.
|
||||
let g: Geometry = geometry_from_f32le_hex("0000803f00000040")
|
||||
let live: Int = geometry_is(g)
|
||||
assert live > 0, "valid hex decodes to a Geometry"
|
||||
let d: Int = geometry_dim(g)
|
||||
assert d == 2, "width is DERIVED from the input, never supplied"
|
||||
let a: Float = geometry_get(g, 0)
|
||||
let da: Float = a - 1.0
|
||||
assert da < 0.001, "first component decoded"
|
||||
assert da > -0.001, "first component decoded"
|
||||
let b: Float = geometry_get(g, 1)
|
||||
let db: Float = b - 2.0
|
||||
assert db < 0.001, "second component decoded"
|
||||
assert db > -0.001, "second component decoded"
|
||||
// Egress adapter is the exact inverse.
|
||||
let back: String = geometry_to_f32le_hex(g)
|
||||
assert str_eq(back, "0000803f00000040"), "hex round-trips exactly"
|
||||
let freed: Int = geometry_free(g)
|
||||
}
|
||||
|
||||
test "hex-rejects-malformed-input" {
|
||||
let empty: Geometry = geometry_from_f32le_hex("")
|
||||
let e: Int = geometry_is(empty)
|
||||
assert e < 1, "empty hex is not a geometry"
|
||||
let ragged: Geometry = geometry_from_f32le_hex("0000803f0000")
|
||||
let r: Int = geometry_is(ragged)
|
||||
assert r < 1, "length not a multiple of 8 is refused"
|
||||
let nonhex: Geometry = geometry_from_f32le_hex("zzzzzzzz")
|
||||
let nh: Int = geometry_is(nonhex)
|
||||
assert nh < 1, "non-hex characters are refused"
|
||||
}
|
||||
|
||||
test "a-realizer-declared-in-el-is-a-first-class-realizer" {
|
||||
// THE CLAIM: tone_realizer is an ordinary El function. It is not in the
|
||||
// runtime and the compiler knows nothing about it. Registering it by name
|
||||
// is enough to make it the organ for a modality.
|
||||
let reg: Int = realizer_register("tone", "tone_realizer")
|
||||
assert reg > 0, "an El fn registers as a realizer by name"
|
||||
let has: Int = realizer_has("tone")
|
||||
assert has > 0, "the modality now has an organ"
|
||||
|
||||
let g: Geometry = transduce("aaa", "tone")
|
||||
let live: Int = geometry_is(g)
|
||||
assert live > 0, "transduce returns real geometry"
|
||||
let d: Int = geometry_dim(g)
|
||||
assert d == 4, "the El realizer determined the width, not the runtime"
|
||||
// str_len("aaa") == 3, so component 0 must be 3.0 — proof the signal
|
||||
// actually reached the El function rather than a stub answering for it.
|
||||
let c0: Float = geometry_get(g, 0)
|
||||
let dc: Float = c0 - 3.0
|
||||
assert dc < 0.001, "the signal reached the El realizer"
|
||||
assert dc > -0.001, "the signal reached the El realizer"
|
||||
let freed: Int = geometry_free(g)
|
||||
}
|
||||
|
||||
test "distinct-signals-transduce-to-distinct-geometry" {
|
||||
let reg: Int = realizer_register("tone", "tone_realizer")
|
||||
let g1: Geometry = transduce("aa", "tone")
|
||||
let g2: Geometry = transduce("aaaaa", "tone")
|
||||
let a: Float = geometry_get(g1, 0)
|
||||
let b: Float = geometry_get(g2, 0)
|
||||
let diff: Float = b - a
|
||||
// 5 - 2 = 3. If transduction were a stub these would be equal.
|
||||
assert diff > 2.9, "different signals produce different geometry"
|
||||
assert diff < 3.1, "different signals produce different geometry"
|
||||
let f1: Int = geometry_free(g1)
|
||||
let f2: Int = geometry_free(g2)
|
||||
}
|
||||
|
||||
test "the-registry-keys-on-modality" {
|
||||
let r1: Int = realizer_register("tone", "tone_realizer")
|
||||
let r2: Int = realizer_register("pulse", "pulse_realizer")
|
||||
assert r2 > 0, "a second modality registers independently"
|
||||
let gt: Geometry = transduce("aaa", "tone")
|
||||
let gp: Geometry = transduce("aaa", "pulse")
|
||||
let dt: Int = geometry_dim(gt)
|
||||
let dp: Int = geometry_dim(gp)
|
||||
assert dt == 4, "tone still routes to its own realizer"
|
||||
assert dp == 2, "pulse routes to a different realizer"
|
||||
let f1: Int = geometry_free(gt)
|
||||
let f2: Int = geometry_free(gp)
|
||||
}
|
||||
|
||||
test "no-organ-is-reported-as-no-organ" {
|
||||
// A modality with no realizer must transduce to NOTHING. It must never
|
||||
// fall back to embedding a description of the signal and calling that
|
||||
// perception — that silent substitution is the entire defect this change
|
||||
// exists to end.
|
||||
let has: Int = realizer_has("echolocation")
|
||||
assert has < 1, "unregistered modality has no organ"
|
||||
let g: Geometry = transduce("anything", "echolocation")
|
||||
let live: Int = geometry_is(g)
|
||||
assert live < 1, "no realizer means no geometry, not fake geometry"
|
||||
}
|
||||
|
||||
test "registration-of-an-unresolvable-name-fails-loudly" {
|
||||
// Reported at the moment of WIRING, not later as "this modality mysteriously
|
||||
// produces nothing". Distinguishing "no organ" from "broken organ" is the
|
||||
// lesson that made this whole change necessary.
|
||||
let bad: Int = realizer_register("ghost", "no_such_function_anywhere")
|
||||
assert bad < 1, "an unresolvable realizer name is a registration failure"
|
||||
let has: Int = realizer_has("ghost")
|
||||
assert has < 1, "and nothing gets registered"
|
||||
}
|
||||
|
||||
test "a-realizer-returning-non-geometry-transduces-nothing" {
|
||||
let reg: Int = realizer_register("bogus", "bogus_realizer")
|
||||
assert reg > 0, "the symbol resolves, so registration succeeds"
|
||||
// ...but the contract is enforced at the boundary, so the caller never
|
||||
// receives a value that would misbehave far away from here.
|
||||
let g: Geometry = transduce("x", "bogus")
|
||||
let live: Int = geometry_is(g)
|
||||
assert live < 1, "a non-Geometry return transduced nothing"
|
||||
}
|
||||
|
||||
test "norm-lets-a-caller-check-a-realizer-emitted-signal" {
|
||||
let g: Geometry = geometry_new(2)
|
||||
let z: Float = geometry_norm(g)
|
||||
assert z < 0.001, "a fresh geometry is zero — norm says so"
|
||||
let s0: Int = geometry_set(g, 0, 3.0)
|
||||
let s1: Int = geometry_set(g, 1, 4.0)
|
||||
let n: Float = geometry_norm(g)
|
||||
let dn: Float = n - 5.0
|
||||
assert dn < 0.001, "3-4-5: norm is 5"
|
||||
assert dn > -0.001, "3-4-5: norm is 5"
|
||||
let freed: Int = geometry_free(g)
|
||||
}
|
||||
@@ -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