Executor-Tyrant-Framework commited on
Commit
f8615ea
·
verified ·
1 Parent(s): 821dd72

Sync from GitHub: 500d328a89ae2aee6e3c4971d12c2c008ae336c4

Browse files
Files changed (2) hide show
  1. app.py +14 -145
  2. nuwave/organism.py +27 -0
app.py CHANGED
@@ -1243,150 +1243,20 @@ def on_interleaved_benchmark(
1243
  nw_msgs.append({"role": "assistant", "content": resp_nw})
1244
  nw_organism.record_outcome(prompt_text, resp_nw, success=True)
1245
 
1246
- # ── Phase 2: scoped multi-channel feedback (2026-04-26) ──
1247
- # record_outcome already fired GLOBAL success reward (strength=2.0
1248
- # × 3 cycles). This block adds SCOPED signal-modulated reward on
1249
- # the retrieved nodes so the substrate can learn WHICH retrievals
1250
- # actually contributed to outcomes not just that this turn was
1251
- # successful overall.
1252
- #
1253
- # Per Josh's directive ("put it all in, the substrate can learn
1254
- # from it"), three signals fire per turn — substrate's three-
1255
- # factor learning correlates eligibility traces with each:
1256
- # 1. Per-node response-similarity was this content related
1257
- # to the answer we generated?
1258
- # 2. Per-node query-similarity was this content relevant
1259
- # to the question?
1260
- # 3. Collective tokens-saved (scoped to entire pith set) —
1261
- # did the surfacing as a whole help compress the prompt?
1262
- #
1263
- # Stimulate-then-step before reward is the canonical inject_reward
1264
- # pattern. surface_extract used write_mode=False, leaving no
1265
- # eligibility traces; we re-fire the pith nodes here briefly so
1266
- # traces form, then scoped reward modulates them.
1267
- # Phase 2 (revised 2026-04-27 after Runs 19-20 showed actively-
1268
- # harmful behavior). Two bugs in the prior implementation, fixed
1269
- # here:
1270
- #
1271
- # Bug 1 (DROPPED): Channel 3 (collective tokens-saved penalty)
1272
- # applied uniformly to all pith nodes. When NuWave used more
1273
- # tokens than baseline, it depressed legitimate retrieval
1274
- # pathways (high-prior-weight question nodes) faster than
1275
- # marginal noise (low-prior-weight narrative nodes), inverting
1276
- # the intended effect. Tokens-saved is a turn-level signal
1277
- # with no per-node attribution — feeding it as a scoped
1278
- # penalty was a wrong signal-to-scope binding. Removed.
1279
- #
1280
- # Bug 2 (FIXED): stimulate(current=1.5) + step() left residual
1281
- # voltage on stimulated nodes that bled into the next turn's
1282
- # surface_extract, biasing retrieval toward the very nodes
1283
- # Phase 2 was trying to selectively reward. Replaced with
1284
- # prime_and_propagate(write_mode=True), the canonical method
1285
- # for forming eligibility traces — bounded propagation,
1286
- # designed for plasticity, used by Tonic's heuristic
1287
- # ouroboros and by canonical surface_extract patterns.
1288
- #
1289
- # Telemetry is now surfaced INTO the per-turn result dict as
1290
- # phase2_diag, visible in the JSON output rather than only in
1291
- # runtime logs (Josh: "you set this up, that's your department").
1292
- _phase2_diag = {"fired": False, "reason": None}
1293
- try:
1294
- if pith_ids and resp_nw and nw_organism._graph is not None:
1295
- _rewards_before = getattr(
1296
- nw_organism._graph, "_total_rewards_injected", 0,
1297
- )
1298
-
1299
- _resp_emb = np.asarray(
1300
- nw_organism._embed_fn(resp_nw), dtype=np.float32,
1301
- )
1302
- _resp_n = _resp_emb / (np.linalg.norm(_resp_emb) + 1e-8)
1303
- _query_emb = np.asarray(
1304
- nw_organism._embed_fn(prompt_text), dtype=np.float32,
1305
- )
1306
- _query_n = _query_emb / (np.linalg.norm(_query_emb) + 1e-8)
1307
-
1308
- _per_node_strengths = []
1309
-
1310
- with nw_organism._graph_lock:
1311
- # Use prime_and_propagate(write_mode=True) — the
1312
- # canonical trace-forming method. Bounded propagation
1313
- # means voltages don't leak into next turn, and
1314
- # write_mode enables STDP plasticity from the activity.
1315
- _seeds = [
1316
- _pid for _pid in pith_ids
1317
- if _pid in nw_organism._graph.nodes
1318
- ]
1319
- if _seeds:
1320
- _prop_result = nw_organism._graph.prime_and_propagate(
1321
- node_ids=_seeds,
1322
- currents=[1.0] * len(_seeds),
1323
- steps=3,
1324
- write_mode=True,
1325
- )
1326
- _fired_count = (
1327
- len(_prop_result.fired_nodes)
1328
- if hasattr(_prop_result, "fired_nodes")
1329
- else 0
1330
- )
1331
-
1332
- # Per-node similarity rewards. Channel 3 (collective
1333
- # tokens-saved) DELETED — see bug analysis above.
1334
- for _pid in pith_ids:
1335
- _node_emb = nw_organism._embeddings.get(_pid)
1336
- if _node_emb is None:
1337
- continue
1338
- _node_n = _node_emb / (
1339
- np.linalg.norm(_node_emb) + 1e-8
1340
- )
1341
- _strength_resp = max(-1.0, min(
1342
- 1.0,
1343
- (float(np.dot(_node_n, _resp_n)) - 0.3) * 2.0,
1344
- ))
1345
- _strength_query = max(-1.0, min(
1346
- 1.0,
1347
- (float(np.dot(_node_n, _query_n)) - 0.3) * 2.0,
1348
- ))
1349
- nw_organism._graph.inject_reward(
1350
- strength=_strength_resp, scope={_pid},
1351
- )
1352
- nw_organism._graph.inject_reward(
1353
- strength=_strength_query, scope={_pid},
1354
- )
1355
- _per_node_strengths.append({
1356
- "id": _pid,
1357
- "resp_sim": round(_strength_resp, 3),
1358
- "query_sim": round(_strength_query, 3),
1359
- })
1360
-
1361
- # Consolidate — two step()s let STDP propagate
1362
- # the rewarded eligibility traces into actual
1363
- # weight changes.
1364
- for _ in range(2):
1365
- nw_organism._graph.step()
1366
- else:
1367
- _fired_count = 0
1368
-
1369
- _rewards_after = getattr(
1370
- nw_organism._graph, "_total_rewards_injected", 0,
1371
- )
1372
- _phase2_diag = {
1373
- "fired": True,
1374
- "seeds": len(_seeds) if _seeds else 0,
1375
- "fired_count_post_pap": _fired_count,
1376
- "rewards_injected_delta": _rewards_after - _rewards_before,
1377
- "per_node_strengths": _per_node_strengths,
1378
- }
1379
- except Exception as exc:
1380
- # Promoted from debug → warning. Surface in JSON so we don't
1381
- # need runtime logs to see what's happening.
1382
- logger.warning(
1383
- "Phase 2 feedback loop FAILED on turn %d: %s: %s",
1384
- i + 1, type(exc).__name__, exc,
1385
- )
1386
- _phase2_diag = {
1387
- "fired": False,
1388
- "reason": f"{type(exc).__name__}: {exc}",
1389
- }
1390
 
1391
  # Drain the concept queue before the next turn — makes tree
1392
  # extraction synchronous for benchmark reproducibility. Without
@@ -1458,7 +1328,6 @@ def on_interleaved_benchmark(
1458
  "ignition_size": len(ignition_sets[i]),
1459
  "pith_ids": list(pith_ids),
1460
  "surfaced_context": _surfaced_context,
1461
- "phase2_diag": _phase2_diag,
1462
  "trees": trees_for_turn,
1463
  "raw_extractor_output": raw_output,
1464
  "extractor_elapsed_s": extractor_elapsed,
 
1243
  nw_msgs.append({"role": "assistant", "content": resp_nw})
1244
  nw_organism.record_outcome(prompt_text, resp_nw, success=True)
1245
 
1246
+ # Phase 2 (scoped multi-channel substrate feedback) was attempted
1247
+ # in commits 468fd09, ab0fdd3, e4dd297 then removed 2026-04-27.
1248
+ # The architectural idea (substrate-feedback-via-inject_reward) is
1249
+ # canonical Substrate Authority Pattern and remains correct, but
1250
+ # all three implementations had bugs that made them either no-op
1251
+ # or actively harmful: stimulate residual voltage created positive
1252
+ # feedback loops, Channel 3 collective penalty had wrong signal-
1253
+ # to-scope binding, prime_and_propagate(currents=1.0) didn't fire
1254
+ # seeds, and concurrent-modification races crashed 3/8 turns.
1255
+ # See feedback_substrate_representation_first.md Phase 2 redesign
1256
+ # is deferred until representation work (discover_hyperedges hook,
1257
+ # type-aware retrieval scoring with expert decay) gives the
1258
+ # substrate the structural inductive biases that make relevance
1259
+ # learnable in the first place.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1260
 
1261
  # Drain the concept queue before the next turn — makes tree
1262
  # extraction synchronous for benchmark reproducibility. Without
 
1328
  "ignition_size": len(ignition_sets[i]),
1329
  "pith_ids": list(pith_ids),
1330
  "surfaced_context": _surfaced_context,
 
1331
  "trees": trees_for_turn,
1332
  "raw_extractor_output": raw_output,
1333
  "extractor_elapsed_s": extractor_elapsed,
nuwave/organism.py CHANGED
@@ -916,6 +916,33 @@ class NuWaveOrganism:
916
  self._last_active_ts = time.time()
917
  with self._graph_lock:
918
  step_result = self._graph.step()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
919
  self._step_result = step_result
920
  self._step_count += 1
921
 
 
916
  self._last_active_ts = time.time()
917
  with self._graph_lock:
918
  step_result = self._graph.step()
919
+
920
+ # Hand fired nodes to the canonical hyperedge discovery
921
+ # primitive. discover_hyperedges() is built into NG's
922
+ # neuro_foundation.py but is host-driven — the substrate
923
+ # API is host-agnostic and doesn't auto-call discovery
924
+ # from inside step(). NuWave is the host for this graph,
925
+ # so we hand it the freshly-fired set every step. The
926
+ # method tracks co-fire patterns over he_discovery_window
927
+ # (config default 10 timesteps); creates a hyperedge when
928
+ # a fired pattern hits he_discovery_min_co_fires (5) and
929
+ # has at least he_discovery_min_nodes (3) members. All
930
+ # config knobs already in place from Phase 1's graph
931
+ # config block. Non-zero return = newly discovered HEs.
932
+ # Wrapped in try/except — discovery failure must not
933
+ # break the step() lifecycle for the rest of the organism.
934
+ fired_for_discovery = list(
935
+ getattr(step_result, "fired_node_ids", []) or []
936
+ )
937
+ if len(fired_for_discovery) >= 3:
938
+ try:
939
+ self._graph.discover_hyperedges(fired_for_discovery)
940
+ except Exception as exc:
941
+ logger.debug(
942
+ "discover_hyperedges raised: %s: %s",
943
+ type(exc).__name__, exc,
944
+ )
945
+
946
  self._step_result = step_result
947
  self._step_count += 1
948