agent-working-memory 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/README.md +19 -9
  2. package/dist/adapters/claude-code.d.ts +4 -0
  3. package/dist/adapters/claude-code.d.ts.map +1 -0
  4. package/dist/adapters/claude-code.js +218 -0
  5. package/dist/adapters/claude-code.js.map +1 -0
  6. package/dist/adapters/codex.d.ts +4 -0
  7. package/dist/adapters/codex.d.ts.map +1 -0
  8. package/dist/adapters/codex.js +226 -0
  9. package/dist/adapters/codex.js.map +1 -0
  10. package/dist/adapters/common.d.ts +34 -0
  11. package/dist/adapters/common.d.ts.map +1 -0
  12. package/dist/adapters/common.js +145 -0
  13. package/dist/adapters/common.js.map +1 -0
  14. package/dist/adapters/cursor.d.ts +4 -0
  15. package/dist/adapters/cursor.d.ts.map +1 -0
  16. package/dist/adapters/cursor.js +138 -0
  17. package/dist/adapters/cursor.js.map +1 -0
  18. package/dist/adapters/http.d.ts +4 -0
  19. package/dist/adapters/http.d.ts.map +1 -0
  20. package/dist/adapters/http.js +88 -0
  21. package/dist/adapters/http.js.map +1 -0
  22. package/dist/adapters/index.d.ts +7 -0
  23. package/dist/adapters/index.d.ts.map +1 -0
  24. package/dist/adapters/index.js +21 -0
  25. package/dist/adapters/index.js.map +1 -0
  26. package/dist/adapters/types.d.ts +65 -0
  27. package/dist/adapters/types.d.ts.map +1 -0
  28. package/dist/adapters/types.js +4 -0
  29. package/dist/adapters/types.js.map +1 -0
  30. package/dist/api/routes.d.ts.map +1 -1
  31. package/dist/api/routes.js +1 -0
  32. package/dist/api/routes.js.map +1 -1
  33. package/dist/cli.js +104 -230
  34. package/dist/cli.js.map +1 -1
  35. package/dist/coordination/events.d.ts +59 -0
  36. package/dist/coordination/events.d.ts.map +1 -0
  37. package/dist/coordination/events.js +28 -0
  38. package/dist/coordination/events.js.map +1 -0
  39. package/dist/coordination/index.d.ts +10 -1
  40. package/dist/coordination/index.d.ts.map +1 -1
  41. package/dist/coordination/index.js +87 -3
  42. package/dist/coordination/index.js.map +1 -1
  43. package/dist/coordination/peer-decisions.d.ts +40 -0
  44. package/dist/coordination/peer-decisions.d.ts.map +1 -0
  45. package/dist/coordination/peer-decisions.js +82 -0
  46. package/dist/coordination/peer-decisions.js.map +1 -0
  47. package/dist/coordination/plugin-loader.d.ts +18 -0
  48. package/dist/coordination/plugin-loader.d.ts.map +1 -0
  49. package/dist/coordination/plugin-loader.js +55 -0
  50. package/dist/coordination/plugin-loader.js.map +1 -0
  51. package/dist/coordination/plugin.d.ts +40 -0
  52. package/dist/coordination/plugin.d.ts.map +1 -0
  53. package/dist/coordination/plugin.js +22 -0
  54. package/dist/coordination/plugin.js.map +1 -0
  55. package/dist/coordination/routes.d.ts +2 -1
  56. package/dist/coordination/routes.d.ts.map +1 -1
  57. package/dist/coordination/routes.js +899 -76
  58. package/dist/coordination/routes.js.map +1 -1
  59. package/dist/coordination/schema.d.ts.map +1 -1
  60. package/dist/coordination/schema.js +72 -14
  61. package/dist/coordination/schema.js.map +1 -1
  62. package/dist/coordination/schemas.d.ts +84 -3
  63. package/dist/coordination/schemas.d.ts.map +1 -1
  64. package/dist/coordination/schemas.js +71 -1
  65. package/dist/coordination/schemas.js.map +1 -1
  66. package/dist/coordination/stale.d.ts.map +1 -1
  67. package/dist/coordination/stale.js +2 -1
  68. package/dist/coordination/stale.js.map +1 -1
  69. package/dist/coordination/types.d.ts +252 -0
  70. package/dist/coordination/types.d.ts.map +1 -0
  71. package/dist/coordination/types.js +8 -0
  72. package/dist/coordination/types.js.map +1 -0
  73. package/dist/coordination/write-mutex.d.ts +26 -0
  74. package/dist/coordination/write-mutex.d.ts.map +1 -0
  75. package/dist/coordination/write-mutex.js +63 -0
  76. package/dist/coordination/write-mutex.js.map +1 -0
  77. package/dist/core/embeddings.d.ts +2 -0
  78. package/dist/core/embeddings.d.ts.map +1 -1
  79. package/dist/core/embeddings.js +4 -0
  80. package/dist/core/embeddings.js.map +1 -1
  81. package/dist/core/hebbian.d.ts +25 -1
  82. package/dist/core/hebbian.d.ts.map +1 -1
  83. package/dist/core/hebbian.js +71 -3
  84. package/dist/core/hebbian.js.map +1 -1
  85. package/dist/engine/activation.d.ts +22 -4
  86. package/dist/engine/activation.d.ts.map +1 -1
  87. package/dist/engine/activation.js +152 -76
  88. package/dist/engine/activation.js.map +1 -1
  89. package/dist/engine/consolidation.d.ts.map +1 -1
  90. package/dist/engine/consolidation.js +15 -6
  91. package/dist/engine/consolidation.js.map +1 -1
  92. package/dist/engine/retraction.d.ts +3 -1
  93. package/dist/engine/retraction.d.ts.map +1 -1
  94. package/dist/engine/retraction.js +19 -6
  95. package/dist/engine/retraction.js.map +1 -1
  96. package/dist/index.js +6 -18
  97. package/dist/index.js.map +1 -1
  98. package/dist/mcp.js +61 -4
  99. package/dist/mcp.js.map +1 -1
  100. package/dist/storage/sqlite.d.ts +23 -1
  101. package/dist/storage/sqlite.d.ts.map +1 -1
  102. package/dist/storage/sqlite.js +112 -3
  103. package/dist/storage/sqlite.js.map +1 -1
  104. package/dist/types/engram.d.ts +1 -0
  105. package/dist/types/engram.d.ts.map +1 -1
  106. package/package.json +1 -1
  107. package/src/adapters/claude-code.ts +234 -0
  108. package/src/adapters/codex.ts +262 -0
  109. package/src/adapters/common.ts +172 -0
  110. package/src/adapters/cursor.ts +150 -0
  111. package/src/adapters/http.ts +100 -0
  112. package/src/adapters/index.ts +31 -0
  113. package/src/adapters/types.ts +75 -0
  114. package/src/api/routes.ts +2 -0
  115. package/src/cli.ts +107 -238
  116. package/src/coordination/events.ts +90 -0
  117. package/src/coordination/index.ts +102 -3
  118. package/src/coordination/peer-decisions.ts +105 -0
  119. package/src/coordination/plugin-loader.ts +60 -0
  120. package/src/coordination/plugin.ts +44 -0
  121. package/src/coordination/routes.ts +1176 -105
  122. package/src/coordination/schema.ts +67 -14
  123. package/src/coordination/schemas.ts +85 -1
  124. package/src/coordination/stale.ts +3 -2
  125. package/src/coordination/types.ts +311 -0
  126. package/src/coordination/write-mutex.ts +69 -0
  127. package/src/core/embeddings.ts +5 -0
  128. package/src/core/hebbian.ts +84 -3
  129. package/src/engine/activation.ts +149 -73
  130. package/src/engine/consolidation.ts +15 -6
  131. package/src/engine/retraction.ts +22 -6
  132. package/src/index.ts +6 -15
  133. package/src/mcp.ts +77 -4
  134. package/src/storage/sqlite.ts +116 -3
  135. package/src/types/engram.ts +1 -0
@@ -15,7 +15,16 @@
15
15
  export declare function strengthenAssociation(currentWeight: number, signal?: number, rate?: number): number;
16
16
  /**
17
17
  * Weaken an association weight due to lack of co-activation.
18
- * Called periodically by the connection engine.
18
+ * Uses power-law decay (DASH model) instead of exponential.
19
+ * Power law has a longer tail — old but valuable associations
20
+ * don't vanish as aggressively as exponential decay.
21
+ *
22
+ * DASH: weight = initial × (1 + t/scale)^(-exponent)
23
+ * vs exponential: weight = initial × 0.5^(t/halfLife)
24
+ *
25
+ * At 7 days: power-law retains ~58% vs exponential 50%
26
+ * At 30 days: power-law retains ~32% vs exponential 6%
27
+ * At 90 days: power-law retains ~20% vs exponential 0.02%
19
28
  */
20
29
  export declare function decayAssociation(currentWeight: number, daysSinceActivation: number, halfLife?: number): number;
21
30
  /**
@@ -35,4 +44,19 @@ export declare class CoActivationBuffer {
35
44
  getCoActivatedPairs(windowMs?: number): [string, string][];
36
45
  clear(): void;
37
46
  }
47
+ export declare class ValidationGatedBuffer {
48
+ private pending;
49
+ /** Record co-activated pairs as pending (awaiting feedback validation) */
50
+ addPending(engramIds: string[], pairs: [string, string][]): void;
51
+ /**
52
+ * Resolve pending updates for an engram that received feedback.
53
+ * Returns pairs to strengthen (positive) or weaken (negative).
54
+ */
55
+ resolveFeedback(engramId: string, useful: boolean): {
56
+ pairs: [string, string][];
57
+ signal: number;
58
+ };
59
+ /** Get count of pending updates (for stats) */
60
+ get pendingCount(): number;
61
+ }
38
62
  //# sourceMappingURL=hebbian.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"hebbian.d.ts","sourceRoot":"","sources":["../../src/core/hebbian.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AAKH;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,aAAa,EAAE,MAAM,EACrB,MAAM,GAAE,MAAY,EACpB,IAAI,GAAE,MAAa,GAClB,MAAM,CAIR;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,aAAa,EAAE,MAAM,EACrB,mBAAmB,EAAE,MAAM,EAC3B,QAAQ,GAAE,MAAY,GACrB,MAAM,CAGR;AAED;;;;GAIG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAAiD;IAC/D,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,GAAE,MAAW;IAIhC,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAO5B,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI;IAMpC;;OAEG;IACH,mBAAmB,CAAC,QAAQ,GAAE,MAAa,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE;IAiBhE,KAAK,IAAI,IAAI;CAGd"}
1
+ {"version":3,"file":"hebbian.d.ts","sourceRoot":"","sources":["../../src/core/hebbian.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;GAUG;AAKH;;GAEG;AACH,wBAAgB,qBAAqB,CACnC,aAAa,EAAE,MAAM,EACrB,MAAM,GAAE,MAAY,EACpB,IAAI,GAAE,MAAa,GAClB,MAAM,CAIR;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,gBAAgB,CAC9B,aAAa,EAAE,MAAM,EACrB,mBAAmB,EAAE,MAAM,EAC3B,QAAQ,GAAE,MAAY,GACrB,MAAM,CAKR;AAED;;;;GAIG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,MAAM,CAAiD;IAC/D,OAAO,CAAC,OAAO,CAAS;gBAEZ,OAAO,GAAE,MAAW;IAIhC,IAAI,CAAC,QAAQ,EAAE,MAAM,GAAG,IAAI;IAO5B,SAAS,CAAC,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI;IAMpC;;OAEG;IACH,mBAAmB,CAAC,QAAQ,GAAE,MAAa,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE;IAiBhE,KAAK,IAAI,IAAI;CAGd;AAsBD,qBAAa,qBAAqB;IAChC,OAAO,CAAC,OAAO,CAA8B;IAE7C,0EAA0E;IAC1E,UAAU,CAAC,SAAS,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,GAAG,IAAI;IAOhE;;;OAGG;IACH,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG;QAAE,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE;IA+BjG,+CAA+C;IAC/C,IAAI,YAAY,IAAI,MAAM,CAAgC;CAC3D"}
@@ -23,11 +23,22 @@ export function strengthenAssociation(currentWeight, signal = 1.0, rate = 0.25)
23
23
  }
24
24
  /**
25
25
  * Weaken an association weight due to lack of co-activation.
26
- * Called periodically by the connection engine.
26
+ * Uses power-law decay (DASH model) instead of exponential.
27
+ * Power law has a longer tail — old but valuable associations
28
+ * don't vanish as aggressively as exponential decay.
29
+ *
30
+ * DASH: weight = initial × (1 + t/scale)^(-exponent)
31
+ * vs exponential: weight = initial × 0.5^(t/halfLife)
32
+ *
33
+ * At 7 days: power-law retains ~58% vs exponential 50%
34
+ * At 30 days: power-law retains ~32% vs exponential 6%
35
+ * At 90 days: power-law retains ~20% vs exponential 0.02%
27
36
  */
28
- export function decayAssociation(currentWeight, daysSinceActivation, halfLife = 7.0 // days
37
+ export function decayAssociation(currentWeight, daysSinceActivation, halfLife = 7.0 // scale parameter (days)
29
38
  ) {
30
- const decayFactor = Math.pow(0.5, daysSinceActivation / halfLife);
39
+ // Power-law decay: (1 + t/scale)^(-exponent)
40
+ const exponent = 0.8; // Controls steepness — 0.8 gives a good balance
41
+ const decayFactor = Math.pow(1 + daysSinceActivation / halfLife, -exponent);
31
42
  return Math.max(currentWeight * decayFactor, MIN_WEIGHT);
32
43
  }
33
44
  /**
@@ -73,4 +84,61 @@ export class CoActivationBuffer {
73
84
  this.buffer = [];
74
85
  }
75
86
  }
87
+ /**
88
+ * Validation-gated Hebbian buffer (Kairos-inspired).
89
+ *
90
+ * Instead of strengthening associations immediately on co-activation,
91
+ * pairs are held pending until feedback arrives. This prevents hub toxicity
92
+ * from noisy co-retrieval — edges only strengthen when the retrieval was
93
+ * actually useful.
94
+ *
95
+ * - Positive feedback → strengthen the pending pairs
96
+ * - Negative feedback → slightly weaken them
97
+ * - No feedback within GATE_TIMEOUT_MS → discard (neutral)
98
+ */
99
+ const GATE_TIMEOUT_MS = 60_000; // 60 seconds to receive feedback
100
+ export class ValidationGatedBuffer {
101
+ pending = [];
102
+ /** Record co-activated pairs as pending (awaiting feedback validation) */
103
+ addPending(engramIds, pairs) {
104
+ this.pending.push({ pairs, engramIds, timestamp: Date.now() });
105
+ // Evict expired entries
106
+ const cutoff = Date.now() - GATE_TIMEOUT_MS;
107
+ this.pending = this.pending.filter(p => p.timestamp > cutoff);
108
+ }
109
+ /**
110
+ * Resolve pending updates for an engram that received feedback.
111
+ * Returns pairs to strengthen (positive) or weaken (negative).
112
+ */
113
+ resolveFeedback(engramId, useful) {
114
+ const cutoff = Date.now() - GATE_TIMEOUT_MS;
115
+ const matching = [];
116
+ // Find all pending updates that include this engram and are still within the gate window
117
+ this.pending = this.pending.filter(p => {
118
+ if (p.timestamp < cutoff)
119
+ return false; // expired
120
+ if (p.engramIds.includes(engramId)) {
121
+ matching.push(...p.pairs);
122
+ return false; // consumed
123
+ }
124
+ return true; // keep
125
+ });
126
+ // Deduplicate pairs
127
+ const seen = new Set();
128
+ const unique = [];
129
+ for (const [a, b] of matching) {
130
+ const key = a < b ? `${a}:${b}` : `${b}:${a}`;
131
+ if (!seen.has(key)) {
132
+ seen.add(key);
133
+ unique.push([a, b]);
134
+ }
135
+ }
136
+ return {
137
+ pairs: unique,
138
+ signal: useful ? 1.0 : -0.3, // positive = full strengthen, negative = slight weaken
139
+ };
140
+ }
141
+ /** Get count of pending updates (for stats) */
142
+ get pendingCount() { return this.pending.length; }
143
+ }
76
144
  //# sourceMappingURL=hebbian.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"hebbian.js","sourceRoot":"","sources":["../../src/core/hebbian.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAChD,sCAAsC;AACtC;;;;;;;;;;GAUG;AAEH,MAAM,UAAU,GAAG,KAAK,CAAC;AACzB,MAAM,UAAU,GAAG,GAAG,CAAC,CAAE,2CAA2C;AAEpE;;GAEG;AACH,MAAM,UAAU,qBAAqB,CACnC,aAAqB,EACrB,SAAiB,GAAG,EACpB,OAAe,IAAI;IAEnB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,aAAa,EAAE,UAAU,CAAC,CAAC,CAAC;IAC3D,MAAM,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;IAClD,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,UAAU,CAAC,CAAC;AAChD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,gBAAgB,CAC9B,aAAqB,EACrB,mBAA2B,EAC3B,WAAmB,GAAG,CAAC,OAAO;;IAE9B,MAAM,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,EAAE,mBAAmB,GAAG,QAAQ,CAAC,CAAC;IAClE,OAAO,IAAI,CAAC,GAAG,CAAC,aAAa,GAAG,WAAW,EAAE,UAAU,CAAC,CAAC;AAC3D,CAAC;AAED;;;;GAIG;AACH,MAAM,OAAO,kBAAkB;IACrB,MAAM,GAA8C,EAAE,CAAC;IACvD,OAAO,CAAS;IAExB,YAAY,UAAkB,EAAE;QAC9B,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IACzB,CAAC;IAED,IAAI,CAAC,QAAgB;QACnB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC,CAAC;QACtD,IAAI,IAAI,CAAC,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,OAAO,EAAE,CAAC;YACtC,IAAI,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QACtB,CAAC;IACH,CAAC;IAED,SAAS,CAAC,SAAmB;QAC3B,KAAK,MAAM,EAAE,IAAI,SAAS,EAAE,CAAC;YAC3B,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAChB,CAAC;IACH,CAAC;IAED;;OAEG;IACH,mBAAmB,CAAC,WAAmB,IAAI;QACzC,MAAM,KAAK,GAAuB,EAAE,CAAC;QACrC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC5C,KAAK,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;gBAChD,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;gBACzB,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;gBACzB,IACE,CAAC,CAAC,QAAQ,KAAK,CAAC,CAAC,QAAQ;oBACzB,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,SAAS,CAAC,IAAI,QAAQ,EAC/C,CAAC;oBACD,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,QAAQ,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC;gBACvC,CAAC;YACH,CAAC;QACH,CAAC;QACD,OAAO,KAAK,CAAC;IACf,CAAC;IAED,KAAK;QACH,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC;IACnB,CAAC;CACF"}
1
+ {"version":3,"file":"hebbian.js","sourceRoot":"","sources":["../../src/core/hebbian.ts"],"names":[],"mappings":"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"}
@@ -16,23 +16,41 @@
16
16
  *
17
17
  * Logs activation events for eval metrics.
18
18
  */
19
+ import { ValidationGatedBuffer } from '../core/hebbian.js';
19
20
  import type { ActivationResult, ActivationQuery } from '../types/index.js';
20
21
  import type { EngramStore } from '../storage/sqlite.js';
21
22
  export declare class ActivationEngine {
22
23
  private store;
23
24
  private coActivationBuffer;
25
+ readonly validationGate: ValidationGatedBuffer;
24
26
  constructor(store: EngramStore);
25
27
  /**
26
28
  * Activate — retrieve the most cognitively relevant engrams for a context.
27
29
  */
28
30
  activate(query: ActivationQuery): Promise<ActivationResult[]>;
29
31
  /**
30
- * Beam search graph walk — replaces naive BFS.
31
- * Scores paths (not just nodes), uses query-dependent edge filtering,
32
- * and supports deeper exploration with focused beams.
32
+ * Multi-graph traversal (MAGMA-inspired).
33
+ *
34
+ * Instead of one beam search over all edge types, runs independent traversals
35
+ * per graph type with specialized scoring, then fuses the boosts.
36
+ *
37
+ * Four sub-graphs:
38
+ * - Semantic (connection + hebbian edges) → standard weight-based walk
39
+ * - Temporal (temporal edges) → recency-weighted (favor recent connections)
40
+ * - Causal (causal edges) → full weight walk (causal links are high-value)
41
+ * - Entity (bridge edges) → entity-tag-weighted walk
42
+ *
43
+ * Each sub-graph contributes independently to the final graph boost,
44
+ * weighted by configurable per-graph weights.
33
45
  */
46
+ private static readonly GRAPH_WEIGHTS;
34
47
  private graphWalk;
35
- private updateHebbianWeights;
48
+ /**
49
+ * Resolve validation-gated Hebbian update for a specific engram.
50
+ * Called by memory_feedback — only strengthens when retrieval was useful.
51
+ * This prevents hub toxicity from noisy co-retrieval (Kairos-inspired).
52
+ */
53
+ resolveHebbianFeedback(engramId: string, useful: boolean): number;
36
54
  private explain;
37
55
  }
38
56
  //# sourceMappingURL=activation.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"activation.d.ts","sourceRoot":"","sources":["../../src/engine/activation.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AAQH,OAAO,KAAK,EACF,gBAAgB,EAAE,eAAe,EAC1C,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAoIxD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,kBAAkB,CAAqB;gBAEnC,KAAK,EAAE,WAAW;IAK9B;;OAEG;IACG,QAAQ,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IA2dnE;;;;OAIG;IACH,OAAO,CAAC,SAAS;IAwEjB,OAAO,CAAC,oBAAoB;IAiB5B,OAAO,CAAC,OAAO;CAchB"}
1
+ {"version":3,"file":"activation.d.ts","sourceRoot":"","sources":["../../src/engine/activation.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AAIH,OAAO,EAA6C,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAItG,OAAO,KAAK,EACF,gBAAgB,EAAE,eAAe,EAC1C,MAAM,mBAAmB,CAAC;AAC3B,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AAoIxD,qBAAa,gBAAgB;IAC3B,OAAO,CAAC,KAAK,CAAc;IAC3B,OAAO,CAAC,kBAAkB,CAAqB;IAC/C,QAAQ,CAAC,cAAc,EAAE,qBAAqB,CAAC;gBAEnC,KAAK,EAAE,WAAW;IAM9B;;OAEG;IACG,QAAQ,CAAC,KAAK,EAAE,eAAe,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAifnE;;;;;;;;;;;;;;OAcG;IACH,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,aAAa,CAKnC;IAEF,OAAO,CAAC,SAAS;IA+FjB;;;;OAIG;IACH,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,MAAM;IAwBjE,OAAO,CAAC,OAAO;CAchB"}
@@ -20,7 +20,7 @@
20
20
  */
21
21
  import { randomUUID } from 'node:crypto';
22
22
  import { baseLevelActivation, softplus } from '../core/decay.js';
23
- import { strengthenAssociation, CoActivationBuffer } from '../core/hebbian.js';
23
+ import { strengthenAssociation, CoActivationBuffer, ValidationGatedBuffer } from '../core/hebbian.js';
24
24
  import { embed, cosineSimilarity } from '../core/embeddings.js';
25
25
  import { rerank } from '../core/reranker.js';
26
26
  import { expandQuery } from '../core/query-expander.js';
@@ -150,9 +150,11 @@ function jaccard(a, b) {
150
150
  export class ActivationEngine {
151
151
  store;
152
152
  coActivationBuffer;
153
+ validationGate;
153
154
  constructor(store) {
154
155
  this.store = store;
155
156
  this.coActivationBuffer = new CoActivationBuffer(50);
157
+ this.validationGate = new ValidationGatedBuffer();
156
158
  }
157
159
  /**
158
160
  * Activate — retrieve the most cognitively relevant engrams for a context.
@@ -165,14 +167,17 @@ export class ActivationEngine {
165
167
  const useExpansion = query.useExpansion ?? true;
166
168
  const abstentionThreshold = query.abstentionThreshold ?? 0;
167
169
  const adaptive = resolveAdaptiveParams(query);
170
+ // Resolve workspace scope: if workspace is set, search across all agents in that workspace
171
+ const agentIds = query.workspace
172
+ ? this.store.getWorkspaceAgentIds(query.agentId, query.workspace)
173
+ : [query.agentId];
174
+ const isWorkspaceScoped = agentIds.length > 1;
168
175
  // Phase -1: Coref expansion — if query has pronouns, append recent entity names
169
- // Helps conversational recall where "she/he/they/it" refers to a named entity.
170
176
  let queryContext = query.context;
171
177
  const pronounPattern = /\b(she|he|they|her|his|him|their|it|that|this|there)\b/i;
172
178
  if (pronounPattern.test(queryContext)) {
173
- // Pull recent entity names from the agent's most-accessed memories
174
179
  try {
175
- const recentEntities = this.store.getEngramsByAgent(query.agentId, 'active')
180
+ const recentEntities = this.store.getEngramsByAgents(agentIds, 'active')
176
181
  .sort((a, b) => b.accessCount - a.accessCount)
177
182
  .slice(0, 10)
178
183
  .flatMap(e => e.tags.filter(t => t.length >= 3 && !/^(session-|low-|D\d)/.test(t)))
@@ -187,11 +192,19 @@ export class ActivationEngine {
187
192
  // Phase 0: Query expansion — add related terms to improve BM25 recall
188
193
  let searchContext = queryContext;
189
194
  if (useExpansion) {
195
+ let timer;
190
196
  try {
191
- searchContext = await expandQuery(query.context);
197
+ searchContext = await Promise.race([
198
+ expandQuery(query.context),
199
+ new Promise((_, reject) => { timer = setTimeout(() => reject(new Error('expansion timeout')), 5000); }),
200
+ ]);
192
201
  }
193
202
  catch {
194
- // Expansion unavailable — use original query
203
+ // Expansion unavailable or timed out — use original query
204
+ }
205
+ finally {
206
+ if (timer)
207
+ clearTimeout(timer);
195
208
  }
196
209
  }
197
210
  // Phase 1: Embed query for vector similarity (uses coref-expanded context)
@@ -206,9 +219,9 @@ export class ActivationEngine {
206
219
  // Two-pass BM25: (1) keyword-stripped query for precision, (2) expanded query for recall.
207
220
  const keywordQuery = Array.from(tokenize(query.context)).join(' ');
208
221
  const bm25Keyword = keywordQuery.length > 2
209
- ? this.store.searchBM25WithRank(query.agentId, keywordQuery, limit * 3)
222
+ ? this.store.searchBM25WithRankMultiAgent(agentIds, keywordQuery, limit * 3)
210
223
  : [];
211
- const bm25Expanded = this.store.searchBM25WithRank(query.agentId, searchContext, limit * 3);
224
+ const bm25Expanded = this.store.searchBM25WithRankMultiAgent(agentIds, searchContext, limit * 3);
212
225
  // Merge: take the best BM25 score per engram from either pass
213
226
  const bm25ScoreMap = new Map();
214
227
  const bm25EngramMap = new Map();
@@ -222,7 +235,7 @@ export class ActivationEngine {
222
235
  const bm25Ranked = Array.from(bm25EngramMap.entries()).map(([id, engram]) => ({
223
236
  engram, bm25Score: bm25ScoreMap.get(id) ?? 0,
224
237
  }));
225
- const allActive = this.store.getEngramsByAgent(query.agentId, query.includeStaging ? undefined : 'active', query.includeRetracted ?? false);
238
+ const allActive = this.store.getEngramsByAgents(agentIds, query.includeStaging ? undefined : 'active', query.includeRetracted ?? false);
226
239
  // Merge candidates (deduplicate)
227
240
  const candidateMap = new Map();
228
241
  for (const r of bm25Ranked)
@@ -354,7 +367,7 @@ export class ActivationEngine {
354
367
  // Take top 5 feedback terms and re-search
355
368
  const extraTerms = Array.from(feedbackTerms).slice(0, 5).join(' ');
356
369
  if (extraTerms) {
357
- const feedbackBM25 = this.store.searchBM25WithRank(query.agentId, `${searchContext} ${extraTerms}`, limit * 2);
370
+ const feedbackBM25 = this.store.searchBM25WithRankMultiAgent(agentIds, `${searchContext} ${extraTerms}`, limit * 2);
358
371
  for (const r of feedbackBM25) {
359
372
  if (!candidateMap.has(r.engram.id)) {
360
373
  candidateMap.set(r.engram.id, r.engram);
@@ -472,7 +485,12 @@ export class ActivationEngine {
472
485
  if (useReranker && rerankPool.length > 0) {
473
486
  try {
474
487
  const passages = rerankPool.map(r => `${r.engram.concept}: ${r.engram.content}`);
475
- const rerankResults = await rerank(query.context, passages);
488
+ let rerankTimer;
489
+ const rerankResults = await Promise.race([
490
+ rerank(query.context, passages),
491
+ new Promise((_, reject) => { rerankTimer = setTimeout(() => reject(new Error('reranker timeout')), 10000); }),
492
+ ]).finally(() => { if (rerankTimer)
493
+ clearTimeout(rerankTimer); });
476
494
  // Adaptive reranker blend (Codex recommendation):
477
495
  // When BM25/text signals are strong, trust them more; when weak, lean on reranker.
478
496
  const bm25Max = Math.max(...rerankPool.map(r => r.phaseScores.textMatch));
@@ -558,13 +576,24 @@ export class ActivationEngine {
558
576
  associations: r.associations,
559
577
  }));
560
578
  const activatedIds = results.map(r => r.engram.id);
561
- // Side effects: touch, co-activate, Hebbian update (skip for internal/system calls)
579
+ // Side effects: touch, co-activate, defer Hebbian to validation gate (skip for internal/system calls)
562
580
  if (!query.internal) {
563
581
  for (const id of activatedIds) {
564
582
  this.store.touchEngram(id);
565
583
  }
566
584
  this.coActivationBuffer.pushBatch(activatedIds);
567
- this.updateHebbianWeights();
585
+ // Validation-gated Hebbian: defer strengthening until feedback arrives
586
+ const pairs = this.coActivationBuffer.getCoActivatedPairs(10_000);
587
+ const seen = new Set();
588
+ const uniquePairs = [];
589
+ for (const [a, b] of pairs) {
590
+ const key = a < b ? `${a}:${b}` : `${b}:${a}`;
591
+ if (!seen.has(key)) {
592
+ seen.add(key);
593
+ uniquePairs.push([a, b]);
594
+ }
595
+ }
596
+ this.validationGate.addPending(activatedIds, uniquePairs);
568
597
  // Log activation event for eval
569
598
  const latencyMs = performance.now() - startTime;
570
599
  this.store.logActivationEvent({
@@ -581,83 +610,130 @@ export class ActivationEngine {
581
610
  return results;
582
611
  }
583
612
  /**
584
- * Beam search graph walk — replaces naive BFS.
585
- * Scores paths (not just nodes), uses query-dependent edge filtering,
586
- * and supports deeper exploration with focused beams.
613
+ * Multi-graph traversal (MAGMA-inspired).
614
+ *
615
+ * Instead of one beam search over all edge types, runs independent traversals
616
+ * per graph type with specialized scoring, then fuses the boosts.
617
+ *
618
+ * Four sub-graphs:
619
+ * - Semantic (connection + hebbian edges) → standard weight-based walk
620
+ * - Temporal (temporal edges) → recency-weighted (favor recent connections)
621
+ * - Causal (causal edges) → full weight walk (causal links are high-value)
622
+ * - Entity (bridge edges) → entity-tag-weighted walk
623
+ *
624
+ * Each sub-graph contributes independently to the final graph boost,
625
+ * weighted by configurable per-graph weights.
587
626
  */
627
+ static GRAPH_WEIGHTS = {
628
+ semantic: 0.40, // connection + hebbian
629
+ temporal: 0.20, // temporal edges
630
+ causal: 0.25, // causal edges (high-value signal)
631
+ entity: 0.15, // bridge edges
632
+ };
588
633
  graphWalk(scored, maxDepth, hopPenalty, beamWidth = 15) {
589
634
  const scoreMap = new Map(scored.map(s => [s.engram.id, s]));
590
635
  const MAX_TOTAL_BOOST = 0.25;
591
- const BEAM_WIDTH = beamWidth;
592
- // Seed the beam with high-scoring, text-relevant items
593
- const beam = scored
594
- .filter(item => item.phaseScores.textMatch >= 0.15)
595
- .sort((a, b) => b.score - a.score)
596
- .slice(0, BEAM_WIDTH);
597
- // Track which engrams have been explored (avoid cycles)
598
- const explored = new Set();
599
- for (let depth = 0; depth < maxDepth; depth++) {
600
- const nextBeam = [];
601
- for (const item of beam) {
602
- if (explored.has(item.engram.id))
603
- continue;
604
- explored.add(item.engram.id);
605
- // Get associations for depth > 0, fetch from store if not in scored set
606
- const associations = item.associations.length > 0
607
- ? item.associations
608
- : this.store.getAssociationsFor(item.engram.id);
609
- for (const assoc of associations) {
610
- const neighborId = assoc.fromEngramId === item.engram.id
611
- ? assoc.toEngramId
612
- : assoc.fromEngramId;
613
- if (explored.has(neighborId))
614
- continue;
615
- const neighbor = scoreMap.get(neighborId);
616
- if (!neighbor)
617
- continue;
618
- // Query-dependent edge filtering: neighbor must have SOME relevance
619
- // (textMatch > 0.05 for deeper hops, relaxed from 0.1)
620
- const relevanceFloor = depth === 0 ? 0.1 : 0.05;
621
- if (neighbor.phaseScores.textMatch < relevanceFloor)
622
- continue;
623
- // Skip if neighbor already at boost cap
624
- if (neighbor.phaseScores.graphBoost >= MAX_TOTAL_BOOST)
636
+ // Define which edge types belong to each sub-graph
637
+ const graphTypes = {
638
+ semantic: ['connection', 'hebbian'],
639
+ temporal: ['temporal'],
640
+ causal: ['causal'],
641
+ entity: ['bridge'],
642
+ };
643
+ // Run independent traversals per sub-graph, accumulate boosts
644
+ const boostAccum = new Map(); // engramId total boost
645
+ for (const [graphName, edgeTypes] of Object.entries(graphTypes)) {
646
+ const graphWeight = ActivationEngine.GRAPH_WEIGHTS[graphName];
647
+ const subBeamWidth = Math.max(3, Math.ceil(beamWidth * graphWeight));
648
+ // Seed beam
649
+ const beam = scored
650
+ .filter(item => item.phaseScores.textMatch >= 0.15)
651
+ .sort((a, b) => b.score - a.score)
652
+ .slice(0, subBeamWidth);
653
+ const explored = new Set();
654
+ for (let depth = 0; depth < maxDepth; depth++) {
655
+ const nextBeam = [];
656
+ for (const item of beam) {
657
+ if (explored.has(item.engram.id))
625
658
  continue;
626
- // Path score: source score * edge weight * hop penalty^(depth+1)
627
- const normalizedWeight = Math.min(assoc.weight, 5.0) / 5.0;
628
- const pathScore = item.score * normalizedWeight * Math.pow(hopPenalty, depth + 1);
629
- const boost = Math.min(pathScore, 0.15, MAX_TOTAL_BOOST - neighbor.phaseScores.graphBoost);
630
- if (boost > 0.001) {
631
- neighbor.score += boost;
632
- neighbor.phaseScores.graphBoost += boost;
633
- nextBeam.push(neighbor);
659
+ explored.add(item.engram.id);
660
+ const associations = item.associations.length > 0
661
+ ? item.associations
662
+ : this.store.getAssociationsFor(item.engram.id);
663
+ // Filter to only edges of this sub-graph type
664
+ const relevantEdges = associations.filter(a => edgeTypes.includes(a.type));
665
+ for (const assoc of relevantEdges) {
666
+ const neighborId = assoc.fromEngramId === item.engram.id
667
+ ? assoc.toEngramId
668
+ : assoc.fromEngramId;
669
+ if (explored.has(neighborId))
670
+ continue;
671
+ const neighbor = scoreMap.get(neighborId);
672
+ if (!neighbor)
673
+ continue;
674
+ const relevanceFloor = depth === 0 ? 0.1 : 0.05;
675
+ if (neighbor.phaseScores.textMatch < relevanceFloor)
676
+ continue;
677
+ // Path score with graph-type-specific weighting
678
+ const normalizedWeight = Math.min(assoc.weight, 5.0) / 5.0;
679
+ let pathScore = item.score * normalizedWeight * Math.pow(hopPenalty, depth + 1);
680
+ // Causal edges get a 2x boost — they represent verified reasoning chains
681
+ if (graphName === 'causal')
682
+ pathScore *= 2.0;
683
+ // Weight by sub-graph importance
684
+ const boost = Math.min(pathScore * graphWeight, 0.15);
685
+ if (boost > 0.001) {
686
+ boostAccum.set(neighborId, (boostAccum.get(neighborId) ?? 0) + boost);
687
+ nextBeam.push(neighbor);
688
+ }
634
689
  }
635
690
  }
691
+ if (nextBeam.length === 0)
692
+ break;
693
+ beam.length = 0;
694
+ beam.push(...nextBeam
695
+ .sort((a, b) => b.score - a.score)
696
+ .slice(0, subBeamWidth));
697
+ }
698
+ }
699
+ // Apply fused boosts to scored items
700
+ for (const [engramId, totalBoost] of boostAccum) {
701
+ const item = scoreMap.get(engramId);
702
+ if (!item)
703
+ continue;
704
+ const capped = Math.min(totalBoost, MAX_TOTAL_BOOST - item.phaseScores.graphBoost);
705
+ if (capped > 0.001) {
706
+ item.score += capped;
707
+ item.phaseScores.graphBoost += capped;
636
708
  }
637
- // Prune beam for next depth level
638
- if (nextBeam.length === 0)
639
- break;
640
- beam.length = 0;
641
- beam.push(...nextBeam
642
- .sort((a, b) => b.score - a.score)
643
- .slice(0, BEAM_WIDTH));
644
709
  }
645
710
  }
646
- updateHebbianWeights() {
647
- const pairs = this.coActivationBuffer.getCoActivatedPairs(10_000);
648
- // Deduplicate pairs to prevent repeated strengthening
649
- const seen = new Set();
711
+ /**
712
+ * Resolve validation-gated Hebbian update for a specific engram.
713
+ * Called by memory_feedback only strengthens when retrieval was useful.
714
+ * This prevents hub toxicity from noisy co-retrieval (Kairos-inspired).
715
+ */
716
+ resolveHebbianFeedback(engramId, useful) {
717
+ const { pairs, signal } = this.validationGate.resolveFeedback(engramId, useful);
718
+ let updated = 0;
650
719
  for (const [a, b] of pairs) {
651
- const key = a < b ? `${a}:${b}` : `${b}:${a}`;
652
- if (seen.has(key))
653
- continue;
654
- seen.add(key);
655
720
  const existing = this.store.getAssociation(a, b) ?? this.store.getAssociation(b, a);
656
721
  const currentWeight = existing?.weight ?? 0.1;
657
- const newWeight = strengthenAssociation(currentWeight);
658
- this.store.upsertAssociation(a, b, newWeight, 'hebbian');
659
- this.store.upsertAssociation(b, a, newWeight, 'hebbian');
722
+ if (signal > 0) {
723
+ // Positive feedback → strengthen
724
+ const newWeight = strengthenAssociation(currentWeight, signal);
725
+ this.store.upsertAssociation(a, b, newWeight, 'hebbian');
726
+ this.store.upsertAssociation(b, a, newWeight, 'hebbian');
727
+ }
728
+ else {
729
+ // Negative feedback → slight weakening (decay by signal magnitude)
730
+ const newWeight = Math.max(0.001, currentWeight * (1 + signal)); // signal is -0.3
731
+ this.store.upsertAssociation(a, b, newWeight, 'hebbian');
732
+ this.store.upsertAssociation(b, a, newWeight, 'hebbian');
733
+ }
734
+ updated++;
660
735
  }
736
+ return updated;
661
737
  }
662
738
  explain(phases, engram, associations) {
663
739
  const parts = [];