M-INTEROCEPTION P4: afferent input counters in act-stats (additive observability)

Extends engram_act_stats_json with five monotonic counters for the raw incoming
signals the mind receives — aff_activations (spreading activations run),
aff_queries (activate_json entries), aff_node_creates, aff_ise_ingests (ISE
nodes), aff_edge_creates. Module-scope statics incremented at the entry points,
emitted via the existing act-stats mechanism and rotated with it — MEASURED, and
never accreted as memory nodes. Process-lifetime totals, reset on restart like
the other _eg_act_* gauges. Additive JSON fields (backward-compatible); no graph
behavior changes. act-stats buffer grown 896->1088 to hold them.

MEASURED on a copy: after 5 node creates (2 ISE), 2 edge creates, then 4+3
queries — counters read exactly node_creates=5, ise_ingests=2, edge_creates=2,
queries=activations=4 then 7; create counters unchanged by queries; queries
strictly monotonic across readings. ASan+UBSan clean.
This commit is contained in:
2026-08-12 23:47:43 -05:00
parent 816b258255
commit 65ca0a3253
3 changed files with 130 additions and 3 deletions
+69
View File
@@ -0,0 +1,69 @@
#!/usr/bin/env bash
# M-INTEROCEPTION P4 gate: afferent input counters in act-stats (additive).
set -u
HERE="$(cd "$(dirname "$0")" && pwd)"
RT="$HERE/../../lang/runtime/el_runtime.c"
ST="$HERE/../../lang/runtime/engram_store.c"
GEO="$HERE/../../lang/runtime/engram_geometry.c"
VIDX="$HERE/../../lang/runtime/engram_vindex.c"
INC="$HERE/../../lang/runtime"
WORK="$(mktemp -d /tmp/engram-p4-XXXXXX)"
export HOME="$WORK/home"; mkdir -p "$HOME"
unset ENGRAM_STORE
fail=0
echo "== compile =="
gcc -O1 -std=c11 -I "$INC" "$HERE/test_interoception_p4_afferent.c" "$RT" "$ST" "$GEO" "$VIDX" \
-lcurl -lm -o "$WORK/p4" 2>"$WORK/cc.log" || { echo "COMPILE FAILED"; cat "$WORK/cc.log"; rm -rf "$WORK"; exit 1; }
"$WORK/p4" > "$WORK/out.txt" 2>&1 || { echo "FAIL run"; cat "$WORK/out.txt"; fail=1; }
grep -oE 'aff_[a-z_]+":[0-9]+' "$WORK/out.txt" | sed 's/^/ /' | head -30
echo
echo "== assertions =="
python3 - "$WORK/out.txt" <<'PY'
import sys,re,json
S={}
for line in open(sys.argv[1]):
m=re.match(r'(STATS\d) (\{.*\})',line.strip())
if m: S[m.group(1)]=json.loads(m.group(2))
rc=0
def check(c,msg):
global rc; print((" PASS: " if c else " FAIL: ")+msg)
if not c: rc=1
s0,s1,s2=S["STATS0"],S["STATS1"],S["STATS2"]
# after creation, before any query
check(s0["aff_node_creates"]==5, f"node_creates==5 (got {s0['aff_node_creates']})")
check(s0["aff_ise_ingests"]==2, f"ise_ingests==2 (got {s0['aff_ise_ingests']})")
check(s0["aff_edge_creates"]==2, f"edge_creates==2 (got {s0['aff_edge_creates']})")
check(s0["aff_queries"]==0 and s0["aff_activations"]==0, "queries/activations start at 0")
# after 4 queries
check(s1["aff_queries"]==4, f"queries==4 (got {s1['aff_queries']})")
check(s1["aff_activations"]==4, f"activations==4 (got {s1['aff_activations']})")
check(s1["aff_node_creates"]==5 and s1["aff_ise_ingests"]==2 and s1["aff_edge_creates"]==2,
"create counters unchanged by queries")
# after 3 more queries — monotonic
check(s2["aff_queries"]==7, f"queries==7 monotonic (got {s2['aff_queries']})")
check(s2["aff_activations"]==7, f"activations==7 monotonic (got {s2['aff_activations']})")
check(s2["aff_queries"]>s1["aff_queries"]>s0["aff_queries"], "queries strictly monotonic across readings")
sys.exit(rc)
PY
[ $? -ne 0 ] && fail=1
echo
echo "== ASan+UBSan =="
gcc -O1 -g -std=c11 -fsanitize=address,undefined -fno-sanitize-recover=undefined \
-I "$INC" "$HERE/test_interoception_p4_afferent.c" "$RT" "$ST" "$GEO" "$VIDX" \
-lcurl -lm -o "$WORK/p4.san" 2>"$WORK/san_cc.log" || { echo "SAN COMPILE FAILED"; tail -25 "$WORK/san_cc.log"; fail=1; }
if [ -x "$WORK/p4.san" ]; then
export ASAN_OPTIONS=detect_leaks=0
"$WORK/p4.san" >/dev/null 2>"$WORK/san.log"
if grep -qiE 'runtime error|AddressSanitizer|Sanitizer|ERROR: ' "$WORK/san.log"; then
echo " FAIL: sanitizer findings:"; grep -iE 'runtime error|Sanitizer|ERROR' "$WORK/san.log" | head; fail=1
else echo " ok: ASan+UBSan clean"; fi
fi
echo
if [ "$fail" -eq 0 ]; then echo "====== P4 AFFERENT-COUNTERS GATE: PASS ======"; else echo "====== P4 AFFERENT-COUNTERS GATE: FAIL ======"; fi
rm -rf "$WORK"
exit $fail
@@ -0,0 +1,35 @@
/* test_interoception_p4_afferent.c — M-INTEROCEPTION Priority 4.
* Afferent input counters in engram_act_stats_json: additive observability.
* Drives KNOWN counts and asserts the emitted counters match and are monotonic.
*/
#include "el_runtime.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static el_val_t S(const char* s){ return EL_STR(s); }
static el_val_t F(double d){ return el_from_float(d); }
int main(void){
/* 3 plain node creates + 2 ISE creates = 5 node_creates, 2 ise_ingests */
el_val_t a=engram_node_full(S("alpha concept about memory and time"),S("Concept"),S("a"),F(0.9),F(0.8),F(1.0),S("Semantic"),S("x"));
el_val_t b=engram_node_full(S("beta concept about memory and links"),S("Concept"),S("b"),F(0.9),F(0.8),F(1.0),S("Semantic"),S("x"));
engram_node_full(S("gamma distractor"),S("Fact"),S("c"),F(0.4),F(0.4),F(1.0),S("Semantic"),S("y"));
engram_node_full(S("heartbeat internal state one"),S("InternalStateEvent"),S("i1"),F(0.5),F(0.5),F(1.0),S("Working"),S("ise"));
engram_node_full(S("curiosity internal state two"),S("InternalStateEvent"),S("i2"),F(0.5),F(0.5),F(1.0),S("Working"),S("ise"));
/* 2 edge creates */
engram_connect(a,b,F(0.8),S("associate"));
engram_connect(b,a,F(0.3),S("associate"));
/* first reading (0 queries so far) */
printf("STATS0 %s\n", EL_CSTR(engram_act_stats_json()));
/* 4 queries -> 4 activations */
for(int i=0;i<4;i++) engram_activate_json(S("memory and time and links"), (el_val_t)2);
printf("STATS1 %s\n", EL_CSTR(engram_act_stats_json()));
/* 3 more queries -> monotonic increase */
for(int i=0;i<3;i++) engram_activate_json(S("memory and time and links"), (el_val_t)2);
printf("STATS2 %s\n", EL_CSTR(engram_act_stats_json()));
return 0;
}
+26 -3
View File
@@ -6534,6 +6534,15 @@ static int64_t _eg_embed_breaker_until = 0;
* rates keep the previous reading and diff. Restart legitimately resets to 0. */
static int64_t _eg_act_breakthroughs = 0; /* forced promotions at the floor, cumulative */
static int64_t _eg_act_wm_evicted = 0; /* ALL WM evictions, cumulative (see below) */
/* M-INTEROCEPTION P4: AFFERENT input counters — monotonic raw counts of the
* incoming signals the mind receives. Additive observability, MEASURED and
* rotated via the existing act-stats mechanism, NEVER accreted as memory nodes.
* Process-lifetime totals (reset to 0 on restart, like the other _eg_act_*). */
static int64_t _eg_aff_activations = 0; /* engram_activate calls (core spreading) */
static int64_t _eg_aff_queries = 0; /* engram_activate_json entries */
static int64_t _eg_aff_node_creates = 0; /* engram_node_full calls */
static int64_t _eg_aff_ise_ingests = 0; /* InternalStateEvent nodes created */
static int64_t _eg_aff_edge_creates = 0; /* engram_connect calls */
/* Redundancy suppression counters (2026-08-05 self-review) — see
* ENGRAM_DEDUP_COS. dup_seeds = semantic seed slots reclaimed from redundant
* copies; dup_wm = WM candidates dropped for duplicating a higher-ranked
@@ -7986,6 +7995,9 @@ el_val_t engram_node_full(el_val_t content, el_val_t node_type, el_val_t label,
engram_idmap_put(g, n->id, new_idx_full);
engram_adj_on_node_added(g);
if (engram_store_enabled()) eg_store_put_node(n);
/* P4 afferent counters: a node was created; ISE ingests counted separately. */
_eg_aff_node_creates++;
if (n->node_type && strcmp(n->node_type, "InternalStateEvent") == 0) _eg_aff_ise_ingests++;
/* P1 CONNECTION threshold: self-gated (inert unless ENGRAM_CONSOLIDATION). */
eg_consolidate_ise_connect(n);
return el_wrap_str(el_strdup(n->id));
@@ -8549,6 +8561,7 @@ void engram_connect(el_val_t from_id, el_val_t to_id, el_val_t weight, el_val_t
const char* t = EL_CSTR(to_id);
const char* r = EL_CSTR(relation);
if (!f || !t) return;
_eg_aff_edge_creates++; /* P4 afferent counter: an edge was authored */
engram_grow_edges();
EngramEdge* e = &g->edges[g->edge_count];
memset(e, 0, sizeof(*e));
@@ -9222,6 +9235,7 @@ el_val_t engram_activate(el_val_t query, el_val_t depth) {
int64_t max_depth = (int64_t)depth; if (max_depth <= 0) max_depth = 2;
el_val_t out = el_list_empty();
if (!q || g->node_count == 0) return out;
_eg_aff_activations++; /* P4 afferent counter: a real spreading activation ran */
/* Rebuild adjacency index if the edge/node topology changed since the
* last activation call. This is O(E) one-time cost vs O(E) per BFS step
@@ -12249,6 +12263,7 @@ el_val_t engram_activate_json(el_val_t query, el_val_t depth) {
* Each entry includes both activation_strength (layer 1 background) and
* working_memory_weight (layer 2 executive filter), plus promoted flag.
* Callers performing context compilation should filter to promoted=1. */
_eg_aff_queries++; /* P4 afferent counter: a query entered the mind */
el_val_t lst = engram_activate(query, depth);
ElList* arr = (ElList*)(uintptr_t)lst;
JsonBuf b; jb_init(&b);
@@ -12455,7 +12470,7 @@ el_val_t engram_act_stats_json(void) {
/* 768, not 512: the write-back gauges added 2026-08-07 push the worst-case
* rendering past the old bound, and snprintf would truncate the JSON into
* an unparseable tail rather than fail loudly. */
char buf[896];
char buf[1088];
/* ctx_cos (2026-07-29): cos(query, context centroid) at the LAST
* activate call, measured before the query was folded in. ~1.0 =
* context aligned with current query; low = divergence (expected at
@@ -12476,7 +12491,12 @@ el_val_t engram_act_stats_json(void) {
* any climb means a write path is mangling text again. Cheap
* (counted at creation) the full census lives in
* engram_text_health_json. (2026-08-08 self-review) */
"\"txt_damaged\":%lld}",
"\"txt_damaged\":%lld,"
/* P4 afferent input counters (M-INTEROCEPTION): monotonic raw
* counts of incoming signals; MEASURED here, never memory nodes. */
"\"aff_activations\":%lld,\"aff_queries\":%lld,"
"\"aff_node_creates\":%lld,\"aff_ise_ingests\":%lld,"
"\"aff_edge_creates\":%lld}",
(long long)_eg_act_wm_evicted,
(long long)_eg_act_breakthroughs,
breaker_open, _eg_embed_consec_fail,
@@ -12488,7 +12508,10 @@ el_val_t engram_act_stats_json(void) {
(long long)_eg_hebb_wb_dropped,
(long long)_eg_act_dup_seeds, (long long)_eg_act_dup_wm,
(long long)_eg_act_dup_wm_global,
(long long)_eg_txt_write_damaged);
(long long)_eg_txt_write_damaged,
(long long)_eg_aff_activations, (long long)_eg_aff_queries,
(long long)_eg_aff_node_creates, (long long)_eg_aff_ise_ingests,
(long long)_eg_aff_edge_creates);
return el_wrap_str(el_strdup(buf));
}