talon-agent 4.6.0 → 5.0.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 (225) hide show
  1. package/README.md +3 -3
  2. package/package.json +3 -2
  3. package/prompts/README.md +2 -2
  4. package/prompts/system/memory-recall.md +16 -0
  5. package/src/app.ts +19 -4
  6. package/src/backend/claude-sdk/handler.ts +28 -7
  7. package/src/backend/claude-sdk/one-shot.ts +1 -1
  8. package/src/backend/claude-sdk/options.ts +7 -6
  9. package/src/backend/claude-sdk/stream.ts +30 -3
  10. package/src/backend/claude-sdk/warm.ts +1 -1
  11. package/src/backend/codex/constants.ts +1 -1
  12. package/src/backend/codex/factory.ts +2 -2
  13. package/src/backend/codex/handler/events.ts +1 -1
  14. package/src/backend/codex/handler/message.ts +26 -12
  15. package/src/backend/codex/handler/rollout-accounting.ts +1 -1
  16. package/src/backend/codex/init.ts +1 -1
  17. package/src/backend/codex/mcp-config.ts +1 -1
  18. package/src/backend/codex/one-shot.ts +1 -1
  19. package/src/backend/kilo/handler/message.ts +4 -1
  20. package/src/backend/openai-agents/constants.ts +1 -1
  21. package/src/backend/openai-agents/factory.ts +2 -2
  22. package/src/backend/openai-agents/handler/events.ts +1 -1
  23. package/src/backend/openai-agents/handler/message.ts +8 -4
  24. package/src/backend/openai-agents/init.ts +1 -1
  25. package/src/backend/openai-agents/mcp-pool.ts +1 -1
  26. package/src/backend/opencode/handler/message.ts +4 -1
  27. package/src/backend/remote-server/chat-turn.ts +31 -24
  28. package/src/backend/remote-server/events.ts +3 -3
  29. package/src/backend/remote-server/factory.ts +6 -3
  30. package/src/backend/remote-server/index.ts +1 -1
  31. package/src/backend/remote-server/mcp.ts +1 -1
  32. package/src/backend/remote-server/one-shot.ts +1 -1
  33. package/src/backend/remote-server/server-bindings.ts +1 -1
  34. package/src/backend/remote-server/turn.ts +1 -1
  35. package/src/backend/runtime/cache/cache-metrics.ts +126 -0
  36. package/src/backend/{shared → runtime/cache}/cache-telemetry.ts +22 -2
  37. package/src/backend/{shared → runtime}/index.ts +32 -23
  38. package/src/backend/{shared → runtime/prompt}/delivery-contract.ts +1 -1
  39. package/src/backend/{shared → runtime/prompt}/prompt-format.ts +46 -2
  40. package/src/backend/{shared → runtime/prompt}/system-prompt.ts +4 -4
  41. package/src/backend/{shared → runtime/turn}/delivered-text.ts +1 -1
  42. package/src/backend/{shared → runtime/turn}/delivery.ts +2 -2
  43. package/src/backend/{shared → runtime/turn}/handle-retry.ts +5 -5
  44. package/src/backend/{shared → runtime/turn}/handler-to-events.ts +24 -10
  45. package/src/backend/{shared → runtime/turn}/handler-types.ts +7 -1
  46. package/src/backend/{shared → runtime/turn}/model-retry.ts +2 -2
  47. package/src/backend/{shared → runtime/turn}/result-events.ts +2 -2
  48. package/src/backend/{shared → runtime/turn}/stream-state.ts +2 -2
  49. package/src/backend/{shared → runtime/turn}/turn-interrupt.ts +2 -2
  50. package/src/backend/{shared → runtime/turn}/turn-phases.ts +6 -6
  51. package/src/bootstrap.ts +4 -19
  52. package/src/cli.ts +1 -1
  53. package/src/core/agent-runtime/capabilities.ts +11 -0
  54. package/src/core/agent-runtime/contract-tests.ts +92 -1
  55. package/src/core/agent-runtime/events.ts +1 -1
  56. package/src/core/background/{isolated-agent.ts → cron/isolated-agent.ts} +3 -3
  57. package/src/core/background/{job-health.ts → cron/job-health.ts} +1 -1
  58. package/src/core/background/{job-oneshot.ts → cron/job-oneshot.ts} +5 -5
  59. package/src/core/background/{job-prompt.ts → cron/job-prompt.ts} +1 -1
  60. package/src/core/background/{cron.ts → cron/scheduler.ts} +6 -6
  61. package/src/core/background/{cron-spec.ts → cron/spec.ts} +1 -1
  62. package/src/core/background/{dream.ts → dream/index.ts} +10 -15
  63. package/src/core/background/{plan-alerts.ts → pulse/plan-alerts.ts} +3 -3
  64. package/src/core/background/{pulse.ts → pulse/pulse.ts} +8 -5
  65. package/src/core/background/triggers/command.ts +1 -1
  66. package/src/core/config/index.ts +15 -11
  67. package/src/core/daemon/resource-sampler.ts +121 -0
  68. package/src/core/engine/dispatcher.ts +16 -0
  69. package/src/core/engine/gateway-actions/cron.ts +2 -2
  70. package/src/core/engine/gateway-actions/index.ts +3 -0
  71. package/src/core/engine/gateway-actions/memory.ts +335 -0
  72. package/src/core/engine/gateway.ts +0 -6
  73. package/src/core/memory/import.ts +3 -2
  74. package/src/core/memory/taps.ts +199 -0
  75. package/src/core/memory/turn-retrieval.ts +222 -0
  76. package/src/core/prompt/assemble.ts +2 -12
  77. package/src/core/prompt/index.ts +2 -2
  78. package/src/core/prompt/invalidation.ts +1 -1
  79. package/src/core/tasks/index.ts +1 -1
  80. package/src/core/tools/index.ts +2 -0
  81. package/src/core/tools/memory.ts +128 -0
  82. package/src/core/tools/types.ts +1 -0
  83. package/src/core/weaver/turn-cpu.ts +32 -0
  84. package/src/core/weaver/weaver.ts +36 -0
  85. package/src/frontend/discord/admin.ts +1 -1
  86. package/src/frontend/discord/callbacks/components/backend-select.ts +1 -1
  87. package/src/frontend/discord/callbacks/components/pulse.ts +1 -1
  88. package/src/frontend/discord/callbacks/components/settings.ts +1 -1
  89. package/src/frontend/discord/callbacks/modals.ts +4 -1
  90. package/src/frontend/discord/commands/admin.ts +1 -35
  91. package/src/frontend/discord/commands/definitions.ts +0 -11
  92. package/src/frontend/discord/commands/router.ts +0 -3
  93. package/src/frontend/discord/commands/session.ts +6 -0
  94. package/src/frontend/discord/commands/settings.ts +1 -1
  95. package/src/frontend/discord/middleware.ts +1 -25
  96. package/src/frontend/discord/runtime.ts +1 -2
  97. package/src/frontend/native/{auth.ts → bridge/auth.ts} +2 -2
  98. package/src/frontend/native/{discovery.ts → bridge/discovery.ts} +3 -3
  99. package/src/frontend/native/{routes → bridge/routes}/chats.ts +1 -1
  100. package/src/frontend/native/{routes → bridge/routes}/daemon.ts +1 -1
  101. package/src/frontend/native/{routes → bridge/routes}/host.ts +6 -3
  102. package/src/frontend/native/{routes → bridge/routes}/pre-auth.ts +1 -1
  103. package/src/frontend/native/{server.ts → bridge/server.ts} +3 -3
  104. package/src/frontend/native/{tls.ts → bridge/tls.ts} +2 -2
  105. package/src/frontend/native/{chat-lifecycle.ts → chats/chat-lifecycle.ts} +3 -3
  106. package/src/frontend/native/{chat-wire.ts → chats/chat-wire.ts} +4 -4
  107. package/src/frontend/native/{chats.ts → chats/chats.ts} +4 -4
  108. package/src/frontend/native/{empty-chat-sweep.ts → chats/empty-chat-sweep.ts} +4 -4
  109. package/src/frontend/native/{history.ts → chats/history.ts} +7 -7
  110. package/src/frontend/native/{reset.ts → chats/reset.ts} +7 -7
  111. package/src/frontend/native/index.ts +13 -10
  112. package/src/frontend/native/{media.ts → media/media.ts} +3 -3
  113. package/src/frontend/native/runtime.ts +1 -1
  114. package/src/frontend/native/{control.ts → surface/control.ts} +4 -4
  115. package/src/frontend/native/{extensions.ts → surface/extensions.ts} +12 -9
  116. package/src/frontend/native/{handlers.ts → surface/handlers.ts} +19 -14
  117. package/src/frontend/native/{logs.ts → surface/logs.ts} +2 -2
  118. package/src/frontend/native/{memory.ts → surface/memory.ts} +2 -2
  119. package/src/frontend/native/{models.ts → surface/models.ts} +17 -10
  120. package/src/frontend/native/{settings.ts → surface/settings.ts} +9 -9
  121. package/src/frontend/native/{status.ts → surface/status.ts} +3 -3
  122. package/src/frontend/native/{actions.ts → turn/actions.ts} +7 -4
  123. package/src/frontend/native/{context.ts → turn/context.ts} +8 -8
  124. package/src/frontend/native/{emit.ts → turn/emit.ts} +7 -7
  125. package/src/frontend/native/{queue.ts → turn/queue.ts} +3 -3
  126. package/src/frontend/native/{turn-meta.ts → turn/turn-meta.ts} +2 -2
  127. package/src/frontend/native/{turn.ts → turn/turn.ts} +9 -9
  128. package/src/frontend/shared/model-commands.ts +1 -1
  129. package/src/frontend/shared/session-status.ts +35 -8
  130. package/src/frontend/shared/status-context.ts +101 -2
  131. package/src/frontend/telegram/admin/background.ts +1 -1
  132. package/src/frontend/telegram/callbacks/model/backend.ts +1 -1
  133. package/src/frontend/telegram/callbacks/pulse.ts +1 -1
  134. package/src/frontend/telegram/callbacks/settings.ts +1 -1
  135. package/src/frontend/telegram/commands/admin.ts +2 -37
  136. package/src/frontend/telegram/commands/index.ts +1 -1
  137. package/src/frontend/telegram/commands/session.ts +6 -0
  138. package/src/frontend/telegram/commands/settings.ts +1 -1
  139. package/src/frontend/telegram/handlers/messages.ts +0 -10
  140. package/src/frontend/telegram/index.ts +1 -5
  141. package/src/frontend/telegram/middleware.ts +1 -15
  142. package/src/frontend/whatsapp/access.ts +2 -2
  143. package/src/frontend/whatsapp/actions/history.ts +2 -2
  144. package/src/frontend/whatsapp/actions/messaging.ts +2 -2
  145. package/src/frontend/whatsapp/actions/moderation.ts +1 -1
  146. package/src/frontend/whatsapp/actions/send.ts +1 -1
  147. package/src/frontend/whatsapp/commands.ts +8 -2
  148. package/src/frontend/whatsapp/{connection.ts → connection/connection.ts} +5 -5
  149. package/src/frontend/whatsapp/{pairing-service.ts → connection/pairing-service.ts} +3 -3
  150. package/src/frontend/whatsapp/{wa-logger.ts → connection/wa-logger.ts} +2 -2
  151. package/src/frontend/whatsapp/index.ts +4 -4
  152. package/src/frontend/whatsapp/{inbound.ts → messages/inbound.ts} +15 -15
  153. package/src/frontend/whatsapp/{media-store.ts → messages/media-store.ts} +4 -4
  154. package/src/frontend/whatsapp/{message-store.ts → messages/message-store.ts} +1 -1
  155. package/src/frontend/whatsapp/{turn-recovery.ts → messages/turn-recovery.ts} +4 -4
  156. package/src/frontend/whatsapp/registry.ts +1 -1
  157. package/src/frontend/whatsapp/runtime.ts +1 -1
  158. package/src/index.ts +1 -1
  159. package/src/storage/db.ts +6 -1
  160. package/src/storage/memory.ts +59 -8
  161. package/src/storage/metrics.ts +38 -0
  162. package/src/storage/repositories/goals-repo.ts +2 -1
  163. package/src/storage/repositories/sessions-repo.ts +6 -0
  164. package/src/storage/session-record.ts +13 -2
  165. package/src/storage/sessions.ts +12 -0
  166. package/src/storage/sql/db.sql +5 -0
  167. package/src/storage/sql/schema.sql +4 -0
  168. package/src/storage/sql/sessions.sql +3 -3
  169. package/src/storage/sql/statements.generated.ts +10 -3
  170. package/src/storage/sql/turn-meta.sql +1 -1
  171. package/src/util/boot-timer.ts +15 -1
  172. package/src/util/chat-id.ts +30 -0
  173. package/src/util/concurrency.ts +1 -1
  174. package/src/util/log.ts +1 -1
  175. package/src/util/paths.ts +0 -2
  176. package/src/core/soul/README.md +0 -110
  177. package/src/core/soul/RESEARCH.md +0 -98
  178. package/src/core/soul/associative.ts +0 -98
  179. package/src/core/soul/centrality.ts +0 -98
  180. package/src/core/soul/cluster.ts +0 -83
  181. package/src/core/soul/compiler.ts +0 -207
  182. package/src/core/soul/consolidate.ts +0 -179
  183. package/src/core/soul/critic.ts +0 -162
  184. package/src/core/soul/dag.ts +0 -265
  185. package/src/core/soul/delta.ts +0 -123
  186. package/src/core/soul/drift.ts +0 -99
  187. package/src/core/soul/embedder.ts +0 -129
  188. package/src/core/soul/emergent-critic.ts +0 -96
  189. package/src/core/soul/forgetting.ts +0 -131
  190. package/src/core/soul/governance.ts +0 -93
  191. package/src/core/soul/hash.ts +0 -97
  192. package/src/core/soul/hdc.ts +0 -154
  193. package/src/core/soul/kernel.ts +0 -540
  194. package/src/core/soul/lattice.ts +0 -103
  195. package/src/core/soul/lens.ts +0 -110
  196. package/src/core/soul/projector.ts +0 -240
  197. package/src/core/soul/reflect.ts +0 -170
  198. package/src/core/soul/reflex.ts +0 -164
  199. package/src/core/soul/retrieve.ts +0 -146
  200. package/src/core/soul/salience.ts +0 -146
  201. package/src/core/soul/service.ts +0 -204
  202. package/src/core/soul/settings.ts +0 -47
  203. package/src/core/soul/signals.ts +0 -117
  204. package/src/core/soul/talon-embedder.ts +0 -80
  205. package/src/core/soul/taps.ts +0 -199
  206. package/src/core/soul/types.ts +0 -298
  207. package/src/core/soul/valence.ts +0 -83
  208. /package/src/backend/{shared → runtime}/frontends.ts +0 -0
  209. /package/src/backend/{shared → runtime}/metrics.ts +0 -0
  210. /package/src/backend/{shared → runtime}/sleep.ts +0 -0
  211. /package/src/backend/{shared → runtime/turn}/flow-violation.ts +0 -0
  212. /package/src/backend/{shared → runtime}/usage.ts +0 -0
  213. /package/src/core/{scripting/lua-runner.ts → scripts/lua.ts} +0 -0
  214. /package/src/frontend/native/{routes → bridge/routes}/index.ts +0 -0
  215. /package/src/frontend/native/{routes → bridge/routes}/memory.ts +0 -0
  216. /package/src/frontend/native/{routes → bridge/routes}/mesh.ts +0 -0
  217. /package/src/frontend/native/{routes → bridge/routes}/models.ts +0 -0
  218. /package/src/frontend/native/{routes → bridge/routes}/params.ts +0 -0
  219. /package/src/frontend/native/{routes → bridge/routes}/table.ts +0 -0
  220. /package/src/frontend/native/{tool-result.ts → turn/tool-result.ts} +0 -0
  221. /package/src/frontend/whatsapp/{auth-state.ts → connection/auth-state.ts} +0 -0
  222. /package/src/frontend/whatsapp/{identity.ts → connection/identity.ts} +0 -0
  223. /package/src/frontend/whatsapp/{pairing-lock.ts → connection/pairing-lock.ts} +0 -0
  224. /package/src/frontend/whatsapp/{pairing.ts → connection/pairing.ts} +0 -0
  225. /package/src/frontend/whatsapp/{pins.ts → messages/pins.ts} +0 -0
@@ -1,129 +0,0 @@
1
- /**
2
- * Soul Kernel — the embedder boundary and the vector math.
3
- *
4
- * The embedder is the ONLY neural component the kernel may touch, and it is a
5
- * frozen fixed function, not a reasoning agent — so it stays inside the
6
- * model-free principle. It is injected: production wires MemPalace's local
7
- * embedding model; tests and offline operation use the deterministic
8
- * `HashingEmbedder` below.
9
- *
10
- * Everything the kernel does *with* embeddings — clustering evidence into values,
11
- * picking a medoid label, measuring contradiction/drift — is plain vector
12
- * arithmetic implemented here. No model decides what relates to what; geometry
13
- * does.
14
- */
15
-
16
- import { createHash } from "node:crypto";
17
-
18
- /** A pluggable text→vector function. Vectors should be L2-normalized. */
19
- export interface Embedder {
20
- readonly dim: number;
21
- embed(texts: readonly string[]): Promise<number[][]>;
22
- }
23
-
24
- // ── Vector math ──────────────────────────────────────────────────────────────
25
-
26
- function dot(a: readonly number[], b: readonly number[]): number {
27
- let s = 0;
28
- for (let i = 0; i < a.length; i++) s += a[i]! * b[i]!;
29
- return s;
30
- }
31
-
32
- function norm(a: readonly number[]): number {
33
- return Math.sqrt(dot(a, a));
34
- }
35
-
36
- /** L2-normalize a vector; a zero vector is returned unchanged. */
37
- export function normalize(a: readonly number[]): number[] {
38
- const n = norm(a);
39
- if (n === 0) return [...a];
40
- return a.map((x) => x / n);
41
- }
42
-
43
- /** Cosine similarity in [-1, 1] (assumes finite inputs). */
44
- export function cosineSimilarity(
45
- a: readonly number[],
46
- b: readonly number[],
47
- ): number {
48
- const na = norm(a);
49
- const nb = norm(b);
50
- if (na === 0 || nb === 0) return 0;
51
- return dot(a, b) / (na * nb);
52
- }
53
-
54
- /** Cosine distance in [0, 2]. 0 = identical direction. */
55
- export function cosineDistance(
56
- a: readonly number[],
57
- b: readonly number[],
58
- ): number {
59
- return 1 - cosineSimilarity(a, b);
60
- }
61
-
62
- /** Mean vector of a set, L2-normalized. The cluster centroid. */
63
- export function centroid(vectors: readonly (readonly number[])[]): number[] {
64
- if (vectors.length === 0) return [];
65
- const dim = vectors[0]!.length;
66
- const acc = Array.from({ length: dim }, () => 0);
67
- for (const v of vectors) {
68
- for (let i = 0; i < dim; i++) acc[i]! += v[i]!;
69
- }
70
- for (let i = 0; i < dim; i++) acc[i]! /= vectors.length;
71
- return normalize(acc);
72
- }
73
-
74
- /**
75
- * Index of the medoid: the member with minimum total cosine distance to all
76
- * others — the single most central *real* item. This is what labels a value:
77
- * never generated prose, always a genuine fragment.
78
- */
79
- export function medoidIndex(vectors: readonly (readonly number[])[]): number {
80
- if (vectors.length === 0) return -1;
81
- let best = 0;
82
- let bestSum = Infinity;
83
- for (let i = 0; i < vectors.length; i++) {
84
- let sum = 0;
85
- for (let j = 0; j < vectors.length; j++) {
86
- if (i !== j) sum += cosineDistance(vectors[i]!, vectors[j]!);
87
- }
88
- if (sum < bestSum) {
89
- bestSum = sum;
90
- best = i;
91
- }
92
- }
93
- return best;
94
- }
95
-
96
- // ── Deterministic offline embedder ───────────────────────────────────────────
97
-
98
- /**
99
- * A frozen, dependency-free embedder via feature hashing over character n-grams
100
- * and word tokens. Deterministic and model-free: similar strings share n-grams
101
- * and therefore land near each other in cosine space. Crude next to a real
102
- * sentence encoder, but it is a genuine fixed function — suitable for tests,
103
- * offline operation, and as a fallback when the local model is unavailable.
104
- */
105
- export class HashingEmbedder implements Embedder {
106
- constructor(readonly dim = 256) {}
107
-
108
- async embed(texts: readonly string[]): Promise<number[][]> {
109
- return texts.map((t) => this.embedOne(t));
110
- }
111
-
112
- private embedOne(text: string): number[] {
113
- const vec = Array.from({ length: this.dim }, () => 0);
114
- const lower = text.toLowerCase();
115
- const add = (feature: string): void => {
116
- const h = createHash("md5").update(feature).digest();
117
- const idx = ((h[0]! << 8) | h[1]!) % this.dim;
118
- const sign = (h[2]! & 1) === 0 ? 1 : -1; // sign hashing reduces bias
119
- vec[idx]! += sign;
120
- };
121
- // word tokens
122
- for (const w of lower.split(/[^a-z0-9]+/).filter(Boolean)) add(`w:${w}`);
123
- // character 3-grams over the normalized stream
124
- const stream = lower.replace(/\s+/g, " ");
125
- for (let i = 0; i + 3 <= stream.length; i++)
126
- add(`g:${stream.slice(i, i + 3)}`);
127
- return normalize(vec);
128
- }
129
- }
@@ -1,96 +0,0 @@
1
- /**
2
- * Soul Kernel — the emergent critic.
3
- *
4
- * The lexical Critic (critic.ts) hardcodes three failure modes and their word
5
- * lists. That is a useful bootstrap, but it is exactly the kind of "overly
6
- * programmed and specific" thing the soul should grow past. The emergent critic
7
- * derives failure modes from data: corrections carry negative valence, and
8
- * clusters of corrections ARE the failure modes — discovered, not declared.
9
- *
10
- * To assess a candidate reply, embed it and measure its proximity to those
11
- * correction clusters. If it lands near a cluster of past corrections, it is at
12
- * risk of repeating that mistake — and the warning is a real past correction
13
- * (the cluster's medoid), not an invented category. As Dylan corrects Talon, new
14
- * failure modes appear on their own; nothing here is hand-authored.
15
- */
16
-
17
- import { clusterEvidence } from "./cluster.js";
18
- import type { SoulDag } from "./dag.js";
19
- import { cosineDistance, type Embedder } from "./embedder.js";
20
- import type { EvidencePayload, Hash } from "./types.js";
21
-
22
- export interface EmergentFailureMode {
23
- /** A real past correction representing the cluster — never generated. */
24
- readonly label: string;
25
- readonly medoid: Hash;
26
- readonly centroid: readonly number[];
27
- /** Number of corrections in this mode. */
28
- readonly size: number;
29
- }
30
-
31
- export interface FailureRisk {
32
- readonly label: string;
33
- readonly distance: number;
34
- readonly atRisk: boolean;
35
- }
36
-
37
- function correctionEvidence(dag: SoulDag): { hash: Hash; text: string }[] {
38
- const out: { hash: Hash; text: string }[] = [];
39
- for (const node of dag.nodesOfKind("evidence")) {
40
- const p = node.payload as EvidencePayload;
41
- if (p.source.origin === "correction")
42
- out.push({ hash: node.hash, text: p.text });
43
- }
44
- return out;
45
- }
46
-
47
- /**
48
- * Derive failure modes by clustering Talon's actual corrections. `threshold` is
49
- * the cluster tightness; each resulting cluster is one emergent failure mode,
50
- * labeled by its medoid (a representative real correction).
51
- */
52
- export async function deriveFailureModes(
53
- dag: SoulDag,
54
- embedder: Embedder,
55
- threshold: number,
56
- ): Promise<EmergentFailureMode[]> {
57
- const corrections = correctionEvidence(dag);
58
- if (corrections.length === 0) return [];
59
-
60
- const vectors = await embedder.embed(corrections.map((c) => c.text));
61
- const embedded = corrections.map((c, i) => ({
62
- hash: c.hash,
63
- vector: vectors[i]!,
64
- }));
65
- const clusters = clusterEvidence(embedded, threshold);
66
-
67
- const textByHash = new Map(corrections.map((c) => [c.hash, c.text]));
68
- return clusters.map((cl) => ({
69
- label: textByHash.get(cl.medoid) ?? "",
70
- medoid: cl.medoid,
71
- centroid: cl.centroid,
72
- size: cl.members.length,
73
- }));
74
- }
75
-
76
- /**
77
- * Assess a candidate reply against the emergent failure modes. Returns the risk
78
- * per mode (sorted nearest first); `atRisk` when the candidate sits within
79
- * `riskDistance` of a correction cluster — i.e. it resembles something Talon was
80
- * already corrected for.
81
- */
82
- export async function assessText(
83
- text: string,
84
- modes: readonly EmergentFailureMode[],
85
- embedder: Embedder,
86
- riskDistance: number,
87
- ): Promise<FailureRisk[]> {
88
- if (modes.length === 0) return [];
89
- const [vec] = await embedder.embed([text]);
90
- return modes
91
- .map((m) => {
92
- const distance = cosineDistance(vec!, m.centroid);
93
- return { label: m.label, distance, atRisk: distance <= riskDistance };
94
- })
95
- .sort((a, b) => a.distance - b.distance);
96
- }
@@ -1,131 +0,0 @@
1
- /**
2
- * Soul Kernel — adaptive forgetting (FSRS / DSR-inspired).
3
- *
4
- * Fixed-half-life exponential decay treats a trait recalled a hundred times the
5
- * same as one recalled once. Human memory does not work that way, and neither
6
- * should the soul. This module implements a Difficulty-Stability-Retrievability
7
- * style model (Wozniak's DSR; the basis of FSRS, the algorithm modern Anki uses),
8
- * adapted to continuous time:
9
- *
10
- * - Each node carries a STABILITY S (a time constant). Retrievability follows a
11
- * POWER LAW, R(t) = (1 + FACTOR·t/S)^DECAY, which fits human forgetting far
12
- * better than an exponential (Wickelgren; FSRS). At t = S, R ≈ 0.9.
13
- * - Successful recall GROWS stability, and grows it MORE when the recall
14
- * happened at low retrievability (the spacing effect) — so a trait that keeps
15
- * proving itself across time becomes durable. A correction (negative valence)
16
- * SHRINKS stability, so a discredited trait becomes easy to forget.
17
- *
18
- * The effective strength used for projection is salience × retrievability. The
19
- * net effect: core identity persists, fads evaporate — emergently, from the
20
- * timing and outcome of activations, not a hand-set constant.
21
- */
22
-
23
- import type { ActivationState, NodeKind, SoulConfig } from "./types.js";
24
- import { halfLifeForKind } from "./types.js";
25
-
26
- // FSRS power-law constants: R = (1 + FACTOR·t/S)^DECAY, with R(S) ≈ 0.9.
27
- const DECAY = -0.5;
28
- const FACTOR = 19 / 81;
29
-
30
- const MIN_STABILITY = 1; // never let a node become un-forgettable-fast to 0
31
- const GROWTH = 2.0; // spacing-effect stability gain scale
32
- const LAPSE = 0.5; // stability multiplier on a correction
33
-
34
- /** Power-law retrievability in (0, 1]. */
35
- export function retrievability(elapsedMs: number, stability: number): number {
36
- if (elapsedMs <= 0) return 1;
37
- const s = Math.max(MIN_STABILITY, stability);
38
- return Math.pow(1 + (FACTOR * elapsedMs) / s, DECAY);
39
- }
40
-
41
- /**
42
- * Initial stability for a node's first exposure, honoring the per-kind
43
- * half-life when the kind is known. No-decay kinds (infinite half-life) never
44
- * reach the FSRS path, but fall back to the base rate defensively so an
45
- * Infinity is never stored into state (it would not survive JSON persistence).
46
- */
47
- export function initialStability(cfg: SoulConfig, kind?: NodeKind): number {
48
- const hl = halfLifeForKind(cfg, kind);
49
- return Number.isFinite(hl) ? hl : cfg.decayHalfLifeMs;
50
- }
51
-
52
- /**
53
- * Stability after a recall. Positive valence grows it (more when retrievability
54
- * was low — the spacing effect); negative valence (a correction) shrinks it.
55
- */
56
- export function nextStability(
57
- stability: number,
58
- r: number,
59
- valence: number,
60
- ): number {
61
- const s = Math.max(MIN_STABILITY, stability);
62
- if (valence < 0) return Math.max(MIN_STABILITY, s * LAPSE);
63
- // grow more when r is low (recall that "shouldn't" have succeeded teaches most)
64
- const gain = 1 + GROWTH * (1 - r) * Math.max(0.25, Math.min(1, valence));
65
- return s * gain;
66
- }
67
-
68
- /**
69
- * Salience after lazy power-law decay to `now`, using the node's stability when
70
- * present. Falls back to the caller's exponential decay when stability is unset.
71
- * A no-decay kind (infinite half-life, e.g. reflexes) returns the stored
72
- * salience untouched regardless of stability.
73
- */
74
- export function effectiveStrength(
75
- state: ActivationState,
76
- now: number,
77
- cfg: SoulConfig,
78
- kind?: NodeKind,
79
- ): number {
80
- const halfLifeMs = halfLifeForKind(cfg, kind);
81
- if (!Number.isFinite(halfLifeMs)) return state.salience;
82
- if (state.stability === undefined) {
83
- // exponential fallback (mirrors salience.effectiveSalience)
84
- const f = Math.pow(
85
- 0.5,
86
- Math.max(0, now - state.lastActivatedAt) / halfLifeMs,
87
- );
88
- return state.salience * f;
89
- }
90
- return (
91
- state.salience *
92
- retrievability(now - state.lastActivatedAt, state.stability)
93
- );
94
- }
95
-
96
- export interface FsrsReinforceOptions {
97
- readonly now: number;
98
- readonly cfg: SoulConfig;
99
- readonly amount: number;
100
- readonly valence: number;
101
- /** Node kind, when known — selects the per-kind decay half-life. */
102
- readonly kind?: NodeKind;
103
- }
104
-
105
- /**
106
- * Reinforcement under adaptive forgetting: decays salience by retrievability,
107
- * adds the increment, accumulates evidence, and updates stability per the
108
- * spacing/lapse rule. Mutates and returns the state. No-decay kinds (infinite
109
- * half-life) accumulate undecayed and skip stability bookkeeping entirely —
110
- * their strength is time-invariant by construction.
111
- */
112
- export function reinforceFsrs(
113
- state: ActivationState,
114
- opts: FsrsReinforceOptions,
115
- ): ActivationState {
116
- if (!Number.isFinite(halfLifeForKind(opts.cfg, opts.kind))) {
117
- state.salience += opts.amount;
118
- state.evidence += opts.valence;
119
- state.activations += 1;
120
- state.lastActivatedAt = opts.now;
121
- return state;
122
- }
123
- const stability = state.stability ?? initialStability(opts.cfg, opts.kind);
124
- const r = retrievability(opts.now - state.lastActivatedAt, stability);
125
- state.salience = state.salience * r + opts.amount;
126
- state.evidence += opts.valence;
127
- state.stability = nextStability(stability, r, opts.valence);
128
- state.activations += 1;
129
- state.lastActivatedAt = opts.now;
130
- return state;
131
- }
@@ -1,93 +0,0 @@
1
- /**
2
- * Soul Kernel — governance (the protected-node approval queue).
3
- *
4
- * Most of the soul grows autonomously; that is the point. But some facets are
5
- * load-bearing enough that drift must never happen silently — the reflexes that
6
- * the harness enforces, and any change to core identity. The mechanical compiler
7
- * may *propose* such mutations, but they do not take effect until a human
8
- * approves them. This is the concrete machinery behind "identity cannot drift
9
- * behind your back".
10
- *
11
- * The queue is pure data (persisted with the kernel). Proposing enqueues a
12
- * payload + reason; approving returns the payload to the kernel to materialize;
13
- * rejecting drops it. Nothing here writes to the DAG — the kernel owns that — so
14
- * the queue stays a simple, auditable ledger.
15
- */
16
-
17
- import { randomUUID } from "node:crypto";
18
- import { hashPayload } from "./hash.js";
19
- import type { NodePayload } from "./types.js";
20
-
21
- type ProposalStatus = "pending" | "approved" | "rejected";
22
-
23
- export interface Proposal {
24
- readonly id: string;
25
- readonly at: number;
26
- /** Content hash of the proposed node — stable identity for dedup. */
27
- readonly target: string;
28
- readonly payload: NodePayload;
29
- /** Why the compiler proposed this (templated, never model-written). */
30
- readonly reason: string;
31
- status: ProposalStatus;
32
- resolvedAt?: number;
33
- }
34
-
35
- export interface ApprovalSnapshot {
36
- readonly proposals: readonly Proposal[];
37
- }
38
-
39
- export class ApprovalQueue {
40
- private proposals: Proposal[] = [];
41
-
42
- /**
43
- * Enqueue a proposed protected mutation. Idempotent on payload content: a
44
- * pending proposal for the same node is returned rather than duplicated.
45
- */
46
- propose(payload: NodePayload, reason: string, at: number): Proposal {
47
- const target = hashPayload(payload);
48
- const existing = this.proposals.find(
49
- (p) => p.target === target && p.status === "pending",
50
- );
51
- if (existing) return existing;
52
- const proposal: Proposal = {
53
- id: randomUUID(),
54
- at,
55
- target,
56
- payload,
57
- reason,
58
- status: "pending",
59
- };
60
- this.proposals.push(proposal);
61
- return proposal;
62
- }
63
-
64
- pending(): Proposal[] {
65
- return this.proposals.filter((p) => p.status === "pending");
66
- }
67
-
68
- get(id: string): Proposal | undefined {
69
- return this.proposals.find((p) => p.id === id);
70
- }
71
-
72
- /**
73
- * Resolve a pending proposal. Returns the payload to apply when approved, or
74
- * undefined otherwise (rejected, missing, or already resolved).
75
- */
76
- resolve(id: string, approved: boolean, at: number): NodePayload | undefined {
77
- const p = this.proposals.find((x) => x.id === id && x.status === "pending");
78
- if (!p) return undefined;
79
- p.status = approved ? "approved" : "rejected";
80
- p.resolvedAt = at;
81
- return approved ? p.payload : undefined;
82
- }
83
-
84
- snapshot(): ApprovalSnapshot {
85
- return { proposals: this.proposals.map((p) => ({ ...p })) };
86
- }
87
-
88
- static restore(snap: ApprovalSnapshot): ApprovalQueue {
89
- const q = new ApprovalQueue();
90
- q.proposals = snap.proposals.map((p) => ({ ...p }));
91
- return q;
92
- }
93
- }
@@ -1,97 +0,0 @@
1
- /**
2
- * Content addressing for the Soul kernel.
3
- *
4
- * A node's identity is the sha-256 of its *canonical* serialization. Canonical
5
- * means: deterministic key ordering, no whitespace, explicit rejection of
6
- * non-data values. Two nodes with semantically identical content therefore hash
7
- * identically — which is what makes dedup, provenance, and partial recompilation
8
- * structural rather than bolted-on.
9
- *
10
- * Only CONTENT is ever hashed here. Mutable state (salience, edge weights) is
11
- * deliberately excluded from node identity (see types.ts).
12
- */
13
-
14
- import { createHash } from "node:crypto";
15
- import type { Hash, NodePayload } from "./types.js";
16
-
17
- /**
18
- * Canonical JSON: object keys sorted lexicographically, arrays preserved in
19
- * order, no insignificant whitespace. Rejects `undefined`, functions, symbols,
20
- * NaN and ±Infinity so a hash can never depend on an unserializable value.
21
- *
22
- * This is intentionally stricter than JSON.stringify: we want a single
23
- * canonical byte string per logical value, and we want loud failure on anything
24
- * that could silently vary.
25
- */
26
- export function canonicalize(value: unknown): string {
27
- return encode(value);
28
- }
29
-
30
- function encode(value: unknown): string {
31
- if (value === null) return "null";
32
-
33
- const t = typeof value;
34
-
35
- if (t === "string") return JSON.stringify(value);
36
-
37
- if (t === "number") {
38
- if (!Number.isFinite(value as number)) {
39
- throw new TypeError(`canonicalize: non-finite number ${String(value)}`);
40
- }
41
- return JSON.stringify(value);
42
- }
43
-
44
- if (t === "boolean") return value ? "true" : "false";
45
-
46
- if (t === "bigint") {
47
- throw new TypeError("canonicalize: bigint is not serializable");
48
- }
49
-
50
- if (Array.isArray(value)) {
51
- return `[${value.map(encode).join(",")}]`;
52
- }
53
-
54
- if (t === "object") {
55
- const obj = value as Record<string, unknown>;
56
- const keys = Object.keys(obj).sort();
57
- const parts: string[] = [];
58
- for (const key of keys) {
59
- const v = obj[key];
60
- // Drop keys whose value is undefined, mirroring JSON semantics, but only
61
- // for objects — undefined inside arrays is rejected below.
62
- if (v === undefined) continue;
63
- parts.push(`${JSON.stringify(key)}:${encode(v)}`);
64
- }
65
- return `{${parts.join(",")}}`;
66
- }
67
-
68
- // undefined, function, symbol
69
- throw new TypeError(`canonicalize: cannot serialize ${t}`);
70
- }
71
-
72
- /** sha-256 of an arbitrary canonicalized value, prefixed "sha256:". */
73
- export function hashContent(value: unknown): Hash {
74
- const canonical = canonicalize(value);
75
- const digest = createHash("sha256").update(canonical, "utf8").digest("hex");
76
- return `sha256:${digest}` as Hash;
77
- }
78
-
79
- /** The content address of a node payload. This is the node's identity. */
80
- export function hashPayload(payload: NodePayload): Hash {
81
- return hashContent(payload);
82
- }
83
-
84
- /**
85
- * A Merkle root over a set of child hashes. Order-independent: the children are
86
- * sorted before hashing, so the root depends only on the *set* of content
87
- * present, not the order it was inserted. This is the structural version id.
88
- */
89
- export function merkleRoot(childHashes: Iterable<Hash>): Hash {
90
- const sorted = [...childHashes].sort();
91
- return hashContent({ merkle: sorted });
92
- }
93
-
94
- /** True when a string is a well-formed kernel content address. */
95
- export function isHash(value: unknown): value is Hash {
96
- return typeof value === "string" && /^sha256:[0-9a-f]{64}$/.test(value);
97
- }
@@ -1,154 +0,0 @@
1
- /**
2
- * Soul Kernel — hyperdimensional computing (Vector-Symbolic Architecture).
3
- *
4
- * Kanerva, "Hyperdimensional Computing" (2009). High-dimensional bipolar vectors
5
- * (±1, here a few thousand dims) support a small algebra that is the basis of a
6
- * compositional, model-free associative memory:
7
- *
8
- * - bind (⊗) elementwise product. Combines two hypervectors into one
9
- * DISSIMILAR to both, and is its own inverse (bind twice by the
10
- * same key recovers the other operand). Used to pair a role with a
11
- * filler: context ⊗ value.
12
- * - bundle (+) elementwise majority. Superposes many hypervectors into one
13
- * SIMILAR to all of them — a set, or a memory of many episodes.
14
- * - permute (ρ) cyclic shift. Encodes order/role without collision.
15
- * - cleanup nearest stored item under cosine — denoises a recalled vector
16
- * back to a known symbol.
17
- *
18
- * The soul uses this for episodic recall: bind the current context to the value
19
- * that worked, bundle those bindings into one memory hypervector, and later query
20
- * "in a context like this, what do I do?" by unbinding and cleaning up. It is a
21
- * genuinely compositional memory, and it is pure integer arithmetic — no model.
22
- */
23
-
24
- import { createHash } from "node:crypto";
25
-
26
- export type Hypervector = Int8Array;
27
-
28
- const DEFAULT_HD_DIM = 4096;
29
-
30
- /** Deterministic ±1 hypervector for a symbol — the same token always maps here. */
31
- export function symbolVector(token: string, dim = DEFAULT_HD_DIM): Hypervector {
32
- const v = new Int8Array(dim);
33
- // Seed a tiny xorshift PRNG from the token hash; fill with ±1.
34
- const digest = createHash("sha256").update(token).digest();
35
- let s0 = digest.readUInt32LE(0) || 1;
36
- let s1 = digest.readUInt32LE(4) || 2;
37
- for (let i = 0; i < dim; i++) {
38
- // xorshift128-ish
39
- let x = s0;
40
- const y = s1;
41
- s0 = y;
42
- x ^= x << 23;
43
- x ^= x >>> 17;
44
- x ^= y ^ (y >>> 26);
45
- s1 = x >>> 0;
46
- v[i] = ((s1 >>> (i % 31)) & 1) === 0 ? 1 : -1;
47
- }
48
- return v;
49
- }
50
-
51
- /** Elementwise bind (product). Self-inverse for bipolar vectors. */
52
- export function bind(a: Hypervector, b: Hypervector): Hypervector {
53
- const out = new Int8Array(a.length);
54
- for (let i = 0; i < a.length; i++) out[i] = (a[i]! * b[i]!) as number;
55
- return out;
56
- }
57
-
58
- /** Majority-rule bundle of several hypervectors; empty ⇒ zero vector. */
59
- export function bundle(vectors: readonly Hypervector[]): Hypervector {
60
- const dim = vectors[0]?.length ?? DEFAULT_HD_DIM;
61
- const acc = new Int32Array(dim);
62
- for (const v of vectors) for (let i = 0; i < dim; i++) acc[i]! += v[i]!;
63
- const out = new Int8Array(dim);
64
- for (let i = 0; i < dim; i++) out[i] = acc[i]! >= 0 ? 1 : -1; // ties → +1
65
- return out;
66
- }
67
-
68
- /** Cyclic shift by k (encodes role/order). */
69
- export function permute(v: Hypervector, k = 1): Hypervector {
70
- const dim = v.length;
71
- const out = new Int8Array(dim);
72
- const shift = ((k % dim) + dim) % dim;
73
- for (let i = 0; i < dim; i++) out[(i + shift) % dim] = v[i]!;
74
- return out;
75
- }
76
-
77
- /** Cosine similarity of two bipolar vectors (= normalized dot). */
78
- export function hdCosine(a: Hypervector, b: Hypervector): number {
79
- let dot = 0;
80
- for (let i = 0; i < a.length; i++) dot += a[i]! * b[i]!;
81
- return dot / a.length;
82
- }
83
-
84
- /** Nearest stored item to a (noisy) query, by cosine. */
85
- export function cleanup(
86
- query: Hypervector,
87
- items: ReadonlyMap<string, Hypervector>,
88
- ): { token: string; score: number } | undefined {
89
- let best: { token: string; score: number } | undefined;
90
- for (const [token, v] of items) {
91
- const score = hdCosine(query, v);
92
- if (!best || score > best.score) best = { token, score };
93
- }
94
- return best;
95
- }
96
-
97
- /**
98
- * A compositional associative memory: a running superposition of role⊗filler
99
- * bindings. Querying with a role unbinds and returns a noisy filler to be cleaned
100
- * up against an item memory. Stored as an integer accumulator so many episodes
101
- * can be bundled without saturating.
102
- */
103
- export class CompositionalMemory {
104
- private readonly acc: Int32Array;
105
- private count = 0;
106
-
107
- constructor(
108
- readonly dim = DEFAULT_HD_DIM,
109
- acc?: Int32Array,
110
- count = 0,
111
- ) {
112
- this.acc = acc ?? new Int32Array(dim);
113
- this.count = count;
114
- }
115
-
116
- /** Superpose one role⊗filler binding into the memory. */
117
- add(role: Hypervector, filler: Hypervector): void {
118
- const bound = bind(role, filler);
119
- for (let i = 0; i < this.dim; i++) this.acc[i]! += bound[i]!;
120
- this.count += 1;
121
- }
122
-
123
- /** The signed memory hypervector. */
124
- vector(): Hypervector {
125
- const out = new Int8Array(this.dim);
126
- for (let i = 0; i < this.dim; i++) out[i] = this.acc[i]! >= 0 ? 1 : -1;
127
- return out;
128
- }
129
-
130
- /** Unbind a role to recover the (noisy) filler it was paired with. */
131
- query(role: Hypervector): Hypervector {
132
- return bind(this.vector(), role);
133
- }
134
-
135
- get episodes(): number {
136
- return this.count;
137
- }
138
-
139
- snapshot(): { dim: number; acc: number[]; count: number } {
140
- return { dim: this.dim, acc: Array.from(this.acc), count: this.count };
141
- }
142
-
143
- static restore(snap: {
144
- dim: number;
145
- acc: number[];
146
- count: number;
147
- }): CompositionalMemory {
148
- return new CompositionalMemory(
149
- snap.dim,
150
- Int32Array.from(snap.acc),
151
- snap.count,
152
- );
153
- }
154
- }