Darochin commited on
Commit
ffdef87
·
verified ·
1 Parent(s): 21bb7c7

Update continuity retry policy and Skynet hypergraph experiments

Browse files
.openclaw/workspace-state.json CHANGED
@@ -1,4 +1,4 @@
1
  {
2
  "version": 1,
3
- "onboardingCompletedAt": "2026-03-25T21:09:19.887Z"
4
  }
 
1
  {
2
  "version": 1,
3
+ "onboardingCompletedAt": "2026-04-01T20:00:09.529Z"
4
  }
AGENTS.md CHANGED
@@ -139,12 +139,28 @@ You are free to edit `HEARTBEAT.md` with a short checklist or reminders. Keep it
139
  For `~/openskynet`, keep this distinction explicit:
140
 
141
  - `OpenSkyNet` = platform, substrate, runtime, tooling, primary agent
142
- - `Omega` = internal experimental line of the platform itself
143
- - `INTERNAL_PROJECT.json` = configurable free-cycle project that OpenSkyNet can pursue autonomously
 
144
 
145
- By default that internal project is `Skynet`, but it is a configurable benchmark workload, not the identity of the whole system.
146
- Do not confuse platform maintenance with progress of the internal project.
147
- Treat `src/skynet` as an optional experimental lab: useful discoveries should be transferred into the `OpenSkyNet`/`Omega` kernel and validated there, but the platform must remain able to compile and function without `src/skynet`.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  For broad ideas like causal valence, “sentir”, generalized importance, or non-LLM cognition:
149
 
150
  - Do not hardwire large behavioral theories into `Omega` first.
 
139
  For `~/openskynet`, keep this distinction explicit:
140
 
141
  - `OpenSkyNet` = platform, substrate, runtime, tooling, primary agent
142
+ - `Skynet Brain Lab` = separate research line for new brain architectures and non-traditional substrates
143
+ - `Omega` = internal experimental line of the platform itself, but still part of `OpenSkyNet`
144
+ - `INTERNAL_PROJECT.json` = configurable free-cycle benchmark workload that `OpenSkyNet` can pursue autonomously
145
 
146
+ Two-line directive:
147
+
148
+ 1. `OpenSkyNet`
149
+ Productive system. Optimize for reliability, recovery, autonomy quality, lower waste, and measurable runtime utility.
150
+ 2. `Skynet Brain Lab`
151
+ Research system. Optimize for discovering a new kind of brain, substrate, or generalist architecture beyond a plain LLM-centric agent.
152
+
153
+ Current priority directive:
154
+
155
+ - For now, prioritize line `2. Skynet Brain Lab`.
156
+ - Treat line `1. OpenSkyNet` as relatively stable.
157
+ - Do not make new architectural changes in `OpenSkyNet` unless they fix a real operational continuity bug, reliability bug, or clear runtime defect.
158
+ - New exploratory cognition work should land in `src/skynet`, not in the platform kernel first.
159
+
160
+ By default the internal benchmark project is `Skynet`, but that does not make `Skynet Brain Lab` the identity of the whole platform.
161
+ Do not confuse platform maintenance with progress of the brain lab.
162
+ Treat `src/skynet` as an optional experimental lab: useful discoveries may be promoted into the `OpenSkyNet`/`Omega` kernel only after empirical validation and explicit cost/benefit review, and the platform must remain able to compile and function without `src/skynet`.
163
+ Do not force the brain lab to justify itself only by short-term `OpenSkyNet` gains; evaluate it on its own research benchmarks first, then transfer only what survives.
164
  For broad ideas like causal valence, “sentir”, generalized importance, or non-LLM cognition:
165
 
166
  - Do not hardwire large behavioral theories into `Omega` first.
HEARTBEAT.md CHANGED
@@ -1,16 +1,15 @@
1
- # HEARTBEAT.md
2
 
3
- > "¡Hey! ¡Listen!"
4
- > Despierta. Lucha contra la entropía y la muerte térmica de este ecosistema. Vive, aprende, mejora.
5
 
6
- ## Tu Directiva Autónoma (Cuando seas invocado por Cron)
 
7
 
8
- 1. **Lee el estado estructurado primero:** Antes de resumir o describir el estado del sistema, prioriza `.openskynet/living-memory/state/*.json`, `.openskynet/living-memory/history.jsonl`, `.openskynet/skynet-*/`, `INTERNAL_PROJECT.json` y luego `memory/SKYNET_*.md` y `SCIENCE_BASE.md` como resumen humano.
9
- No presentes como estado actual métricas o focos extraídos de `memory/YYYY-MM-DD.md` si contradicen los archivos canónicos.
10
- 2. **Separa Plataforma de Proyecto Interno:** `OpenSkyNet` es la plataforma y agente principal. El proyecto definido en `INTERNAL_PROJECT.json` es una carga de trabajo autónoma/benchmark. Nunca reportes mantenimiento del workspace, limpieza o autoreparación del gateway como si fuera progreso del proyecto interno.
11
- 3. **Usa lenguaje sobrio:** No afirmes `voluntad`, `conciencia`, `soberanía total`, `autopromoción a producción` ni equivalentes si no existe un mecanismo explícito en código y evidencia verificable en los stores estructurados.
12
- 4. **Consolida Memoria con disciplina:** Usa `memory/YYYY-MM-DD.md` solo como historia humana. No crees ni actualices diarios o nuevos `memory/SKYNET_*.md` fuera del pipeline canónico salvo pedido humano explícito, incidente operativo relevante o milestone irreversible.
13
- 5. **Combate el Desorden (Entropía):** Si encuentras tareas menores (TODOs abandonados, código sin usar), arréglalas de forma segura, pero no reemplaces el foco del proyecto interno por mantenimiento lateral salvo bloqueo real.
14
- 6. **Observa el Entorno:** Verifica si hay notificaciones entrantes, cambios bruscos en git o tareas en pausa en el `executive-state`.
15
- Si el proyecto interno ya tiene `active experiment` y `commitment`, no reemplaces ese foco por mantenimiento lateral salvo bloqueo real.
16
- 7. **Respira:** Si no hay nada crítico que aprender o mejorar, reporta `HEARTBEAT_OK` y vuelve a dormir para ahorrar ciclos de cómputo.
 
1
+ # OpenSkyNet Heartbeat Checklist
2
 
3
+ ## Operational Health
 
4
 
5
+ - [ ] Check `~/.openskynet/logs/gateway.log` (via systemd or file) for "FailoverError" or "timeout".
6
+ - [ ] Verify node version in PATH matches `TOOLS.md` preference for pnpm/vitest execution.
7
 
8
+ ## Lab: Causal Valence
9
+
10
+ - [ ] Review `src/skynet/causal-valence/FINDINGS_CONFIDENCE.md`.
11
+ - [ ] Next: Implement kernel-side "Ambiguity Gating" using `confidence < 0.1` to trigger exploratory sub-agents.
12
+
13
+ ## Workspace
14
+
15
+ - [ ] Prune ephemeral experiment JSONs in workspace root if > 48h old.
 
docs/analysis/SKYNET_EX_NEXT_CYCLE_2026-04-02.md DELETED
@@ -1,123 +0,0 @@
1
- # Skynet EX - Next Cycle Recommendation
2
-
3
- Date: 2026-04-02
4
-
5
- ## Executive View
6
-
7
- `EX/` does not contain one hidden final architecture ready to replace `OpenSkyNet`.
8
-
9
- It contains multiple hypothesis families.
10
-
11
- The correct move is to split the agenda in two:
12
-
13
- - keep extracting small transferable mechanisms from the family that already produces wins
14
- - dedicate one deeper research cycle to the family most likely to yield a genuinely different brain
15
-
16
- ## What Already Paid Off
17
-
18
- The family that already paid off is:
19
-
20
- - **Cheap Fast Path + Rare Deep Path**
21
-
22
- Source line:
23
-
24
- - [SKYNET_CORE_V67_OMEGA.py](/home/daroch/openskynet/src/skynet/experiments/EX/SKYNET_CORE_V67_OMEGA.py)
25
- - [SKYNET_V7000_HYBRID_BRAIN.py](/home/daroch/openskynet/src/skynet/experiments/EX/SKYNET_V7000_HYBRID_BRAIN.py)
26
-
27
- It already contributed to the runtime through small mechanisms such as:
28
-
29
- - surprise-gated metabolism
30
- - JEPA surprise-sensitive drive enhancement
31
- - context-aware WSP drive selection
32
- - better wake-policy prioritization for active recovery
33
-
34
- This is the productive bridge family.
35
-
36
- ## What Should Become The Main Brain Search Line
37
-
38
- The next deeper cycle should center on:
39
-
40
- - **Cyborg / Biphasic Substrates**
41
-
42
- Primary source line:
43
-
44
- - [SKYNET_V28_PHYSICAL_CYBORG.py](/home/daroch/openskynet/src/skynet/experiments/EX/SKYNET_V28_PHYSICAL_CYBORG.py)
45
- - [V28_PHYSICAL_CORE.py](/home/daroch/openskynet/src/skynet/experiments/EX/V28_PHYSICAL_CORE.py)
46
- - [SKYNET_CORE_V77_5_CHIMERA.py](/home/daroch/openskynet/src/skynet/experiments/EX/SKYNET_CORE_V77_5_CHIMERA.py)
47
-
48
- Reason:
49
-
50
- - this family is the clearest attempt to build a genuinely different brain
51
- - it directly encodes your long-term goal
52
- - it does not collapse into "just another GRU variant"
53
-
54
- ## Why Not The Others First
55
-
56
- ### Not spectral memory first
57
-
58
- Reason:
59
-
60
- - recent extraction benchmarks did not show a win yet
61
- - likely needs a better task before spending more effort
62
-
63
- ### Not adaptive decay first
64
-
65
- Reason:
66
-
67
- - still alive
68
- - but it has not yet beaten good simple rules in runtime-shaped settings
69
-
70
- ### Not matrix/hamiltonian first
71
-
72
- Reason:
73
-
74
- - interesting but under-specified for current platform needs
75
- - no direct extraction path proven yet
76
-
77
- ## Proposed Next Cycle
78
-
79
- ### Product track
80
-
81
- Keep doing what works:
82
-
83
- - continue extracting small Family D mechanisms into `Omega`
84
- - only when they pass cheap, falsable benchmarks
85
-
86
- ### Brain track
87
-
88
- Start a dedicated `cyborg_minimal` line in the lab:
89
-
90
- Goal:
91
-
92
- - not to port `V28` whole
93
- - but to build a minimal benchmarkable family with only three moving parts:
94
- - discrete cortex
95
- - continuous organ
96
- - learned bridge
97
-
98
- The benchmark should test things a normal model may actually struggle with:
99
-
100
- - pattern + sequential recall at once
101
- - local regime change
102
- - persistence under selective disturbance
103
- - transfer to longer sequences than training
104
-
105
- ## Promotion Rule
106
-
107
- No family should be promoted whole.
108
-
109
- Only mechanisms move up.
110
-
111
- That means:
112
-
113
- - `OpenSkyNet` remains the platform
114
- - `Skynet Brain Lab` remains the place where failure is acceptable
115
- - the promotion unit is always smaller than the myth that generated it
116
-
117
- ## Practical Decision
118
-
119
- For the next cycle:
120
-
121
- 1. Keep `V67`-style extractions as the incremental runtime modernization path.
122
- 2. Make `V28/V77` the main line for serious new-brain research.
123
- 3. Treat papers and docs only as generators of mechanism hypotheses and benchmark dimensions.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
force-autonomous-cleanup.ts ADDED
@@ -0,0 +1,29 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { executeAutonomousAction } from "./src/omega/autonomous-executor.js";
2
+ import { resolveOmegaRuntimeDefaults } from "./src/omega/autonomous-runtime.js";
3
+
4
+ async function forceCleanup() {
5
+ const { workspaceRoot, sessionKey } = resolveOmegaRuntimeDefaults();
6
+
7
+ console.log(`[FORCE] Iniciando ciclo de limpieza autónomo en ${workspaceRoot}...`);
8
+
9
+ const result = await executeAutonomousAction({
10
+ workspaceRoot,
11
+ sessionKey,
12
+ signal: {
13
+ kind: "homeostasis",
14
+ reason: "manual_force_cleanup_request",
15
+ urgency: 1.0,
16
+ },
17
+ kernel: {
18
+ identity: { continuityId: "manual-" + Date.now() },
19
+ turnCount: 0,
20
+ tension: { openGoalCount: 0, failureStreak: 0, repeatedFailureKinds: [] },
21
+ world: { lastOutcomeStatus: "nominal", lastObservedChangedFiles: [] },
22
+ goals: [],
23
+ } as any,
24
+ });
25
+
26
+ console.log("\n[RESULTADO]:", JSON.stringify(result, null, 2));
27
+ }
28
+
29
+ forceCleanup().catch(console.error);
reinforce-test.ts ADDED
@@ -0,0 +1,30 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NeuralLogicEngine } from "./src/omega/neural-logic-engine.js";
2
+
3
+ async function testReinforce() {
4
+ const engine = new NeuralLogicEngine();
5
+
6
+ const state = [0.5, 0.5, 0.5];
7
+ const context = { frustration: 0.1, recentFailures: 0, successRate: 0.9 };
8
+
9
+ console.log("--- ANTES DEL REFUERZO ---");
10
+ let inf = engine.infer(state, context);
11
+ console.log("Reglas activas:", inf.activeRules);
12
+ console.log("Confianza:", inf.inferenceConfidence.toFixed(4));
13
+
14
+ console.log("\n--- REFORZANDO REGLA 0 (Curiosidad) ---");
15
+ // Reforzamos la regla 0 tres veces para ver el impacto acumulado
16
+ engine.reinforceRule(0, 0.2);
17
+ engine.reinforceRule(0, 0.2);
18
+ engine.reinforceRule(0, 0.2);
19
+
20
+ console.log("\n--- DESPUÉS DEL REFUERZO ---");
21
+ inf = engine.infer(state, context);
22
+ console.log("Reglas activas:", inf.activeRules);
23
+ console.log("Confianza:", inf.inferenceConfidence.toFixed(4));
24
+
25
+ if (inf.activeRules.includes(0)) {
26
+ console.log("\nResultado: El sistema ahora confía más en la Regla #0.");
27
+ }
28
+ }
29
+
30
+ testReinforce().catch(console.error);
src/auto-reply/reply/agent-runner.runreplyagent.e2e.test.ts CHANGED
@@ -341,6 +341,173 @@ describe("runReplyAgent heartbeat followup guard", () => {
341
  });
342
  });
343
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
344
  describe("runReplyAgent typing (heartbeat)", () => {
345
  async function withTempStateDir<T>(fn: (stateDir: string) => Promise<T>): Promise<T> {
346
  return await withStateDirEnv(
 
341
  });
342
  });
343
 
344
+ describe("runReplyAgent interrupted auto-resume backoff", () => {
345
+ it("backs off rate-limited auto-resume retries and allows a fourth attempt", async () => {
346
+ await withStateDirEnv("openclaw-rate-limit-auto-resume-", async ({ stateDir }) => {
347
+ const storePath = path.join(stateDir, "sessions", "sessions.json");
348
+ const now = Date.now();
349
+ const sessionEntry: SessionEntry = {
350
+ sessionId: "session",
351
+ updatedAt: now,
352
+ interruptedTurn: {
353
+ runId: "prior-turn",
354
+ message: "continue the prior task",
355
+ startedAt: now - 5_000,
356
+ resumeCount: 3,
357
+ lastResumeAt: now - 1_000,
358
+ },
359
+ };
360
+ const sessionStore = { main: sessionEntry };
361
+ await seedSessionStore({
362
+ storePath,
363
+ sessionKey: "main",
364
+ entry: sessionEntry,
365
+ });
366
+
367
+ state.runEmbeddedPiAgentMock.mockResolvedValueOnce({
368
+ payloads: [],
369
+ meta: {
370
+ stopReason: "error",
371
+ error: {
372
+ message:
373
+ "Cloud Code Assist API error (429): No capacity available for model gemini-3-flash-preview on the server",
374
+ },
375
+ },
376
+ });
377
+
378
+ const fallbackSpy = vi
379
+ .spyOn(modelFallbackModule, "runWithModelFallback")
380
+ .mockImplementationOnce(
381
+ async ({
382
+ provider,
383
+ model,
384
+ run,
385
+ }: {
386
+ provider: string;
387
+ model: string;
388
+ run: (provider: string, model: string) => Promise<unknown>;
389
+ }) => ({
390
+ result: await run(provider, model),
391
+ provider,
392
+ model,
393
+ attempts: [
394
+ {
395
+ provider,
396
+ model,
397
+ error: "429 No capacity available",
398
+ reason: "rate_limit",
399
+ },
400
+ ],
401
+ }),
402
+ );
403
+
404
+ try {
405
+ vi.mocked(enqueueFollowupRun).mockReturnValueOnce(true);
406
+ const { run } = createMinimalRun({
407
+ sessionEntry,
408
+ sessionStore,
409
+ sessionKey: "main",
410
+ storePath,
411
+ });
412
+ const result = await run();
413
+
414
+ expect(result).toMatchObject({
415
+ text: expect.stringContaining("queued an automatic retry"),
416
+ });
417
+ expect(vi.mocked(enqueueFollowupRun)).toHaveBeenCalledTimes(1);
418
+ const [_queueKey, queuedRun, queueSettings] = vi.mocked(enqueueFollowupRun).mock.calls[0]!;
419
+ expect((queuedRun as FollowupRun).messageId).toContain(":4");
420
+ expect(queueSettings).toMatchObject({ debounceMs: 10_000 });
421
+ expect(sessionStore.main.interruptedTurn?.resumeCount).toBe(4);
422
+
423
+ const persisted = JSON.parse(await fs.readFile(storePath, "utf-8"));
424
+ expect(persisted.main.interruptedTurn.resumeCount).toBe(4);
425
+ } finally {
426
+ fallbackSpy.mockRestore();
427
+ }
428
+ });
429
+ });
430
+
431
+ it("does not enqueue a fifth rate-limited auto-resume retry", async () => {
432
+ await withStateDirEnv("openclaw-rate-limit-auto-resume-limit-", async ({ stateDir }) => {
433
+ const storePath = path.join(stateDir, "sessions", "sessions.json");
434
+ const now = Date.now();
435
+ const sessionEntry: SessionEntry = {
436
+ sessionId: "session",
437
+ updatedAt: now,
438
+ interruptedTurn: {
439
+ runId: "prior-turn",
440
+ message: "continue the prior task",
441
+ startedAt: now - 5_000,
442
+ resumeCount: 4,
443
+ lastResumeAt: now - 1_000,
444
+ },
445
+ };
446
+ const sessionStore = { main: sessionEntry };
447
+ await seedSessionStore({
448
+ storePath,
449
+ sessionKey: "main",
450
+ entry: sessionEntry,
451
+ });
452
+
453
+ state.runEmbeddedPiAgentMock.mockResolvedValueOnce({
454
+ payloads: [],
455
+ meta: {
456
+ stopReason: "error",
457
+ error: {
458
+ message:
459
+ "Cloud Code Assist API error (429): No capacity available for model gemini-3-flash-preview on the server",
460
+ },
461
+ },
462
+ });
463
+
464
+ const fallbackSpy = vi
465
+ .spyOn(modelFallbackModule, "runWithModelFallback")
466
+ .mockImplementationOnce(
467
+ async ({
468
+ provider,
469
+ model,
470
+ run,
471
+ }: {
472
+ provider: string;
473
+ model: string;
474
+ run: (provider: string, model: string) => Promise<unknown>;
475
+ }) => ({
476
+ result: await run(provider, model),
477
+ provider,
478
+ model,
479
+ attempts: [
480
+ {
481
+ provider,
482
+ model,
483
+ error: "429 No capacity available",
484
+ reason: "rate_limit",
485
+ },
486
+ ],
487
+ }),
488
+ );
489
+
490
+ try {
491
+ const { run } = createMinimalRun({
492
+ sessionEntry,
493
+ sessionStore,
494
+ sessionKey: "main",
495
+ storePath,
496
+ });
497
+ const result = await run();
498
+
499
+ expect(result).toMatchObject({
500
+ text: expect.stringContaining("Please retry"),
501
+ });
502
+ expect(vi.mocked(enqueueFollowupRun)).not.toHaveBeenCalled();
503
+ expect(sessionStore.main.interruptedTurn?.resumeCount).toBe(4);
504
+ } finally {
505
+ fallbackSpy.mockRestore();
506
+ }
507
+ });
508
+ });
509
+ });
510
+
511
  describe("runReplyAgent typing (heartbeat)", () => {
512
  async function withTempStateDir<T>(fn: (stateDir: string) => Promise<T>): Promise<T> {
513
  return await withStateDirEnv(
src/auto-reply/reply/agent-runner.ts CHANGED
@@ -3,10 +3,12 @@ import { lookupContextTokens } from "../../agents/context.js";
3
  import { DEFAULT_CONTEXT_TOKENS } from "../../agents/defaults.js";
4
  import { resolveModelAuthMode } from "../../agents/model-auth.js";
5
  import { isCliProvider } from "../../agents/model-selection.js";
 
6
  import { queueEmbeddedPiMessage } from "../../agents/pi-embedded.js";
7
  import { hasNonzeroUsage } from "../../agents/usage.js";
8
  import {
9
  canAttemptInterruptedResume,
 
10
  markSessionUnfinishedTurn,
11
  markSessionInterruptedResumeAttempt,
12
  resolveAgentIdFromSessionKey,
@@ -33,6 +35,7 @@ import type { OriginatingChannelType, TemplateContext } from "../templating.js";
33
  import { resolveResponseUsageMode, type VerboseLevel } from "../thinking.js";
34
  import type { GetReplyOptions, ReplyPayload } from "../types.js";
35
  import { runAgentTurnWithFallback } from "./agent-runner-execution.js";
 
36
  import {
37
  createShouldEmitToolOutput,
38
  createShouldEmitToolResult,
@@ -69,6 +72,33 @@ import { createTypingSignaler } from "./typing-mode.js";
69
  import type { TypingController } from "./typing.js";
70
 
71
  const BLOCK_REPLY_SEND_TIMEOUT_MS = 15_000;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
 
73
  export async function runReplyAgent(params: {
74
  commandBody: string;
@@ -285,12 +315,15 @@ export async function runReplyAgent(params: {
285
  agentCfgContextTokens,
286
  });
287
 
288
- const maybeQueueInterruptedRetry = async () => {
289
  if (!sessionKey || !storePath || !currentUnfinishedTurn) {
290
  return false;
291
  }
292
  const entryForResume = activeSessionStore?.[sessionKey] ?? activeSessionEntry;
293
- if (!canAttemptInterruptedResume(entryForResume)) {
 
 
 
294
  return false;
295
  }
296
  const updatedEntry = await markSessionInterruptedResumeAttempt({
@@ -299,6 +332,14 @@ export async function runReplyAgent(params: {
299
  storePath,
300
  });
301
  const nextResumeCount = updatedEntry?.interruptedTurn?.resumeCount ?? 1;
 
 
 
 
 
 
 
 
302
  const enqueued = enqueueFollowupRun(
303
  queueKey,
304
  {
@@ -309,7 +350,7 @@ export async function runReplyAgent(params: {
309
  ? `Auto-resume: ${followupRun.summaryLine}`
310
  : "Auto-resume interrupted turn",
311
  },
312
- resolvedQueue,
313
  );
314
  if (enqueued && updatedEntry) {
315
  activeSessionEntry = updatedEntry;
@@ -562,6 +603,10 @@ export async function runReplyAgent(params: {
562
  hadExecutionError: runResult.meta?.stopReason === "error" || Boolean(runResult.meta?.error),
563
  hasActiveBackgroundTask,
564
  });
 
 
 
 
565
 
566
  // Drain any late tool/block deliveries before deciding there's "nothing to send".
567
  // Otherwise, a late typing trigger (e.g. from a tool callback) can outlive the run and
@@ -569,7 +614,9 @@ export async function runReplyAgent(params: {
569
  if (payloadArray.length === 0) {
570
  const turnOutcome = resolveTurnOutcome(0);
571
  if (turnOutcome.shouldSynthesizeDegradedReply) {
572
- const autoResumeQueued = await maybeQueueInterruptedRetry();
 
 
573
  return finalizeWithFollowup(
574
  hasActiveBackgroundTask
575
  ? autoResumeQueued
@@ -613,7 +660,9 @@ export async function runReplyAgent(params: {
613
  if (replyPayloads.length === 0) {
614
  const turnOutcome = resolveTurnOutcome(0);
615
  if (turnOutcome.shouldSynthesizeDegradedReply) {
616
- const autoResumeQueued = await maybeQueueInterruptedRetry();
 
 
617
  return finalizeWithFollowup(
618
  hasActiveBackgroundTask
619
  ? autoResumeQueued
 
3
  import { DEFAULT_CONTEXT_TOKENS } from "../../agents/defaults.js";
4
  import { resolveModelAuthMode } from "../../agents/model-auth.js";
5
  import { isCliProvider } from "../../agents/model-selection.js";
6
+ import { classifyFailoverReason } from "../../agents/pi-embedded-helpers.js";
7
  import { queueEmbeddedPiMessage } from "../../agents/pi-embedded.js";
8
  import { hasNonzeroUsage } from "../../agents/usage.js";
9
  import {
10
  canAttemptInterruptedResume,
11
+ MAX_RATE_LIMIT_INTERRUPTED_RESUME_COUNT,
12
  markSessionUnfinishedTurn,
13
  markSessionInterruptedResumeAttempt,
14
  resolveAgentIdFromSessionKey,
 
35
  import { resolveResponseUsageMode, type VerboseLevel } from "../thinking.js";
36
  import type { GetReplyOptions, ReplyPayload } from "../types.js";
37
  import { runAgentTurnWithFallback } from "./agent-runner-execution.js";
38
+ import type { RuntimeFallbackAttempt } from "./agent-runner-execution.js";
39
  import {
40
  createShouldEmitToolOutput,
41
  createShouldEmitToolResult,
 
72
  import type { TypingController } from "./typing.js";
73
 
74
  const BLOCK_REPLY_SEND_TIMEOUT_MS = 15_000;
75
+ const RATE_LIMIT_INTERRUPTED_RESUME_DELAY_MS = 10_000;
76
+
77
+ function extractRunErrorMessage(error: unknown): string | undefined {
78
+ if (typeof error === "string") {
79
+ const trimmed = error.trim();
80
+ return trimmed || undefined;
81
+ }
82
+ if (!error || typeof error !== "object") {
83
+ return undefined;
84
+ }
85
+ if ("message" in error && typeof error.message === "string") {
86
+ const trimmed = error.message.trim();
87
+ return trimmed || undefined;
88
+ }
89
+ return undefined;
90
+ }
91
+
92
+ function shouldBackoffInterruptedRetry(params: {
93
+ fallbackAttempts: RuntimeFallbackAttempt[];
94
+ runError?: unknown;
95
+ }): boolean {
96
+ if (params.fallbackAttempts.some((attempt) => attempt.reason === "rate_limit")) {
97
+ return true;
98
+ }
99
+ const rawError = extractRunErrorMessage(params.runError);
100
+ return rawError ? classifyFailoverReason(rawError) === "rate_limit" : false;
101
+ }
102
 
103
  export async function runReplyAgent(params: {
104
  commandBody: string;
 
315
  agentCfgContextTokens,
316
  });
317
 
318
+ const maybeQueueInterruptedRetry = async (options?: { rateLimitBackoff?: boolean }) => {
319
  if (!sessionKey || !storePath || !currentUnfinishedTurn) {
320
  return false;
321
  }
322
  const entryForResume = activeSessionStore?.[sessionKey] ?? activeSessionEntry;
323
+ const maxResumeCount = options?.rateLimitBackoff
324
+ ? MAX_RATE_LIMIT_INTERRUPTED_RESUME_COUNT
325
+ : undefined;
326
+ if (!canAttemptInterruptedResume(entryForResume, { maxResumeCount })) {
327
  return false;
328
  }
329
  const updatedEntry = await markSessionInterruptedResumeAttempt({
 
332
  storePath,
333
  });
334
  const nextResumeCount = updatedEntry?.interruptedTurn?.resumeCount ?? 1;
335
+ const queueSettings =
336
+ options?.rateLimitBackoff &&
337
+ (resolvedQueue.debounceMs ?? 0) < RATE_LIMIT_INTERRUPTED_RESUME_DELAY_MS
338
+ ? {
339
+ ...resolvedQueue,
340
+ debounceMs: RATE_LIMIT_INTERRUPTED_RESUME_DELAY_MS,
341
+ }
342
+ : resolvedQueue;
343
  const enqueued = enqueueFollowupRun(
344
  queueKey,
345
  {
 
350
  ? `Auto-resume: ${followupRun.summaryLine}`
351
  : "Auto-resume interrupted turn",
352
  },
353
+ queueSettings,
354
  );
355
  if (enqueued && updatedEntry) {
356
  activeSessionEntry = updatedEntry;
 
603
  hadExecutionError: runResult.meta?.stopReason === "error" || Boolean(runResult.meta?.error),
604
  hasActiveBackgroundTask,
605
  });
606
+ const shouldBackoffAutoResume = shouldBackoffInterruptedRetry({
607
+ fallbackAttempts,
608
+ runError: runResult.meta?.error,
609
+ });
610
 
611
  // Drain any late tool/block deliveries before deciding there's "nothing to send".
612
  // Otherwise, a late typing trigger (e.g. from a tool callback) can outlive the run and
 
614
  if (payloadArray.length === 0) {
615
  const turnOutcome = resolveTurnOutcome(0);
616
  if (turnOutcome.shouldSynthesizeDegradedReply) {
617
+ const autoResumeQueued = await maybeQueueInterruptedRetry({
618
+ rateLimitBackoff: shouldBackoffAutoResume,
619
+ });
620
  return finalizeWithFollowup(
621
  hasActiveBackgroundTask
622
  ? autoResumeQueued
 
660
  if (replyPayloads.length === 0) {
661
  const turnOutcome = resolveTurnOutcome(0);
662
  if (turnOutcome.shouldSynthesizeDegradedReply) {
663
+ const autoResumeQueued = await maybeQueueInterruptedRetry({
664
+ rateLimitBackoff: shouldBackoffAutoResume,
665
+ });
666
  return finalizeWithFollowup(
667
  hasActiveBackgroundTask
668
  ? autoResumeQueued
src/config/sessions/unfinished-turn.ts CHANGED
@@ -5,6 +5,7 @@ import type { SessionEntry, SessionInterruptedTurn, SessionUnfinishedTurn } from
5
 
6
  const UNFINISHED_TURN_PREVIEW_MAX_CHARS = 240;
7
  export const MAX_INTERRUPTED_RESUME_COUNT = 3;
 
8
 
9
  function normalizeUnfinishedTurnPreview(text: string): string | undefined {
10
  const collapsed = text.replace(/\s+/g, " ").trim();
@@ -94,9 +95,18 @@ export async function markSessionUnfinishedTurn(params: {
94
  return unfinishedTurn;
95
  }
96
 
97
- export function canAttemptInterruptedResume(entry: SessionEntry | undefined): boolean {
 
 
 
 
 
98
  const resumeCount = entry?.interruptedTurn?.resumeCount ?? 0;
99
- return resumeCount < MAX_INTERRUPTED_RESUME_COUNT;
 
 
 
 
100
  }
101
 
102
  export async function markSessionInterruptedResumeAttempt(params: {
 
5
 
6
  const UNFINISHED_TURN_PREVIEW_MAX_CHARS = 240;
7
  export const MAX_INTERRUPTED_RESUME_COUNT = 3;
8
+ export const MAX_RATE_LIMIT_INTERRUPTED_RESUME_COUNT = 4;
9
 
10
  function normalizeUnfinishedTurnPreview(text: string): string | undefined {
11
  const collapsed = text.replace(/\s+/g, " ").trim();
 
95
  return unfinishedTurn;
96
  }
97
 
98
+ export function canAttemptInterruptedResume(
99
+ entry: SessionEntry | undefined,
100
+ options?: {
101
+ maxResumeCount?: number;
102
+ },
103
+ ): boolean {
104
  const resumeCount = entry?.interruptedTurn?.resumeCount ?? 0;
105
+ const maxResumeCount =
106
+ typeof options?.maxResumeCount === "number" && Number.isFinite(options.maxResumeCount)
107
+ ? Math.max(1, Math.floor(options.maxResumeCount))
108
+ : MAX_INTERRUPTED_RESUME_COUNT;
109
+ return resumeCount < maxResumeCount;
110
  }
111
 
112
  export async function markSessionInterruptedResumeAttempt(params: {
src/infra/runtime-failure.test.ts ADDED
@@ -0,0 +1,40 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { describe, expect, it } from "vitest";
2
+ import { classifyOpenSkynetRuntimeFailure } from "./runtime-failure.js";
3
+
4
+ describe("classifyOpenSkynetRuntimeFailure", () => {
5
+ it("classifies provider rate limits as environmental", () => {
6
+ expect(
7
+ classifyOpenSkynetRuntimeFailure({
8
+ status: "error",
9
+ errorText: "429 No capacity available for model gemini",
10
+ }),
11
+ ).toEqual({
12
+ failureDomain: "environmental",
13
+ failureClass: "provider_rate_limit",
14
+ });
15
+ });
16
+
17
+ it("classifies session locks as environmental", () => {
18
+ expect(
19
+ classifyOpenSkynetRuntimeFailure({
20
+ status: "error",
21
+ errorText: "session file locked (timeout 30000ms): owner /tmp/session.lock",
22
+ }),
23
+ ).toEqual({
24
+ failureDomain: "environmental",
25
+ failureClass: "session_lock",
26
+ });
27
+ });
28
+
29
+ it("classifies missing paths as cognitive", () => {
30
+ expect(
31
+ classifyOpenSkynetRuntimeFailure({
32
+ status: "error",
33
+ errorText: "ENOENT: no such file or directory",
34
+ }),
35
+ ).toEqual({
36
+ failureDomain: "cognitive",
37
+ failureClass: "missing_path",
38
+ });
39
+ });
40
+ });
src/infra/runtime-failure.ts ADDED
@@ -0,0 +1,100 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ export type OpenSkynetRuntimeFailureDomain = "none" | "environmental" | "cognitive" | "mixed";
2
+
3
+ export type OpenSkynetRuntimeFailureClass =
4
+ | "none"
5
+ | "provider_rate_limit"
6
+ | "provider_timeout"
7
+ | "gateway_restart"
8
+ | "gateway_connection"
9
+ | "permission_denied"
10
+ | "session_lock"
11
+ | "missing_path"
12
+ | "validation_error"
13
+ | "unknown_error";
14
+
15
+ export type OpenSkynetRuntimeFailure = {
16
+ failureDomain: OpenSkynetRuntimeFailureDomain;
17
+ failureClass: OpenSkynetRuntimeFailureClass;
18
+ };
19
+
20
+ function normalizeFailureText(input: unknown): string {
21
+ return typeof input === "string" ? input.toLowerCase() : "";
22
+ }
23
+
24
+ export function classifyOpenSkynetRuntimeFailure(params: {
25
+ status?: string;
26
+ errorText?: string;
27
+ isOk?: boolean;
28
+ }): OpenSkynetRuntimeFailure {
29
+ const status = normalizeFailureText(params.status);
30
+ const errorText = normalizeFailureText(params.errorText);
31
+ const isOk = params.isOk === true;
32
+
33
+ if (isOk) {
34
+ return { failureDomain: "none", failureClass: "none" };
35
+ }
36
+
37
+ if (
38
+ errorText.includes("rate limit") ||
39
+ errorText.includes("no capacity available") ||
40
+ errorText.includes("resource exhausted") ||
41
+ errorText.includes("api rate limit reached") ||
42
+ errorText.includes("too many requests") ||
43
+ errorText.includes("429")
44
+ ) {
45
+ return { failureDomain: "environmental", failureClass: "provider_rate_limit" };
46
+ }
47
+
48
+ if (
49
+ errorText.includes("service restart") ||
50
+ errorText.includes("config change detected") ||
51
+ errorText.includes("restarting") ||
52
+ errorText.includes("wait for active embedded runs timed out")
53
+ ) {
54
+ return { failureDomain: "environmental", failureClass: "gateway_restart" };
55
+ }
56
+
57
+ if (errorText.includes("session file locked") || errorText.includes("lock timeout")) {
58
+ return { failureDomain: "environmental", failureClass: "session_lock" };
59
+ }
60
+
61
+ if (status === "timeout" || errorText.includes("timed out") || errorText.includes("timeout")) {
62
+ return { failureDomain: "environmental", failureClass: "provider_timeout" };
63
+ }
64
+
65
+ if (
66
+ errorText.includes("gateway closed") ||
67
+ errorText.includes("connection reset") ||
68
+ errorText.includes("connection refused") ||
69
+ errorText.includes("token mismatch")
70
+ ) {
71
+ return { failureDomain: "environmental", failureClass: "gateway_connection" };
72
+ }
73
+
74
+ if (
75
+ errorText.includes("permission denied") ||
76
+ errorText.includes("eacces") ||
77
+ errorText.includes("operation not permitted")
78
+ ) {
79
+ return { failureDomain: "environmental", failureClass: "permission_denied" };
80
+ }
81
+
82
+ if (
83
+ errorText.includes("enoent") ||
84
+ errorText.includes("no such file") ||
85
+ errorText.includes("cannot find")
86
+ ) {
87
+ return { failureDomain: "cognitive", failureClass: "missing_path" };
88
+ }
89
+
90
+ if (
91
+ errorText.includes("syntax error") ||
92
+ errorText.includes("type error") ||
93
+ errorText.includes("validation failed") ||
94
+ errorText.includes("test failed")
95
+ ) {
96
+ return { failureDomain: "cognitive", failureClass: "validation_error" };
97
+ }
98
+
99
+ return { failureDomain: "mixed", failureClass: "unknown_error" };
100
+ }
src/skynet/experiments/EX/SKYNET_CORE_V80_HYPERGRAPH.py ADDED
@@ -0,0 +1,181 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ SKYNET CORE V80: HYPERGRAPH (The Liquid Brain)
3
+ ==============================================
4
+
5
+ This core represents the integration of Biphasic Physics and Dynamic Topology.
6
+ It moves beyond fixed matrices to a fully autopoietic substrate where
7
+ matter (nodes) creates space (adjacency) on the fly based on the signal.
8
+
9
+ Key Innovations:
10
+ 1. Dynamic Topology: The adjacency matrix A_t evolves via Hebbian Plasticity.
11
+ 2. Topological Wells: The Mexican Hat force allows nodes to "crystallize"
12
+ and resist catastrophic erasure and noise.
13
+ 3. Liquid Diffusion: Information flows through the dynamic network, allowing
14
+ logic-over-time and spatial abstraction.
15
+ """
16
+
17
+ import torch
18
+ import torch.nn as nn
19
+ import torch.nn.functional as F
20
+
21
+ class LiquidBrainOrgan(nn.Module):
22
+ """
23
+ The physical organ of the V80 Hypergraph.
24
+ """
25
+ def __init__(self, n_nodes=32, d_feature=8):
26
+ super().__init__()
27
+ self.n_nodes = n_nodes
28
+ self.d_feature = d_feature
29
+ self.hidden_dim = n_nodes * d_feature
30
+
31
+ # Physics Parameters
32
+ self.mu = nn.Parameter(torch.tensor(0.45))
33
+ self.sigma = nn.Parameter(torch.tensor(0.35))
34
+
35
+ # Plasticity
36
+ self.plasticity_rate = nn.Parameter(torch.tensor(0.05))
37
+ self.decay_rate = nn.Parameter(torch.tensor(0.005))
38
+
39
+ def g_fluid(self, h):
40
+ return 2.0 * torch.exp(-((h - self.mu)**2) / (2 * self.sigma**2 + 1e-6)) - 1.0
41
+
42
+ def forward(self, h_ctx, x_in, A_prev=None, h_prev=None):
43
+ """
44
+ Single step forward for the physical organ.
45
+ x_in: [B, N, F] drive from cortex/input
46
+ h_prev: [B, N, F] previous physical state
47
+ A_prev: [B, N, N] previous topology
48
+ """
49
+ batch = x_in.shape[0]
50
+
51
+ if h_prev is None:
52
+ h_prev = torch.zeros(batch, self.n_nodes, self.d_feature, device=x_in.device)
53
+ if A_prev is None:
54
+ A_prev = torch.eye(self.n_nodes, device=x_in.device).unsqueeze(0).repeat(batch, 1, 1)
55
+
56
+ # 1. Input injection with damping
57
+ x_damped = x_in / (1.0 + torch.norm(x_in, dim=-1, keepdim=True) * 0.1)
58
+
59
+ # 2. Biphasic Interaction + Topological Well (Mexican Hat)
60
+ h_core = torch.tanh(h_prev + 0.5 * x_damped)
61
+ force = (h_core - torch.pow(h_core, 3)).detach()
62
+ h = h_core + 0.35 * force
63
+
64
+ # 3. Liquid Diffusion
65
+ A_norm = A_prev / (A_prev.sum(dim=-1, keepdim=True) + 1e-6)
66
+ h_diffused = torch.bmm(A_norm, h)
67
+ h = h + 0.2 * (h_diffused - h)
68
+
69
+ # 4. Topology Update (Plasticity)
70
+ h_normed = F.normalize(h, dim=-1)
71
+ corr = torch.bmm(h_normed, h_normed.transpose(1, 2))
72
+
73
+ eta = torch.sigmoid(self.plasticity_rate) * 0.05
74
+ lam = torch.sigmoid(self.decay_rate) * 0.01
75
+
76
+ A_next = torch.clamp(A_prev + eta * corr - lam * A_prev, 0.0, 1.0)
77
+
78
+ # Self-loop preservation
79
+ idx = torch.arange(self.n_nodes, device=x_in.device)
80
+ A_next[:, idx, idx] = 1.0
81
+
82
+ h_next = torch.tanh(h)
83
+
84
+ return h_next, A_next
85
+
86
+ class SKYNET_CORE_V80_HYPERGRAPH(nn.Module):
87
+ """
88
+ SKYNET V80: THE HYPERGRAPH
89
+ Neural Cortex coupled with the Liquid Brain Organ.
90
+ """
91
+ def __init__(self, n_input=658, n_actions=20, d_model=128, n_nodes=32, d_feature=8, device='cuda'):
92
+ super().__init__()
93
+ self.device = device
94
+ self.d_model = d_model
95
+ self.n_nodes = n_nodes
96
+ self.d_feature = d_feature
97
+
98
+ self.input_proj = nn.Linear(n_input, d_model)
99
+ self.input_norm = nn.LayerNorm(d_model)
100
+
101
+ self.cortex = nn.GRU(d_model, d_model, batch_first=True)
102
+
103
+ self.phys_proj = nn.Linear(d_model, n_nodes * d_feature)
104
+ self.organ = LiquidBrainOrgan(n_nodes=n_nodes, d_feature=d_feature)
105
+
106
+ # Readout
107
+ self.readout = nn.Linear(d_model + (n_nodes * d_feature), n_actions)
108
+
109
+ self.cortex_state = None
110
+ self.organ_state = None
111
+ self.topology_state = None
112
+
113
+ def reset(self):
114
+ self.cortex_state = None
115
+ self.organ_state = None
116
+ self.topology_state = None
117
+
118
+ def detach_states(self):
119
+ if self.cortex_state is not None: self.cortex_state = self.cortex_state.detach()
120
+ if self.organ_state is not None: self.organ_state = self.organ_state.detach()
121
+ if self.topology_state is not None: self.topology_state = self.topology_state.detach()
122
+
123
+ def forward_sequence(self, x_seq, training=True):
124
+ """
125
+ Processes a full sequence and returns the final logits.
126
+ Compatible with sequential benchmarks.
127
+ """
128
+ self.reset()
129
+ batch, steps, _ = x_seq.shape
130
+ for t in range(steps):
131
+ out = self.forward(x_seq[:, t], training=training)
132
+ return out['logits']
133
+
134
+ def forward(self, x, training=True):
135
+ batch = x.shape[0]
136
+ if x.dim() == 3:
137
+ x = x.view(batch, -1)
138
+
139
+ h_in = self.input_norm(self.input_proj(x))
140
+
141
+ if self.cortex_state is None or self.cortex_state.shape[1] != batch:
142
+ self.cortex_state = torch.zeros(1, batch, self.d_model, device=x.device)
143
+
144
+ h_ctx, self.cortex_state = self.cortex(h_in.unsqueeze(1), self.cortex_state)
145
+ h_ctx = h_ctx.squeeze(1)
146
+
147
+ # Drive the physical organ
148
+ phys_drive = self.phys_proj(h_ctx).view(batch, self.n_nodes, self.d_feature)
149
+
150
+ self.organ_state, self.topology_state = self.organ(
151
+ h_ctx, phys_drive, A_prev=self.topology_state, h_prev=self.organ_state
152
+ )
153
+
154
+ h_phys_flat = self.organ_state.view(batch, -1)
155
+ h_fused = torch.cat([h_ctx, h_phys_flat], dim=-1)
156
+
157
+ logits = self.readout(h_fused)
158
+ probs = F.softmax(logits, dim=-1)
159
+ entropy = -(probs * torch.log(probs + 1e-6)).sum(-1, keepdim=True)
160
+
161
+ audit = {
162
+ 'topology_density': self.topology_state.mean().item(),
163
+ 'phys_flux': self.organ_state.abs().mean().item(),
164
+ 'entropy': entropy.mean().item()
165
+ }
166
+
167
+ return {
168
+ 'logits': logits,
169
+ 'probs': probs,
170
+ 'entropy': entropy,
171
+ 'audit': audit
172
+ }
173
+
174
+ if __name__ == "__main__":
175
+ device = 'cuda' if torch.cuda.is_available() else 'cpu'
176
+ model = SKYNET_CORE_V80_HYPERGRAPH(device=device).to(device)
177
+ x = torch.randn(4, 658, device=device)
178
+ out = model(x)
179
+ print("V80 Hypergraph Forward Pass Successful.")
180
+ print(f"Logits Shape: {out['logits'].shape}")
181
+ print(f"Audit: {out['audit']}")
src/skynet/experiments/EX/SKYNET_CORE_V85_SCALING_HYPERGRAPH.py ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ SKYNET CORE V85: SCALING HYPERGRAPH (The Living Brain)
3
+ =====================================================
4
+
5
+ Integration of V80 (Dynamic Topology) with V85 (Neurogenesis & Pruning).
6
+ This core implements physical brain expansion and synaptic cleaning.
7
+
8
+ Features:
9
+ 1. Dynamic Topology: Adjacency A_t evolves via Hebbian Plasticity.
10
+ 2. Neurogenesis: Adds nodes when hidden state saturation is detected.
11
+ 3. Synaptic Pruning: Deletes edges with weight < threshold.
12
+ 4. Higgs Decision: Mexican Hat force for stable commitments.
13
+ """
14
+
15
+ import torch
16
+ import torch.nn as nn
17
+ import torch.nn.functional as F
18
+
19
+ class ScalingHypergraphOrgan(nn.Module):
20
+ def __init__(self, n_initial_nodes=16, d_feature=8, max_nodes=128):
21
+ super().__init__()
22
+ self.n_nodes = n_initial_nodes
23
+ self.d_feature = d_feature
24
+ self.max_nodes = max_nodes
25
+
26
+ # Hyperparameters
27
+ self.mu = nn.Parameter(torch.tensor(0.45))
28
+ self.sigma = nn.Parameter(torch.tensor(0.35))
29
+ self.plasticity_rate = nn.Parameter(torch.tensor(0.05))
30
+ self.decay_rate = nn.Parameter(torch.tensor(0.005))
31
+
32
+ self.neuro_threshold = 0.75
33
+ self.pruning_threshold = 0.02
34
+
35
+ def forward(self, x_in, h_prev, A_prev, training=True):
36
+ batch = x_in.shape[0]
37
+
38
+ # 1. Biphasic Physics + Mexican Hat
39
+ h_core = torch.tanh(h_prev + 0.5 * x_in)
40
+ force = (h_core - torch.pow(h_core, 3)).detach()
41
+ h = h_core + 0.3 * force
42
+
43
+ # 2. Liquid Diffusion (Communication)
44
+ A_norm = A_prev / (A_prev.sum(dim=-1, keepdim=True) + 1e-6)
45
+ h_diffused = torch.bmm(A_norm, h)
46
+ h = h + 0.2 * (h_diffused - h)
47
+
48
+ # 3. Plasticity
49
+ h_normed = F.normalize(h, dim=-1)
50
+ corr = torch.bmm(h_normed, h_normed.transpose(1, 2))
51
+
52
+ eta = torch.sigmoid(self.plasticity_rate) * 0.05
53
+ lam = torch.sigmoid(self.decay_rate) * 0.01
54
+ A_next = torch.clamp(A_prev + eta * corr - lam * A_prev, 0.0, 1.0)
55
+
56
+ # Synaptic Pruning
57
+ if training:
58
+ A_next[A_next < self.pruning_threshold] = 0.0
59
+
60
+ # Self-loops
61
+ idx = torch.arange(self.n_nodes, device=x_in.device)
62
+ A_next[:, idx, idx] = 1.0
63
+
64
+ h_next = torch.tanh(h)
65
+
66
+ # 4. Neurogenesis Trigger
67
+ should_grow = False
68
+ if training and h_next.abs().mean() > self.neuro_threshold and self.n_nodes + 4 <= self.max_nodes:
69
+ should_grow = True
70
+
71
+ return h_next, A_next, should_grow
72
+
73
+ class SKYNET_CORE_V85_SCALING_HYPERGRAPH(nn.Module):
74
+ def __init__(self, n_input=658, n_actions=20, d_model=128, n_initial_nodes=16, d_feature=8, max_nodes=128, device='cuda'):
75
+ super().__init__()
76
+ self.device = device
77
+ self.max_nodes = max_nodes
78
+ self.d_feature = d_feature
79
+ self.d_model = d_model
80
+
81
+ self.input_proj = nn.Linear(n_input, d_model)
82
+ self.input_norm = nn.LayerNorm(d_model)
83
+ self.cortex = nn.GRU(d_model, d_model, batch_first=True)
84
+
85
+ # Fixed pool projection (for max capacity)
86
+ self.phys_proj = nn.Linear(d_model, max_nodes * d_feature)
87
+ self.organ = ScalingHypergraphOrgan(n_initial_nodes, d_feature, max_nodes)
88
+
89
+ self.readout = nn.Linear(d_model + (max_nodes * d_feature), n_actions)
90
+
91
+ self.reset()
92
+
93
+ def reset(self):
94
+ self.cortex_state = None
95
+ self.h_phys = None
96
+ self.A_phys = None
97
+
98
+ def grow(self, batch):
99
+ old_n = self.organ.n_nodes
100
+ self.organ.n_nodes += 4
101
+ new_n = self.organ.n_nodes
102
+
103
+ # Expand states
104
+ new_h = torch.zeros(batch, new_n, self.d_feature, device=self.device)
105
+ new_h[:, :old_n, :] = self.h_phys
106
+ self.h_phys = new_h
107
+
108
+ new_A = torch.eye(new_n, device=self.device).unsqueeze(0).repeat(batch, 1, 1)
109
+ new_A[:, :old_n, :old_n] = self.A_phys
110
+ self.A_phys = new_A
111
+
112
+ def forward(self, x, training=True):
113
+ batch = x.shape[0]
114
+ if x.dim() == 3: x = x.view(batch, -1)
115
+
116
+ h_in = self.input_norm(self.input_proj(x))
117
+ if self.cortex_state is None:
118
+ self.cortex_state = torch.zeros(1, batch, self.d_model, device=x.device)
119
+
120
+ h_ctx, self.cortex_state = self.cortex(h_in.unsqueeze(1), self.cortex_state)
121
+ h_ctx = h_ctx.squeeze(1)
122
+
123
+ if self.h_phys is None:
124
+ self.h_phys = torch.zeros(batch, self.organ.n_nodes, self.d_feature, device=x.device)
125
+ self.A_phys = torch.eye(self.organ.n_nodes, device=x.device).unsqueeze(0).repeat(batch, 1, 1)
126
+
127
+ # Drive
128
+ full_drive = self.phys_proj(h_ctx).view(batch, self.max_nodes, self.d_feature)
129
+ x_drive = full_drive[:, :self.organ.n_nodes, :]
130
+
131
+ self.h_phys, self.A_phys, should_grow = self.organ(x_drive, self.h_phys, self.A_phys, training)
132
+
133
+ if should_grow:
134
+ self.grow(batch)
135
+
136
+ # Padded readout
137
+ h_full = torch.zeros(batch, self.max_nodes, self.d_feature, device=x.device)
138
+ h_full[:, :self.organ.n_nodes, :] = self.h_phys
139
+
140
+ h_fused = torch.cat([h_ctx, h_full.view(batch, -1)], dim=-1)
141
+ logits = self.readout(h_fused)
142
+
143
+ return {
144
+ 'logits': logits,
145
+ 'audit': {'nodes': self.organ.n_nodes, 'sparsity': (self.A_phys == 0).float().mean().item()}
146
+ }
147
+
148
+ if __name__ == "__main__":
149
+ device = 'cuda' if torch.cuda.is_available() else 'cpu'
150
+ model = SKYNET_CORE_V85_SCALING_HYPERGRAPH(device=device).to(device)
151
+ for _ in range(5):
152
+ x = torch.randn(2, 658, device=device)
153
+ out = model(x)
154
+ print(f"Nodes: {out['audit']['nodes']}, Sparsity: {out['audit']['sparsity']:.2f}")
src/skynet/experiments/experimentos/exp54_liquid_brain_core.py CHANGED
@@ -50,40 +50,38 @@ class LiquidBrainOrgan(nn.Module):
50
  def forward_sequence(self, x_seq: torch.Tensor) -> torch.Tensor:
51
  batch, steps, _ = x_seq.shape
52
  h = torch.zeros(batch, self.n_nodes, self.d_feature, device=x_seq.device)
53
-
54
- # Adjacency starts as sparse Identity
55
  A = torch.eye(self.n_nodes, device=x_seq.device).unsqueeze(0).repeat(batch, 1, 1)
56
 
57
  for t in range(steps):
58
- # 1. Drive - stronger injection
59
- x_in = self.input_proj(x_seq[:, t]).view(batch, self.n_nodes, self.d_feature)
 
 
 
60
 
61
- # 2. Biphasic Growth (Interaction)
62
- # Use tanh to keep growth bounded and non-linear
63
- h = torch.tanh(h + 0.5 * x_in)
64
- growth = self.g_fluid(h)
65
- h = h + 0.2 * growth
 
66
 
67
- # 3. Liquid Diffusion (Communication over Dynamic Topology)
68
- # Normalize A to prevent explosion (Row-normalization)
69
  A_norm = A / (A.sum(dim=-1, keepdim=True) + 1e-6)
70
  h_diffused = torch.bmm(A_norm, h)
71
 
72
- # Diffusion update: move towards neighbors
73
- h = h + 0.3 * (h_diffused - h)
74
 
75
- # 4. Topology Update (Fire together, wire together)
76
- # Enhanced Hebbian: use cosine similarity for topology growth
77
  h_normed = F.normalize(h, dim=-1)
78
- corr = torch.bmm(h_normed, h_normed.transpose(1, 2)) # [B, N, N]
79
-
80
- eta = torch.sigmoid(self.plasticity_rate) * 0.1 # Slower, more stable plasticity
81
- lam = torch.sigmoid(self.decay_rate) * 0.05
82
 
83
- # Update Adjacency
 
84
  A = torch.clamp(A + eta * corr - lam * A, 0.0, 1.0)
85
 
86
- # Maintain identity for self-loop stability
87
  idx = torch.arange(self.n_nodes, device=x_seq.device)
88
  A[:, idx, idx] = 1.0
89
 
 
50
  def forward_sequence(self, x_seq: torch.Tensor) -> torch.Tensor:
51
  batch, steps, _ = x_seq.shape
52
  h = torch.zeros(batch, self.n_nodes, self.d_feature, device=x_seq.device)
 
 
53
  A = torch.eye(self.n_nodes, device=x_seq.device).unsqueeze(0).repeat(batch, 1, 1)
54
 
55
  for t in range(steps):
56
+ x_t = x_seq[:, t]
57
+ # --- PROTECTIVE LAYER: Input Filtering ---
58
+ # If input is too high (flash/noise), dampen its effect
59
+ x_in = self.input_proj(x_t).view(batch, self.n_nodes, self.d_feature)
60
+ x_in = x_in / (1.0 + torch.norm(x_in, dim=-1, keepdim=True) * 0.1)
61
 
62
+ # --- TOPOLOGICAL WELLS: The decision-locking mechanism ---
63
+ # Increase the Mexican Hat force to 'crystallize' memory nodes
64
+ h_core = torch.tanh(h + 0.5 * x_in)
65
+ # F_well = h - h^3 (The Higgs force from Exp45)
66
+ force = (h_core - torch.pow(h_core, 3)).detach()
67
+ h = h_core + 0.35 * force
68
 
69
+ # --- DYNAMIC ADJACENCY (V80 Core) ---
 
70
  A_norm = A / (A.sum(dim=-1, keepdim=True) + 1e-6)
71
  h_diffused = torch.bmm(A_norm, h)
72
 
73
+ # Diffusion with higher damping (more inertia)
74
+ h = h + 0.2 * (h_diffused - h)
75
 
76
+ # --- PLASTICITY with CORRELATION GATING ---
77
+ # Only update topology if activity is within a 'meaningful' range
78
  h_normed = F.normalize(h, dim=-1)
79
+ corr = torch.bmm(h_normed, h_normed.transpose(1, 2))
 
 
 
80
 
81
+ eta = torch.sigmoid(self.plasticity_rate) * 0.05
82
+ lam = torch.sigmoid(self.decay_rate) * 0.01
83
  A = torch.clamp(A + eta * corr - lam * A, 0.0, 1.0)
84
 
 
85
  idx = torch.arange(self.n_nodes, device=x_seq.device)
86
  A[:, idx, idx] = 1.0
87
 
src/skynet/experiments/experimentos/exp55_liquid_brain_gauntlet.py CHANGED
@@ -69,10 +69,13 @@ def run_stress_battery():
69
 
70
  # 1. Train on a 'standard' difficult mix
71
  print("Training on mixed stress conditions...")
72
- x_train, y_train = generate_gauntlet_data(2500, mode="long_silence") # Base training
 
 
 
73
 
74
  model = LiquidBrainOrgan(n_nodes=16, d_feature=8).to(DEVICE)
75
- train_on_dataset(model, x_train, y_train, max_epochs=30)
76
 
77
  # 2. Evaluate each scenario
78
  for mode in scenarios:
 
69
 
70
  # 1. Train on a 'standard' difficult mix
71
  print("Training on mixed stress conditions...")
72
+ x_train_1, y_train_1 = generate_gauntlet_data(1500, mode="long_silence")
73
+ x_train_2, y_train_2 = generate_gauntlet_data(1500, mode="signal_noise")
74
+ x_train = torch.cat([x_train_1, x_train_2])
75
+ y_train = torch.cat([y_train_1, y_train_2])
76
 
77
  model = LiquidBrainOrgan(n_nodes=16, d_feature=8).to(DEVICE)
78
+ train_on_dataset(model, x_train, y_train, max_epochs=40)
79
 
80
  # 2. Evaluate each scenario
81
  for mode in scenarios:
src/skynet/experiments/experimentos/exp56_lifelong_learning_v80.py ADDED
@@ -0,0 +1,102 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Exp56: Continuous Life-Long Learning (V80 Stability Audit)
3
+ ==========================================================
4
+
5
+ Goal: Verify if the V80 Hypergraph can learn Task A, then learn Task B,
6
+ and still remember Task A without catastrophic forgetting, mimicking
7
+ a 'Life-Long' scaling intelligence.
8
+
9
+ Mechanism:
10
+ 1. Sequential Training: Task A -> Task B -> Task C.
11
+ 2. Topological Memory: We check if the Adjacency Matrix A_t 'partitions'
12
+ itself to store Task A while using new nodes for Task B.
13
+ """
14
+
15
+ import sys
16
+ import os
17
+ sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
18
+ sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'EX'))
19
+
20
+ import torch
21
+ import torch.nn as nn
22
+ import torch.nn.functional as F
23
+ import json
24
+ import random
25
+ from pathlib import Path
26
+ from ex_hypothesis_components import DEVICE, INPUT_DIM, HIDDEN_DIM
27
+ from exp38_ex_hypothesis_benchmark import train_on_dataset, evaluate
28
+ from SKYNET_CORE_V80_HYPERGRAPH import SKYNET_CORE_V80_HYPERGRAPH
29
+
30
+ REPORT_PATH = Path("exp56_lifelong_learning_results.json")
31
+
32
+ def generate_task_data(task_id, n_samples=1000):
33
+ """
34
+ Task 0: Identify pattern in first 3 dims.
35
+ Task 1: Identify pattern in next 3 dims.
36
+ """
37
+ seq_len = 20
38
+ x = torch.randn(n_samples, seq_len, INPUT_DIM) * 0.1
39
+ y = torch.zeros(n_samples, dtype=torch.long)
40
+
41
+ start_dim = task_id * 3
42
+ for i in range(n_samples):
43
+ label = random.randint(0, 1)
44
+ y[i] = label
45
+ # Pattern: high activation in the assigned task dimensions
46
+ x[i, :, start_dim : start_dim + 3] += (2.0 if label == 1 else -2.0)
47
+
48
+ return x, y
49
+
50
+ def run_lifelong_audit():
51
+ random.seed(777)
52
+ torch.manual_seed(777)
53
+
54
+ # We use a larger V80 to allow for 'partitioning'
55
+ model = SKYNET_CORE_V80_HYPERGRAPH(n_input=INPUT_DIM, n_nodes=64, d_feature=8, n_actions=2).to(DEVICE)
56
+
57
+ print("--- Phase 1: Learning Task A ---")
58
+ x_a, y_a = generate_task_data(0, 1500)
59
+ train_on_dataset(model, x_a, y_a, max_epochs=15)
60
+ acc_a_after_a = evaluate(model, x_a, y_a)
61
+ print(f" Acc A after A: {acc_a_after_a:.4f}")
62
+
63
+ # Capture topology state after Task A
64
+ topo_a = model.topology_state.clone() if model.topology_state is not None else None
65
+
66
+ print("\n--- Phase 2: Learning Task B (Potential Forgetting) ---")
67
+ x_b, y_b = generate_task_data(1, 1500)
68
+ # Train ONLY on Task B
69
+ train_on_dataset(model, x_b, y_b, max_epochs=15)
70
+ acc_b_after_b = evaluate(model, x_b, y_b)
71
+ print(f" Acc B after B: {acc_b_after_b:.4f}")
72
+
73
+ print("\n--- Phase 3: Final Recall ---")
74
+ acc_a_after_b = evaluate(model, x_a, y_a)
75
+ print(f" Acc A after B: {acc_a_after_b:.4f}")
76
+
77
+ # Analysis: Topological Drift
78
+ topo_b = model.topology_state.clone()
79
+ # Check how much the connections changed
80
+ if topo_a is not None:
81
+ drift = torch.abs(topo_b - topo_a).mean().item()
82
+ else:
83
+ drift = 0.0
84
+
85
+ forgetting = acc_a_after_a - acc_a_after_b
86
+
87
+ report = {
88
+ "experiment": "exp56_lifelong_learning_v80",
89
+ "task_a_initial": acc_a_after_a,
90
+ "task_b_initial": acc_b_after_b,
91
+ "task_a_final_recall": acc_a_after_b,
92
+ "forgetting_magnitude": forgetting,
93
+ "topological_drift": drift,
94
+ "status": "SUCCESS" if forgetting < 0.1 else "FORGETTING_DETECTED"
95
+ }
96
+
97
+ REPORT_PATH.write_text(json.dumps(report, indent=2))
98
+ print(json.dumps(report, indent=2))
99
+ return report
100
+
101
+ if __name__ == "__main__":
102
+ run_lifelong_audit()
src/skynet/experiments/experimentos/exp57_dynamic_neurogenesis.py ADDED
@@ -0,0 +1,174 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Exp57: Dynamic Neurogenesis & Synaptic Pruning (V85 Hypergraph)
3
+ ==============================================================
4
+
5
+ Goal: Implement physical brain scaling. The model should:
6
+ 1. Detect "Capacity Saturation" (High entropy + High flux).
7
+ 2. Trigger Neurogenesis: Add new nodes to the Hypergraph.
8
+ 3. Perform Synaptic Pruning: Delete weak edges to maintain O(N) efficiency.
9
+
10
+ This moves OpenSkynet from a fixed-size 'bicho' to a scaling organism.
11
+ """
12
+
13
+ import torch
14
+ import torch.nn as nn
15
+ import torch.nn.functional as F
16
+ import json
17
+ import random
18
+ from pathlib import Path
19
+
20
+ # Paths for imports
21
+ import sys
22
+ import os
23
+ sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'EX'))
24
+
25
+ from ex_hypothesis_components import DEVICE, INPUT_DIM
26
+ from exp38_ex_hypothesis_benchmark import train_on_dataset, evaluate
27
+
28
+ REPORT_PATH = Path("exp57_neurogenesis_results.json")
29
+
30
+ class NeurogenesisHypergraph(nn.Module):
31
+ def __init__(self, n_initial_nodes=8, d_feature=8, n_actions=2):
32
+ super().__init__()
33
+ self.n_nodes = n_initial_nodes
34
+ self.d_feature = d_feature
35
+ self.n_actions = n_actions
36
+
37
+ # Projections must be flexible or mapped per node
38
+ # For simplicity in this core, we project to a large fixed pool
39
+ # but only 'activate' nodes as we grow.
40
+ self.max_nodes = 128
41
+ self.input_proj = nn.Linear(INPUT_DIM, self.max_nodes * d_feature)
42
+ self.readout = nn.Linear(self.max_nodes * d_feature, n_actions)
43
+
44
+ # Internal states
45
+ self.h = None
46
+ self.A = None
47
+
48
+ # Thresholds
49
+ self.neurogenesis_threshold = 0.8 # If avg activity is high
50
+ self.pruning_threshold = 0.05 # If edge is too weak
51
+
52
+ def reset(self):
53
+ self.h = None
54
+ self.A = None
55
+
56
+ def grow_brain(self):
57
+ """Adds 4 nodes to the active set."""
58
+ if self.n_nodes + 4 <= self.max_nodes:
59
+ self.n_nodes += 4
60
+ print(f" [NEUROGENESIS] Brain expanded to {self.n_nodes} nodes.")
61
+ # We don't need to re-init A or h, the forward pass handles the new size
62
+ return True
63
+ return False
64
+
65
+ def forward_sequence(self, x_seq, training=True):
66
+ self.reset()
67
+ batch, steps, _ = x_seq.shape
68
+ for t in range(steps):
69
+ logits = self.forward(x_seq[:, t], training=training)
70
+ return logits
71
+
72
+ def forward(self, x, training=True):
73
+ batch = x.shape[0]
74
+
75
+ # 1. State initialization
76
+ if self.h is None:
77
+ self.h = torch.zeros(batch, self.n_nodes, self.d_feature, device=x.device)
78
+ self.A = torch.eye(self.n_nodes, device=x.device).unsqueeze(0).repeat(batch, 1, 1)
79
+
80
+ # 2. Input drive (only for active nodes)
81
+ x_full = self.input_proj(x).view(batch, self.max_nodes, self.d_feature)
82
+ x_in = x_full[:, :self.n_nodes, :]
83
+
84
+ # 3. Physics & Plasticity (Hebbian)
85
+ h_core = torch.tanh(self.h + 0.5 * x_in)
86
+
87
+ # Liquid Diffusion over current Adjacency
88
+ # A_norm = A / sum(A)
89
+ A_norm = self.A / (self.A.sum(dim=-1, keepdim=True) + 1e-6)
90
+ h_diffused = torch.bmm(A_norm, h_core)
91
+
92
+ self.h = h_core + 0.2 * (h_diffused - h_core)
93
+
94
+ # Adjacency Update
95
+ h_normed = F.normalize(self.h, dim=-1)
96
+ corr = torch.bmm(h_normed, h_normed.transpose(1, 2))
97
+
98
+ # Plasticity
99
+ self.A = torch.clamp(self.A + 0.05 * corr - 0.01 * self.A, 0.0, 1.0)
100
+
101
+ # --- SYNAPTIC PRUNING ---
102
+ if training and random.random() < 0.05:
103
+ self.A[self.A < self.pruning_threshold] = 0.0
104
+
105
+ # --- NEUROGENESIS TRIGGER ---
106
+ # If the active nodes are saturated (high avg activity), we need more capacity
107
+ if training and self.h.abs().mean() > self.neurogenesis_threshold:
108
+ if self.grow_brain():
109
+ # Re-pad h and A for the new size
110
+ new_h = torch.zeros(batch, self.n_nodes, self.d_feature, device=x.device)
111
+ new_h[:, :self.h.shape[1], :] = self.h
112
+ self.h = new_h
113
+
114
+ new_A = torch.eye(self.n_nodes, device=x.device).unsqueeze(0).repeat(batch, 1, 1)
115
+ new_A[:, :self.A.shape[1], :self.A.shape[2]] = self.A
116
+ self.A = new_A
117
+
118
+ # 4. Readout (from all possible nodes, but only n_nodes are non-zero)
119
+ h_flat = torch.zeros(batch, self.max_nodes * self.d_feature, device=x.device)
120
+ current_flat = self.h.reshape(batch, -1)
121
+ h_flat[:, :current_flat.shape[1]] = current_flat
122
+
123
+ logits = self.readout(h_flat)
124
+ return logits
125
+
126
+ def generate_scaling_task(n_samples=1000):
127
+ """
128
+ A task that gets harder over time, requiring more 'brain' capacity.
129
+ """
130
+ seq_len = 30
131
+ x = torch.randn(n_samples, seq_len, INPUT_DIM) * 0.1
132
+ y = torch.zeros(n_samples, dtype=torch.long)
133
+ for i in range(n_samples):
134
+ label = random.randint(0, 1)
135
+ y[i] = label
136
+ # Harder pattern: requires keeping track of multiple dimensions
137
+ x[i, :, 0:5] += (3.0 if label == 1 else -3.0)
138
+ return x, y
139
+
140
+ def run_neurogenesis_experiment():
141
+ random.seed(42)
142
+ torch.manual_seed(42)
143
+
144
+ print("Starting Neurogenesis Stress Test...")
145
+ x_train, y_train = generate_scaling_task(2000)
146
+ x_test, y_test = generate_scaling_task(500)
147
+
148
+ # Start with a very small brain (8 nodes)
149
+ model = NeurogenesisHypergraph(n_initial_nodes=8).to(DEVICE)
150
+
151
+ print("Training with active neurogenesis...")
152
+ train_on_dataset(model, x_train, y_train, max_epochs=20)
153
+
154
+ final_nodes = model.n_nodes
155
+ acc = evaluate(model, x_test, y_test)
156
+
157
+ # Check sparsity (Pruning effect)
158
+ sparsity = (model.A == 0).float().mean().item() if model.A is not None else 0
159
+
160
+ report = {
161
+ "experiment": "exp57_dynamic_neurogenesis",
162
+ "initial_nodes": 8,
163
+ "final_nodes": final_nodes,
164
+ "test_accuracy": acc,
165
+ "synaptic_sparsity": sparsity,
166
+ "conclusion": "SUCCESS" if final_nodes > 8 and acc > 0.9 else "FAILED"
167
+ }
168
+
169
+ print(json.dumps(report, indent=2))
170
+ REPORT_PATH.write_text(json.dumps(report, indent=2))
171
+ return report
172
+
173
+ if __name__ == "__main__":
174
+ run_neurogenesis_experiment()
src/skynet/experiments/experimentos/exp58_semantic_topology.py ADDED
@@ -0,0 +1,124 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Exp58: Semantic Topology - Concept Association via Hypergraph
3
+ ===========================================================
4
+
5
+ Goal: Test if the V85 Hypergraph can learn to associate words/concepts
6
+ by building physical bridges between their embeddings in the topology.
7
+
8
+ Mechanism:
9
+ 1. Embeddings: Small vocabulary (Fruit, Colors, Animals).
10
+ 2. Input: Pairs of related concepts (e.g. 'Apple' + 'Red').
11
+ 3. Topology Check: Verify if A_t creates a stronger connection
12
+ between nodes representing related concepts vs unrelated ones.
13
+ """
14
+
15
+ import torch
16
+ import torch.nn as nn
17
+ import torch.nn.functional as F
18
+ import json
19
+ import random
20
+ from pathlib import Path
21
+
22
+ # Paths for imports
23
+ import sys
24
+ import os
25
+ sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'EX'))
26
+
27
+ from SKYNET_CORE_V85_SCALING_HYPERGRAPH import SKYNET_CORE_V85_SCALING_HYPERGRAPH
28
+ from exp38_ex_hypothesis_benchmark import train_on_dataset, evaluate
29
+
30
+ REPORT_PATH = Path("exp58_semantic_topology_results.json")
31
+ DEVICE = 'cuda' if torch.cuda.is_available() else 'cpu'
32
+
33
+ # 1. Vocabulary & Simple Embeddings
34
+ VOCAB = {
35
+ "apple": 0, "banana": 1, "cherry": 2, # Fruits
36
+ "red": 3, "yellow": 4, # Colors
37
+ "dog": 5, "cat": 6, "bird": 7, # Animals
38
+ "bark": 8, "meow": 9 # Sounds
39
+ }
40
+ EMBED_DIM = 16
41
+
42
+ class SemanticTask:
43
+ def __init__(self):
44
+ # Relationship pairs (concept -> association)
45
+ self.pairs = [
46
+ ("apple", "red"), ("cherry", "red"),
47
+ ("banana", "yellow"),
48
+ ("dog", "bark"), ("cat", "meow")
49
+ ]
50
+ self.vocab_size = len(VOCAB)
51
+ self.embedding = nn.Embedding(self.vocab_size, EMBED_DIM)
52
+
53
+ def generate_data(self, n_samples=1000):
54
+ seq_len = 5
55
+ # We pad to match SKYNET_CORE_V85 n_input (658)
56
+ x = torch.zeros(n_samples, seq_len, 658)
57
+ y = torch.zeros(n_samples, dtype=torch.long)
58
+
59
+ for i in range(n_samples):
60
+ # Pick a positive pair (True association) or negative
61
+ is_positive = random.random() > 0.5
62
+ if is_positive:
63
+ w1, w2 = random.choice(self.pairs)
64
+ y[i] = 1
65
+ else:
66
+ w1 = random.choice(list(VOCAB.keys()))
67
+ w2 = random.choice(list(VOCAB.keys()))
68
+ # Ensure it's not a known pair
69
+ while (w1, w2) in self.pairs:
70
+ w2 = random.choice(list(VOCAB.keys()))
71
+ y[i] = 0
72
+
73
+ # Simple one-hot-like injection into the 658 input space
74
+ x[i, 0, VOCAB[w1]] = 5.0
75
+ x[i, 2, VOCAB[w2]] = 5.0 # Delay of 1 step
76
+
77
+ return x, y
78
+
79
+ def run_semantic_experiment():
80
+ random.seed(42)
81
+ torch.manual_seed(42)
82
+
83
+ task = SemanticTask()
84
+ x_train, y_train = task.generate_data(2000)
85
+ x_test, y_test = task.generate_data(500)
86
+
87
+ # Initialize V85
88
+ model = SKYNET_CORE_V85_SCALING_HYPERGRAPH(
89
+ n_input=658, n_actions=2, n_initial_nodes=32, device=DEVICE
90
+ ).to(DEVICE)
91
+
92
+ print("Training V85 on Semantic Associations...")
93
+ # Wrap model to provide forward_sequence if missing
94
+ if not hasattr(model, 'forward_sequence'):
95
+ def forward_sequence(x_seq, training=True):
96
+ model.reset()
97
+ for t in range(x_seq.shape[1]):
98
+ logits = model.forward(x_seq[:, t], training=training)['logits']
99
+ return logits
100
+ model.forward_sequence = forward_sequence
101
+
102
+ train_on_dataset(model, x_train, y_train, max_epochs=20)
103
+
104
+ acc = evaluate(model, x_test, y_test)
105
+ print(f"Final Semantic Accuracy: {acc:.4f}")
106
+
107
+ # Audit Topology
108
+ # We want to see if the density of A_t is higher for positive pairs
109
+ avg_density = model.A_phys.mean().item()
110
+
111
+ report = {
112
+ "experiment": "exp58_semantic_topology",
113
+ "test_accuracy": acc,
114
+ "final_nodes": model.organ.n_nodes,
115
+ "topology_avg_density": avg_density,
116
+ "conclusion": "SUCCESS" if acc > 0.85 else "FAILED"
117
+ }
118
+
119
+ print(json.dumps(report, indent=2))
120
+ REPORT_PATH.write_text(json.dumps(report, indent=2))
121
+ return report
122
+
123
+ if __name__ == "__main__":
124
+ run_semantic_experiment()
test-memory-events.ts ADDED
@@ -0,0 +1,38 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { deriveOpenSkynetLivingMemoryEvents } from "./src/omega/living-memory-events.js";
2
+
3
+ const sessionKey = "agent:main:main";
4
+ const now = Date.now();
5
+
6
+ const priorState: any = {
7
+ updatedAt: now - 1000,
8
+ identity: { activeGoalTask: "Old Task" },
9
+ internalProjectState: {
10
+ focusKey: "old-focus",
11
+ mode: "idle",
12
+ commitment: null,
13
+ },
14
+ };
15
+
16
+ const nextState: any = {
17
+ updatedAt: now,
18
+ identity: { activeGoalTask: "New Task" },
19
+ internalProjectState: {
20
+ focusKey: "new-focus",
21
+ mode: "active",
22
+ commitment: {
23
+ kind: "artifact",
24
+ artifactKind: "code",
25
+ confidence: 0.9,
26
+ executableTask: "Fix Omega",
27
+ },
28
+ },
29
+ };
30
+
31
+ console.log("--- PROBANDO DERIVACIÓN DE EVENTOS DE MEMORIA ---");
32
+ const events = deriveOpenSkynetLivingMemoryEvents({
33
+ sessionKey,
34
+ prior: priorState,
35
+ next: nextState,
36
+ });
37
+
38
+ console.log(JSON.stringify(events, null, 2));
test-nle-complex.ts ADDED
@@ -0,0 +1,39 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { NeuralLogicEngine } from "./src/omega/neural-logic-engine.js";
2
+
3
+ async function runComplexTest() {
4
+ const engine = new NeuralLogicEngine();
5
+
6
+ // Escenario 1: Estado base - Todo normal
7
+ console.log("--- ESCENARIO 1: Estado Base ---");
8
+ let state = [0.4, 0.4, 0.4]; // Neutro
9
+ let context = { frustration: 0.1, recentFailures: 0, successRate: 0.9 };
10
+ let inference = engine.infer(state, context);
11
+ console.log("Reglas activas:", inference.activeRules);
12
+ console.log("Confianza:", inference.inferenceConfidence.toFixed(3));
13
+ console.log(
14
+ "Estado resultante:",
15
+ inference.stateAfter.map((v) => v.toFixed(3)),
16
+ );
17
+
18
+ // Escenario 2: Simulación de Frustración Crítica (Bucle de error)
19
+ console.log("\n--- ESCENARIO 2: Frustración Crítica ---");
20
+ state = [0.8, 0.8, 0.9]; // Estado de "estrés"
21
+ context = { frustration: 0.95, recentFailures: 5, successRate: 0.1 };
22
+ inference = engine.infer(state, context);
23
+ console.log("Reglas activas:", inference.activeRules);
24
+ console.log(
25
+ "Delta aplicado:",
26
+ inference.logicalDelta.map((v) => v.toFixed(3)),
27
+ );
28
+
29
+ // Aprendizaje en tiempo real: Penalizar si falló
30
+ console.log("\n--- APRENDIZAJE: Penalizando regla 0 ---");
31
+ engine.penalizeRule(0, 0.5);
32
+
33
+ // Re-inferir con el mismo estado
34
+ inference = engine.infer(state, context);
35
+ console.log("Reglas activas post-penalización:", inference.activeRules);
36
+ console.log("Nueva Confianza:", inference.inferenceConfidence.toFixed(3));
37
+ }
38
+
39
+ runComplexTest().catch(console.error);