talon-agent 4.6.1 → 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.
- package/README.md +3 -3
- package/package.json +2 -1
- package/prompts/README.md +2 -2
- package/prompts/system/memory-recall.md +16 -0
- package/src/app.ts +19 -4
- package/src/backend/claude-sdk/handler.ts +28 -7
- package/src/backend/claude-sdk/one-shot.ts +1 -1
- package/src/backend/claude-sdk/options.ts +7 -6
- package/src/backend/claude-sdk/stream.ts +30 -3
- package/src/backend/claude-sdk/warm.ts +1 -1
- package/src/backend/codex/constants.ts +1 -1
- package/src/backend/codex/factory.ts +2 -2
- package/src/backend/codex/handler/events.ts +1 -1
- package/src/backend/codex/handler/message.ts +26 -12
- package/src/backend/codex/handler/rollout-accounting.ts +1 -1
- package/src/backend/codex/init.ts +1 -1
- package/src/backend/codex/mcp-config.ts +1 -1
- package/src/backend/codex/one-shot.ts +1 -1
- package/src/backend/kilo/handler/message.ts +4 -1
- package/src/backend/openai-agents/constants.ts +1 -1
- package/src/backend/openai-agents/factory.ts +2 -2
- package/src/backend/openai-agents/handler/events.ts +1 -1
- package/src/backend/openai-agents/handler/message.ts +8 -4
- package/src/backend/openai-agents/init.ts +1 -1
- package/src/backend/openai-agents/mcp-pool.ts +1 -1
- package/src/backend/opencode/handler/message.ts +4 -1
- package/src/backend/remote-server/chat-turn.ts +31 -23
- package/src/backend/remote-server/events.ts +3 -3
- package/src/backend/remote-server/factory.ts +6 -3
- package/src/backend/remote-server/index.ts +1 -1
- package/src/backend/remote-server/mcp.ts +1 -1
- package/src/backend/remote-server/one-shot.ts +1 -1
- package/src/backend/remote-server/server-bindings.ts +1 -1
- package/src/backend/remote-server/turn.ts +1 -1
- package/src/backend/runtime/cache/cache-metrics.ts +126 -0
- package/src/backend/{shared → runtime/cache}/cache-telemetry.ts +22 -2
- package/src/backend/{shared → runtime}/index.ts +32 -23
- package/src/backend/{shared → runtime/prompt}/delivery-contract.ts +1 -1
- package/src/backend/{shared → runtime/prompt}/prompt-format.ts +46 -2
- package/src/backend/{shared → runtime/prompt}/system-prompt.ts +4 -4
- package/src/backend/{shared → runtime/turn}/delivered-text.ts +1 -1
- package/src/backend/{shared → runtime/turn}/delivery.ts +2 -2
- package/src/backend/{shared → runtime/turn}/handle-retry.ts +5 -5
- package/src/backend/{shared → runtime/turn}/handler-to-events.ts +24 -10
- package/src/backend/{shared → runtime/turn}/handler-types.ts +7 -1
- package/src/backend/{shared → runtime/turn}/model-retry.ts +2 -2
- package/src/backend/{shared → runtime/turn}/result-events.ts +2 -2
- package/src/backend/{shared → runtime/turn}/stream-state.ts +2 -2
- package/src/backend/{shared → runtime/turn}/turn-interrupt.ts +2 -2
- package/src/backend/{shared → runtime/turn}/turn-phases.ts +6 -6
- package/src/bootstrap.ts +4 -19
- package/src/cli.ts +1 -1
- package/src/core/agent-runtime/capabilities.ts +11 -0
- package/src/core/agent-runtime/contract-tests.ts +92 -1
- package/src/core/agent-runtime/events.ts +1 -1
- package/src/core/background/{isolated-agent.ts → cron/isolated-agent.ts} +3 -3
- package/src/core/background/{job-health.ts → cron/job-health.ts} +1 -1
- package/src/core/background/{job-oneshot.ts → cron/job-oneshot.ts} +5 -5
- package/src/core/background/{job-prompt.ts → cron/job-prompt.ts} +1 -1
- package/src/core/background/{cron.ts → cron/scheduler.ts} +6 -6
- package/src/core/background/{cron-spec.ts → cron/spec.ts} +1 -1
- package/src/core/background/{dream.ts → dream/index.ts} +10 -15
- package/src/core/background/{plan-alerts.ts → pulse/plan-alerts.ts} +3 -3
- package/src/core/background/{pulse.ts → pulse/pulse.ts} +8 -5
- package/src/core/background/triggers/command.ts +1 -1
- package/src/core/config/index.ts +15 -11
- package/src/core/daemon/resource-sampler.ts +121 -0
- package/src/core/engine/dispatcher.ts +16 -0
- package/src/core/engine/gateway-actions/cron.ts +2 -2
- package/src/core/engine/gateway-actions/index.ts +3 -0
- package/src/core/engine/gateway-actions/memory.ts +335 -0
- package/src/core/engine/gateway.ts +0 -6
- package/src/core/memory/import.ts +3 -2
- package/src/core/memory/taps.ts +199 -0
- package/src/core/memory/turn-retrieval.ts +222 -0
- package/src/core/prompt/assemble.ts +2 -12
- package/src/core/prompt/index.ts +2 -2
- package/src/core/prompt/invalidation.ts +1 -1
- package/src/core/tasks/index.ts +1 -1
- package/src/core/tools/index.ts +2 -0
- package/src/core/tools/memory.ts +128 -0
- package/src/core/tools/types.ts +1 -0
- package/src/core/weaver/turn-cpu.ts +32 -0
- package/src/core/weaver/weaver.ts +36 -0
- package/src/frontend/discord/admin.ts +1 -1
- package/src/frontend/discord/callbacks/components/backend-select.ts +1 -1
- package/src/frontend/discord/callbacks/components/pulse.ts +1 -1
- package/src/frontend/discord/callbacks/components/settings.ts +1 -1
- package/src/frontend/discord/callbacks/modals.ts +4 -1
- package/src/frontend/discord/commands/admin.ts +1 -35
- package/src/frontend/discord/commands/definitions.ts +0 -11
- package/src/frontend/discord/commands/router.ts +0 -3
- package/src/frontend/discord/commands/session.ts +6 -0
- package/src/frontend/discord/commands/settings.ts +1 -1
- package/src/frontend/discord/middleware.ts +1 -25
- package/src/frontend/discord/runtime.ts +1 -2
- package/src/frontend/native/{auth.ts → bridge/auth.ts} +2 -2
- package/src/frontend/native/{discovery.ts → bridge/discovery.ts} +3 -3
- package/src/frontend/native/{routes → bridge/routes}/chats.ts +1 -1
- package/src/frontend/native/{routes → bridge/routes}/daemon.ts +1 -1
- package/src/frontend/native/{routes → bridge/routes}/host.ts +6 -3
- package/src/frontend/native/{routes → bridge/routes}/pre-auth.ts +1 -1
- package/src/frontend/native/{server.ts → bridge/server.ts} +3 -3
- package/src/frontend/native/{tls.ts → bridge/tls.ts} +2 -2
- package/src/frontend/native/{chat-lifecycle.ts → chats/chat-lifecycle.ts} +3 -3
- package/src/frontend/native/{chat-wire.ts → chats/chat-wire.ts} +4 -4
- package/src/frontend/native/{chats.ts → chats/chats.ts} +4 -4
- package/src/frontend/native/{empty-chat-sweep.ts → chats/empty-chat-sweep.ts} +4 -4
- package/src/frontend/native/{history.ts → chats/history.ts} +7 -7
- package/src/frontend/native/{reset.ts → chats/reset.ts} +7 -7
- package/src/frontend/native/index.ts +13 -10
- package/src/frontend/native/{media.ts → media/media.ts} +3 -3
- package/src/frontend/native/runtime.ts +1 -1
- package/src/frontend/native/{control.ts → surface/control.ts} +4 -4
- package/src/frontend/native/{extensions.ts → surface/extensions.ts} +12 -9
- package/src/frontend/native/{handlers.ts → surface/handlers.ts} +19 -14
- package/src/frontend/native/{logs.ts → surface/logs.ts} +2 -2
- package/src/frontend/native/{memory.ts → surface/memory.ts} +2 -2
- package/src/frontend/native/{models.ts → surface/models.ts} +17 -10
- package/src/frontend/native/{settings.ts → surface/settings.ts} +9 -9
- package/src/frontend/native/{status.ts → surface/status.ts} +3 -3
- package/src/frontend/native/{actions.ts → turn/actions.ts} +7 -4
- package/src/frontend/native/{context.ts → turn/context.ts} +8 -8
- package/src/frontend/native/{emit.ts → turn/emit.ts} +7 -7
- package/src/frontend/native/{queue.ts → turn/queue.ts} +3 -3
- package/src/frontend/native/{turn-meta.ts → turn/turn-meta.ts} +2 -2
- package/src/frontend/native/{turn.ts → turn/turn.ts} +9 -9
- package/src/frontend/shared/model-commands.ts +1 -1
- package/src/frontend/shared/session-status.ts +35 -8
- package/src/frontend/shared/status-context.ts +101 -2
- package/src/frontend/telegram/admin/background.ts +1 -1
- package/src/frontend/telegram/callbacks/model/backend.ts +1 -1
- package/src/frontend/telegram/callbacks/pulse.ts +1 -1
- package/src/frontend/telegram/callbacks/settings.ts +1 -1
- package/src/frontend/telegram/commands/admin.ts +2 -37
- package/src/frontend/telegram/commands/index.ts +1 -1
- package/src/frontend/telegram/commands/session.ts +6 -0
- package/src/frontend/telegram/commands/settings.ts +1 -1
- package/src/frontend/telegram/handlers/messages.ts +0 -10
- package/src/frontend/telegram/index.ts +1 -5
- package/src/frontend/telegram/middleware.ts +1 -15
- package/src/frontend/whatsapp/access.ts +2 -2
- package/src/frontend/whatsapp/actions/history.ts +2 -2
- package/src/frontend/whatsapp/actions/messaging.ts +2 -2
- package/src/frontend/whatsapp/actions/moderation.ts +1 -1
- package/src/frontend/whatsapp/actions/send.ts +1 -1
- package/src/frontend/whatsapp/commands.ts +8 -2
- package/src/frontend/whatsapp/{connection.ts → connection/connection.ts} +5 -5
- package/src/frontend/whatsapp/{pairing-service.ts → connection/pairing-service.ts} +3 -3
- package/src/frontend/whatsapp/{wa-logger.ts → connection/wa-logger.ts} +2 -2
- package/src/frontend/whatsapp/index.ts +4 -4
- package/src/frontend/whatsapp/{inbound.ts → messages/inbound.ts} +15 -15
- package/src/frontend/whatsapp/{media-store.ts → messages/media-store.ts} +4 -4
- package/src/frontend/whatsapp/{message-store.ts → messages/message-store.ts} +1 -1
- package/src/frontend/whatsapp/{turn-recovery.ts → messages/turn-recovery.ts} +4 -4
- package/src/frontend/whatsapp/registry.ts +1 -1
- package/src/frontend/whatsapp/runtime.ts +1 -1
- package/src/index.ts +1 -1
- package/src/storage/db.ts +6 -1
- package/src/storage/memory.ts +59 -8
- package/src/storage/metrics.ts +38 -0
- package/src/storage/repositories/sessions-repo.ts +6 -0
- package/src/storage/session-record.ts +13 -2
- package/src/storage/sessions.ts +12 -0
- package/src/storage/sql/db.sql +5 -0
- package/src/storage/sql/schema.sql +4 -0
- package/src/storage/sql/sessions.sql +3 -3
- package/src/storage/sql/statements.generated.ts +10 -3
- package/src/storage/sql/turn-meta.sql +1 -1
- package/src/util/boot-timer.ts +15 -1
- package/src/util/chat-id.ts +30 -0
- package/src/util/log.ts +1 -1
- package/src/util/paths.ts +0 -2
- package/src/core/soul/README.md +0 -110
- package/src/core/soul/RESEARCH.md +0 -98
- package/src/core/soul/associative.ts +0 -98
- package/src/core/soul/centrality.ts +0 -98
- package/src/core/soul/cluster.ts +0 -83
- package/src/core/soul/compiler.ts +0 -207
- package/src/core/soul/consolidate.ts +0 -179
- package/src/core/soul/critic.ts +0 -162
- package/src/core/soul/dag.ts +0 -265
- package/src/core/soul/delta.ts +0 -123
- package/src/core/soul/drift.ts +0 -99
- package/src/core/soul/embedder.ts +0 -129
- package/src/core/soul/emergent-critic.ts +0 -96
- package/src/core/soul/forgetting.ts +0 -131
- package/src/core/soul/governance.ts +0 -93
- package/src/core/soul/hash.ts +0 -97
- package/src/core/soul/hdc.ts +0 -154
- package/src/core/soul/kernel.ts +0 -540
- package/src/core/soul/lattice.ts +0 -103
- package/src/core/soul/lens.ts +0 -110
- package/src/core/soul/projector.ts +0 -240
- package/src/core/soul/reflect.ts +0 -170
- package/src/core/soul/reflex.ts +0 -164
- package/src/core/soul/retrieve.ts +0 -146
- package/src/core/soul/salience.ts +0 -146
- package/src/core/soul/service.ts +0 -204
- package/src/core/soul/settings.ts +0 -47
- package/src/core/soul/signals.ts +0 -117
- package/src/core/soul/talon-embedder.ts +0 -80
- package/src/core/soul/taps.ts +0 -199
- package/src/core/soul/types.ts +0 -298
- package/src/core/soul/valence.ts +0 -83
- /package/src/backend/{shared → runtime}/frontends.ts +0 -0
- /package/src/backend/{shared → runtime}/metrics.ts +0 -0
- /package/src/backend/{shared → runtime}/sleep.ts +0 -0
- /package/src/backend/{shared → runtime/turn}/flow-violation.ts +0 -0
- /package/src/backend/{shared → runtime}/usage.ts +0 -0
- /package/src/core/{scripting/lua-runner.ts → scripts/lua.ts} +0 -0
- /package/src/frontend/native/{routes → bridge/routes}/index.ts +0 -0
- /package/src/frontend/native/{routes → bridge/routes}/memory.ts +0 -0
- /package/src/frontend/native/{routes → bridge/routes}/mesh.ts +0 -0
- /package/src/frontend/native/{routes → bridge/routes}/models.ts +0 -0
- /package/src/frontend/native/{routes → bridge/routes}/params.ts +0 -0
- /package/src/frontend/native/{routes → bridge/routes}/table.ts +0 -0
- /package/src/frontend/native/{tool-result.ts → turn/tool-result.ts} +0 -0
- /package/src/frontend/whatsapp/{auth-state.ts → connection/auth-state.ts} +0 -0
- /package/src/frontend/whatsapp/{identity.ts → connection/identity.ts} +0 -0
- /package/src/frontend/whatsapp/{pairing-lock.ts → connection/pairing-lock.ts} +0 -0
- /package/src/frontend/whatsapp/{pairing.ts → connection/pairing.ts} +0 -0
- /package/src/frontend/whatsapp/{pins.ts → messages/pins.ts} +0 -0
package/src/storage/metrics.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { emptyPhases, TURN_PHASES, type TurnPhase } from "./session-record.js";
|
|
|
15
15
|
const PHASE_HISTOGRAM: Record<TurnPhase, string> = {
|
|
16
16
|
queueWait: "turn.queue_wait_ms",
|
|
17
17
|
warpResolve: "turn.warp_resolve_ms",
|
|
18
|
+
memory: "turn.memory_ms",
|
|
18
19
|
firstToken: "turn.first_token_ms",
|
|
19
20
|
stream: "turn.stream_ms",
|
|
20
21
|
delivery: "turn.delivery_ms",
|
|
@@ -30,6 +31,42 @@ const legacyCounters = new Map<string, number>();
|
|
|
30
31
|
*/
|
|
31
32
|
const processHistograms = new Map<string, MetricsLatencyAgg>();
|
|
32
33
|
|
|
34
|
+
/**
|
|
35
|
+
* A chat's last cross-turn cache verdict — did the turn's FIRST request
|
|
36
|
+
* read the previous turn's prefix (`hit`), pay to re-write it (`miss`), or
|
|
37
|
+
* was nothing cacheable at all (`none`)? Mirrors `CrossTurnVerdict` in
|
|
38
|
+
* backend/runtime/cache/cache-telemetry.ts, which owns the classification; the
|
|
39
|
+
* union is restated here because `/status` renders it and frontends may
|
|
40
|
+
* not import backend/ (.dependency-cruiser.cjs: frontend-not-to-backend).
|
|
41
|
+
*/
|
|
42
|
+
export type CacheVerdict = "hit" | "miss" | "none";
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Per-chat last verdict. Process-local and deliberately unpersisted: it
|
|
46
|
+
* describes the live prefix, which dies with the process anyway, and
|
|
47
|
+
* `/status` is the only reader. Bounded like the per-chat maps in
|
|
48
|
+
* cache-telemetry.ts, insertion-ordered eviction.
|
|
49
|
+
*/
|
|
50
|
+
const MAX_TRACKED_CHATS = 256;
|
|
51
|
+
const lastCacheVerdicts = new Map<string, CacheVerdict>();
|
|
52
|
+
|
|
53
|
+
/** Record the chat's newest cross-turn cache verdict. */
|
|
54
|
+
export function noteCacheVerdict(chatId: string, verdict: CacheVerdict): void {
|
|
55
|
+
if (
|
|
56
|
+
lastCacheVerdicts.size >= MAX_TRACKED_CHATS &&
|
|
57
|
+
!lastCacheVerdicts.has(chatId)
|
|
58
|
+
) {
|
|
59
|
+
const oldest = lastCacheVerdicts.keys().next().value;
|
|
60
|
+
if (oldest !== undefined) lastCacheVerdicts.delete(oldest);
|
|
61
|
+
}
|
|
62
|
+
lastCacheVerdicts.set(chatId, verdict);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** The chat's last cross-turn verdict, or undefined if none was recorded. */
|
|
66
|
+
export function getCacheVerdict(chatId: string): CacheVerdict | undefined {
|
|
67
|
+
return lastCacheVerdicts.get(chatId);
|
|
68
|
+
}
|
|
69
|
+
|
|
33
70
|
export type MetricsSnapshot = {
|
|
34
71
|
counters: Record<string, number>;
|
|
35
72
|
histograms: Record<
|
|
@@ -202,5 +239,6 @@ export function getTodayMetrics(): MetricsSnapshot {
|
|
|
202
239
|
export function resetMetrics(): void {
|
|
203
240
|
legacyCounters.clear();
|
|
204
241
|
processHistograms.clear();
|
|
242
|
+
lastCacheVerdicts.clear();
|
|
205
243
|
resetAllSessionMetrics();
|
|
206
244
|
}
|
|
@@ -42,6 +42,7 @@ type Row = {
|
|
|
42
42
|
total_response_ms: number;
|
|
43
43
|
last_response_ms: number;
|
|
44
44
|
fastest_response_ms: number | null;
|
|
45
|
+
last_turn_ended_at: number | null;
|
|
45
46
|
metrics: string | null;
|
|
46
47
|
};
|
|
47
48
|
|
|
@@ -81,6 +82,7 @@ function rowToSession(row: Row): SessionState {
|
|
|
81
82
|
lastBotMessageId: row.last_bot_message_id ?? undefined,
|
|
82
83
|
sessionName: row.session_name ?? undefined,
|
|
83
84
|
lastModel: row.last_model ?? undefined,
|
|
85
|
+
lastTurnEndedAt: row.last_turn_ended_at ?? undefined,
|
|
84
86
|
};
|
|
85
87
|
}
|
|
86
88
|
|
|
@@ -120,6 +122,10 @@ export function upsert(chatId: string, session: SessionState): void {
|
|
|
120
122
|
num(usage?.totalResponseMs),
|
|
121
123
|
num(usage?.lastResponseMs),
|
|
122
124
|
typeof fastest === "number" && Number.isFinite(fastest) ? fastest : null,
|
|
125
|
+
typeof session.lastTurnEndedAt === "number" &&
|
|
126
|
+
Number.isFinite(session.lastTurnEndedAt)
|
|
127
|
+
? session.lastTurnEndedAt
|
|
128
|
+
: null,
|
|
123
129
|
JSON.stringify(session.metrics ?? emptyMetrics()),
|
|
124
130
|
);
|
|
125
131
|
}
|
|
@@ -56,12 +56,14 @@ export type MetricsLatencyAgg = {
|
|
|
56
56
|
|
|
57
57
|
/**
|
|
58
58
|
* Where a turn's wall-clock went, as seen from the Weaver: waiting in the
|
|
59
|
-
* chat's FIFO, resolving the warp,
|
|
60
|
-
* whole stream, and the slice of it spent
|
|
59
|
+
* chat's FIFO, resolving the warp, retrieving this turn's memory, until
|
|
60
|
+
* the backend's first event, the whole stream, and the slice of it spent
|
|
61
|
+
* inside frontend delivery.
|
|
61
62
|
*/
|
|
62
63
|
export const TURN_PHASES = [
|
|
63
64
|
"queueWait",
|
|
64
65
|
"warpResolve",
|
|
66
|
+
"memory",
|
|
65
67
|
"firstToken",
|
|
66
68
|
"stream",
|
|
67
69
|
"delivery",
|
|
@@ -104,6 +106,14 @@ export type SessionState = {
|
|
|
104
106
|
sessionName?: string;
|
|
105
107
|
/** Model used for this session's cost tracking. */
|
|
106
108
|
lastModel?: string;
|
|
109
|
+
/**
|
|
110
|
+
* When the last turn of this chat finished (ms epoch), or undefined when
|
|
111
|
+
* no turn has completed yet. Distinct from `lastActive`, which any
|
|
112
|
+
* session write moves: this one only ever means "the transcript stopped
|
|
113
|
+
* growing here", which is what the prompt cache's TTL runs against —
|
|
114
|
+
* warm iff `now - lastTurnEndedAt < ttl` (docs/cache-economics.md).
|
|
115
|
+
*/
|
|
116
|
+
lastTurnEndedAt?: number;
|
|
107
117
|
};
|
|
108
118
|
|
|
109
119
|
export const emptyUsage = (): SessionUsage => ({
|
|
@@ -146,6 +156,7 @@ export const emptyLatency = (): MetricsLatencyAgg => ({
|
|
|
146
156
|
export const emptyPhases = (): MetricsPhaseAggs => ({
|
|
147
157
|
queueWait: emptyLatency(),
|
|
148
158
|
warpResolve: emptyLatency(),
|
|
159
|
+
memory: emptyLatency(),
|
|
149
160
|
firstToken: emptyLatency(),
|
|
150
161
|
stream: emptyLatency(),
|
|
151
162
|
delivery: emptyLatency(),
|
package/src/storage/sessions.ts
CHANGED
|
@@ -480,6 +480,10 @@ export function recordUsage(
|
|
|
480
480
|
session.usage.estimatedCostUsd += turn.costUsd;
|
|
481
481
|
}
|
|
482
482
|
if (turn.model) session.lastModel = turn.model;
|
|
483
|
+
// A turn just ended — this is the timestamp the prompt cache ages
|
|
484
|
+
// against (docs/cache-economics.md). Set on the one seam every backend
|
|
485
|
+
// funnels a finished turn through, failed-but-billed turns included.
|
|
486
|
+
session.lastTurnEndedAt = Date.now();
|
|
483
487
|
if (turn.durationMs && turn.durationMs > 0) {
|
|
484
488
|
session.usage.totalResponseMs =
|
|
485
489
|
(session.usage.totalResponseMs || 0) + turn.durationMs;
|
|
@@ -580,6 +584,12 @@ export type SessionInfo = {
|
|
|
580
584
|
lastModel?: string;
|
|
581
585
|
/** True when a turn is currently streaming and `usage` includes its live so-far counts. */
|
|
582
586
|
turnInProgress?: boolean;
|
|
587
|
+
/**
|
|
588
|
+
* When this chat's last turn finished (ms epoch), or undefined when none
|
|
589
|
+
* has. `/status` reports the idle gap from it; `lastActive` can't stand in
|
|
590
|
+
* because any session write moves that.
|
|
591
|
+
*/
|
|
592
|
+
lastTurnEndedAt?: number;
|
|
583
593
|
};
|
|
584
594
|
|
|
585
595
|
export function getSessionInfo(chatId: string): SessionInfo {
|
|
@@ -594,6 +604,7 @@ export function getSessionInfo(chatId: string): SessionInfo {
|
|
|
594
604
|
sessionName: session?.sessionName,
|
|
595
605
|
lastModel: session?.lastModel,
|
|
596
606
|
turnInProgress: liveTurns.has(chatId),
|
|
607
|
+
lastTurnEndedAt: session?.lastTurnEndedAt,
|
|
597
608
|
};
|
|
598
609
|
}
|
|
599
610
|
|
|
@@ -615,6 +626,7 @@ export function getAllSessions(): Array<{ chatId: string; info: SessionInfo }> {
|
|
|
615
626
|
sessionName: session.sessionName,
|
|
616
627
|
lastModel: session.lastModel,
|
|
617
628
|
turnInProgress: liveTurns.has(chatId),
|
|
629
|
+
lastTurnEndedAt: session.lastTurnEndedAt,
|
|
618
630
|
},
|
|
619
631
|
}));
|
|
620
632
|
}
|
package/src/storage/sql/db.sql
CHANGED
|
@@ -25,3 +25,8 @@ ALTER TABLE sessions ADD COLUMN metrics TEXT NOT NULL DEFAULT '{"lifetime":{"cou
|
|
|
25
25
|
-- carry more than one attachment. Fresh databases get the column via
|
|
26
26
|
-- schema.sql.
|
|
27
27
|
ALTER TABLE history_messages ADD COLUMN attachments TEXT
|
|
28
|
+
|
|
29
|
+
-- name: addSessionsLastTurnEndedAtColumn
|
|
30
|
+
-- Column reconciliation for databases that shipped before the cache-age
|
|
31
|
+
-- signal existed. Fresh databases get the column via schema.sql.
|
|
32
|
+
ALTER TABLE sessions ADD COLUMN last_turn_ended_at INTEGER
|
|
@@ -161,6 +161,10 @@ CREATE TABLE IF NOT EXISTS sessions (
|
|
|
161
161
|
total_response_ms REAL NOT NULL DEFAULT 0,
|
|
162
162
|
last_response_ms REAL NOT NULL DEFAULT 0,
|
|
163
163
|
fastest_response_ms REAL,
|
|
164
|
+
-- When the chat's last turn finished; NULL until one has. Read as the
|
|
165
|
+
-- prompt cache's age signal (docs/cache-economics.md), which is why it
|
|
166
|
+
-- is separate from last_active (moved by any session write).
|
|
167
|
+
last_turn_ended_at INTEGER,
|
|
164
168
|
metrics TEXT NOT NULL DEFAULT '{"lifetime":{"counters":{"queries":0,"toolCalls":0,"turnsWithTools":0,"apiCalls":0,"inputTokens":0,"outputTokens":0,"cacheReadTokens":0,"cacheWriteTokens":0,"failedTurns":0,"flowViolationRetries":0,"flowViolationCapExhausted":0,"trailingTextDropped":0},"latency":{"count":0,"sumMs":0,"minMs":null,"maxMs":0},"toolCallsByName":{},"backend":{},"cacheHitPercent":{"count":0,"sumMs":0,"minMs":null,"maxMs":0},"toolCallsPerTurn":{"count":0,"sumMs":0,"minMs":null,"maxMs":0},"apiCallsPerTurn":{"count":0,"sumMs":0,"minMs":null,"maxMs":0}},"buckets":{}}'
|
|
165
169
|
);
|
|
166
170
|
|
|
@@ -7,15 +7,15 @@ INSERT OR REPLACE INTO sessions
|
|
|
7
7
|
created_at, last_bot_message_id, total_input_tokens, total_output_tokens,
|
|
8
8
|
total_cache_read, total_cache_write, last_prompt_tokens, context_tokens,
|
|
9
9
|
context_window, num_api_calls, estimated_cost_usd, total_response_ms,
|
|
10
|
-
last_response_ms, fastest_response_ms, metrics)
|
|
11
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
10
|
+
last_response_ms, fastest_response_ms, last_turn_ended_at, metrics)
|
|
11
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
12
12
|
|
|
13
13
|
-- name: all
|
|
14
14
|
SELECT chat_id, session_id, session_name, last_model, turns, last_active,
|
|
15
15
|
created_at, last_bot_message_id, total_input_tokens, total_output_tokens,
|
|
16
16
|
total_cache_read, total_cache_write, last_prompt_tokens, context_tokens,
|
|
17
17
|
context_window, num_api_calls, estimated_cost_usd, total_response_ms,
|
|
18
|
-
last_response_ms, fastest_response_ms, metrics
|
|
18
|
+
last_response_ms, fastest_response_ms, last_turn_ended_at, metrics
|
|
19
19
|
FROM sessions
|
|
20
20
|
|
|
21
21
|
-- name: remove
|
|
@@ -166,6 +166,10 @@ CREATE TABLE IF NOT EXISTS sessions (
|
|
|
166
166
|
total_response_ms REAL NOT NULL DEFAULT 0,
|
|
167
167
|
last_response_ms REAL NOT NULL DEFAULT 0,
|
|
168
168
|
fastest_response_ms REAL,
|
|
169
|
+
-- When the chat's last turn finished; NULL until one has. Read as the
|
|
170
|
+
-- prompt cache's age signal (docs/cache-economics.md), which is why it
|
|
171
|
+
-- is separate from last_active (moved by any session write).
|
|
172
|
+
last_turn_ended_at INTEGER,
|
|
169
173
|
metrics TEXT NOT NULL DEFAULT '{"lifetime":{"counters":{"queries":0,"toolCalls":0,"turnsWithTools":0,"apiCalls":0,"inputTokens":0,"outputTokens":0,"cacheReadTokens":0,"cacheWriteTokens":0,"failedTurns":0,"flowViolationRetries":0,"flowViolationCapExhausted":0,"trailingTextDropped":0},"latency":{"count":0,"sumMs":0,"minMs":null,"maxMs":0},"toolCallsByName":{},"backend":{},"cacheHitPercent":{"count":0,"sumMs":0,"minMs":null,"maxMs":0},"toolCallsPerTurn":{"count":0,"sumMs":0,"minMs":null,"maxMs":0},"apiCallsPerTurn":{"count":0,"sumMs":0,"minMs":null,"maxMs":0}},"buckets":{}}'
|
|
170
174
|
);
|
|
171
175
|
|
|
@@ -414,6 +418,9 @@ ALTER TABLE sessions ADD COLUMN metrics TEXT NOT NULL DEFAULT '{"lifetime":{"cou
|
|
|
414
418
|
-- carry more than one attachment. Fresh databases get the column via
|
|
415
419
|
-- schema.sql.
|
|
416
420
|
ALTER TABLE history_messages ADD COLUMN attachments TEXT`,
|
|
421
|
+
addSessionsLastTurnEndedAtColumn: `-- Column reconciliation for databases that shipped before the cache-age
|
|
422
|
+
-- signal existed. Fresh databases get the column via schema.sql.
|
|
423
|
+
ALTER TABLE sessions ADD COLUMN last_turn_ended_at INTEGER`,
|
|
417
424
|
} as const;
|
|
418
425
|
|
|
419
426
|
export const goalsSql = {
|
|
@@ -657,13 +664,13 @@ export const sessionsSql = {
|
|
|
657
664
|
created_at, last_bot_message_id, total_input_tokens, total_output_tokens,
|
|
658
665
|
total_cache_read, total_cache_write, last_prompt_tokens, context_tokens,
|
|
659
666
|
context_window, num_api_calls, estimated_cost_usd, total_response_ms,
|
|
660
|
-
last_response_ms, fastest_response_ms, metrics)
|
|
661
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
667
|
+
last_response_ms, fastest_response_ms, last_turn_ended_at, metrics)
|
|
668
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
662
669
|
all: `SELECT chat_id, session_id, session_name, last_model, turns, last_active,
|
|
663
670
|
created_at, last_bot_message_id, total_input_tokens, total_output_tokens,
|
|
664
671
|
total_cache_read, total_cache_write, last_prompt_tokens, context_tokens,
|
|
665
672
|
context_window, num_api_calls, estimated_cost_usd, total_response_ms,
|
|
666
|
-
last_response_ms, fastest_response_ms, metrics
|
|
673
|
+
last_response_ms, fastest_response_ms, last_turn_ended_at, metrics
|
|
667
674
|
FROM sessions`,
|
|
668
675
|
remove: `DELETE FROM sessions WHERE chat_id = ?`,
|
|
669
676
|
} as const;
|
package/src/util/boot-timer.ts
CHANGED
|
@@ -4,7 +4,9 @@
|
|
|
4
4
|
* has an answer in the log instead of a profiler session.
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
export type BootPhaseSample = { label: string; ms: number };
|
|
8
|
+
|
|
9
|
+
const phases: BootPhaseSample[] = [];
|
|
8
10
|
|
|
9
11
|
/** Run one startup phase and remember how long it took. */
|
|
10
12
|
export async function bootPhase<T>(
|
|
@@ -25,6 +27,18 @@ export function bootReport(now = Math.round(process.uptime() * 1000)): string {
|
|
|
25
27
|
return parts.length ? `${now}ms (${parts.join(", ")})` : `${now}ms`;
|
|
26
28
|
}
|
|
27
29
|
|
|
30
|
+
/**
|
|
31
|
+
* The recorded phases, in the order they completed.
|
|
32
|
+
*
|
|
33
|
+
* `util/` is a leaf (.dependency-cruiser.cjs: util-is-a-leaf), so this
|
|
34
|
+
* module cannot write to the metrics store itself. It hands the samples
|
|
35
|
+
* out instead and `core/daemon/resource-sampler.ts` turns them into
|
|
36
|
+
* `boot.<phase>_ms` histograms.
|
|
37
|
+
*/
|
|
38
|
+
export function bootPhases(): readonly BootPhaseSample[] {
|
|
39
|
+
return phases;
|
|
40
|
+
}
|
|
41
|
+
|
|
28
42
|
/** Test seam. */
|
|
29
43
|
export function resetBootPhases(): void {
|
|
30
44
|
phases.length = 0;
|
package/src/util/chat-id.ts
CHANGED
|
@@ -67,3 +67,33 @@ export function isWhatsAppChatId(chatId: string): boolean {
|
|
|
67
67
|
export function isDiscordChatId(chatId: string): boolean {
|
|
68
68
|
return chatId.startsWith("discord_");
|
|
69
69
|
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Classify a canonical chat ID as a one-to-one chat, a multi-party one,
|
|
73
|
+
* or something this grammar cannot tell apart.
|
|
74
|
+
*
|
|
75
|
+
* The ID is the only signal engine-side code has: a frontend's own
|
|
76
|
+
* `isGroup` flag rides on the inbound message, while stores, jobs and
|
|
77
|
+
* gateway actions are keyed by the string ID alone. Per frontend:
|
|
78
|
+
*
|
|
79
|
+
* - `discord_guild_…` / `discord_dm_…` and `wa_group_…` / `wa_dm_…`
|
|
80
|
+
* say which they are outright;
|
|
81
|
+
* - a Telegram ID is bare digits — negative for a group, supergroup or
|
|
82
|
+
* channel, positive for the user's own ID (a DM);
|
|
83
|
+
* - terminal (`t_…`, `"1"`) and native (`d_…`) chats are local,
|
|
84
|
+
* single-operator surfaces, so they are DMs;
|
|
85
|
+
* - `teams_chat_…` covers 1:1 *and* group Teams chats alike, so it is
|
|
86
|
+
* `"unknown"` — as is anything a future frontend invents.
|
|
87
|
+
*
|
|
88
|
+
* Callers decide what `"unknown"` means for them; anything trust- or
|
|
89
|
+
* privacy-sensitive should treat it as a group and fail closed.
|
|
90
|
+
*/
|
|
91
|
+
export function chatScope(chatId: string): "dm" | "group" | "unknown" {
|
|
92
|
+
if (chatId.startsWith("discord_guild_")) return "group";
|
|
93
|
+
if (chatId.startsWith("discord_dm_")) return "dm";
|
|
94
|
+
if (chatId.startsWith("wa_group_")) return "group";
|
|
95
|
+
if (chatId.startsWith("wa_dm_")) return "dm";
|
|
96
|
+
if (isTelegramChatId(chatId)) return chatId.startsWith("-") ? "group" : "dm";
|
|
97
|
+
if (isTerminalChatId(chatId) || isNativeChatId(chatId)) return "dm";
|
|
98
|
+
return "unknown";
|
|
99
|
+
}
|
package/src/util/log.ts
CHANGED
package/src/util/paths.ts
CHANGED
|
@@ -116,8 +116,6 @@ export const files = {
|
|
|
116
116
|
triggers: resolve(TALON_ROOT, "data", "triggers.json"),
|
|
117
117
|
/** Legacy JSON native turn meta (imported into talon.db on first boot) */
|
|
118
118
|
nativeTurnMeta: resolve(TALON_ROOT, "data", "native-turn-meta.json"),
|
|
119
|
-
/** Soul kernel state: ~/.talon/data/soul.json (overridable via config.soul.path) */
|
|
120
|
-
soul: resolve(TALON_ROOT, "data", "soul.json"),
|
|
121
119
|
/** Legacy JSON media index (imported into talon.db on first boot) */
|
|
122
120
|
mediaIndex: resolve(TALON_ROOT, "data", "media-index.json"),
|
|
123
121
|
/** Persistent memory: ~/.talon/workspace/memory/memory.md */
|
package/src/core/soul/README.md
DELETED
|
@@ -1,110 +0,0 @@
|
|
|
1
|
-
# Soul Kernel
|
|
2
|
-
|
|
3
|
-
Talon's compiled identity. Not a markdown prompt, not a JSON blob — a
|
|
4
|
-
**content-addressed Merkle DAG** of typed identity nodes, learned **mechanically**
|
|
5
|
-
from behavioral telemetry and projected into the system prompt by **selection**.
|
|
6
|
-
|
|
7
|
-
> The model is the brain. Talon is the body. The Soul is the persistent identity
|
|
8
|
-
> artifact that tells the brain how to be Talon.
|
|
9
|
-
|
|
10
|
-
## The thesis: the model never writes its own soul
|
|
11
|
-
|
|
12
|
-
There are two jobs — _being the self_ (the reasoning model reads the soul and
|
|
13
|
-
acts) and _writing the self_ (turning experience into identity). If the reasoning
|
|
14
|
-
model does the second, it can rationalize, flatter itself, and confabulate a
|
|
15
|
-
coherent self-story that isn't true. So **writing the self is strictly
|
|
16
|
-
model-free**: continuous, deterministic, evidence-grounded. The reasoning model is
|
|
17
|
-
a _reader_ of the soul, never its author. That is the safety property, not just a
|
|
18
|
-
cost saving.
|
|
19
|
-
|
|
20
|
-
The only neural component anywhere is an optional **local embedder** (for
|
|
21
|
-
clustering/dedup/contradiction-distance) — a frozen fixed function, not an agent.
|
|
22
|
-
Everything else is counters, exponential decay, dot products, and selection.
|
|
23
|
-
|
|
24
|
-
## The substrate
|
|
25
|
-
|
|
26
|
-
A node's identity is the sha-256 of its canonical content. The single most
|
|
27
|
-
important decision is the split between:
|
|
28
|
-
|
|
29
|
-
- **Content** (hashed, immutable) — a node's semantic identity: kind + verbatim
|
|
30
|
-
payload + referenced child hashes. A change of content is a new node.
|
|
31
|
-
- **State** (mutable, keyed by hash) — salience, evidence weight, activation
|
|
32
|
-
counts. Churns every tick; lives _outside_ the Merkle structure.
|
|
33
|
-
|
|
34
|
-
So reinforcing a value a thousand times never churns a single content hash.
|
|
35
|
-
Versioning, provenance, dedup, and partial recompilation fall out structurally;
|
|
36
|
-
learning and decay live in the state layer.
|
|
37
|
-
|
|
38
|
-
## The five structures
|
|
39
|
-
|
|
40
|
-
| Structure | Node kind | What it is |
|
|
41
|
-
| --------- | --------------- | ------------------------------------------------------------------------------------------- |
|
|
42
|
-
| Spine | `spine` | append-only causal narrative — _why_ Talon became Talon |
|
|
43
|
-
| Lattice | `value` + edges | values as evidence clusters; tension/affinity edges self-organize via Hebbian co-activation |
|
|
44
|
-
| Reflexes | `reflex` | `trigger → guard → action` rules the harness _enforces_ |
|
|
45
|
-
| Holograph | `lens` | identity refracted per interlocutor (selection + reweighting) |
|
|
46
|
-
| Critic | (classifiers) | frozen feature-based gates over failure modes |
|
|
47
|
-
|
|
48
|
-
The atoms under all of it are `evidence` nodes — verbatim ground truth. The kernel
|
|
49
|
-
never paraphrases; it only ever quotes.
|
|
50
|
-
|
|
51
|
-
## The model-free pipeline
|
|
52
|
-
|
|
53
|
-
```
|
|
54
|
-
telemetry ──▶ signals ──▶ compiler ──▶ DAG (+ salience/Hebbian) ──▶ projector ──▶ runtime.md
|
|
55
|
-
(harness) (structured) (arithmetic) (content + state) (selection) (injected)
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
- **Signals** (`signals.ts`) — structured harness events (reaction, engagement,
|
|
59
|
-
correction, reflex-fire). Interpretation already happened; no transcript reading.
|
|
60
|
-
- **Compiler** (`compiler.ts`) — routes each signal to pure update rules. Outcome
|
|
61
|
-
signals reinforce/penalize the values that were live in that turn's projection
|
|
62
|
-
and Hebbian-coactivate them; corrections store verbatim evidence + a Spine event.
|
|
63
|
-
- **Salience** (`salience.ts`) — lazy exponential decay, reinforcement, Hebbian
|
|
64
|
-
edges, predictive-coding delta rule. The whole "learning" is here, all arithmetic.
|
|
65
|
-
- **Projector** (`projector.ts`) — selects verbatim evidence, salience-ordered
|
|
66
|
-
under a token budget. Reflexes are never budgeted away. The soul cannot
|
|
67
|
-
hallucinate itself.
|
|
68
|
-
- **Critic** (`critic.ts`) — frozen classifiers for wall-of-text, sycophancy,
|
|
69
|
-
emoji-overload.
|
|
70
|
-
- **Kernel** (`kernel.ts`) — orchestrator: genesis → ingest → commit → project,
|
|
71
|
-
with a git-like commit chain and JSON persistence.
|
|
72
|
-
|
|
73
|
-
## File map
|
|
74
|
-
|
|
75
|
-
```
|
|
76
|
-
types.ts node payloads, edges, activation state, commits, config
|
|
77
|
-
hash.ts canonical serialization + content addressing + Merkle root
|
|
78
|
-
dag.ts the DAG store: integrity, dirty propagation, snapshot/restore
|
|
79
|
-
salience.ts decay / reinforce / coactivate / prediction-error (the math)
|
|
80
|
-
reflex.ts predicate registry + enforcer + the three seed reflexes
|
|
81
|
-
signals.ts structured telemetry types + emoji→valence
|
|
82
|
-
compiler.ts signal → arithmetic update routing (writing the self)
|
|
83
|
-
projector.ts DAG → runtime surface by selection (reading the self)
|
|
84
|
-
critic.ts frozen failure-mode classifiers
|
|
85
|
-
kernel.ts orchestrator: lifecycle, commits, persistence
|
|
86
|
-
index.ts public surface
|
|
87
|
-
```
|
|
88
|
-
|
|
89
|
-
## Status
|
|
90
|
-
|
|
91
|
-
Implemented and tested (150+ tests, model-free end to end):
|
|
92
|
-
|
|
93
|
-
- **Substrate** — content-addressed Merkle DAG, dirty-tracking, commits, persistence.
|
|
94
|
-
- **Learning** — salience decay/reinforce, Hebbian edges, predictive-coding updates.
|
|
95
|
-
- **Emergence** — value clustering (`crystallize`), self-reorganizing `consolidate`
|
|
96
|
-
("dream") with adaptive thresholds + state migration, reflection into `theme`s.
|
|
97
|
-
- **Embedding** — our own `TalonEmbedder` (model-free) behind a pluggable seam.
|
|
98
|
-
- **Adaptation** — learned valence (meaning from outcomes), FSRS/DSR forgetting,
|
|
99
|
-
ADWIN drift → Spine epochs, emergent failure modes.
|
|
100
|
-
- **Recall** — Generative-Agents context retrieval, PageRank centrality, modern
|
|
101
|
-
Hopfield associative recall, VSA/HDC compositional episodic memory.
|
|
102
|
-
- **Refraction** — relational holograph lens compilation.
|
|
103
|
-
- **Surfaces** — projector (selection), delta stream, mechanical Critic.
|
|
104
|
-
- **Governance** — protected-node approval queue; runtime gate (off by default).
|
|
105
|
-
|
|
106
|
-
See `RESEARCH.md` for the literature behind each mechanism.
|
|
107
|
-
|
|
108
|
-
Remaining: **harness wiring** — telemetry taps (reactions/engagement/corrections →
|
|
109
|
-
signals), prompt injection of the projected surface, and live reflex enforcement.
|
|
110
|
-
This touches the live Talon runtime and is gated behind `TALON_SOUL_ENABLED`.
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
# Soul Kernel — research grounding
|
|
2
|
-
|
|
3
|
-
Every mechanism in the kernel is model-free, but few are invented. This maps the
|
|
4
|
-
load-bearing ideas to the literature they come from, and notes where each lives.
|
|
5
|
-
|
|
6
|
-
## Implemented
|
|
7
|
-
|
|
8
|
-
### Content-addressed Merkle DAG — `hash.ts`, `dag.ts`
|
|
9
|
-
|
|
10
|
-
- Merkle, "A Digital Signature Based on a Conventional Encryption Function" (1987);
|
|
11
|
-
the git object model (Torvalds/Hamano, 2005).
|
|
12
|
-
- Why: content addressing makes dedup, provenance, versioning, and partial
|
|
13
|
-
recompilation structural rather than bolted on.
|
|
14
|
-
|
|
15
|
-
### Hebbian association — `salience.coactivate`, `lattice.ts`
|
|
16
|
-
|
|
17
|
-
- Hebb, _The Organization of Behavior_ (1949): "cells that fire together wire
|
|
18
|
-
together."
|
|
19
|
-
- Why: the value lattice's affinity/tension edges self-organize from
|
|
20
|
-
co-activation counts; no model decides what relates.
|
|
21
|
-
|
|
22
|
-
### Predictive-coding / free-energy updates — `salience.applyPredictionError`
|
|
23
|
-
|
|
24
|
-
- Rao & Ballard (1999); Friston, free-energy principle (2010).
|
|
25
|
-
- Why: the soul is a predictive model of its own behavior; a correction is a
|
|
26
|
-
large prediction error and updates salience proportionally.
|
|
27
|
-
|
|
28
|
-
### Complementary Learning Systems / systems consolidation — `consolidate.ts`
|
|
29
|
-
|
|
30
|
-
- McClelland, McNaughton & O'Reilly (1995); memory replay during sleep
|
|
31
|
-
(Wilson & McNaughton, 1994).
|
|
32
|
-
- Why: fast intake (crystallize) vs. slow reorganization (the "dream" pass that
|
|
33
|
-
merges drifted-together values and migrates learned state) mirrors the
|
|
34
|
-
fast-hippocampal / slow-neocortical split.
|
|
35
|
-
|
|
36
|
-
### Feature hashing embedder — `talon-embedder.ts`
|
|
37
|
-
|
|
38
|
-
- Weinberger et al., "Feature Hashing for Large Scale Multitask Learning" (2009);
|
|
39
|
-
signed hashing to debias collisions.
|
|
40
|
-
- Why: a deterministic, dependency-free, model-free encoder we own — robust to
|
|
41
|
-
typos via shared character n-grams; a real sentence encoder drops in behind the
|
|
42
|
-
same interface.
|
|
43
|
-
|
|
44
|
-
### Generative-Agents retrieval — `retrieve.ts`
|
|
45
|
-
|
|
46
|
-
- Park et al., "Generative Agents: Interactive Simulacra of Human Behavior" (2023).
|
|
47
|
-
- Score = normalized(recency) + normalized(importance) + normalized(relevance),
|
|
48
|
-
each min-max'd to [0,1] before summation, exactly as in the paper. Makes the
|
|
49
|
-
projected soul _relevant to the current moment_, not just globally salient.
|
|
50
|
-
|
|
51
|
-
### FSRS / DSR adaptive forgetting — `forgetting.ts`
|
|
52
|
-
|
|
53
|
-
- Wozniak's DSR model; FSRS (open-spaced-repetition); power-law forgetting
|
|
54
|
-
(Wickelgren, 1974) which fits human retention better than an exponential.
|
|
55
|
-
- Retrievability R(t) = (1 + FACTOR·t/S)^DECAY with R(S) ≈ 0.9; stability S grows
|
|
56
|
-
on spaced successful recall and shrinks on a lapse. Core traits become durable,
|
|
57
|
-
fads evaporate — emergently.
|
|
58
|
-
|
|
59
|
-
### Modern Hopfield associative recall — `associative.ts`
|
|
60
|
-
|
|
61
|
-
- Ramsauer et al., "Hopfield Networks is All You Need" (2020); Krotov & Hopfield
|
|
62
|
-
(2016, 2020). Continuous Hopfield retrieval is one softmax step — the same
|
|
63
|
-
operation as attention.
|
|
64
|
-
- Recall from a cue = softmax over Hebbian edge weights, p ∝ exp(β·w); `prime`
|
|
65
|
-
applies the update to the lattice so the soul recalls in constellations.
|
|
66
|
-
|
|
67
|
-
### Reflection — `reflect.ts`
|
|
68
|
-
|
|
69
|
-
- Park et al. (2023): synthesize higher-level inferences over time. Done
|
|
70
|
-
structurally — themes form over values that are coherent AND co-active. The one
|
|
71
|
-
optional, gated model pass (a label-only `synthesize` hook) lives here and can
|
|
72
|
-
never change which values group.
|
|
73
|
-
|
|
74
|
-
### Concept-drift detection — `drift.ts`
|
|
75
|
-
|
|
76
|
-
- Bifet & Gavaldà, "Learning from Time-Changing Data with Adaptive Windowing"
|
|
77
|
-
(ADWIN, 2007). Detects shifts in interaction-outcome distribution and opens a
|
|
78
|
-
developmental epoch in the Spine.
|
|
79
|
-
|
|
80
|
-
### Personalized PageRank centrality — `centrality.ts`
|
|
81
|
-
|
|
82
|
-
- Page, Brin, Motwani & Winograd (1999). Structural importance over the
|
|
83
|
-
co-activation graph; personalizable to a context.
|
|
84
|
-
|
|
85
|
-
### Hyperdimensional / Vector-Symbolic memory — `hdc.ts`
|
|
86
|
-
|
|
87
|
-
- Kanerva, "Hyperdimensional Computing" (2009). bind/bundle/permute/cleanup over
|
|
88
|
-
high-dim bipolar vectors give a compositional, model-free episodic memory:
|
|
89
|
-
context⊗value bound and bundled, recalled by unbinding — "in a situation like
|
|
90
|
-
this, what do I do?"
|
|
91
|
-
|
|
92
|
-
## Future directions (researched, not yet built)
|
|
93
|
-
|
|
94
|
-
- **Holographic Reduced Representations** (Plate, 1995) as a circular-convolution
|
|
95
|
-
alternative to elementwise binding, for graceful capacity scaling.
|
|
96
|
-
- **Sparse distributed memory** (Kanerva, 1988) as an alternative episodic store.
|
|
97
|
-
- **Active-inference planning** (Friston et al.) atop the existing predictive-
|
|
98
|
-
coding updates, to _select_ behavior, not just learn from it.
|
|
@@ -1,98 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Soul Kernel — associative recall (modern Hopfield / attention).
|
|
3
|
-
*
|
|
4
|
-
* Grounded in Ramsauer et al., "Hopfield Networks is All You Need" (2020), which
|
|
5
|
-
* showed that a continuous modern Hopfield network retrieves a stored pattern in
|
|
6
|
-
* ONE update via a softmax over similarities — the very operation at the heart of
|
|
7
|
-
* attention. We apply the same rule to the lattice: the Hebbian coactivation
|
|
8
|
-
* edges are the stored associations, and recalling from a cue is a one-step
|
|
9
|
-
* softmax over the edge weights.
|
|
10
|
-
*
|
|
11
|
-
* p(neighbor) ∝ exp(β · w(cue → neighbor))
|
|
12
|
-
*
|
|
13
|
-
* `prime` then performs the Hopfield update against the lattice itself: it nudges
|
|
14
|
-
* the salience of the associatively-bound partners. The behavioral effect is what
|
|
15
|
-
* you would want from a self — thinking of one value brings its companions to
|
|
16
|
-
* mind ("push back" primes "be direct"), so the soul recalls in coherent
|
|
17
|
-
* constellations rather than isolated points. Still model-free: a softmax over
|
|
18
|
-
* counts.
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import type { SoulDag } from "./dag.js";
|
|
22
|
-
import type { Hash } from "./types.js";
|
|
23
|
-
|
|
24
|
-
export interface RecallResult {
|
|
25
|
-
readonly node: Hash;
|
|
26
|
-
/** Softmax weight in (0, 1]; the constellation sums to 1. */
|
|
27
|
-
readonly weight: number;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export interface RecallOptions {
|
|
31
|
-
/** Inverse temperature; higher β sharpens recall toward the strongest bond. */
|
|
32
|
-
readonly beta?: number;
|
|
33
|
-
/** Keep only the top-K partners (default: all). */
|
|
34
|
-
readonly topK?: number;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* One-step associative retrieval from one or more cue nodes. Aggregates
|
|
39
|
-
* coactivation edge weights to each neighbor, then softmaxes. Cues are excluded
|
|
40
|
-
* from their own recall.
|
|
41
|
-
*/
|
|
42
|
-
export function associativeRecall(
|
|
43
|
-
dag: SoulDag,
|
|
44
|
-
cues: readonly Hash[],
|
|
45
|
-
opts: RecallOptions = {},
|
|
46
|
-
): RecallResult[] {
|
|
47
|
-
const beta = opts.beta ?? 1;
|
|
48
|
-
const cueSet = new Set(cues);
|
|
49
|
-
const score = new Map<Hash, number>();
|
|
50
|
-
for (const cue of cues) {
|
|
51
|
-
for (const e of dag.edgesFrom(cue, "coactivation")) {
|
|
52
|
-
if (cueSet.has(e.to)) continue;
|
|
53
|
-
score.set(e.to, (score.get(e.to) ?? 0) + e.weight);
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
if (score.size === 0) return [];
|
|
57
|
-
|
|
58
|
-
// Numerically-stable softmax over β·score.
|
|
59
|
-
const entries = [...score.entries()];
|
|
60
|
-
const max = Math.max(...entries.map(([, w]) => w));
|
|
61
|
-
const exps = entries.map(
|
|
62
|
-
([h, w]) => [h, Math.exp(beta * (w - max))] as const,
|
|
63
|
-
);
|
|
64
|
-
const z = exps.reduce((s, [, e]) => s + e, 0);
|
|
65
|
-
|
|
66
|
-
const out = exps
|
|
67
|
-
.map(([node, e]) => ({ node, weight: e / z }))
|
|
68
|
-
.sort((a, b) => b.weight - a.weight || a.node.localeCompare(b.node));
|
|
69
|
-
return opts.topK ? out.slice(0, opts.topK) : out;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export interface PrimeOptions extends RecallOptions {
|
|
73
|
-
readonly now: number;
|
|
74
|
-
/** Salience injected, distributed across the recalled constellation. */
|
|
75
|
-
readonly gain: number;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* The Hopfield update applied to the lattice: recall from the cues and add
|
|
80
|
-
* salience to the bound partners in proportion to their recall weight. Returns
|
|
81
|
-
* the primed nodes. Caller commits.
|
|
82
|
-
*/
|
|
83
|
-
export function prime(
|
|
84
|
-
dag: SoulDag,
|
|
85
|
-
cues: readonly Hash[],
|
|
86
|
-
opts: PrimeOptions,
|
|
87
|
-
): Hash[] {
|
|
88
|
-
const recalled = associativeRecall(dag, cues, opts);
|
|
89
|
-
const primed: Hash[] = [];
|
|
90
|
-
for (const { node, weight } of recalled) {
|
|
91
|
-
const s = dag.stateOf(node);
|
|
92
|
-
s.salience += opts.gain * weight;
|
|
93
|
-
s.lastActivatedAt = opts.now;
|
|
94
|
-
dag.touch(node);
|
|
95
|
-
primed.push(node);
|
|
96
|
-
}
|
|
97
|
-
return primed;
|
|
98
|
-
}
|