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,540 +0,0 @@
1
- /**
2
- * Soul Kernel — the orchestrator.
3
- *
4
- * Wraps the DAG with a commit chain and persistence, and exposes the public
5
- * lifecycle: genesis → ingest(signal)* → commit → project. The reasoning model
6
- * touches only `project()` (read); everything that *writes* identity is the
7
- * mechanical compiler.
8
- *
9
- * Commits mirror git: each bundles the structural Merkle root, a digest of the
10
- * mutable state at that instant, and a parent link, so a rollback restores both
11
- * the structure and the "weather". Summaries are templated from the dirty set —
12
- * never model-written.
13
- */
14
-
15
- import { readFileSync, mkdirSync } from "node:fs";
16
- import { dirname } from "node:path";
17
- import writeFileAtomic from "write-file-atomic";
18
- import { SoulDag, type DagSnapshot } from "./dag.js";
19
- import { hashContent } from "./hash.js";
20
- import { ingest, appendSpine, type IngestResult } from "./compiler.js";
21
- import { Adwin } from "./drift.js";
22
- import { diffSnapshots, renderDelta, type SoulDelta } from "./delta.js";
23
- import {
24
- CompositionalMemory,
25
- bundle,
26
- cleanup,
27
- symbolVector,
28
- type Hypervector,
29
- } from "./hdc.js";
30
- import { liveValues } from "./consolidate.js";
31
- import { projectRuntime, type Projection } from "./projector.js";
32
- import { seedReflexes } from "./reflex.js";
33
- import { clusterEvidence } from "./cluster.js";
34
- import { consolidate, type ConsolidateResult } from "./consolidate.js";
35
- import { reflect, type ReflectOptions, type ReflectResult } from "./reflect.js";
36
- import { compileLens } from "./lens.js";
37
- import { detectTensions } from "./lattice.js";
38
- import { ValenceModel, type ValenceSnapshot } from "./valence.js";
39
- import {
40
- ApprovalQueue,
41
- type ApprovalSnapshot,
42
- type Proposal,
43
- } from "./governance.js";
44
- import { retrieveValues, type RetrievalWeights } from "./retrieve.js";
45
- import { pagerank } from "./centrality.js";
46
- import {
47
- associativeRecall,
48
- type RecallOptions,
49
- type RecallResult,
50
- } from "./associative.js";
51
- import type { Embedder } from "./embedder.js";
52
- import type { Signal } from "./signals.js";
53
- import {
54
- DEFAULT_SOUL_CONFIG,
55
- type Hash,
56
- type NodePayload,
57
- type SoulCommit,
58
- type SoulConfig,
59
- } from "./types.js";
60
-
61
- const PERSIST_VERSION = 1;
62
-
63
- interface PersistShape {
64
- readonly version: number;
65
- readonly config: SoulConfig;
66
- readonly dag: DagSnapshot;
67
- readonly commits: readonly SoulCommit[];
68
- readonly valence?: ValenceSnapshot;
69
- readonly drift?: readonly number[];
70
- readonly approvals?: ApprovalSnapshot;
71
- readonly episodic?: { dim: number; acc: number[]; count: number };
72
- }
73
-
74
- export interface GenesisOptions {
75
- readonly config?: SoulConfig;
76
- readonly now?: number;
77
- /** Optional founding directives, each seeded as a single-evidence value. */
78
- readonly seedValues?: readonly { text: string; actor?: string }[];
79
- }
80
-
81
- export class SoulKernel {
82
- private constructor(
83
- private readonly dag: SoulDag,
84
- readonly config: SoulConfig,
85
- private readonly commits: SoulCommit[],
86
- private readonly valenceModel: ValenceModel = new ValenceModel(),
87
- private readonly drift: Adwin = new Adwin(),
88
- private readonly approvals: ApprovalQueue = new ApprovalQueue(),
89
- private readonly episodic: CompositionalMemory = new CompositionalMemory(),
90
- ) {
91
- this.lastSnapshot = dag.snapshot();
92
- }
93
-
94
- // ── Episodic memory (VSA / hyperdimensional) ─────────────────────────────────
95
-
96
- /** Bundle a context string into a single hypervector (set of its words). */
97
- private contextVector(text: string): Hypervector {
98
- const words = (text.toLowerCase().match(/[a-z0-9]+/g) ?? []).map((w) =>
99
- symbolVector(`ctx:${w}`),
100
- );
101
- return words.length ? bundle(words) : symbolVector("ctx:∅");
102
- }
103
-
104
- /**
105
- * Remember an episode: in this context, these values were the right move. Binds
106
- * context ⊗ value and superposes it into the compositional memory. Call on
107
- * positive-outcome turns.
108
- */
109
- rememberEpisode(context: string, valueHashes: readonly Hash[]): void {
110
- const ctx = this.contextVector(context);
111
- for (const v of valueHashes) {
112
- if (this.dag.hasNode(v)) this.episodic.add(ctx, symbolVector(`val:${v}`));
113
- }
114
- }
115
-
116
- /**
117
- * Recall the value most associated with a context (VSA unbind + cleanup over
118
- * live values): "in a situation like this, what do I do?" Returns the best
119
- * value hash and its similarity, or undefined when memory is empty.
120
- */
121
- recallByContext(context: string): { value: Hash; score: number } | undefined {
122
- if (this.episodic.episodes === 0) return undefined;
123
- const items = new Map<string, Hypervector>();
124
- for (const v of liveValues(this.dag))
125
- items.set(v, symbolVector(`val:${v}`));
126
- if (items.size === 0) return undefined;
127
- const recalled = cleanup(
128
- this.episodic.query(this.contextVector(context)),
129
- items,
130
- );
131
- return recalled
132
- ? { value: recalled.token as Hash, score: recalled.score }
133
- : undefined;
134
- }
135
-
136
- /** Snapshot at the previous commit — diffed to produce the delta stream. */
137
- private lastSnapshot: DagSnapshot;
138
- private lastDelta: SoulDelta = {
139
- addedValues: [],
140
- addedThemes: [],
141
- addedSpine: [],
142
- addedReflexes: [],
143
- addedLenses: [],
144
- supersededCount: 0,
145
- newTensions: 0,
146
- };
147
-
148
- // ── Governance (protected mutations) ─────────────────────────────────────────
149
-
150
- /**
151
- * Propose a protected mutation (e.g. a new reflex or core value). It does NOT
152
- * take effect — it queues for human approval, so load-bearing identity can
153
- * never drift silently.
154
- */
155
- propose(payload: NodePayload, reason: string, now = Date.now()): Proposal {
156
- return this.approvals.propose(payload, reason, now);
157
- }
158
-
159
- /** Pending protected proposals awaiting a decision. */
160
- pendingApprovals(): Proposal[] {
161
- return this.approvals.pending();
162
- }
163
-
164
- /** Approve a pending proposal and materialize it in the DAG. Caller commits. */
165
- approve(id: string, now = Date.now()): Hash | undefined {
166
- const payload = this.approvals.resolve(id, true, now);
167
- if (!payload) return undefined;
168
- return this.dag.addNode(payload, now);
169
- }
170
-
171
- /** Reject a pending proposal; nothing is applied. */
172
- reject(id: string, now = Date.now()): boolean {
173
- this.approvals.resolve(id, false, now);
174
- return this.approvals.get(id)?.status === "rejected";
175
- }
176
-
177
- // ── Lifecycle ────────────────────────────────────────────────────────────────
178
-
179
- /** A fresh kernel with the load-bearing reflexes installed. */
180
- static genesis(opts: GenesisOptions = {}): SoulKernel {
181
- const config = opts.config ?? DEFAULT_SOUL_CONFIG;
182
- const now = opts.now ?? Date.now();
183
- const dag = new SoulDag();
184
- for (const reflex of seedReflexes()) dag.addNode(reflex, now);
185
- const kernel = new SoulKernel(dag, config, []);
186
- for (const seed of opts.seedValues ?? []) {
187
- kernel.addSeedValue(seed.text, now, seed.actor);
188
- }
189
- kernel.commit("genesis", now);
190
- return kernel;
191
- }
192
-
193
- /**
194
- * Seed a founding value from a verbatim directive: one evidence node, wrapped
195
- * in a single-member value whose medoid is itself. Later clustering (embedder
196
- * phase) may merge such seeds into larger values.
197
- */
198
- addSeedValue(text: string, now = Date.now(), actor?: string): Hash {
199
- const evidence = this.dag.addNode(
200
- {
201
- kind: "evidence",
202
- text,
203
- observedAt: now,
204
- source: { origin: "seed", ...(actor ? { actor } : {}) },
205
- },
206
- now,
207
- );
208
- return this.dag.addNode(
209
- { kind: "value", members: [evidence], medoid: evidence },
210
- now,
211
- );
212
- }
213
-
214
- // ── Write (mechanical) ───────────────────────────────────────────────────────
215
-
216
- ingest(signal: Signal): IngestResult {
217
- // Engagement outcomes teach the valence model what its cues actually mean.
218
- if (signal.kind === "engagement" && signal.cues?.length) {
219
- const outcome = signal.continued ? 1 : -1;
220
- for (const cue of signal.cues) this.valenceModel.observe(cue, outcome);
221
- }
222
- const result = ingest(this.dag, signal, this.config, (c) =>
223
- this.valenceModel.valence(c),
224
- );
225
- this.trackDrift(signal);
226
- return result;
227
- }
228
-
229
- /**
230
- * Feed an interaction's scalar outcome to ADWIN; a detected distribution shift
231
- * is a developmental inflection, recorded as an epoch in the Spine.
232
- */
233
- private trackDrift(signal: Signal): void {
234
- let outcome: number | undefined;
235
- if (signal.kind === "engagement") outcome = signal.continued ? 1 : -1;
236
- else if (signal.kind === "reaction")
237
- outcome = this.valenceModel.valence(signal.emoji);
238
- else if (signal.kind === "correction") outcome = -1;
239
- if (outcome === undefined) return;
240
-
241
- const change = this.drift.add(outcome);
242
- if (change.changed) {
243
- appendSpine(
244
- this.dag,
245
- `Epoch: behavioral reception shifted (${change.meanBefore!.toFixed(2)} → ${change.meanAfter!.toFixed(2)})`,
246
- signal.at,
247
- );
248
- }
249
- }
250
-
251
- ingestAll(signals: readonly Signal[]): IngestResult[] {
252
- return signals.map((s) => this.ingest(s));
253
- }
254
-
255
- /** The learned cue→valence model (meaning discovered from outcomes). */
256
- valence(): ValenceModel {
257
- return this.valenceModel;
258
- }
259
-
260
- /**
261
- * Evidence not yet absorbed into any value — the raw material awaiting
262
- * clustering. Membership and medoid roles both count as "claimed".
263
- */
264
- private looseEvidence(): Hash[] {
265
- const claimed = new Set<Hash>();
266
- for (const node of this.dag.nodesOfKind("value")) {
267
- if (node.payload.kind !== "value") continue;
268
- claimed.add(node.payload.medoid);
269
- for (const m of node.payload.members) claimed.add(m);
270
- }
271
- const out: Hash[] = [];
272
- for (const node of this.dag.nodesOfKind("evidence")) {
273
- if (!claimed.has(node.hash)) out.push(node.hash);
274
- }
275
- return out;
276
- }
277
-
278
- /**
279
- * Crystallize loose evidence into emergent values: embed the unabsorbed
280
- * fragments, cluster them by cosine geometry, and materialize a value node per
281
- * cluster with the medoid as its verbatim label. The model names nothing —
282
- * geometry discovers the values. Returns the value hashes created. Caller
283
- * commits.
284
- */
285
- async crystallize(embedder: Embedder, now = Date.now()): Promise<Hash[]> {
286
- const loose = this.looseEvidence();
287
- if (loose.length === 0) return [];
288
-
289
- const texts = loose.map((h) => {
290
- const node = this.dag.getNode(h);
291
- return node?.payload.kind === "evidence" ? node.payload.text : "";
292
- });
293
- const vectors = await embedder.embed(texts);
294
- const embedded = loose.map((hash, i) => ({ hash, vector: vectors[i]! }));
295
- const clusters = clusterEvidence(embedded, this.config.clusterDistance);
296
-
297
- const created: Hash[] = [];
298
- for (const cl of clusters) {
299
- const value = this.dag.addNode(
300
- { kind: "value", members: [...cl.members].sort(), medoid: cl.medoid },
301
- now,
302
- );
303
- created.push(value);
304
- }
305
- return created;
306
- }
307
-
308
- /**
309
- * The organic maintenance pass — Talon's "dream". Runs the full mechanical
310
- * growth cycle in order: crystallize loose evidence into values, let the
311
- * lattice reorganize (merge drifted-together values, migrating learned state),
312
- * then recompute tensions. Everything here is geometry + arithmetic; no model.
313
- * Returns a small summary. Caller commits.
314
- */
315
- async dream(
316
- embedder: Embedder,
317
- opts?: {
318
- now?: number;
319
- tension?: { minCoactivation: number; minDistance: number };
320
- },
321
- ): Promise<{
322
- crystallized: number;
323
- consolidated: ConsolidateResult;
324
- tensions: number;
325
- themes: number;
326
- }> {
327
- const now = opts?.now ?? Date.now();
328
- const crystallized = await this.crystallize(embedder, now);
329
- const consolidated = await consolidate(this.dag, embedder, this.config, {
330
- now,
331
- });
332
- const tension = opts?.tension ?? { minCoactivation: 3, minDistance: 0.5 };
333
- const tensions = await detectTensions(this.dag, embedder, {
334
- ...tension,
335
- now,
336
- });
337
- const reflected = await reflect(this.dag, embedder, this.config, { now });
338
- return {
339
- crystallized: crystallized.length,
340
- consolidated,
341
- tensions: tensions.length,
342
- themes: reflected.created.length,
343
- };
344
- }
345
-
346
- /**
347
- * Form higher-order themes over the value graph (Generative-Agents
348
- * reflection, model-free). Pass `synthesize` only if you want the optional,
349
- * label-only gated model pass. Caller commits.
350
- */
351
- async reflect(
352
- embedder: Embedder,
353
- opts?: Partial<ReflectOptions> & { now?: number },
354
- ): Promise<ReflectResult> {
355
- return reflect(this.dag, embedder, this.config, {
356
- now: opts?.now ?? Date.now(),
357
- ...(opts?.affinity !== undefined ? { affinity: opts.affinity } : {}),
358
- ...(opts?.embeddingWeight !== undefined
359
- ? { embeddingWeight: opts.embeddingWeight }
360
- : {}),
361
- ...(opts?.synthesize ? { synthesize: opts.synthesize } : {}),
362
- });
363
- }
364
-
365
- /**
366
- * Compile (or recompile) the lens for a subject from actor-tagged evidence —
367
- * how Talon refracts for that person. Supersedes the prior lens. Caller
368
- * commits. Returns the lens hash or undefined if the subject has no evidence.
369
- */
370
- compileLens(
371
- subject: string,
372
- opts?: { now?: number; boost?: number; maxEvidence?: number },
373
- ): Hash | undefined {
374
- return compileLens(this.dag, subject, {
375
- now: opts?.now ?? Date.now(),
376
- ...(opts?.boost !== undefined ? { boost: opts.boost } : {}),
377
- ...(opts?.maxEvidence !== undefined
378
- ? { maxEvidence: opts.maxEvidence }
379
- : {}),
380
- });
381
- }
382
-
383
- /** Run a single consolidation pass directly. Caller commits. */
384
- async consolidate(
385
- embedder: Embedder,
386
- opts?: { now?: number; mergeThreshold?: number },
387
- ): Promise<ConsolidateResult> {
388
- return consolidate(this.dag, embedder, this.config, {
389
- now: opts?.now ?? Date.now(),
390
- ...(opts?.mergeThreshold !== undefined
391
- ? { mergeThreshold: opts.mergeThreshold }
392
- : {}),
393
- });
394
- }
395
-
396
- // ── Read ─────────────────────────────────────────────────────────────────────
397
-
398
- project(opts?: { now?: number; lens?: string }): Projection {
399
- return projectRuntime(this.dag, {
400
- now: opts?.now ?? Date.now(),
401
- config: this.config,
402
- lens: opts?.lens,
403
- });
404
- }
405
-
406
- /**
407
- * Context-conditioned projection (Generative-Agents retrieval): surfaces the
408
- * values most relevant to the current moment, not just the globally salient
409
- * ones. Async because relevance embeds the context against value medoids.
410
- */
411
- async projectFor(
412
- embedder: Embedder,
413
- opts: {
414
- context?: string;
415
- now?: number;
416
- lens?: string;
417
- weights?: RetrievalWeights;
418
- },
419
- ): Promise<Projection> {
420
- const now = opts.now ?? Date.now();
421
- const ranked = await retrieveValues(this.dag, embedder, {
422
- now,
423
- config: this.config,
424
- centrality: pagerank(this.dag),
425
- ...(opts.context !== undefined ? { context: opts.context } : {}),
426
- ...(opts.weights ? { weights: opts.weights } : {}),
427
- });
428
- return projectRuntime(this.dag, {
429
- now,
430
- config: this.config,
431
- ...(opts.lens !== undefined ? { lens: opts.lens } : {}),
432
- order: ranked.map((r) => r.hash),
433
- });
434
- }
435
-
436
- /**
437
- * Associative recall (modern Hopfield): the constellation of values bound to
438
- * the cues by co-activation, as a one-step softmax. Read-only.
439
- */
440
- recall(cues: Hash[], opts?: RecallOptions): RecallResult[] {
441
- return associativeRecall(this.dag, cues, opts ?? {});
442
- }
443
-
444
- graph(): SoulDag {
445
- return this.dag;
446
- }
447
-
448
- history(): readonly SoulCommit[] {
449
- return this.commits;
450
- }
451
-
452
- head(): SoulCommit | undefined {
453
- return this.commits[this.commits.length - 1];
454
- }
455
-
456
- // ── Commit ───────────────────────────────────────────────────────────────────
457
-
458
- /** Digest of the mutable state, so commits capture the weather, not just structure. */
459
- private stateDigest(): Hash {
460
- const snap = this.dag.snapshot();
461
- return hashContent({
462
- state: snap.state
463
- .map(([h, s]) => [h, s.salience, s.evidence, s.activations] as const)
464
- .sort((a, b) => a[0].localeCompare(b[0])),
465
- edges: snap.edges
466
- .map((e) => [e.from, e.kind, e.to, e.weight] as const)
467
- .sort(),
468
- });
469
- }
470
-
471
- /**
472
- * Snapshot the current identity as a commit. The summary is templated from the
473
- * count of dirty nodes — deterministic, never authored.
474
- */
475
- commit(reason: string, now = Date.now()): SoulCommit {
476
- const current = this.dag.snapshot();
477
- this.lastDelta = diffSnapshots(this.lastSnapshot, current);
478
- const commit: SoulCommit = {
479
- root: this.dag.root(),
480
- stateDigest: this.stateDigest(),
481
- parent: this.head()?.root,
482
- at: now,
483
- summary: `${reason}: ${renderDelta(this.lastDelta)}`,
484
- };
485
- this.commits.push(commit);
486
- this.dag.clearDirty();
487
- this.lastSnapshot = current;
488
- return commit;
489
- }
490
-
491
- /** The structural delta from the most recent commit ("what changed in me"). */
492
- delta(): SoulDelta {
493
- return this.lastDelta;
494
- }
495
-
496
- // ── Persistence ──────────────────────────────────────────────────────────────
497
-
498
- toJSON(): PersistShape {
499
- return {
500
- version: PERSIST_VERSION,
501
- config: this.config,
502
- dag: this.dag.snapshot(),
503
- commits: [...this.commits],
504
- valence: this.valenceModel.snapshot(),
505
- drift: this.drift.snapshot(),
506
- approvals: this.approvals.snapshot(),
507
- episodic: this.episodic.snapshot(),
508
- };
509
- }
510
-
511
- save(path: string): void {
512
- mkdirSync(dirname(path), { recursive: true });
513
- writeFileAtomic.sync(path, JSON.stringify(this.toJSON()));
514
- }
515
-
516
- static fromJSON(data: PersistShape): SoulKernel {
517
- if (data.version !== PERSIST_VERSION) {
518
- throw new Error(
519
- `SoulKernel: unsupported persist version ${data.version}`,
520
- );
521
- }
522
- return new SoulKernel(
523
- SoulDag.restore(data.dag),
524
- data.config,
525
- [...data.commits],
526
- data.valence ? ValenceModel.restore(data.valence) : new ValenceModel(),
527
- data.drift ? Adwin.restore(data.drift) : new Adwin(),
528
- data.approvals
529
- ? ApprovalQueue.restore(data.approvals)
530
- : new ApprovalQueue(),
531
- data.episodic
532
- ? CompositionalMemory.restore(data.episodic)
533
- : new CompositionalMemory(),
534
- );
535
- }
536
-
537
- static load(path: string): SoulKernel {
538
- return SoulKernel.fromJSON(JSON.parse(readFileSync(path, "utf8")));
539
- }
540
- }
@@ -1,103 +0,0 @@
1
- /**
2
- * Soul Kernel — lattice tension detection.
3
- *
4
- * The Lattice is identity as a graph of values, and its most distinctive feature
5
- * is that opposed values are held in *tension* rather than averaged away
6
- * ("concise ⟷ thorough", "push back ⟷ loyal"). A tension is not authored — it is
7
- * detected, mechanically, from two mechanical facts already in the kernel:
8
- *
9
- * - the two values FIRE TOGETHER often (a strong Hebbian coactivation edge), yet
10
- * - they are SEMANTICALLY FAR APART (large cosine distance between medoids).
11
- *
12
- * A pair that is both frequently co-active and far apart is, by definition, a
13
- * tension the self has to navigate. Geometry plus counting; no model judgement.
14
- */
15
-
16
- import type { SoulDag } from "./dag.js";
17
- import { isSuperseded } from "./consolidate.js";
18
- import { cosineDistance, type Embedder } from "./embedder.js";
19
- import type { AssocEdge, Hash, ValuePayload } from "./types.js";
20
-
21
- export interface TensionOptions {
22
- /** Minimum coactivation weight for a pair to be considered. */
23
- readonly minCoactivation: number;
24
- /** Minimum cosine distance between medoids to count as opposed. */
25
- readonly minDistance: number;
26
- readonly now: number;
27
- }
28
-
29
- /** Resolve a value node's medoid text, for embedding. */
30
- function medoidText(dag: SoulDag, valueHash: Hash): string | undefined {
31
- const node = dag.getNode(valueHash);
32
- if (node?.payload.kind !== "value") return undefined;
33
- const medoid = dag.getNode((node.payload as ValuePayload).medoid);
34
- return medoid?.payload.kind === "evidence" ? medoid.payload.text : undefined;
35
- }
36
-
37
- /**
38
- * Detect tension edges across the value graph and write them into the DAG.
39
- * Returns the tension edges created or updated. Symmetric: both directions are
40
- * recorded so either value surfaces the tension.
41
- */
42
- export async function detectTensions(
43
- dag: SoulDag,
44
- embedder: Embedder,
45
- opts: TensionOptions,
46
- ): Promise<AssocEdge[]> {
47
- const values = dag.nodesOfKind("value");
48
- const texts: string[] = [];
49
- const hashes: Hash[] = [];
50
- for (const v of values) {
51
- if (isSuperseded(dag, v.hash)) continue;
52
- const text = medoidText(dag, v.hash);
53
- if (text) {
54
- texts.push(text);
55
- hashes.push(v.hash);
56
- }
57
- }
58
- if (hashes.length < 2) return [];
59
-
60
- const vectors = await embedder.embed(texts);
61
- const index = new Map(hashes.map((h, i) => [h, vectors[i]!]));
62
- const created: AssocEdge[] = [];
63
-
64
- for (let i = 0; i < hashes.length; i++) {
65
- for (let j = i + 1; j < hashes.length; j++) {
66
- const a = hashes[i]!;
67
- const b = hashes[j]!;
68
- const co = dag.getEdge(a, "coactivation", b);
69
- if (!co || co.weight < opts.minCoactivation) continue;
70
-
71
- const dist = cosineDistance(index.get(a)!, index.get(b)!);
72
- if (dist < opts.minDistance) continue;
73
-
74
- // Weight the tension by how strongly the pair both co-fires and diverges.
75
- const weight = co.weight * dist;
76
- for (const [from, to] of [
77
- [a, b],
78
- [b, a],
79
- ] as const) {
80
- const e = dag.edge(from, "tension", to, opts.now);
81
- e.weight = weight;
82
- e.updatedAt = opts.now;
83
- created.push(e);
84
- }
85
- }
86
- }
87
- return created;
88
- }
89
-
90
- /** Unique tension pairs (deduped across symmetric edges), strongest first. */
91
- export function tensionPairs(
92
- dag: SoulDag,
93
- ): { a: Hash; b: Hash; weight: number }[] {
94
- const seen = new Set<string>();
95
- const pairs: { a: Hash; b: Hash; weight: number }[] = [];
96
- for (const e of dag.allEdges("tension")) {
97
- const key = [e.from, e.to].sort().join("|");
98
- if (seen.has(key)) continue;
99
- seen.add(key);
100
- pairs.push({ a: e.from, b: e.to, weight: e.weight });
101
- }
102
- return pairs.sort((x, y) => y.weight - x.weight);
103
- }