93aa96cfafacd131335b39b345b8c8707549f6fd
76 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
addd51209f |
runtime: extract engram_text.c, and repair 10 harnesses that could not link
El SDK CI - dev / build-and-test (pull_request) Failing after 13m39s
First concern moved out of el_runtime.c under the ratchet, and the move is
deliberately small: it exists to prove the mechanism end to end before anything
large depends on it.
engram_text.{c,h} — query tokenization, candidate-token hygiene, word-boundary
matching, and the text-damage signature. Four functions, moved verbatim; only
`static` was dropped and each doc comment travelled with the code. They touch no
EL value type and no engram store type: plain C over <ctype.h>/<string.h> over
char buffers. They were never el_runtime.c's business.
el_runtime.c 20,527 -> 20,427 lines (BUDGET max_lines ratcheted down)
engram fns 279 -> 275 (BUDGET max_engram_fns ratcheted down)
The Stage 1 extension point worked as designed: adding the file to
lang/runtime/SOURCES was one line, and every build path picked it up. The
Stage 2 drift guard then caught that I had NOT added it to install.sh's
standalone list — the exact class of drift it was written for, on its first
real change, before the commit rather than after a broken SDK shipped.
WHY ONLY 100 LINES, AND WHAT ACTUALLY BLOCKS THE REST
Measured, not estimated: of 273 engram-domain functions in el_runtime.c
(~9,700 lines), only 75 (~1,058 lines) can move today, and they are scattered
rather than clustered. The blocker is a single fact:
EngramNode, EngramEdge, EngramStore, EngramLayer, EngramWal and EngramIdSlot
are typedef'd INSIDE el_runtime.c. No sibling can see them. engram_store.h
defines a SEPARATE serializable "node view" struct and maps between the two.
So every engram function that takes an EngramNode* — which is most of them, 109
of 273 by direct type reference — cannot compile in engram_store.c until those
types move to a shared header. That extraction is the real Stage 3 enabler and
it deserves its own change: it touches the most load-bearing struct in the
system, and doing it in the same commit as a code move would make a regression
impossible to bisect.
REPAIRED: 10 engram harnesses that had silently stopped linking
Not new breakage from this move — verified against unmodified dev, where
el_runtime.c + engram_store.c alone already failed with undefined symbols.
They had been dead for as long as el_runtime.c has been calling into the
siblings, and nothing noticed because nothing ran them.
run_m3_parity, run_m7_traversal, run_m35_hebb_persist,
run_interoception_p0..p5 — now build from $(scripts/el-runtime-sources.sh)
run_wal_tests — its two TUs #include "el_runtime.c" directly, so
it links the SIBLINGS ONLY; adding el_runtime.c
to that link line would define every symbol twice
(That #include'd .c is worth recording: the runtime does have one, in
engram/test/test_wal.c and the generated test_failloud.c.)
Verified locally — every one of these was run, not assumed:
* m3_parity ............ PASS, incl. ASan+UBSan clean across seed/on/reboot
* m7_traversal ......... PASS
* m35_hebb_persist ..... PASS (the gate over the original prod hebb bug)
* interoception p0..p5 . PASS (all six)
* wal_tests ............ 66 passed, 0 failed, + fail-loud exit check
* self-host fixpoint ... byte-identical, AND the emitted C is byte-identical
to the pre-move compiler output — the move changes
nothing the compiler produces
* engram/src/server.el . compiles and links
* native suites ........ 8 of 13, unchanged from before the move; the same 5
pre-existing failures, no regression
* both runtime guards .. green at the new, lower budget
Also fixes a block comment left unterminated by the extraction (the deleted
range carried its closing */), restoring the compile to its single pre-existing
-Wcomment warning.
|
||
|
|
b92ec92c48 |
Merge pull request 'engram: intake realizes a signal into a manifold, it does not assume a node' (#158) from wire/write-realizes-signal into dev
El SDK CI - dev / build-and-test (push) Failing after 10m58s
|
||
|
|
99ef855b98 |
engram: intake realizes a signal into a manifold, it does not assume a node
El SDK CI - dev / build-and-test (pull_request) Failing after 13m16s
There is no write node. What arrives at /api/write is a SIGNAL; a node is an
OUTPUT of realization, never an INPUT to it. route_write asserted otherwise in
one line:
let manifold: String = "[" + body + "]" // the body IS a valid manifold node object
A request body is not a manifold, and that assertion is the whole defect. It is
why every written signal landed as one flat node with zero edges, measured on a
clone: {"inserted":1,"nodes_added":1,"edges_added":0} and GET /api/neighbors on
the new id returning [].
PR #155 corrected transduce(signal, modality) to return a Manifold — components
plus relations — but touched only ingest, the runtime and its tests. Nothing
downstream called it: grep 'transduce|realize|Manifold|decompos' over
engram/src/server.el returned exactly one line, a comment. The primitive was
fixed and the engram's entire HTTP surface never reached for it.
This wires the intake seam to the primitive that already exists. It decomposes
nothing itself and must never: transduce dispatches through the dlsym realizer
registry, so adding a modality is registering a realizer, not editing this file
and not patching the runtime. intake_signal only carries what the primitive
returns into the store — components become nodes carrying their OWN geometry
via node_attach_geometry, relations become edges at the weight the realizer
stated, and manifold_member still wires the set into one connected sub-graph
exactly as insert_manifold_json already did.
Built general rather than special-cased: five of the six intake doors (write,
supersede, nodes, knowledge/capture, state-events) are the same hand-written
"content -> engram_node_full -> one flat node", differing only in the
node_type/tier/tags they hardcode. Those are parameters here so each door can
move onto this one function. Only /api/write rides it in this pass.
When no organ is registered the signal is stored flat exactly as before, but
the response now says so ("realized":false,"organ":false,"components":0).
Silent flattening was the real defect — a caller could not tell "nothing
decomposed me" from "I decomposed into one component". el_runtime.c draws the
same line between an absent organ and a broken one, for the same reason.
No realizer is authored here and none is registered, so production behaviour is
unchanged. The mechanism is what landed.
|
||
|
|
45325f7391 |
singleton: guard the state, not the program's name
El SDK CI - dev / build-and-test (pull_request) Failing after 4m6s
The singleton lock protected a filename, not a store. It was keyed on $EL_SINGLETON_DIR|$TMPDIR|/tmp + /el-singleton-<program>.lock — the program's NAME and a temp directory — and never consulted the state it claimed to protect, while its own refusal message read "Refusing to start a second instance against the same state." Measured, it failed in both directions. A second engram against a DIFFERENT data dir was refused, naming the first's pid. And TMPDIR=/tmp/other let a second engram start against the SAME data dir with no complaint — the two-writer data-loss condition the guard exists to prevent, defeated by one environment variable. Both are one error: the identity of the resource had been replaced by a label for it. The lock now lives inside the state it guards — <state>/.el-singleton-<id>.lock — and the program block says what that state is. Same directory is the same file is the same inode, so it contends and there is no TMPDIR left in the key to change. Different directories are different files, so they don't. Different spellings of one directory (trailing slash, x/../x, symlink) collapse in the kernel's own path walk, so they contend without this code comparing strings; canonicalisation is for the message, never the decision. `guards:` is an expression so a program can point at the resolver that already owns its path — guards: engram_resolve_data_dir() — instead of restating that resolver's default, which is the two-owners defect spec 18.4 exists to prevent. A `singleton:` without `guards:` is now a compile error; emitting a name-keyed lock instead would be emitting the defect. Kept: the flock (the kernel drops it on crash and SIGKILL, so there is still no "delete the lock file to get unstuck" ritual — a stale file inside a copied data dir is inert), and the holder's pid in the message. Changed: the message is true. It says "the same state" because the lock it failed to take is in that state, and it names the state it checked. An unguardable state (missing, read-only) now refuses rather than starting unguarded. Also corrects lang/AGENTS.md's compiler rebuild line, which had gone stale: linking el_runtime.c alone no longer resolves. |
||
|
|
914bab11d2 |
docs: mark GeoEdge.discord as design-branch-only, not on dev
The line references were correct but silently implied the code was on dev.
It is on design/correspondence-and-censorship (
|
||
|
|
e239f2894c |
docs: carry the correspondence corrections, because a stale doc builds the wrong thing
The docs described a mind made of subsystems — a grounding subsystem, a wonder manifest, a dreamer on a beat, faculties as arguments to one call. Each of those is a supervisor invented for something that should be a property of the substrate, and two of the documents carrying them are load-bearing for a build agent: cognitive-architecture.design.md says "a build agent executes from this doc", and tools/api-reshape/README.md marks the refuted shapes PROVEN on a live clone. Corrections carried, per lang/spec/correspondence-and-censorship.md (PR #149) and lang/spec/runtime-ownership.md: - Grounding is not a subsystem — it IS the edge weight. 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. - Faculties are operations, not parameters. reason changes the estimate, induce changes the parameters, abduce changes the structure — a write, which GeoGradient cannot express. A write is not a parameter of a read. - Wonder is the boundary, not a manifest. Curiosity is wonder crystallized at a nucleation site: one thing at two phases. Removed wonder from the operator table in AGENTS.md. - Consolidation is ambient, not scheduled. A brain has no cron job. The presence of a ticker is the diagnostic. - co_registration is deprecated — it averaged a per-edge property into a region scalar, so opposing sites cancelled. GeoEdge.discord replaces it. Nothing new may read 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. The two design docs are marked superseded-in-part with the refutation at the point each claim is made, not rewritten. Preserving what was argued down is the point of an immutable record. Also measured and corrected while verifying the above: engram/README.md documented a Rust engram-core crate on sled with "flat cosine scan until scale demands HNSW" — there is no Rust in engram/ and HNSW is the index; lang/releases/ no longer exists, so both README.md and AGENTS.md pointed at a deleted path for the authored runtime; language.md listed the engram_* and http_* runtimes as stubs. Added language.md §20 for geometry-as-a-value, realizers and transduce (#144), which had landed with no spec coverage. Documentation only. No .c, .h, or .el file is touched. |
||
|
|
0ee82d9e91 |
Merge pull request 'Grounding is the edge's weight, and the weight is a vector' (#150) from feat/grounding-gradient into dev
El SDK CI - dev / build-and-test (push) Failing after 3m43s
|
||
|
|
0389bf9363 |
engram: expose the geometry so the frame can be verified
El SDK CI - dev / build-and-test (pull_request) Failing after 11m29s
engram_scan_nodes_emb_json has existed as a builtin with NO ROUTE. The embeddings — the actual positions every distance, angle, membership and grounding is computed from — were unreadable from outside the process. That is not a missing convenience. It means every claim about the coordinate frame was unfalsifiable from the API: whether the space is isotropic, where the centering offset sits, what the origin is, whether a node carries geometry at all. You cannot verify a coordinate system you cannot see, and a system whose frame cannot be checked is exactly the shape this codebase spent 2026-08-16 removing everywhere else. GET /api/nodes/emb?limit=&offset=. Read-only, paged, no writes. Measured consequence of having it: the value manifold and the love component manifold were both decomposed, null-controlled against random node sets drawn from the same graph, and several published claims were retracted because the geometry contradicted them. None of that was possible before this route existed. |
||
|
|
7a1501d097 |
Grounding is the edge's weight, and the weight is a vector
El SDK CI - dev / build-and-test (pull_request) Failing after 3m59s
A relation that keeps holding up strengthens; one that stops corresponding
decays. That is not analogous to grounding, it IS grounding — so it belongs on
the edge, not in a subsystem beside it. The graph was already the grounding
structure; this stops modelling it as something else.
Deleted, not refactored:
- cog_ground_edge and the `grounded-by` relation type. A grounded-by edge
models grounding as a relation BETWEEN nodes when it is a property OF a
relation. #147 fixed which endpoints that edge landed on and left the wrong
idea intact. Measured on the live store: the old path scored two nodes with
ZERO edges between them at 0.925237 and wrote an edge for it.
- ground() writing. It was a read that wrote — the eg_vindex_sync defect.
Three identical calls produced three writes to the same edge id.
- keystone_write_blocked. Its measured cost was 0.00% brier reduction over
n_trials 0 on the keystone: the loop never ran, so the self was never
calibrated and never falsifiable. Nothing replaces it — non-circularity of
the reference frame is temporal, not a permission.
- a graph predicate for "evidence downstream of itself", built and then
withdrawn. Reachability from the self region covers 89.2% of the live graph
(10,580 of 11,861 nodes), so any topological predicate marks nearly all
evidence tainted and degenerates into the total block censorship began as.
The vector, carried in a GRD1 block on the edge's own metadata:
factual, relational, associative (the existing hebb), polarity (SIGNED — near
zero is "no support", negative is "actively contradicts"; `inhibitory` is that
distinction crushed to one bit), provenance class, and a timestamp. Confidence,
recency, staleness and volatility are DERIVED at read and never serialized.
Decay is one model, not two: cog_decay_factor is the single implementation and
engram_temporal_decay now delegates to it — proven bit-identical over 24
(age, reinforcement) points.
Values reference: thirteen regions, aggregate MIN, binding value named. Measured
— the 13 have pairwise centroid cosine min 0.1525 / mean 0.5199 / max 0.9278, so
they demonstrably are not one region, and a mean would let agreement with twelve
mask a violation of the thirteenth.
Supersession versions the whole vector jointly, gated by consequence and
salience with no epsilon anywhere: floor crossings and sign changes only.
Polarity flips and provenance-class changes are inherently significant and
bypass the salience gate.
Also fixed: the frame contract. Descriptors are built over L2-normalized member
embeddings; think() and the grounding path were fitting RAW vectors against them.
Measured on the self region, same data, same 106 members:
magnitude 0.00283443 -> 0.536134, spread 18.7565 -> 0.930163.
Every fit score sat three decimal places below the 0.5 floors that gate on them.
assert() gates on both floors and computes still_held instead of returning a
hardcoded `true` — the old build reported still_held for a node that does not
exist.
|
||
|
|
616815b2ab |
Give cross-cutting concerns an owner instead of a convention (#145)
El SDK CI - dev / build-and-test (push) Failing after 11m4s
|
||
|
|
c18abf799c |
engram: declare configuration once instead of at every read site
Migrates engram to the `program` block. 18 configuration variables that each
carried their default inline at the point of use now declare it in one place,
and engram declares itself a singleton.
The read sites lose their defaults entirely: `let v = env("X")` followed by
`if str_eq(v,"") { "default" } else { v }` collapses to `config("X")`. The
guide_env_or(key, dflt) helper is deleted -- its whole job was supplying a
per-site default, which is the thing being removed.
Fixes ENGRAM_DATA_DIR, which was the clearest instance of the defect. It was
read at six sites. Five were dead: `let dir_raw = env("ENGRAM_DATA_DIR")`
immediately shadowed on the next line by `engram_resolve_data_dir()`. The sixth
was live and defaulted to /tmp/engram, contradicting the canonical resolver's
$HOME/.neuron/engram -- and its consumer is the pre-destructive reseed backup,
so with ENGRAM_DATA_DIR unset the safety copy was written to ephemeral storage
while the store it protected lived elsewhere. All six now go through
engram_resolve_data_dir().
ENGRAM_DATA_DIR is deliberately NOT declared in the program block, and the
source says why: engram_resolve_data_dir() already owns it, and a second
declaration would give it two owners that can disagree -- recreating the exact
defect being removed here. A variable belongs in the block when the block would
be its only owner. HOME stays a raw env() read; it is an environment fact, not
configuration.
singleton: "engram" matters more than it looks. Today a second engram whose
bind() fails merely returns from http_serve -- after it has already replayed
the WAL and written boot-time backup files -- and then exits 0, indistinguishable
from a clean run. That is how two instances came to share one data dir. Verified
that the second instance now refuses before any side effect: with instance 1
holding the lock (lsof pid, shell pid, and lock file contents all agreeing at
5946), the second start named that pid, exited 1, and left the data directory
untouched.
Verified by bijection on the generated C: 18 config() reads, 18 declarations,
no read without a declaration and no declaration without a read. Three bad Int
values are reported in a single run rather than costing one restart each.
ENGRAM_API_KEY keeps its permissive empty default, which disables auth -- that
is pre-existing behaviour and changing it is out of scope. The source marks
making it `required` as the obvious hardening follow-up.
|
||
|
|
3fcc36c2f1 |
runtime: transduction is a language concern, so move it into the language
El SDK CI - dev / build-and-test (pull_request) Failing after 14m58s
#141 let signal enter as geometry and it worked, but it was placed at the CONSUMER and said so in its own commit message. This is the correction. Three defects, all of them placement: 1. It sat in the engram. Ingest 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. The geometry surface is now defined in el_runtime.c immediately ABOVE the engram section and depends on nothing inside it. Delete the entire engram and geometry still enters el. 2. It marshalled the vector as a hex STRING, because el had no first-class geometry value — which reintroduced text as the TRANSPORT medium one layer below the problem being fixed. Geometry is now an el value: a magic-tagged heap object carried in el_val_t, same discipline as List/Map. Hex survives only as an adapter at the edge, which is all an encoding should ever be. 3. It needed an arbitrary `dim <= 8192` bound purely to size an allocation from a caller's CLAIM about a string's length. A value carries its own width, so the width is derived and never asserted. The bound is gone, not raised — there is nothing left to validate. Language surface, none of it engram-prefixed: geometry_new / _dim / _is / _get / _set / _norm / _free, geometry_from_f32le_hex + geometry_to_f32le_hex as the wire adapters, realizer_register(modality, fn_name), realizer_has, and transduce(signal, modality) -> Geometry. REALIZERS ARE DECLARABLE IN EL. This is the part that makes the move real rather than nominal: registration resolves a name with dlsym against the running binary, the identical mechanism http_set_handler already relies on, because every el `fn name(...)` compiles to a global C symbol with that exact name. So an ordinary el function IS a realizer and a new modality needs no runtime patch. Verified end to end in lang/examples/transduce.el: an el-defined tone_realizer is registered by name, transduce dispatches to it, and the signal demonstrably reaches it (distinct signals produce distinct geometry). A modality with no realizer transduces to NOTHING. There is deliberately no built-in realizer, not even for text — silently embedding a description of a signal and calling that perception is the exact defect this ends. engram/src/server.el is migrated: POST /api/nodes decodes "emb" hex exactly once, at the edge, into a Geometry, and everything below that line moves geometry. The wire is unchanged because production clients speak it. "dim" is now an ASSERTION about the vector, not the source of its width; disagreement is a rejected ingest, not a silent reinterpretation. #141's engram_node_set_emb becomes a DEPRECATED WRAPPER over geometry_from_f32le_hex + node_attach_geometry — kept only because the runtime ships as an SDK asset and a downstream binary may link the symbol. Its exact contract, negative cases included, is preserved and re-verified. ingest.el's `fn transduce` is renamed transduce_manifold. Mechanically it had to yield the name (duplicate C symbol, a hard compile error, measured). But it was never signal->geometry: it chunks already-extracted content into a node+edge manifold, one layer up, and had taken the name belonging to the primitive underneath it. Behaviour unchanged. PROPERTIES FROM #141 PRESERVED, each re-measured on a scratch engram (:8971, never prod :8742): - off-dimension vectors stored but NOT indexed — the HNSW build loop still filters on n->emb_dim == dim at four sites, so a 64-dim voice vector is durable and addressable without perturbing the 768-dim canonical index - geometry makes a node ineligible for embed_backfill: after backfill the 64-dim voice node was still 64-dim while the text control acquired 768 - the create response reports whether geometry landed, and the node document always emits emb_dim and embedded Read-back with control and negatives, all verified against a PID-confirmed fresh binary: geometry node emb_dim=64 embedded=true / emb_set=1; text-only control emb_dim=0 embedded=false / emb_set=0; malformed hex, ragged length, and dim-disagreement each emb_set=0. Two compiler landmines found by reading the generated C rather than trusting a successful build, both documented at their sites: elc lowers `a == b` to str_eq unless both operand NAMES are in the per-function int-name set (which does NOT propagate into nested if-expression blocks — the first cut would have strcmp'd two integers as pointers on the first geometry-bearing request), and `+` lowers to string concat when either operand is a user-defined call. |
||
|
|
8e9d88fc01 |
runtime: publish the vector index instead of guarding it
El SDK CI - dev / build-and-test (pull_request) Failing after 10m59s
The crash (SIGTRAP in engram_activate -> eg_vindex_sync -> vindex_insert -> _realloc) had three read paths mutating five process-global statics. engram_activate, eg_knn_for_node (whose own comment says "No writes.") and engram_geo_reify_run_json all called eg_vindex_sync, which frees the index, reallocs the seen-map and inserts — on a read. Three moves, in decreasing order of how much they dissolve: 1. Misfiled scratch is not shared state. visited/visit_epoch/visited_cap were never owned by the index; they are one traversal's local, hoisted into struct VIndex as an allocation optimisation. They want neither a lock nor a capability nor a pool — just to go back in the call frame. Two concurrent READS stomped each other purely because of this. 2. const IS the capability. Once the scratch leaves the struct, search reads and nothing else, so vindex_search takes a const VIndex*. That is exactly what a capability-pointer ABI would have bought — a read path physically cannot call vindex_insert, enforced by the compiler on every future caller — for one qualifier instead of an ABI swept across hundreds of builtins. 3. What survives is publication, not ownership. HNSW insert is NOT an append: it rewires the neighbour links of already-existing elements and reallocs elems[], so the store's append-only property does not transfer to the index derived from it. eg_vindex_sync therefore splits into eg_vindex_maintain (exclusive, sole mutator) and eg_vindex_view (shared, returns const VIndex*). A read path may demand that a current snapshot exist — a request to the owner, not a mutation by the reader. Write-side owner: eg_vindex_note_embedded hooks the embedding-ASSIGNMENT sites rather than the append sites, because a node with no embedding cannot be in a vector index — embedding assignment is the event that owns index membership. One O(log n) insert, no O(node_count) presence scan. This also retires the "STALENESS (honest tradeoff)" note where a lazily-embedded older node stayed invisible to route_nearest/autoconnect until a full rebuild (the embed-gap #20 shape). Evidence. The existing harness conflated two hazards, which is why fixing half of it read as failure. Split into four: single (3000 vec, ASan+UBSan) clean -> clean readers (4 readers, no writer, TSan) RACE -> clean unsynchronized (writer+reader, bare) race -> race, expected forever published (owner + 4 readers) n/a -> clean, 3000/3000 landed RESULT: PASS. recall@10 = 0.9365 at ef_search=128 (gate >= 0.90); determinism byte-identical across two independent builds. The unsynchronized half is now permanently expected to race, deliberately: it is the executable proof that the boundary must live above the data structure, not inside it. fb32d15's guard is KEPT, correcting this design's own section 5. Measured, it guards TWO structures and only one was converted here: g->nodes/g->edges are realloc'd in place (el_runtime.c:7618,7629) and engram_activate_inner's embed-backfill writes n->emb through exactly such a borrowed pointer. Deleting the guard reintroduces a measured 11171->9579 edge loss. Its comment is narrowed to the RAM graph and the deletion precondition named. That corrects the ordering claim too: the residual is not one ABI that dissolves everything at once, it is a PROPERTY applied per structure. Residues evaporate in the order the property is applied, and a residue whose structure has not been converted must be left standing. |
||
|
|
e99a4640e2 |
test: regression harness for the vindex concurrency crash
Promotes the two throwaway sanitizer harnesses used to diagnose the
2026-08-16 soul crash into engram/test/ so the bug cannot silently regress.
The harness has two halves and the PAIR is the point — it is what localises
the defect to concurrency rather than to HNSW logic:
single 3000 clustered vectors, one thread, ASan+UBSan. The CONTROL.
Must always be clean. During diagnosis this cleared all 13,820
real dim-768 vectors from the live store, which DISPROVED an
inspection-derived hypothesis about an out-of-bounds
reverse-link write at engram_vindex.c:340.
concurrent writer + reader on one shared index, TSan. Currently reports a
race at engram_vindex.c:195 (visited_reset) reached from both
vindex_search and vindex_insert, because VIndex still owns its
visited[]/visit_epoch scratch — so even two concurrent READS
corrupt each other's traversal.
Verified: half 1 passes, half 2 reproduces the race.
Gated on EXPECT_RACE, default 1, so the concurrent half documents the known
defect without failing the suite today. When the visited set moves to a
per-query checkout pool (hnswlib VisitedListPool style — NOT thread_local,
since http_worker is a thread per connection and a __thread buffer would leak
~55KB per connection), flip EXPECT_RACE=0 and it becomes a real gate.
|
||
|
|
c79033b749 |
runtime: let signal enter as geometry, not as prose about signal
El SDK CI - dev / build-and-test (pull_request) Failing after 10m55s
No ingest path could carry a vector. engram_node/_full/_layered take text only, and a node acquired an embedding solely via engram_embed_backfill DERIVING one from n->content. That made text the mandatory entry medium: any non-text modality had to be described in prose first, so the geometry we then reasoned over was the geometry OF THE DESCRIPTION, not of the signal. Measured: POST /api/nodes accepted an "emb" field, returned 200 with a fresh id, and stored nothing — emb_dim=None, embedded=false. engram_node_set_emb attaches a vector to an existing node. Off-dimension vectors are stored but not indexed (the HNSW build loop already filters on emb_dim), so modality geometry is durable and addressable without perturbing the canonical index. Setting emb also makes the node ineligible for embed_backfill, so a realizer's vector is never overwritten by a text-derived one. Two reporting fixes ride along, because both are how the drop stayed invisible: the create response now reports emb_set instead of being success-shaped regardless, and the node document now always emits emb_dim and embedded — without which a genuine ingest drop and a mere reporting gap are indistinguishable. Verified live: voice node emb_dim=64 embedded=true; text control emb_dim=0 embedded=false; malformed hex, length mismatch and dim<=0 all reject. KNOWN PLACEMENT DEFECT: this is at the consumer. Ingest is a language concern, not an engram feature — every el program touching any modality needs it. The vector also marshals as a hex STRING because el has no first-class geometry value, which reintroduces text as the transport medium one layer below the problem being fixed. The durable shape is geometry as an el value plus declarable realizers, after which the engram stops having an ingest concept at all. Landing this as the verified probe that proves the path. |
||
|
|
40eb48e92f |
engram: fix silently-wrong query params, and make el_seed.o + el_runtime.o link
El SDK CI - dev / build-and-test (pull_request) Failing after 14m49s
Three real bugs, all found by actually running the thing rather than reading it.
1. query_param never URL-decoded. A GET of /api/search?q=neural%20network
searched for the literal string "neural%20network" and returned []. Every
multi-word search against the live engram has been silently returning empty
results — not an error, an empty result, which is why it went unnoticed.
Affects every GET route that reads query params, not just search.
2. query_param matched key names unanchored. str_index_of(qs, "q=") matches
inside "faq=", so "?faq=X&q=Y" returned X for key "q". Verified live before
the fix. Now searches for "&key=" against "&"+querystring so a match can
only land on a real parameter boundary.
3. el_request_start/el_request_end were defined in BOTH el_seed.c and
el_runtime.c, so linking the two objects together — which is exactly what
the product build does — failed with duplicate symbols. el_seed.c's own
comment already says these moved there ("formerly defined in el_runtime.c.
Now self-contained in el_seed.c"); the el_runtime.c copies were left behind
during that move. Removed them, kept declarations since http_worker calls
them. Also added the three missing prototypes (engram_op_assert_json,
engram_node_full_in, engram_connect_in) that el_seed.c wraps but never
declared, which made it fail to compile standalone under C99+.
Verified: engram builds and links clean from canonical source; before/after
comparison on a copy of the real store shows "neural network" returning a real
match where the live build returns [], and "?faq=WRONG&q=MetaColloc" now
resolving to MetaColloc. Live engram on :8742 was never touched.
|
||
|
|
bacaf3d39c |
engram: reconcile M8 HNSW vindex (#109) onto current dev, restore 3 fixes the branch predated
El SDK CI - dev / build-and-test (pull_request) Failing after 4m49s
Lands feat/reframe-region-setop (PR #109: native set-based reframe_region, decorator-as-seam @route port, teacher-summon, and the M8.1 activate-latency work — lazy-memoized cosq via eg_cosq_at + engram_vindex HNSW-accelerated seed discovery + vindex_harvest_from_store/vindex_bench oracle) onto dev's actual current HEAD, plus engram-tiered-storage's still-unique test suite. RECONCILING #109 WITH engram-tiered-storage (M4-M10 HNSW/geometry/reason/ verify work): not a two-way merge. engram_vindex.c's HNSW core (search_layer/ select_neighbors/prune_links/insert) is BYTE-IDENTICAL between the two branches; #109's copy is a strict superset (adds vindex_harvest_from_store, used by vindex_bench.c's brute-force-vs-HNSW oracle). engram_reason.c and engram_verify.c are also byte-identical. #109's own branch point already carried engram-tiered-storage's M4-M10 lineage forward, so there was nothing left to merge into #109 for those files. The one thing engram-tiered-storage had that #109's tree dropped: its full test suite (test_vindex.c, test_geometry.c, test_reason.c, test_verify.c, test_m7_traversal.c, the interoception P0-P5 tests, bufpool/compaction tests, and their run_*.sh harnesses) — ported over here unchanged. WHY THIS NEEDED HAND RECONCILIATION, NOT A MECHANICAL MERGE: #109's branch forked from dev on 2026-08-14 15:40 (before restructure-adjacent history diverged the file's merge-base for `git merge` — it presented as an add/add conflict). A straight two-dot diff (dev tip -> PR tip) applied cleanly, but it silently reverted THREE dev fixes landed on 2026-08-14/15, after the branch point, that the PR's diff had no way to know about: 1. qgate rescale (2026-08-14 self-review): PR's lazy eg_cosq_at rewrite of the query-aware propagation gate dropped the shift-and-floor rescale about ENGRAM_EMBED_S0 (measured: unrelated-pair median 0.562->raw gate 0.67, i.e. "a small tax, not a gate"). Restored the rescale, wrapped around the lazy accessor -- the PR's actual improvement (WHEN cosq[oi] is computed) is orthogonal to WHAT it gates on and both are kept. 2. Eviction cause decomposition (2026-08-14 self-review): dev decomposes wm_evicted into evict_floor/evict_cap/evict_bll so WM churn is diagnosable (identity: evicted == floor+cap+bll+dup_wm+dup_wm_global). PR's tree predates this and dropped all three counters + their JSON stats fields. Restored declarations, all 4 direct increment sites, the eg_wm_carry_over bll increment, and the act-stats JSON fields -- alongside (not instead of) the PR's own P4 afferent / API-reshape counters already in that same struct/JSON. 3. Hebbian link-formation selection (2026-08-15 self-review, TODAY): dev selects the STRONGEST qualifying candidate for consolidation each call; PR's tree predates this and reverted to hash-slot order (arbitrary wrt association strength) for edge formation -- the one path that writes PERMANENT structure. Restored the strongest-candidate while-loop, keeping the PR's own genuine improvement at that site (engram_adj_on_edge_added incremental-index append instead of a bare adj_dirty=1 full-rebuild flag). engram/src/server.el's 3-way conflicts (autoconnect_on/ise_offgraph_on env flags, /api/nodes connected-count in responses) were pure additive: dev's side was empty, PR's side added the feature. Took PR's side whole. VERIFIED (nsbx sandbox only, live :8742/:7770 never touched): - cc -std=c11 -O2, clean link against the real engram/src/server.el via elc, zero errors. - vindex_bench (built standalone, read-only harvest) against the real production store clone (13,671 embedded nodes, 768-dim nomic-embed-text): recall@10 = 1.0000 at ef 64/128/200; HNSW search 0.28-0.79ms/query vs 2.03ms/query brute-force oracle (2.6x-7.2x). HNSW build itself: 46.5s for the full 13,671-node set -- see the flagged risk below. - Booted the reconciled binary in an isolated nsbx sandbox (:8905, cloned snapshot of the live store, 13,424 nodes / 37,656 edges) and called /api/activate for real: first call after boot 41.5s (pays the one-time HNSW build inline -- matches the standalone bench), second/third calls 356ms/605ms, no crash, correct results, act-stats JSON (including the restored evict_floor/cap/bll fields) reads correctly. KNOWN RISK TO FLAG BEFORE ANY LIVE CUTOVER (not fixed here; out of scope for this dev-only land per instructions not to touch :8742/:7770): eg_vindex_sync builds the HNSW index synchronously, inline, on the first engram_activate() call after every process start (or index invalidation). On the real node count that is a ~46s blocking stall on a single-threaded server -- the first request after every restart (or its concurrent siblings) waits the full build. Recommend a background/incremental build (or a bounded per-call build budget) before this ever reaches the live daemon. See PR description / final report for the fuller writeup. |
||
|
|
1db5694189 |
Merge pull request 'engram: add /api/nodes/reseed so a node body can be repaired at its own id' (#92) from feat/engram-reseed-route into dev
El SDK CI - dev / build-and-test (push) Failing after 3m58s
|
||
|
|
e34ebd4b3d |
Merge pull request 'nsbx + cognitive architecture design + engram self-review series' (#113) from feat/neuron-sandbox into dev
El SDK CI - dev / build-and-test (push) Failing after 4m2s
|
||
|
|
5c6da24033 |
Merge pull request 'spec: grounded edge-propagation (task #50) — gated design artifact' (#108) from worktree-agent-a6577c8211c332c5b into dev
El SDK CI - dev / build-and-test (push) Failing after 3m42s
|
||
|
|
fdf0d6cb64 |
Merge pull request 'nsbx: one-command dev onboarding (branch + worktree + isolated engram)' (#99) from feat/nsbx-dev-env into dev
El SDK CI - dev / build-and-test (push) Failing after 13m56s
|
||
|
|
d8d1b89143 |
Add repo AGENTS.md and two engram design docs (architecture hardening, DB tooling)
El SDK CI - dev / build-and-test (pull_request) Failing after 14m16s
AGENTS.md: root-level guide to the repo — which of the 8 el_runtime.c copies is the one canonical, authored source (lang/releases/v1.0.0-20260501, despite the misleading 'releases/' name) vs. lagging forks/build artifacts, build commands, and session protocol. engram/spec/architecture-hardening.design.md: terse engineering anchor for the 2026-08-14 hardening vision (one calculus over the geometry, core + ephemeral ring, persistence earned by salience, incarnation model) — indexes the fuller whitepaper + Neuron artifact 2b8078cf rather than restating them. engram/spec/engram-db-tooling-design.md: high-level design for engram DB tooling (geometry-native browse/query/ops surface over the existing vantage-read/write/relate/supersede API). Deliberately leaves out of this commit: the uncommitted el_runtime.c/h + codegen.el float-arithmetic-codegen diff in this worktree, which appears to overlap with (or supersede) the fix already preserved via PR #104 — needs manual reconciliation rather than a second competing PR. Also leaves out lang/.promote-backup-floatfix/ (a local backup snapshot, confirms that float-fix work is mid-promotion here), assorted .DS_Store files, engram/dist/engram.* backup binaries, and lang/dist backup binaries — none of it source. |
||
|
|
5f3ddb8b8d |
Add grounded edge-propagation spec (task #50): core algorithm, proof harness, gated integration patches
El SDK CI - dev / build-and-test (pull_request) Failing after 14m31s
LTP/LTD-style belief grounding propagated along graph edges, with union-find independence-guarded corroboration. Package: core C algorithm (gep_core.h), a self-contained deterministic proof harness with recorded output, staged runtime integration, and gated .el patches for the beat hook and HTTP route. Per the author's own LEDGER.md: built + proven on a clone, GATED pending the engine/HNSW cutover — not wired into the live beat or routes. Preserved here as a spec/reference artifact, not a request to merge into the live path. |
||
|
|
6621a4dbc5 |
feat(engram): native set-based reframe_region on the cognition engine
Add the universal engram mutation as ONE operation: isolate a region (cosine + adjacency) -> supersede it as a set (immutable region-tombstone, originals retained, engram_forget never used) -> insert the new manifold as a set -> rebind edges by cosine -> one atomic persist. Single-node write and supersede are the degenerate n=1 case of the same reframe_core path, not a separate CRUD path. Keystones kn-efeb4a5b / kn-5b606390 are write-protected. Purely additive: routes POST /api/reframe, /api/write, /api/supersede. Verified on an isolated clone of the JSON-snapshot engine (set-replace, n=1, no-regression, keystones, durable reboot); compile-verified clean against the cognition multi-TU build. NOT deployed — prod :8742 frozen; blue-verify on the cognition/egm engine required before any cut. |
||
|
|
15f90003c0 |
teacher-summon: default-off (TEACHER_ENABLE) soul-native wake; byte-inert when unset
+282 lines in engram/src/server.el implementing the flag-gated teacher summon (consult_teacher backend abstraction, tier autoselect, GGUF fetch/cache). With TEACHER_ENABLE unset the summon path is byte-inert. Consolidates the proven api-reshape pieces (geometry-ops |
||
|
|
01826421c4 |
seam: implement decorated-fn boundary auto-emit; prove on clone
Will waived diff review -> build it for real. Add engram_boundary_beat() to the runtime (afferent counter++ + engram_chrono_tick + engram_strengthen(self-anchor) + dharma_emit) and two act-stats counters (aff_boundary_ops, dharma_emits). codegen cg_fn injects ONE engram_boundary_beat(op) at the entry of every @manager/@accessor fn (fn_has_decorator, so it fires under @route @manager too) — a decorated op self-reports with ZERO hand-written instrumentation. Rebuilt elc self-host + the cognition engram in the worktree; ran it as the clone daemon on :8900. Proof (/api/boundary-proof, @manager, empty body, 5x): aff_boundary_ops 0->5, dharma_emits 0->5, self activation_count 1510->1513, chrono stamp advanced. Brought in feat/cognitive-architecture engram runtime+server for the build. strengthen = activation bump (not content/edge write) -> identity protection intact. Live :8742 untouched; no push, no cutover. |
||
|
|
d595b3c57e |
cognitive architecture design: cognition as one operation over learnable priors
The buildable form of the "one operation" theory (memory bdc8a488). Maps the
theory onto what is already compiled: the five reasoning operators in
engram_reason.c already collapse onto ONE primitive — engram_reason_point_fit —
plus the geo-algebra (combine/subtract/analogy-rotate/distance), and
engram_verify.c is built on the same fit. So the operator-collapse is already
half-written; what is missing is not the primitive.
What is missing, and what this doc specifies:
- think(anchor, prior) -> gradient (a distribution/direction, not a point); each
named faculty = {point_fit + a prior}, the operation frozen, the prior learned.
- Prior as a first-class stored node (warp + calibration), superseding the
intrinsic importance/salience scalar with a relational, grounded-for-whom edge.
Confirmed against the runtime: importance is already a live activation
computation (el_runtime.c:13013), never trusted as a static field.
- vantage_read(anchor, aperture) — one op, three settings: self / foreign-field /
veil.
- The reflexive correspondence-loop as the learning engine: move the grounding
check from offline Python into the geometry, reflexive, reusing the DORMANT
verifier (engram_verify_grounding has no runtime caller and no El binding today)
turned inward. grounding = learning = one loop.
- hold/ground/assert kept distinct: the engram holds anything, grounding is an
edge, the honesty floor is on assertion only; ungrounded content is first-class.
- metastability: keystone core (read-mostly priors) + plastic everything else.
Seven staged milestones, earliest is a real end-to-end slice (induction as
{primitive + grounded prior} with the loop closing on it, reboot-proven on a
snapshot). Build rails stated: offline/secondary, snapshot-first, reboot-prove,
zero-loss, gated launchctl cutover. Design only; no code changed this pass.
|
||
|
|
7aa847e32a |
Merge origin/dev into engram-tiered-storage
El SDK CI - dev / build-and-test (pull_request) Failing after 10m51s
Resolve 3 conflicts: - lang/el-compiler/runtime/el_runtime.c: keep deletion (deprecated runtime fork; single-source-of-truth is lang/runtime/, enforced by scripts/check-single-runtime.sh). - lang/releases/v1.0.0-20260501/el_runtime.h: keep deletion (releases/ is a generated artifact folder, not a source path; a release is a git tag, not a folder). - lang/runtime/el_platform_win.h: union of dev's Windows port (#80: setsockopt optval wrapper + curl-less libcurl stubs) and our fsync(->_commit) shim needed by engram_store WAL. Nothing in dev's build consumes the deprecated fork or releases/ folder. |
||
|
|
ee71423732 |
ci: publish + link engram_store.{c,h} so official builds are store-enabled
El SDK CI - dev / build-and-test (pull_request) Failing after 13m20s
The live engram now runs the paged store (neuron.egm+WAL), but the SDK
release publishes only el_runtime.{c,h} and the engram build links only
el_runtime.c — so a future official release would silently revert to the
in-memory store. Publish engram_store.{c,h} as SDK release assets and add
them to the engram build's download + cc link so the store transition
cannot regress.
|
||
|
|
bb64a236ed |
engram tiered storage: engram-service wiring + elc fold-hang fix + prune-store mirror
- Wire paged store into the ENGRAM SERVICE (server.el, the authoritative durable owner): boot->engram_store_boot, persist_canonical->engram_store_checkpoint, gated by ENGRAM_STORE. - elc (lang/elc.c + src/parser.el + codegen.el + elc-combined.el): OOB guard in tok_kind/tok_value + parse_block progress backstop — fixes the pre-existing unbounded-memory fold hang on sessions.el. - engram_prune_telemetry mirrors ISE prune to the store (store_forget) so store live-count tracks resident and stale telemetry stays bounded. - Deployed live 2026-08-12: engram :8742 on neuron.egm+WAL, count reconciled 11552. |
||
|
|
9a0266cbf9 |
engram tiered storage M3.5: persist activation field updates (pre-flip gate)
Flag-on checkpoint now full-walks the resident graph: store_put_node (WM weight, activation_count, last_activated, wm_anchor) + store_put_edge (hebb, last_fired) for every node/edge, then engram_checkpoint. Uses store_put_edge (idempotent upsert) not store_hebb_batch, because activation FORMS new hebbian-associate edges that bypass the create hook and delta-only hebb_batch can't create them. Store-on boot now applies the same WM-halving + floor + cap transforms as engram_load. This is the hebb-survives-restart fix. Gate: reboot from neuron.egm with snapshot.json deleted -> edge hebb + activation_count survive unchanged, WM weight survives with identical boot transform; negative control proves persist is load-bearing (hebb->0 without it). M1 33/33 + M2 36/36 + M3 parity PASS, ASan/UBSan clean, flag-off untouched. Engine unchanged (boundary held). |
||
|
|
a72145b44e |
engram tiered storage M3: wire store behind ENGRAM_STORE (default off) + .egm rename
Caller-side shim in el_runtime.c maps EngramNode/Edge <-> StoreNode/Edge; engine keeps zero soul deps (libengram boundary, design §10). Flag off = today's JSON path byte-for-byte (proven: no neuron.egm created, graph identical). Flag on = engram_open (import snapshot.json once into neuron.egm, else WAL-replay) + resident load; node/edge create + forget dual-write via guarded hooks. Files renamed engram.store->neuron.egm, engram.wal->neuron.wal. Gate: M3 parity PASS (graph on==off byte-exact modulo ordering; snapshot round-trip; reboot-from-egm with snapshot.json deleted; activation set+sequence identical; ASan/UBSan clean). M1 33/33 + M2 36/36 green post-rename. Known gap (pre-flip): in-place hebb/WM/activation_count updates during activation are not yet persisted to the store (create/connect/forget are). Must close before live flip so learned edges survive restart. |
||
|
|
8affb1d6e0 |
engram tiered storage M2: WAL + checkpoint + crash recovery + legacy import
Write-back no-steal buffer pool makes the fsync'd WAL load-bearing (M1 was write-through). Logical WAL with record-granularity page-LSN redo idempotency. Checkpoint = flush dirty pages, fsync store, advance last_checkpoint_lsn, reclaim WAL prefix. One-time snapshot.json import only when store absent; JSON never read as the ongoing store thereafter. Gates: 33/33 M1 (no regression) + 36/36 M2 — replay parity, torn-tail fuzz (every byte offset), checkpoint-crash at all 5 phases, torn-page+WAL redo, legacy-import parity, hebb-survives-crash. |
||
|
|
fa47b98d18 |
engram tiered storage M1: on-disk paged store format + round-trip tests
Self-contained paged store (lang/runtime/engram_store.{c,h}): 16KiB slotted pages,
u32 TLV self-describing records (forward-compatible), overflow chains, B+-tree
id-index + from/to adjacency, page free-list, tombstones, double superblock + crc
recovery. Not yet wired to activation (M3). 33/33 tests pass (ASan/UBSan clean);
5k nodes/20k edges round-trip bit-exact incl 768xf32 emb + hebb; store 25MB vs 64MB
JSON. Format is final — see design §2.4.
|
||
|
|
0a72fced28 |
engram: WAL persistence + integrity hardening + single canonical runtime
El SDK CI - dev / build-and-test (pull_request) Failing after 13m17s
Establish lang/runtime/ as the ONE canonical el runtime (from the active runtime that carries hebb/emb persistence + the new WAL); repoint the el CI publish, engram build, elb default, and in-repo build scripts to it; delete the el-compiler/runtime + lang/releases/ forks; add scripts/check-single-runtime.sh drift guard. Fixes a live prod bug: the el CI published el-runtime-c/-h from the LAGGING el-compiler fork (0 hebb refs), so the shipped soul never persisted Hebbian edge weights — learned co-activation was wiped on every restart. Publishing from canonical ships the stranded 'learning that cannot outlive the process' fix. WAL storage engine + integrity fixes (DELETE->tombstone + store-layer protection, safe data-dir default) ride in behind ENGRAM_WAL (default off = byte-identical to today). Verified: engram elb per-module build clean, WAL gate 66/66, native smoke ok, drift-guard green. |
||
|
|
edcec3bdf4 |
engram: add /api/nodes/reseed so a node body can be repaired at its own id
El SDK Release / build-and-release (pull_request) Failing after 11m24s
Two write paths could put a node in the graph and neither could put a body on an id that already exists. POST /api/nodes mints a fresh id via engram_node_full; POST /api/load-merge honors a declared id but skips anything already present. That is right for the additive case and leaves a hole: a node resident with a truncated body cannot be repaired. Forge's genesis seed sits in that hole. Two of Neuron's identity nodes carry only their own label as content -- 30 and 22 bytes against 4263 and 2590 declared. Their ids are load-bearing (is_protected_node keys on them and 214 declared edges reference them), so recreating them under a new id is not a repair, it is a second break. Engram has no in-place node update, so a replace is forget-then-merge, and engram_forget also drops every incident edge -- 85 and 93 on those two nodes, nearly all tag edges and accumulated hebbian associations the seed does not declare and could not restore. preserve_edges (default true) therefore snapshots before the forget and re-merges after: the replaced node is back by then so it is skipped, and every dropped edge returns through the (from_id,to_id,relation) dedup. The same re-merge is the failure path -- if the seed merge does not produce the node, the backup puts the original back. Rollback, not data loss. With no replace list the route is exactly /api/load-merge. Verified on a sandbox engram seeded to mirror the live graph's state for this seed (15 resident nodes, 694 incident edges): 87 nodes created at their declared ids, 2 replaced in place, 214/214 edges laid, 682/682 non-seed incident edges preserved, and a second run reports 0 added. |
||
|
|
791b0880b7 |
self-review 2026-08-10: make save/load/persist report real results
route_load was a stub response over the most destructive operation in the
server: engram_load resets the store before parsing, so a readable-but-
malformed snapshot left a hollow graph and the route answered {"ok":true}.
With 37GB of stale dated snapshots in the data dir as restore targets, that
is a live risk. Now returns the real return value plus node/edge counts and
an explicit hollow flag.
route_save discarded engram_save's return the same way; persist_canonical
returned a hardcoded 1, making 'let saved: Int = persist_canonical()' a dead
variable at six durable write paths.
|
||
|
|
fa2b49365b |
self-review 2026-08-08: stop the JSON parser destroying every non-ASCII character
jp_parse_string_raw handled \uXXXX by skipping the four hex digits and emitting a literal '?'. JSON writers escape non-ASCII by default (Python's json.dumps ships ensure_ascii=True; MCP clients do the same), so every em dash, curly quote, accented letter and emoji arriving over MCP or HTTP was silently replaced by one question mark on the way in. Measured on the live store: 3,119 of 4,081 non-telemetry nodes carried the damage, including the self traversal root and all 13 values nodes. Contents split cleanly into fully-clean or fully-mangled with zero overlap, which is the tell that it was one write path rather than gradual rot. No snapshot on disk predates it, and 3 bytes collapsing to 1 is not invertible, so the existing damage is permanent; only the forward path could be fixed. Decode properly instead: 4 hex digits, surrogate-pair reassembly for astral codepoints, U+FFFD for lone surrogates, UTF-8 encode. Malformed escapes keep the old '?' so a truncated body still parses. The deeper failure was that nothing measured this for two months. Every gauge in the system reports whether the machinery is running; none reported whether the text it carries is intact. Adds both halves: engram_text_health_json() / GET /api/text-health for the daily census, and a txt_damaged counter on the heartbeat for live regression. Verified in both directions - clean UTF-8 does not trip it, a deliberately damaged node does. |
||
|
|
971b21751a |
self-review 2026-08-07: learning that cannot outlive the process is not learning
Yesterday's eligibility-trace fix made Hebbian consolidation numerically real: hebb_max 0.000799 -> 0.4725, and 1,198 hebbian-associate edges formed in 23h48m. This morning's census found where they went: nowhere. soul daemon (in-process graph): 42,426 edges, 1,198 hebbian engram server (:8742, durable): 41,213 edges, 49 hebbian Two processes, two graphs, one direction of travel. The soul pulls from the server every 10 min (GET /api/sync) and never pushes. It cannot fall back on saving its own copy either: soul.el sets soul_snapshot_path only inside `if is_genesis && safe_to_seed`, and safe_to_seed is unconditionally false whenever ENGRAM_URL is set -- because the server owns persistence and a soul writing snapshot.json would clobber it. That guard is correct. The consequence was not: mem_save() has never once executed. The soul is the ONLY process running idle cognition, so it is where essentially all co-activation happens -- and it was throwing away every association it learned, every restart, silently. The mechanism worked and the learning still evaporated. Consolidation is now a message, not a file. Fast volatile store hands each newly-formed association to the slow durable store over the API the server already exposes; only edges past ENGRAM_HEBB_LINK_MIN are ever queued, so what crosses the process boundary already earned it. - el_runtime.c: 512-slot overwrite-oldest write-back ring; enqueue at edge formation; engram_hebb_drain_json() pops a postable JSON batch. Drops and drains are counted, not silent -- a consolidation path that quietly discards is the exact failure this entry exists to correct. - server.el: POST /api/edges/batch. persist_canonical() writes the full 60MB snapshot per call, and route_create_edge calls it per edge -- correct for one interactive edge, ruinous for bulk (~840MB/beat to persist 14 associations). Batch connects all, snapshots once. Same durability, 1/N the writes. - act-stats: hebb_wb_pending / _drained / _dropped. pending climbing with drained flat = drain not called; drained climbing with sent 0 = POST refused. Both failure modes are now visible in the stream instead of in an autopsy. Verified live: batch route accepts valid entries, skips malformed ones without aborting the batch, and enforces _auth. All 1,256 learned associations are now in the canonical store; the soul booted at 42,431 edges with hebb_max 0.4941 carried across the restart for the first time. |
||
|
|
a43a35bd10 |
self-review 2026-08-04: restore working-memory continuity; learn graph structure from co-activation
WM continuity (the significant one). A node reached by the current query but scoring under its type threshold was zeroed outright, while a node the query did NOT reach got the full ACT-R carry-over treatment. Being found was punished relative to not being found. Measured consequence: WM turned over 100% every call — three activations of a byte-identical query gave |A∩B| = |B∩C| = 0 — and wm_evicted stayed 0 the whole time because that path never counted. WM was not a working set; it was six suppression-breakthrough nodes re-drawn per call. Both exits from a WM slot now share one extracted retention rule. Result: WM 6 -> 24 nodes (the designed Cowan capacity), top weight 0.097 -> 0.748 (natural promotion, not the breakthrough floor), and contents that are actually query-relevant. Hebbian learning. Edge weights were written once at engram_connect and never changed; last_fired's only writer in 12.5k lines was an unrelated dharma path. Every learning mechanism operated on nodes — the wiring between them was frozen. Adds co-activation potentiation (HeLa-Mem arXiv:2604.16839) in a separate `hebb` field so authored structure is never mutated, with homeostatic per-node scaling the source lacks (PNAS 2422602122) to prevent hub saturation. Measuring it produced the finding that mattered: zero edges existed between co-active WM members, so reweighting existing edges was a no-op. This graph's 41k edges were all authored by explicit tool calls — nothing had ever formed an association from experience. So Hebb literally: if the wire is absent, grow it. Consolidation is gated hard (sustained EWMA past 0.15, <=2/call, 5% ceiling, in-memory candidates discarded on restart) because it permanently mutates the graph. Two bugs caught only by instrumenting rather than assuming: the snap-to-zero floor sat above the per-step increment, so nothing could ever accumulate; and the reached-but-sub-threshold eviction above. Verified live end to end — 53 links formed under load, then discarded with the test snapshot. Also exposes engram_act_stats_json over GET /api/act-stats. It had existed since 2026-07-27 but was reachable only through the soul daemon, so diagnosing the activation layer required a working soul. This review needed it and could not get at it. |
||
|
|
005e84e5d3 |
self-review 2026-08-02: bound the WM breakthrough storm; stop punishing semantic relevance for recency
Working memory was thrashing behind a healthy-looking gauge. wm_active sat
at 22-24 while breakthroughs ran 661-903 and evictions 485-717 PER 60s tick
- roughly 825-1125 nodes cycling in 5-call lockstep.
Root cause: the breakthrough path was an anti-starvation mechanism that reset
its own counter on firing, with no budget and no refractory. A node failing
its type threshold 5 times was force-promoted at exactly 0.10 and had its
suppression_count reset to 0, so it immediately restarted the identical
climb. Since BREAKTHROUGH_WEIGHT (0.10) > WM_FLOOR (0.05), every one of them
cleared the admission floor and entered the rank contest tied at 0.10, where
the tie-break degenerated to node-array index order. Cap-evicted nodes are
skipped by retrieval reinforcement, so they never got an access_ts record and
the STI inhibition-of-return damper never applied to them. That closed the
loop: re-suppressed, completely unmarked, forever.
An anti-starvation rule that resets its own counter without a bound is not a
fairness valve, it is an oscillator.
Fixes in engram_activate Pass 2:
- ENGRAM_BREAKTHROUGH_BUDGET (WM_CAP/4 = 6) caps intrusive thoughts per call.
- ENGRAM_BREAKTHROUGH_COOLDOWN (55) via NEGATIVE suppression_count. The field
already serializes as %d and parses through eg_get_int_field, so negatives
round-trip through snapshots with no struct or format change.
- Blocked breakthroughs no longer reset the counter; it saturates so a starved
node surfaces on a later call instead of restarting from zero.
- Graded breakthrough weight by nearness to own threshold, so the rank
tie-break is cognitive rather than insertion order. Invariant preserved:
WM_FLOOR < weight < min(type_threshold).
Also: moved the additive cosine term AFTER the STI multiplier. It was applied
before, so an incumbent re-reached 30s later took t_n/(t_n+120) = 0.2x, which
cut the semantic term's ceiling from 0.20 to 0.04 - below every per-type
threshold. Meaning-match was being punished for having been recently useful.
Inhibition-of-return should rotate the structural score, not the semantic one.
Also: _eg_act_wm_evicted counted 3 of 5 eviction paths. The two carry-over
paths were silent, so the reported rate was an undercount of unknown
magnitude - while being used to diagnose an eviction pathology. All five now
increment.
Also: route_sync returned {"nodes":[],"edges":[]} when the snapshot export
failed. The soul's sync_ok check only tests for "" and "{}", so that
placeholder passed as a healthy sync: last_sync_ok_ts stamped, sync_age_ms
green, sync_empty never fired, added:0 forever. A broken sync was
indistinguishable from a quiet healthy one - the exact class this route was
added to fix. Returns a real error now.
Verified live (boot 20 vs boot 19): breakthroughs 661-903 -> 36/tick,
evictions 485-717 -> 12-46/tick against a counter that now covers more paths,
wm_active unchanged at 22-24, wm_avg_weight 0.138-0.273 -> 0.186-0.446.
Working memory is holding strong nodes instead of breakthrough-floor filler.
|
||
|
|
7f03876e26 |
self-review 2026-08-01: fix double-encode score mangling; expose similarity probe; presence-aware defaults
- route_create_node passed already-boxed Floats through el_from_float a second time, reinterpreting boxed bits as raw doubles — every HTTP-created node silently stored default salience/importance/confidence regardless of input (verified live: 0.9/0.25/0.6 in -> 0.5/0.5/1.0 stored). Floats now passed bare, matching the route_emit_ise pattern that always worked. - Presence-aware defaults via json_get_raw: absent key != explicit value; confidence now honored from payload instead of hardcoded 1.0. - GET /api/similarity?a=&b= wires engram_cosine_sim (built 2026-07-24, zero callers until now) into the introspection API. - /health reports live node/edge counts instead of a hardcoded literal. |
||
|
|
599073cb92 |
self-review 2026-07-31: strip emb from consumer API JSON; cumulative eviction/breakthrough counters
Every node object on consumer read routes (/api/nodes, /api/search, activation results, neighbors, compiled context) carried the full ~5.7KB emb vector — responses 10-50x oversized, blowing MCP token limits. engram_emit_node_json now takes include_emb; only engram_save passes 1, so persistence and the /api/sync//api/edges replication paths (which serve engram_save output) keep embeddings intact. _eg_act_wm_evicted/_eg_act_breakthroughs were reset at the top of every engram_activate, so act_stats reported only the last call and the 60s heartbeat missed nearly all events (curiosity runs 2 activates per 30s). Both are now monotonic process-lifetime totals; consumers diff readings. |
||
|
|
227f158a05 |
self-review 2026-07-25: short-term inhibition-of-return + explicit embedding backfill
Working memory was winner-take-all: suppression_count never entered the promotion score and was reset on promotion, so two high-salience nodes pinned a saturated 24-slot WM for hours. Add Lebiere-Best (CogSci 2009) short-term inhibition — raw_wm *= t_n/(t_n + 120s) from the most recent recorded access — producing emergent round-robin over WM candidates. embedded_count stalled at 93/12175 after restart: the lazy backfill only runs inside engram_activate, which nothing calls on the authoritative store in production, and in-RAM vectors were never snapshotted. Add engram_embed_backfill(n) + GET/POST /api/embed-backfill route that persists the canonical snapshot whenever it embeds anything; the soul heartbeat pumps it at 32/min. |
||
|
|
97e484221d |
self-review 2026-07-24: wire embedding cosine similarity into activation (bl-b2d1c944)
Semantic activation was spec-only since 2026-06-30 — the seed loop used istr_contains and nothing else. Per the 07-21 integration brief: - EngramNode gains a lazily-backfilled nomic-embed-text vector (8/call inside engram_activate, newest-first; no create-path latency, no bulk Ollama hammering during sync seeds) - query embedding (cached) drives a top-K cosine seed supplement (HippoRAG use-similarity-twice) plus an additive WM term with shift-and-floor at 0.45 — raw cosine is a constant bias in anisotropic spaces (unrelated pairs read 0.4-0.7), floor-and-ramp makes it a signal - 4s embed timeout (http_do_t) + 3-strike circuit breaker: activation never wedges on a dead embedder; everything degrades to lexical - embeddings persist as %.4g comma lists in snapshots, parsed by both loaders; embedded_count in /api/stats tracks coverage - engram_cosine_sim + http_delete_json exposed (DELETE now carries a body — the server's _auth scheme requires it) - route_create_node honored only content/node_type/salience; label, importance, tier, tags were silently dropped (label defaulted to content). Now honored via engram_node_full. Verified live: embedded_count 0->96 across activations, semantic-only promotion observed (zero token overlap), snapshot round-trip intact. |
||
|
|
391bd818ea | Merge #66: tokenized+ranked engram lexical search + engram natives (via stage) | ||
|
|
8f8ccc945e |
self-review 2026-07-22: persist canonical snapshot on write routes; newest-first tie-break in node listings
El SDK Release / build-and-release (pull_request) Failing after 14m24s
Durability: the 2026-07-21 fix stopped read routes writing the canonical snapshot but left no save on ANY write path — every mutation lived in RAM until a manual POST /api/save. Observed live: two restarts reverted the store to a 17h-old snapshot, destroying same-day writes. persist_canonical() now runs after node/edge create, knowledge capture, forget, strengthen, and load-merge. ISE telemetry excluded deliberately (48h-pruned, loss-tolerant, ~2/min; snapshotting 28MB per heartbeat is waste). Listing order: scan routes sort by salience with store-order ties, so equal-salience telemetry (all ISEs are 0.3) returned OLDEST first — a limited /api/nodes query silently returned a stale window, and a 41h-old heartbeat series read as a live outage during this review. Ties now break newest-first by created_at. |
||
|
|
409ec99397 |
self-review 2026-07-22: ACT-R/Petrov base-level WM decay replaces per-call multiplicative carry-over
The old carry-over (weight *= 0.7 per engram_activate call) was call-rate- dependent — carried context died in seconds under rapid curiosity scans and lingered for hours under quiet loops — and a decayed scalar cannot represent access frequency at all. Now: k=10 access-timestamp ring + Petrov (2006) closed-form tail, d=0.5. WM promotion and engram_strengthen record presentations; carry-over evicts at base-level tau=-3.0 (Soar forgetting, ~403s single-touch) and shapes the weight held at promotion (wm_anchor) with the ACT-R retrieval logistic (s=0.4) — a pure function of wall-clock time, idempotent per call. Persisted as access_ts/wm_anchor in snapshots; legacy nodes fall back to the optimized form ln(n/(1-d)) - d*ln(L). base_level exposed in both node serializers for observability. Backing spec: 2026-07-21 integration brief (bl-b17facdd). Verified live: carried weight ~anchor seconds after two disjoint activations (old code: 0.49x); frequency-hot nodes hold B=1.9 vs -0.14 single-touch. |
||
|
|
dc39a61e2c |
self-review 2026-07-21: stop read routes clobbering canonical snapshot; add /api/load-merge
Root cause of the 2026-05→07 identity-node loss: route_scan_edges and route_sync serialized state by engram_save()ing over the canonical snapshot.json on every GET, so one bad boot load meant the first read request overwrote the good snapshot. Read routes now export to scratch paths. Boot guard preserves evidence on non-empty-file/zero-node loads and keeps a boot-time backup on good loads. New POST /api/load-merge (explicit path required) used to restore 385 identity nodes + 1115 edges from the 2026-05-13 backup. |
||
|
|
eba9eac8a8 |
self-review 2026-07-19: port stranded fixes to the release runtime (production copy)
Three fixes that existed elsewhere but never reached the runtime the engram
binary actually builds against:
- tokenized + ranked query matching (search/search_json/activate seeds/
goal_bias) ported from the el-compiler copy (
|