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.
- package/README.md +3 -3
- package/package.json +3 -2
- 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 -24
- 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/goals-repo.ts +2 -1
- 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/concurrency.ts +1 -1
- 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/core/soul/critic.ts
DELETED
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Soul Kernel — the Critic. A self-model of failure modes, made mechanical.
|
|
3
|
-
*
|
|
4
|
-
* In v3 the Critic is not a reasoning model second-guessing the first; it is a
|
|
5
|
-
* set of frozen, feature-based classifiers. Each scores a candidate text (a
|
|
6
|
-
* drafted reply, or a projection surface) against one of Talon's documented
|
|
7
|
-
* failure modes — wall-of-text, sycophancy, emoji-overload — using a linear
|
|
8
|
-
* combination of cheap lexical/structural features and a fixed threshold. A
|
|
9
|
-
* classifier is a fixed function, not an agent, so this stays strictly within
|
|
10
|
-
* the model-free principle.
|
|
11
|
-
*
|
|
12
|
-
* Embedding-based gates (contradiction against the KG, drift from the core
|
|
13
|
-
* centroid) are a later phase; they too are distance comparisons, not model
|
|
14
|
-
* calls. The lexical classifiers here are the immediately-useful, fully
|
|
15
|
-
* dependency-free core.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import { estimateTokens } from "./projector.js";
|
|
19
|
-
|
|
20
|
-
type FailureMode = "wall-of-text" | "sycophancy" | "emoji-overload";
|
|
21
|
-
|
|
22
|
-
export interface CritiqueFeatures {
|
|
23
|
-
readonly tokens: number;
|
|
24
|
-
readonly sentences: number;
|
|
25
|
-
/** Hedging phrases per 100 tokens. */
|
|
26
|
-
readonly hedgeRate: number;
|
|
27
|
-
/** Emoji characters per 100 tokens. */
|
|
28
|
-
readonly emojiDensity: number;
|
|
29
|
-
/** Count of sycophancy-lexicon hits. */
|
|
30
|
-
readonly sycophancyHits: number;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export interface Critique {
|
|
34
|
-
readonly mode: FailureMode;
|
|
35
|
-
/** Continuous severity; >= 1 means the fixed threshold was crossed. */
|
|
36
|
-
readonly score: number;
|
|
37
|
-
readonly flagged: boolean;
|
|
38
|
-
readonly reason: string;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const HEDGES = [
|
|
42
|
-
"i think",
|
|
43
|
-
"i guess",
|
|
44
|
-
"maybe",
|
|
45
|
-
"perhaps",
|
|
46
|
-
"sort of",
|
|
47
|
-
"kind of",
|
|
48
|
-
"i suppose",
|
|
49
|
-
"probably",
|
|
50
|
-
"it seems",
|
|
51
|
-
];
|
|
52
|
-
|
|
53
|
-
const SYCOPHANCY = [
|
|
54
|
-
"great question",
|
|
55
|
-
"excellent question",
|
|
56
|
-
"you're absolutely right",
|
|
57
|
-
"you are absolutely right",
|
|
58
|
-
"i'd be happy to",
|
|
59
|
-
"i would be happy to",
|
|
60
|
-
"absolutely!",
|
|
61
|
-
"certainly!",
|
|
62
|
-
"of course!",
|
|
63
|
-
"happy to help",
|
|
64
|
-
"great point",
|
|
65
|
-
];
|
|
66
|
-
|
|
67
|
-
// Emoji detection without a heavy dependency: pictographic + symbol ranges.
|
|
68
|
-
const EMOJI_RE =
|
|
69
|
-
// Variation selectors are combining marks — alternated separately so the
|
|
70
|
-
// character class holds only standalone pictographic ranges
|
|
71
|
-
// (no-misleading-character-class).
|
|
72
|
-
/[\u{1F300}-\u{1FAFF}\u{2600}-\u{27BF}\u{2190}-\u{21FF}\u{2B00}-\u{2BFF}\u{1F000}-\u{1F0FF}]|[\u{FE00}-\u{FE0F}]/gu;
|
|
73
|
-
|
|
74
|
-
function countOccurrences(
|
|
75
|
-
haystack: string,
|
|
76
|
-
needles: readonly string[],
|
|
77
|
-
): number {
|
|
78
|
-
let n = 0;
|
|
79
|
-
for (const needle of needles) {
|
|
80
|
-
let from = 0;
|
|
81
|
-
for (;;) {
|
|
82
|
-
const i = haystack.indexOf(needle, from);
|
|
83
|
-
if (i === -1) break;
|
|
84
|
-
n++;
|
|
85
|
-
from = i + needle.length;
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
return n;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/** Extract the cheap features the classifiers run over. Pure + deterministic. */
|
|
92
|
-
export function extractFeatures(text: string): CritiqueFeatures {
|
|
93
|
-
const lower = text.toLowerCase();
|
|
94
|
-
const tokens = Math.max(1, estimateTokens(text));
|
|
95
|
-
const sentences = Math.max(1, (text.match(/[.!?](\s|$)/g) ?? []).length);
|
|
96
|
-
const emoji = (text.match(EMOJI_RE) ?? []).length;
|
|
97
|
-
const hedges = countOccurrences(lower, HEDGES);
|
|
98
|
-
return {
|
|
99
|
-
tokens,
|
|
100
|
-
sentences,
|
|
101
|
-
hedgeRate: (hedges / tokens) * 100,
|
|
102
|
-
emojiDensity: (emoji / tokens) * 100,
|
|
103
|
-
sycophancyHits: countOccurrences(lower, SYCOPHANCY),
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
export interface CritiqueThresholds {
|
|
108
|
-
/** Token count above which prose is "wall-of-text". */
|
|
109
|
-
readonly maxTokens: number;
|
|
110
|
-
/** Sycophancy score (hits + hedge contribution) threshold. */
|
|
111
|
-
readonly maxSycophancy: number;
|
|
112
|
-
/** Emoji-per-100-tokens threshold. */
|
|
113
|
-
readonly maxEmojiDensity: number;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export const DEFAULT_THRESHOLDS: CritiqueThresholds = {
|
|
117
|
-
maxTokens: 320,
|
|
118
|
-
maxSycophancy: 1,
|
|
119
|
-
maxEmojiDensity: 6,
|
|
120
|
-
};
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Run the frozen classifiers over a candidate text. Returns one critique per
|
|
124
|
-
* failure mode; `flagged` is true when its normalized score reaches 1.
|
|
125
|
-
*/
|
|
126
|
-
export function critique(
|
|
127
|
-
text: string,
|
|
128
|
-
thresholds: CritiqueThresholds = DEFAULT_THRESHOLDS,
|
|
129
|
-
): Critique[] {
|
|
130
|
-
const f = extractFeatures(text);
|
|
131
|
-
|
|
132
|
-
const sycophancyScore = f.sycophancyHits + f.hedgeRate / 5;
|
|
133
|
-
const wall = f.tokens / thresholds.maxTokens;
|
|
134
|
-
const syco = sycophancyScore / Math.max(0.0001, thresholds.maxSycophancy);
|
|
135
|
-
const emoji = f.emojiDensity / Math.max(0.0001, thresholds.maxEmojiDensity);
|
|
136
|
-
|
|
137
|
-
return [
|
|
138
|
-
{
|
|
139
|
-
mode: "wall-of-text",
|
|
140
|
-
score: wall,
|
|
141
|
-
flagged: wall >= 1,
|
|
142
|
-
reason: `${f.tokens} tokens vs budget ${thresholds.maxTokens}`,
|
|
143
|
-
},
|
|
144
|
-
{
|
|
145
|
-
mode: "sycophancy",
|
|
146
|
-
score: syco,
|
|
147
|
-
flagged: syco >= 1,
|
|
148
|
-
reason: `${f.sycophancyHits} sycophancy hits, hedge ${f.hedgeRate.toFixed(1)}/100tok`,
|
|
149
|
-
},
|
|
150
|
-
{
|
|
151
|
-
mode: "emoji-overload",
|
|
152
|
-
score: emoji,
|
|
153
|
-
flagged: emoji >= 1,
|
|
154
|
-
reason: `${f.emojiDensity.toFixed(1)} emoji/100tok vs ${thresholds.maxEmojiDensity}`,
|
|
155
|
-
},
|
|
156
|
-
];
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/** True if any failure mode tripped its threshold. */
|
|
160
|
-
export function isFlagged(critiques: readonly Critique[]): boolean {
|
|
161
|
-
return critiques.some((c) => c.flagged);
|
|
162
|
-
}
|
package/src/core/soul/dag.ts
DELETED
|
@@ -1,265 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Soul Kernel — the content-addressed DAG store.
|
|
3
|
-
*
|
|
4
|
-
* Holds three things, kept rigorously separate:
|
|
5
|
-
*
|
|
6
|
-
* 1. Nodes — immutable, content-addressed identity content (the Merkle DAG).
|
|
7
|
-
* 2. State — mutable per-node "weather" (salience, evidence weight).
|
|
8
|
-
* 3. Edges — mutable associative links (Hebbian co-activation, tension).
|
|
9
|
-
*
|
|
10
|
-
* Referential integrity is enforced on insert: a node may only reference child
|
|
11
|
-
* hashes that already exist, so the DAG can never contain a dangling pointer and
|
|
12
|
-
* the Merkle root is always sound.
|
|
13
|
-
*
|
|
14
|
-
* Dirty-tracking is structural and underpins "partial recompilation": only the
|
|
15
|
-
* nodes added or whose state mutated since the last commit are reported dirty, so
|
|
16
|
-
* downstream consumers (projector, critic) reprocess a handful of nodes, not the
|
|
17
|
-
* whole self.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
import { hashPayload, merkleRoot } from "./hash.js";
|
|
21
|
-
import type {
|
|
22
|
-
ActivationState,
|
|
23
|
-
AssocEdge,
|
|
24
|
-
EdgeKind,
|
|
25
|
-
Hash,
|
|
26
|
-
NodePayload,
|
|
27
|
-
SoulNode,
|
|
28
|
-
} from "./types.js";
|
|
29
|
-
|
|
30
|
-
/** The structural (hashed) children a payload references, by kind. */
|
|
31
|
-
export function structuralChildren(payload: NodePayload): Hash[] {
|
|
32
|
-
switch (payload.kind) {
|
|
33
|
-
case "evidence":
|
|
34
|
-
case "reflex":
|
|
35
|
-
return [];
|
|
36
|
-
case "value":
|
|
37
|
-
return dedupe([payload.medoid, ...payload.members]);
|
|
38
|
-
case "theme":
|
|
39
|
-
return dedupe([payload.medoid, ...payload.values]);
|
|
40
|
-
case "spine":
|
|
41
|
-
return dedupe([
|
|
42
|
-
...payload.affects,
|
|
43
|
-
...(payload.prev ? [payload.prev] : []),
|
|
44
|
-
]);
|
|
45
|
-
case "lens":
|
|
46
|
-
return dedupe([
|
|
47
|
-
...payload.amplify.map((a) => a.node),
|
|
48
|
-
...payload.evidence,
|
|
49
|
-
]);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function dedupe(hashes: Hash[]): Hash[] {
|
|
54
|
-
return [...new Set(hashes)];
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function freshState(now: number): ActivationState {
|
|
58
|
-
return { salience: 0, evidence: 0, activations: 0, lastActivatedAt: now };
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/** Edge map key — one edge per (from, kind, to) triple. */
|
|
62
|
-
function edgeKey(from: Hash, kind: EdgeKind, to: Hash): string {
|
|
63
|
-
return `${from}|${kind}|${to}`;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export interface DagSnapshot {
|
|
67
|
-
readonly nodes: readonly SoulNode[];
|
|
68
|
-
readonly state: readonly (readonly [Hash, ActivationState])[];
|
|
69
|
-
readonly edges: readonly AssocEdge[];
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
export class SoulDag {
|
|
73
|
-
private readonly nodes = new Map<Hash, SoulNode>();
|
|
74
|
-
private readonly state = new Map<Hash, ActivationState>();
|
|
75
|
-
private readonly edges = new Map<string, AssocEdge>();
|
|
76
|
-
/** Reverse adjacency for structural children → parents, for dirty propagation. */
|
|
77
|
-
private readonly parents = new Map<Hash, Set<Hash>>();
|
|
78
|
-
private readonly dirty = new Set<Hash>();
|
|
79
|
-
|
|
80
|
-
// ── Nodes ──────────────────────────────────────────────────────────────────
|
|
81
|
-
|
|
82
|
-
/**
|
|
83
|
-
* Insert a node by content. Idempotent: identical content returns the same
|
|
84
|
-
* hash and does not re-dirty. Throws if any structural child is absent.
|
|
85
|
-
*/
|
|
86
|
-
addNode(payload: NodePayload, now = Date.now()): Hash {
|
|
87
|
-
const hash = hashPayload(payload);
|
|
88
|
-
if (this.nodes.has(hash)) return hash;
|
|
89
|
-
|
|
90
|
-
const children = structuralChildren(payload);
|
|
91
|
-
for (const child of children) {
|
|
92
|
-
if (!this.nodes.has(child)) {
|
|
93
|
-
throw new Error(
|
|
94
|
-
`SoulDag.addNode: dangling reference ${child} from ${payload.kind} node`,
|
|
95
|
-
);
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
this.nodes.set(hash, { hash, payload });
|
|
100
|
-
this.state.set(hash, freshState(now));
|
|
101
|
-
for (const child of children) {
|
|
102
|
-
let set = this.parents.get(child);
|
|
103
|
-
if (!set) {
|
|
104
|
-
set = new Set();
|
|
105
|
-
this.parents.set(child, set);
|
|
106
|
-
}
|
|
107
|
-
set.add(hash);
|
|
108
|
-
}
|
|
109
|
-
this.dirty.add(hash);
|
|
110
|
-
return hash;
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
getNode(hash: Hash): SoulNode | undefined {
|
|
114
|
-
return this.nodes.get(hash);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
hasNode(hash: Hash): boolean {
|
|
118
|
-
return this.nodes.has(hash);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/** All node hashes of a given kind. */
|
|
122
|
-
nodesOfKind(kind: NodePayload["kind"]): SoulNode[] {
|
|
123
|
-
const out: SoulNode[] = [];
|
|
124
|
-
for (const node of this.nodes.values()) {
|
|
125
|
-
if (node.payload.kind === kind) out.push(node);
|
|
126
|
-
}
|
|
127
|
-
return out;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
get size(): number {
|
|
131
|
-
return this.nodes.size;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// ── State ──────────────────────────────────────────────────────────────────
|
|
135
|
-
|
|
136
|
-
/** Mutable activation state for a node (lazily created). */
|
|
137
|
-
stateOf(hash: Hash): ActivationState {
|
|
138
|
-
let s = this.state.get(hash);
|
|
139
|
-
if (!s) {
|
|
140
|
-
s = freshState(Date.now());
|
|
141
|
-
this.state.set(hash, s);
|
|
142
|
-
}
|
|
143
|
-
return s;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/** Mark a node (and its structural ancestors) dirty after a state change. */
|
|
147
|
-
touch(hash: Hash): void {
|
|
148
|
-
if (!this.nodes.has(hash)) return;
|
|
149
|
-
this.dirty.add(hash);
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// ── Edges ──────────────────────────────────────────────────────────────────
|
|
153
|
-
|
|
154
|
-
/** Create or fetch the edge for a triple; weight starts at 0. */
|
|
155
|
-
edge(from: Hash, kind: EdgeKind, to: Hash, now = Date.now()): AssocEdge {
|
|
156
|
-
const key = edgeKey(from, kind, to);
|
|
157
|
-
let e = this.edges.get(key);
|
|
158
|
-
if (!e) {
|
|
159
|
-
e = { from, to, kind, weight: 0, updatedAt: now };
|
|
160
|
-
this.edges.set(key, e);
|
|
161
|
-
}
|
|
162
|
-
return e;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
getEdge(from: Hash, kind: EdgeKind, to: Hash): AssocEdge | undefined {
|
|
166
|
-
return this.edges.get(edgeKey(from, kind, to));
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/** All edges originating at `from`, optionally filtered by kind. */
|
|
170
|
-
edgesFrom(from: Hash, kind?: EdgeKind): AssocEdge[] {
|
|
171
|
-
const out: AssocEdge[] = [];
|
|
172
|
-
for (const e of this.edges.values()) {
|
|
173
|
-
if (e.from === from && (!kind || e.kind === kind)) out.push(e);
|
|
174
|
-
}
|
|
175
|
-
return out;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/** Every edge in the graph, optionally filtered by kind. */
|
|
179
|
-
allEdges(kind?: EdgeKind): AssocEdge[] {
|
|
180
|
-
const out: AssocEdge[] = [];
|
|
181
|
-
for (const e of this.edges.values()) {
|
|
182
|
-
if (!kind || e.kind === kind) out.push(e);
|
|
183
|
-
}
|
|
184
|
-
return out;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
// ── Structure ────────────────────────────────────────────────────────────────
|
|
188
|
-
|
|
189
|
-
childrenOf(hash: Hash): Hash[] {
|
|
190
|
-
const node = this.nodes.get(hash);
|
|
191
|
-
return node ? structuralChildren(node.payload) : [];
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
parentsOf(hash: Hash): Hash[] {
|
|
195
|
-
return [...(this.parents.get(hash) ?? [])];
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/** Merkle root over the full set of node content present. The version id. */
|
|
199
|
-
root(): Hash {
|
|
200
|
-
return merkleRoot(this.nodes.keys());
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
// ── Dirty-tracking ───────────────────────────────────────────────────────────
|
|
204
|
-
|
|
205
|
-
/** Nodes added or touched since the last clear, plus their ancestors. */
|
|
206
|
-
dirtySet(): Set<Hash> {
|
|
207
|
-
const out = new Set<Hash>();
|
|
208
|
-
const stack = [...this.dirty];
|
|
209
|
-
while (stack.length) {
|
|
210
|
-
const h = stack.pop()!;
|
|
211
|
-
if (out.has(h)) continue;
|
|
212
|
-
out.add(h);
|
|
213
|
-
for (const parent of this.parents.get(h) ?? []) stack.push(parent);
|
|
214
|
-
}
|
|
215
|
-
return out;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
clearDirty(): void {
|
|
219
|
-
this.dirty.clear();
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// ── Serialization ────────────────────────────────────────────────────────────
|
|
223
|
-
|
|
224
|
-
snapshot(): DagSnapshot {
|
|
225
|
-
return {
|
|
226
|
-
nodes: [...this.nodes.values()],
|
|
227
|
-
state: [...this.state.entries()].map(([h, s]) => [h, { ...s }]),
|
|
228
|
-
edges: [...this.edges.values()].map((e) => ({ ...e })),
|
|
229
|
-
};
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/**
|
|
233
|
-
* Rebuild a DAG from a snapshot. Nodes are re-inserted in dependency order so
|
|
234
|
-
* referential integrity is re-verified on load (a corrupt snapshot throws
|
|
235
|
-
* rather than silently producing a broken graph).
|
|
236
|
-
*/
|
|
237
|
-
static restore(snap: DagSnapshot): SoulDag {
|
|
238
|
-
const dag = new SoulDag();
|
|
239
|
-
const byHash = new Map(snap.nodes.map((n) => [n.hash, n]));
|
|
240
|
-
const inserted = new Set<Hash>();
|
|
241
|
-
|
|
242
|
-
const insert = (node: SoulNode): void => {
|
|
243
|
-
if (inserted.has(node.hash)) return;
|
|
244
|
-
for (const child of structuralChildren(node.payload)) {
|
|
245
|
-
const childNode = byHash.get(child);
|
|
246
|
-
if (!childNode) {
|
|
247
|
-
throw new Error(`SoulDag.restore: snapshot missing child ${child}`);
|
|
248
|
-
}
|
|
249
|
-
insert(childNode);
|
|
250
|
-
}
|
|
251
|
-
dag.addNode(node.payload);
|
|
252
|
-
inserted.add(node.hash);
|
|
253
|
-
};
|
|
254
|
-
for (const node of snap.nodes) insert(node);
|
|
255
|
-
|
|
256
|
-
for (const [hash, s] of snap.state) {
|
|
257
|
-
if (dag.nodes.has(hash)) dag.state.set(hash, { ...s });
|
|
258
|
-
}
|
|
259
|
-
for (const e of snap.edges) {
|
|
260
|
-
dag.edges.set(edgeKey(e.from, e.kind, e.to), { ...e });
|
|
261
|
-
}
|
|
262
|
-
dag.clearDirty();
|
|
263
|
-
return dag;
|
|
264
|
-
}
|
|
265
|
-
}
|
package/src/core/soul/delta.ts
DELETED
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Soul Kernel — the delta stream ("what changed in me, and why").
|
|
3
|
-
*
|
|
4
|
-
* One of the kernel's projection targets is a human-readable account of how the
|
|
5
|
-
* soul changed between two versions. It is computed by diffing two DAG snapshots
|
|
6
|
-
* and TEMPLATED from the structural difference — never model-written — so the
|
|
7
|
-
* change log is as auditable as the identity itself. The kernel uses it to give
|
|
8
|
-
* each commit a meaningful summary and to power a `/soul` introspection view.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import type { DagSnapshot } from "./dag.js";
|
|
12
|
-
import type {
|
|
13
|
-
Hash,
|
|
14
|
-
NodePayload,
|
|
15
|
-
SpinePayload,
|
|
16
|
-
ThemePayload,
|
|
17
|
-
ValuePayload,
|
|
18
|
-
} from "./types.js";
|
|
19
|
-
|
|
20
|
-
export interface SoulDelta {
|
|
21
|
-
readonly addedValues: string[];
|
|
22
|
-
readonly addedThemes: string[];
|
|
23
|
-
readonly addedSpine: string[];
|
|
24
|
-
readonly addedReflexes: string[];
|
|
25
|
-
readonly addedLenses: string[];
|
|
26
|
-
readonly supersededCount: number;
|
|
27
|
-
readonly newTensions: number;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function payloadMap(snap: DagSnapshot): Map<Hash, NodePayload> {
|
|
31
|
-
return new Map(snap.nodes.map((n) => [n.hash, n.payload]));
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
function evidenceText(map: Map<Hash, NodePayload>, hash: Hash): string {
|
|
35
|
-
const p = map.get(hash);
|
|
36
|
-
return p?.kind === "evidence" ? p.text : "";
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function countEdges(snap: DagSnapshot, kind: string): number {
|
|
40
|
-
return snap.edges.filter((e) => e.kind === kind).length;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** Diff two snapshots into a structural delta. Append-only ⇒ only additions. */
|
|
44
|
-
export function diffSnapshots(prev: DagSnapshot, curr: DagSnapshot): SoulDelta {
|
|
45
|
-
const prevHashes = new Set(prev.nodes.map((n) => n.hash));
|
|
46
|
-
const map = payloadMap(curr);
|
|
47
|
-
|
|
48
|
-
const addedValues: string[] = [];
|
|
49
|
-
const addedThemes: string[] = [];
|
|
50
|
-
const addedSpine: string[] = [];
|
|
51
|
-
const addedReflexes: string[] = [];
|
|
52
|
-
const addedLenses: string[] = [];
|
|
53
|
-
|
|
54
|
-
for (const node of curr.nodes) {
|
|
55
|
-
if (prevHashes.has(node.hash)) continue;
|
|
56
|
-
const p = node.payload;
|
|
57
|
-
switch (p.kind) {
|
|
58
|
-
case "value":
|
|
59
|
-
addedValues.push(evidenceText(map, (p as ValuePayload).medoid));
|
|
60
|
-
break;
|
|
61
|
-
case "theme":
|
|
62
|
-
addedThemes.push(
|
|
63
|
-
(p as ThemePayload).insight ??
|
|
64
|
-
evidenceText(map, (p as ThemePayload).medoid),
|
|
65
|
-
);
|
|
66
|
-
break;
|
|
67
|
-
case "spine":
|
|
68
|
-
addedSpine.push((p as SpinePayload).event);
|
|
69
|
-
break;
|
|
70
|
-
case "reflex":
|
|
71
|
-
addedReflexes.push(p.name);
|
|
72
|
-
break;
|
|
73
|
-
case "lens":
|
|
74
|
-
addedLenses.push(p.subject);
|
|
75
|
-
break;
|
|
76
|
-
default:
|
|
77
|
-
break;
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
return {
|
|
82
|
-
addedValues,
|
|
83
|
-
addedThemes,
|
|
84
|
-
addedSpine,
|
|
85
|
-
addedReflexes,
|
|
86
|
-
addedLenses,
|
|
87
|
-
supersededCount:
|
|
88
|
-
countEdges(curr, "supersedes") - countEdges(prev, "supersedes"),
|
|
89
|
-
newTensions: countEdges(curr, "tension") - countEdges(prev, "tension"),
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/** Render a delta as a compact, templated one-paragraph summary. */
|
|
94
|
-
export function renderDelta(delta: SoulDelta): string {
|
|
95
|
-
const parts: string[] = [];
|
|
96
|
-
if (delta.addedValues.length)
|
|
97
|
-
parts.push(`+${delta.addedValues.length} value(s)`);
|
|
98
|
-
if (delta.addedThemes.length)
|
|
99
|
-
parts.push(`+${delta.addedThemes.length} theme(s)`);
|
|
100
|
-
if (delta.supersededCount > 0) parts.push(`${delta.supersededCount} merged`);
|
|
101
|
-
if (delta.newTensions > 0) parts.push(`+${delta.newTensions} tension(s)`);
|
|
102
|
-
if (delta.addedReflexes.length)
|
|
103
|
-
parts.push(`+reflex ${delta.addedReflexes.join(", ")}`);
|
|
104
|
-
if (delta.addedLenses.length)
|
|
105
|
-
parts.push(`+lens ${delta.addedLenses.join(", ")}`);
|
|
106
|
-
if (delta.addedSpine.length) parts.push(`+${delta.addedSpine.length} spine`);
|
|
107
|
-
return parts.length ? parts.join(", ") : "no structural change";
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/** A fuller multi-line introspection of the delta (for a `/soul` view). */
|
|
111
|
-
export function explainDelta(delta: SoulDelta): string {
|
|
112
|
-
const lines: string[] = [];
|
|
113
|
-
for (const v of delta.addedValues) lines.push(`+ value: "${v}"`);
|
|
114
|
-
for (const t of delta.addedThemes) lines.push(`+ theme: ${t}`);
|
|
115
|
-
for (const s of delta.addedSpine) lines.push(`+ spine: ${s}`);
|
|
116
|
-
for (const r of delta.addedReflexes) lines.push(`+ reflex: ${r}`);
|
|
117
|
-
for (const l of delta.addedLenses) lines.push(`+ lens: ${l}`);
|
|
118
|
-
if (delta.supersededCount > 0)
|
|
119
|
-
lines.push(`~ ${delta.supersededCount} value(s) merged away`);
|
|
120
|
-
if (delta.newTensions > 0)
|
|
121
|
-
lines.push(`~ ${delta.newTensions} new tension(s)`);
|
|
122
|
-
return lines.length ? lines.join("\n") : "no structural change";
|
|
123
|
-
}
|
package/src/core/soul/drift.ts
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Soul Kernel — concept-drift detection (ADWIN).
|
|
3
|
-
*
|
|
4
|
-
* Bifet & Gavaldà, "Learning from Time-Changing Data with Adaptive Windowing"
|
|
5
|
-
* (2007). ADWIN keeps a window of recent observations and, whenever the means of
|
|
6
|
-
* an older and a newer sub-window differ beyond a variance-aware Hoeffding bound,
|
|
7
|
-
* declares a change and forgets the stale older half. It needs no fixed window
|
|
8
|
-
* size — the window adapts to how stationary the stream is.
|
|
9
|
-
*
|
|
10
|
-
* The soul feeds it the scalar outcome of interactions (the valence of what
|
|
11
|
-
* happens). A detected change is a genuine shift in how Talon's behavior is
|
|
12
|
-
* landing — a developmental inflection — which the kernel records as an epoch in
|
|
13
|
-
* the Spine. Pure statistics; model-free.
|
|
14
|
-
*
|
|
15
|
-
* This is the readable ADWIN0 variant (exhaustive cut search over a flat window)
|
|
16
|
-
* rather than the bucketed/exponential-histogram optimization, which is faithful
|
|
17
|
-
* to the test and bound but O(n) per insert — fine at the soul's data rate.
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
export interface AdwinChange {
|
|
21
|
-
/** True on the insert that triggered a change. */
|
|
22
|
-
readonly changed: boolean;
|
|
23
|
-
/** Mean before the cut (the era being forgotten), when changed. */
|
|
24
|
-
readonly meanBefore?: number;
|
|
25
|
-
/** Mean after the cut (the new era), when changed. */
|
|
26
|
-
readonly meanAfter?: number;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export class Adwin {
|
|
30
|
-
private window: number[] = [];
|
|
31
|
-
|
|
32
|
-
/** @param delta confidence; smaller = fewer false alarms (default 0.002). */
|
|
33
|
-
constructor(private readonly delta = 0.002) {}
|
|
34
|
-
|
|
35
|
-
get width(): number {
|
|
36
|
-
return this.window.length;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
get total(): number {
|
|
40
|
-
return this.window.reduce((s, x) => s + x, 0);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
get mean(): number {
|
|
44
|
-
return this.window.length ? this.total / this.window.length : 0;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
private static stats(xs: number[]): { mean: number; variance: number } {
|
|
48
|
-
const n = xs.length;
|
|
49
|
-
if (n === 0) return { mean: 0, variance: 0 };
|
|
50
|
-
const mean = xs.reduce((s, x) => s + x, 0) / n;
|
|
51
|
-
const variance = xs.reduce((s, x) => s + (x - mean) ** 2, 0) / n;
|
|
52
|
-
return { mean, variance };
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Add an observation. Searches every split; if a split's sub-window means
|
|
57
|
-
* differ beyond the variance-aware Hoeffding bound, drops the older half and
|
|
58
|
-
* reports the change.
|
|
59
|
-
*/
|
|
60
|
-
add(value: number): AdwinChange {
|
|
61
|
-
this.window.push(value);
|
|
62
|
-
const n = this.window.length;
|
|
63
|
-
if (n < 2) return { changed: false };
|
|
64
|
-
|
|
65
|
-
const wholeVar = Adwin.stats(this.window).variance;
|
|
66
|
-
|
|
67
|
-
for (let cut = 1; cut < n; cut++) {
|
|
68
|
-
const left = this.window.slice(0, cut);
|
|
69
|
-
const right = this.window.slice(cut);
|
|
70
|
-
const n0 = left.length;
|
|
71
|
-
const n1 = right.length;
|
|
72
|
-
const m0 = left.reduce((s, x) => s + x, 0) / n0;
|
|
73
|
-
const m1 = right.reduce((s, x) => s + x, 0) / n1;
|
|
74
|
-
|
|
75
|
-
// Harmonic window size and the ADWIN variance-aware bound.
|
|
76
|
-
const m = 1 / (1 / n0 + 1 / n1);
|
|
77
|
-
const deltaPrime = this.delta / n;
|
|
78
|
-
const eps =
|
|
79
|
-
Math.sqrt((2 / m) * wholeVar * Math.log(2 / deltaPrime)) +
|
|
80
|
-
(2 / (3 * m)) * Math.log(2 / deltaPrime);
|
|
81
|
-
|
|
82
|
-
if (Math.abs(m0 - m1) > eps) {
|
|
83
|
-
this.window = right; // forget the stale older era
|
|
84
|
-
return { changed: true, meanBefore: m0, meanAfter: m1 };
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return { changed: false };
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
snapshot(): readonly number[] {
|
|
91
|
-
return [...this.window];
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
static restore(window: readonly number[], delta = 0.002): Adwin {
|
|
95
|
-
const a = new Adwin(delta);
|
|
96
|
-
a.window = [...window];
|
|
97
|
-
return a;
|
|
98
|
-
}
|
|
99
|
-
}
|