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
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Soul Kernel — Talon's own embedder.
|
|
3
|
-
*
|
|
4
|
-
* We own this rather than borrowing MemPalace's: the soul should not depend on
|
|
5
|
-
* another subsystem's model lifecycle, and identity geometry deserves a fixed,
|
|
6
|
-
* versioned function we control. It is deterministic, dependency-free, and
|
|
7
|
-
* model-free — a strong lexical encoder, not a neural one — so it can run
|
|
8
|
-
* anywhere, forever, without weights or a runtime.
|
|
9
|
-
*
|
|
10
|
-
* Construction (richer than plain n-gram hashing):
|
|
11
|
-
* - word unigrams and bigrams capture phrasing,
|
|
12
|
-
* - character 3/4/5-grams capture morphology and typos (paraphrase robustness),
|
|
13
|
-
* - sublinear term weighting (1 + log count) damps repetition,
|
|
14
|
-
* - signed feature hashing into a wide space limits collision bias,
|
|
15
|
-
* - L2 normalization makes cosine the natural metric.
|
|
16
|
-
*
|
|
17
|
-
* The Embedder interface is the seam: if we ever want a real sentence-transformer
|
|
18
|
-
* (e.g. ONNX, local), it drops in behind the same interface with zero changes
|
|
19
|
-
* upstream. Until then, this is the canonical embedder for the kernel.
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import { createHash } from "node:crypto";
|
|
23
|
-
import { normalize, type Embedder } from "./embedder.js";
|
|
24
|
-
|
|
25
|
-
const TOKEN_RE = /[a-z0-9]+/g;
|
|
26
|
-
|
|
27
|
-
export interface TalonEmbedderOptions {
|
|
28
|
-
readonly dim?: number;
|
|
29
|
-
readonly charNgrams?: readonly number[];
|
|
30
|
-
readonly wordBigrams?: boolean;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
export class TalonEmbedder implements Embedder {
|
|
34
|
-
readonly dim: number;
|
|
35
|
-
private readonly charNgrams: readonly number[];
|
|
36
|
-
private readonly wordBigrams: boolean;
|
|
37
|
-
|
|
38
|
-
constructor(opts: TalonEmbedderOptions = {}) {
|
|
39
|
-
this.dim = opts.dim ?? 1024;
|
|
40
|
-
this.charNgrams = opts.charNgrams ?? [3, 4, 5];
|
|
41
|
-
this.wordBigrams = opts.wordBigrams ?? true;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
async embed(texts: readonly string[]): Promise<number[][]> {
|
|
45
|
-
return texts.map((t) => this.embedOne(t));
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** Synchronous single-text embedding — handy for internal hot paths. */
|
|
49
|
-
embedOne(text: string): number[] {
|
|
50
|
-
const counts = new Map<string, number>();
|
|
51
|
-
const bump = (feature: string): void => {
|
|
52
|
-
counts.set(feature, (counts.get(feature) ?? 0) + 1);
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const lower = text.toLowerCase();
|
|
56
|
-
const words = lower.match(TOKEN_RE) ?? [];
|
|
57
|
-
for (const w of words) bump(`w:${w}`);
|
|
58
|
-
if (this.wordBigrams) {
|
|
59
|
-
for (let i = 0; i + 1 < words.length; i++) {
|
|
60
|
-
bump(`b:${words[i]}_${words[i + 1]}`);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
const stream = ` ${words.join(" ")} `;
|
|
64
|
-
for (const n of this.charNgrams) {
|
|
65
|
-
for (let i = 0; i + n <= stream.length; i++) {
|
|
66
|
-
bump(`c${n}:${stream.slice(i, i + n)}`);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const vec = Array.from({ length: this.dim }, () => 0);
|
|
71
|
-
for (const [feature, count] of counts) {
|
|
72
|
-
const h = createHash("md5").update(feature).digest();
|
|
73
|
-
const idx = ((h[0]! << 16) | (h[1]! << 8) | h[2]!) % this.dim;
|
|
74
|
-
const sign = (h[3]! & 1) === 0 ? 1 : -1;
|
|
75
|
-
const weight = 1 + Math.log(count); // sublinear term weighting
|
|
76
|
-
vec[idx]! += sign * weight;
|
|
77
|
-
}
|
|
78
|
-
return normalize(vec);
|
|
79
|
-
}
|
|
80
|
-
}
|
package/src/core/soul/taps.ts
DELETED
|
@@ -1,199 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Soul Kernel — runtime taps.
|
|
3
|
-
*
|
|
4
|
-
* The thin glue that turns live runtime events into kernel signals. The kernel
|
|
5
|
-
* itself never reads a transcript; it consumes *structured* events the harness
|
|
6
|
-
* already produces. This module is where those events are recognized and handed
|
|
7
|
-
* to {@link SoulService}.
|
|
8
|
-
*
|
|
9
|
-
* Everything here is gated on the soul being enabled (`getSoul().enabled`). When
|
|
10
|
-
* it's off, every function is a cheap no-op and nothing is tracked or allocated
|
|
11
|
-
* beyond the empty maps — so a deployment without a soul behaves byte-identically.
|
|
12
|
-
*
|
|
13
|
-
* Two responsibilities:
|
|
14
|
-
* 1. Remember which messages Talon *itself* sent, so a later reaction update
|
|
15
|
-
* can be attributed to one of our own messages. A reaction to Talon is
|
|
16
|
-
* feedback about Talon; a reaction to someone else's message is not.
|
|
17
|
-
* 2. Classify inbound user text that is addressed to Talon into directives and
|
|
18
|
-
* corrections, using high-precision mechanical heuristics (no model).
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
import { getSoul } from "./service.js";
|
|
22
|
-
import { log } from "../../util/log.js";
|
|
23
|
-
|
|
24
|
-
// ── Bot-message memory ───────────────────────────────────────────────────────
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Per-chat bounded ring of recently-sent bot message IDs. Telegram's
|
|
28
|
-
* `message_reaction` update tells us *which* message was reacted to but not who
|
|
29
|
-
* authored it, so we keep a small FIFO of our own outgoing IDs and attribute a
|
|
30
|
-
* reaction only when it lands on one of them. Bounded so a long-lived process
|
|
31
|
-
* never grows this without limit.
|
|
32
|
-
*/
|
|
33
|
-
const RING_PER_CHAT = 200;
|
|
34
|
-
const botMessages = new Map<string, number[]>();
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* The action names that produce a *new* outgoing bot message worth tracking.
|
|
38
|
-
* Deliberately excludes `react`, `edit_message`, `pin_message`, etc.: those
|
|
39
|
-
* either return someone else's message id or mutate an existing one, so crediting
|
|
40
|
-
* a reaction to them would be wrong.
|
|
41
|
-
*/
|
|
42
|
-
export const BOT_MESSAGE_ACTIONS: ReadonlySet<string> = new Set([
|
|
43
|
-
"send_message",
|
|
44
|
-
"reply_to",
|
|
45
|
-
"send_message_with_buttons",
|
|
46
|
-
"send_file",
|
|
47
|
-
"send_photo",
|
|
48
|
-
"send_video",
|
|
49
|
-
"send_animation",
|
|
50
|
-
"send_voice",
|
|
51
|
-
"send_audio",
|
|
52
|
-
"send_sticker",
|
|
53
|
-
"send_poll",
|
|
54
|
-
"send_location",
|
|
55
|
-
"send_contact",
|
|
56
|
-
"send_dice",
|
|
57
|
-
"copy_message",
|
|
58
|
-
"forward_message",
|
|
59
|
-
]);
|
|
60
|
-
|
|
61
|
-
/** Record that Talon sent a message, so a later reaction can be attributed. */
|
|
62
|
-
export function noteBotMessage(chatId: string | number, msgId: number): void {
|
|
63
|
-
if (!getSoul().enabled) return;
|
|
64
|
-
if (!Number.isFinite(msgId) || msgId <= 0) return;
|
|
65
|
-
const key = String(chatId);
|
|
66
|
-
const ring = botMessages.get(key) ?? [];
|
|
67
|
-
if (ring.includes(msgId)) return;
|
|
68
|
-
ring.push(msgId);
|
|
69
|
-
if (ring.length > RING_PER_CHAT) ring.shift();
|
|
70
|
-
botMessages.set(key, ring);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** True when `msgId` is one of Talon's own recently-sent messages in this chat. */
|
|
74
|
-
export function isBotMessage(chatId: string | number, msgId: number): boolean {
|
|
75
|
-
return botMessages.get(String(chatId))?.includes(msgId) ?? false;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** Drop all tracked bot messages (tests / explicit reset). */
|
|
79
|
-
export function resetBotMessages(): void {
|
|
80
|
-
botMessages.clear();
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// ── Reaction tap ─────────────────────────────────────────────────────────────
|
|
84
|
-
|
|
85
|
-
/** A `ReactionType`-shaped item from Telegram's reaction arrays. */
|
|
86
|
-
export interface ReactionTypeLike {
|
|
87
|
-
readonly type: string;
|
|
88
|
-
readonly emoji?: string;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* The emojis present in `next` but not in `prev` — i.e. reactions just *added*.
|
|
93
|
-
* Custom and paid reactions (no `.emoji`) are ignored; only standard emoji carry
|
|
94
|
-
* a valence the kernel understands.
|
|
95
|
-
*/
|
|
96
|
-
export function newlyAddedEmojis(
|
|
97
|
-
prev: readonly ReactionTypeLike[],
|
|
98
|
-
next: readonly ReactionTypeLike[],
|
|
99
|
-
): string[] {
|
|
100
|
-
const before = new Set(
|
|
101
|
-
prev.filter((r) => r.type === "emoji" && r.emoji).map((r) => r.emoji),
|
|
102
|
-
);
|
|
103
|
-
return next
|
|
104
|
-
.filter((r) => r.type === "emoji" && r.emoji && !before.has(r.emoji))
|
|
105
|
-
.map((r) => r.emoji as string);
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* A user added reactions to one of Talon's messages → reinforce/penalize the
|
|
110
|
-
* values that were on stage when that message was produced. No-op unless the
|
|
111
|
-
* soul is on, at least one emoji was added, and the target is a tracked bot
|
|
112
|
-
* message.
|
|
113
|
-
*/
|
|
114
|
-
export function recordReactionToBot(
|
|
115
|
-
chatId: string | number,
|
|
116
|
-
msgId: number,
|
|
117
|
-
addedEmojis: readonly string[],
|
|
118
|
-
): boolean {
|
|
119
|
-
if (!getSoul().enabled) return false;
|
|
120
|
-
if (addedEmojis.length === 0) return false;
|
|
121
|
-
if (!isBotMessage(chatId, msgId)) return false;
|
|
122
|
-
for (const emoji of addedEmojis) getSoul().recordReaction(emoji);
|
|
123
|
-
log("soul", `reaction ${addedEmojis.join(" ")} on bot msg ${msgId}`);
|
|
124
|
-
return true;
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// ── Directive / correction tap ───────────────────────────────────────────────
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* High-precision cues that a message is a *correction* of Talon's behavior. Kept
|
|
131
|
-
* tight on purpose: a false positive penalizes whatever values were on stage, so
|
|
132
|
-
* we would rather miss a soft correction than mislabel ordinary chat.
|
|
133
|
-
*/
|
|
134
|
-
const CORRECTION_PATTERNS: readonly RegExp[] = [
|
|
135
|
-
/^\s*(no|nope|nah)\b[\s,.!:-]/i,
|
|
136
|
-
/\bthat'?s (wrong|incorrect|not (right|correct)|false)\b/i,
|
|
137
|
-
/\b(you'?re|you are) wrong\b/i,
|
|
138
|
-
/\bnot what i (asked|meant|wanted|said)\b/i,
|
|
139
|
-
/\bnever (do|say) that( again)?\b/i,
|
|
140
|
-
/\b(stop|quit) (doing|saying) that\b/i,
|
|
141
|
-
/\bwrong[\s,.!]/i,
|
|
142
|
-
/\byou (messed|screwed|fucked) (that|this|it)? ?up\b/i,
|
|
143
|
-
];
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* High-precision cues that a message is a *directive* about how to be — a
|
|
147
|
-
* standing instruction rather than a one-off request. These are stored verbatim
|
|
148
|
-
* as evidence, so again we favor precision over recall.
|
|
149
|
-
*/
|
|
150
|
-
const DIRECTIVE_PATTERNS: readonly RegExp[] = [
|
|
151
|
-
/\bfrom now on\b/i,
|
|
152
|
-
/\bgoing forward\b/i,
|
|
153
|
-
/\bin (the )?future\b/i,
|
|
154
|
-
/\byou should (always|never)\b/i,
|
|
155
|
-
/\b(always|never) (do|say|reply|respond|answer|use|be|call|check)\b/i,
|
|
156
|
-
/\bi (want|need|'?d like) you to\b/i,
|
|
157
|
-
/\bmake sure (you|to|that)\b/i,
|
|
158
|
-
/\bremember to\b/i,
|
|
159
|
-
];
|
|
160
|
-
|
|
161
|
-
export type MessageClass = "directive" | "correction" | null;
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* Classify a single inbound message. Returns "correction" or "directive" when a
|
|
165
|
-
* cue matches, else null. Corrections are checked first because a correction is
|
|
166
|
-
* the more specific (and more consequential) signal. Very long messages are
|
|
167
|
-
* skipped — standing instructions and corrections are terse.
|
|
168
|
-
*/
|
|
169
|
-
export function classifyMessage(text: string): MessageClass {
|
|
170
|
-
const t = text.trim();
|
|
171
|
-
if (t.length === 0 || t.length > 500) return null;
|
|
172
|
-
if (CORRECTION_PATTERNS.some((re) => re.test(t))) return "correction";
|
|
173
|
-
if (DIRECTIVE_PATTERNS.some((re) => re.test(t))) return "directive";
|
|
174
|
-
return null;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* Feed an inbound message to the kernel if it is addressed to Talon and reads as
|
|
179
|
-
* a directive or correction. Returns the class recorded (or null). No-op unless
|
|
180
|
-
* the soul is on.
|
|
181
|
-
*/
|
|
182
|
-
export function recordMessageSignal(opts: {
|
|
183
|
-
readonly text: string;
|
|
184
|
-
readonly actor?: string;
|
|
185
|
-
readonly addressedToBot: boolean;
|
|
186
|
-
}): MessageClass {
|
|
187
|
-
if (!getSoul().enabled) return null;
|
|
188
|
-
if (!opts.addressedToBot) return null;
|
|
189
|
-
const cls = classifyMessage(opts.text);
|
|
190
|
-
const text = opts.text.trim();
|
|
191
|
-
if (cls === "correction") {
|
|
192
|
-
getSoul().recordCorrection(text, opts.actor);
|
|
193
|
-
log("soul", `correction recorded from ${opts.actor ?? "user"}`);
|
|
194
|
-
} else if (cls === "directive") {
|
|
195
|
-
getSoul().recordDirective(text, opts.actor);
|
|
196
|
-
log("soul", `directive recorded from ${opts.actor ?? "user"}`);
|
|
197
|
-
}
|
|
198
|
-
return cls;
|
|
199
|
-
}
|
package/src/core/soul/types.ts
DELETED
|
@@ -1,298 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Soul Kernel — core type system.
|
|
3
|
-
*
|
|
4
|
-
* The Soul is Talon's compiled identity: a content-addressed Merkle DAG of
|
|
5
|
-
* typed nodes, layered with mutable "activation state" (salience, evidence
|
|
6
|
-
* weights) that changes with experience. The reasoning model never *writes*
|
|
7
|
-
* this structure — it is compiled mechanically from behavioral telemetry and a
|
|
8
|
-
* frozen local embedder, then *read* by the model at runtime.
|
|
9
|
-
*
|
|
10
|
-
* The single most important design decision lives here: the split between
|
|
11
|
-
* IMMUTABLE CONTENT and MUTABLE STATE.
|
|
12
|
-
*
|
|
13
|
-
* - Content — a node's semantic identity: its kind, its verbatim payload,
|
|
14
|
-
* and the hashes of the nodes it structurally references. This
|
|
15
|
-
* is what gets hashed; the hash IS the node's identity. Content
|
|
16
|
-
* never changes — a change of content is, by definition, a new
|
|
17
|
-
* node.
|
|
18
|
-
*
|
|
19
|
-
* - State — salience, accumulated evidence weight, activation counts,
|
|
20
|
-
* timestamps. This churns every tick. It is keyed BY the content
|
|
21
|
-
* hash and lives outside the Merkle structure, so reinforcing a
|
|
22
|
-
* value a thousand times does not churn a single content hash.
|
|
23
|
-
*
|
|
24
|
-
* Consequence: the structural root hash versions *what Talon is made of*
|
|
25
|
-
* (git-like), while salience is the "weather" layered on top. Rollback,
|
|
26
|
-
* provenance, and dedup fall out of the content layer; learning and decay live
|
|
27
|
-
* in the state layer.
|
|
28
|
-
*/
|
|
29
|
-
|
|
30
|
-
/** A content address: lowercase hex sha-256, prefixed. e.g. "sha256:ab12…". */
|
|
31
|
-
export type Hash = string & { readonly __brand: "SoulHash" };
|
|
32
|
-
|
|
33
|
-
/** The kinds of node the kernel can hold. */
|
|
34
|
-
export type NodeKind =
|
|
35
|
-
| "evidence" // a verbatim ground-truth fragment — the atoms
|
|
36
|
-
| "value" // an emergent cluster of evidence (a trait/value)
|
|
37
|
-
| "theme" // a higher-order reflection over co-activating, coherent values
|
|
38
|
-
| "spine" // an append-only developmental-narrative event
|
|
39
|
-
| "reflex" // a compiled behavioral rule (trigger → guard → action)
|
|
40
|
-
| "lens"; // a per-relationship refraction of identity
|
|
41
|
-
|
|
42
|
-
// ── Node payloads (the hashed content) ───────────────────────────────────────
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Where a piece of ground truth came from. Provenance is content, not metadata:
|
|
46
|
-
* an evidence node's identity includes *who said it, when, and where*.
|
|
47
|
-
*/
|
|
48
|
-
interface EvidenceSource {
|
|
49
|
-
/** Origin channel: a chat correction, a logged event, a diary line, etc. */
|
|
50
|
-
readonly origin:
|
|
51
|
-
| "correction" // someone (usually Dylan) corrected behavior
|
|
52
|
-
| "directive" // an explicit instruction about how to be
|
|
53
|
-
| "event" // something that happened
|
|
54
|
-
| "reaction" // an emoji / engagement signal rendered to text
|
|
55
|
-
| "diary" // a self-written reflection (still ground truth: it was written)
|
|
56
|
-
| "seed"; // hand-seeded at genesis from identity.md / About Me
|
|
57
|
-
/** Free-form locator, e.g. "telegram:msg:4360" or "memory.md#about-me". */
|
|
58
|
-
readonly ref?: string;
|
|
59
|
-
/** Who produced the ground truth, if known (e.g. "dylan"). */
|
|
60
|
-
readonly actor?: string;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** A verbatim fragment of ground truth. The kernel never paraphrases these. */
|
|
64
|
-
export interface EvidencePayload {
|
|
65
|
-
readonly kind: "evidence";
|
|
66
|
-
/** The exact text. Never generated, never rewritten. */
|
|
67
|
-
readonly text: string;
|
|
68
|
-
/** When the ground truth was observed (unix ms). Part of identity. */
|
|
69
|
-
readonly observedAt: number;
|
|
70
|
-
readonly source: EvidenceSource;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* An emergent value/trait. Not authored — discovered by clustering evidence
|
|
75
|
-
* embeddings. Its "label" is the medoid: the single most central *real*
|
|
76
|
-
* evidence fragment, never generated prose.
|
|
77
|
-
*/
|
|
78
|
-
export interface ValuePayload {
|
|
79
|
-
readonly kind: "value";
|
|
80
|
-
/** Hashes of the evidence nodes in this cluster (sorted, deduped). */
|
|
81
|
-
readonly members: readonly Hash[];
|
|
82
|
-
/** The medoid evidence node — the representative real sentence. */
|
|
83
|
-
readonly medoid: Hash;
|
|
84
|
-
/**
|
|
85
|
-
* Optional axis this value sits on, for tension detection. Discovered, not
|
|
86
|
-
* authored — two values on the same axis with high embedding distance but
|
|
87
|
-
* frequent co-activation form a tension edge.
|
|
88
|
-
*/
|
|
89
|
-
readonly axis?: string;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* A higher-order reflection: an abstraction over values that are both
|
|
94
|
-
* semantically coherent and frequently co-active. The soul's "higher-level
|
|
95
|
-
* inferences over time" (Generative Agents). Structure is discovered
|
|
96
|
-
* mechanically; the optional `insight` is the ONE place a single gated model
|
|
97
|
-
* pass may write a natural-language label — it can never change which values are
|
|
98
|
-
* grouped, and falls back to the medoid evidence when absent.
|
|
99
|
-
*/
|
|
100
|
-
export interface ThemePayload {
|
|
101
|
-
readonly kind: "theme";
|
|
102
|
-
/** The value-node hashes this theme abstracts over (sorted, deduped). */
|
|
103
|
-
readonly values: readonly Hash[];
|
|
104
|
-
/** Representative real evidence fragment across all member values. */
|
|
105
|
-
readonly medoid: Hash;
|
|
106
|
-
/** Optional model-written label; absent ⇒ the medoid text is used. */
|
|
107
|
-
readonly insight?: string;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* An append-only developmental-narrative event: the *causal story* of how
|
|
112
|
-
* Talon became Talon. Never rewritten, only appended. Gives continuity of self
|
|
113
|
-
* across resets and model swaps.
|
|
114
|
-
*/
|
|
115
|
-
export interface SpinePayload {
|
|
116
|
-
readonly kind: "spine";
|
|
117
|
-
/** What happened, verbatim where possible. */
|
|
118
|
-
readonly event: string;
|
|
119
|
-
readonly at: number;
|
|
120
|
-
/** Nodes this event created or reweighted — the causal links. */
|
|
121
|
-
readonly affects: readonly Hash[];
|
|
122
|
-
/** Prior spine node, forming the narrative chain (genesis = undefined). */
|
|
123
|
-
readonly prev?: Hash;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/** Severity of a reflex when its guard trips. */
|
|
127
|
-
export type ReflexSeverity = "block" | "warn" | "advise";
|
|
128
|
-
|
|
129
|
-
/**
|
|
130
|
-
* A compiled behavioral rule. The genuinely model-free, *enforceable* core:
|
|
131
|
-
* load-bearing rules become `trigger → guard → action`, checked by the harness,
|
|
132
|
-
* not merely hoped-for from the prompt. Trigger/guard reference a registry of
|
|
133
|
-
* named predicates (see reflex.ts) so they stay pure data, not code.
|
|
134
|
-
*/
|
|
135
|
-
export interface ReflexPayload {
|
|
136
|
-
readonly kind: "reflex";
|
|
137
|
-
/** Stable human name, e.g. "RULE-0-DELIVERY". */
|
|
138
|
-
readonly name: string;
|
|
139
|
-
/** Named predicate that arms the rule. */
|
|
140
|
-
readonly trigger: string;
|
|
141
|
-
/** Named predicate that, when true under trigger, fires the action. */
|
|
142
|
-
readonly guard: string;
|
|
143
|
-
/** What to do when it fires. */
|
|
144
|
-
readonly action: string;
|
|
145
|
-
readonly severity: ReflexSeverity;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* A per-relationship refraction. Identity is not global — it shows up
|
|
150
|
-
* differently per interlocutor. A lens is a *selection + reweighting*, never
|
|
151
|
-
* generated text: which values amplify, which reflexes tighten, which evidence
|
|
152
|
-
* applies for this person.
|
|
153
|
-
*/
|
|
154
|
-
export interface LensPayload {
|
|
155
|
-
readonly kind: "lens";
|
|
156
|
-
/** Subject identifier, e.g. "dylan". */
|
|
157
|
-
readonly subject: string;
|
|
158
|
-
/** Value-node hashes to amplify for this subject, with multipliers. */
|
|
159
|
-
readonly amplify: readonly { readonly node: Hash; readonly factor: number }[];
|
|
160
|
-
/** Evidence specific to this relationship. */
|
|
161
|
-
readonly evidence: readonly Hash[];
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
export type NodePayload =
|
|
165
|
-
| EvidencePayload
|
|
166
|
-
| ValuePayload
|
|
167
|
-
| ThemePayload
|
|
168
|
-
| SpinePayload
|
|
169
|
-
| ReflexPayload
|
|
170
|
-
| LensPayload;
|
|
171
|
-
|
|
172
|
-
/** A node = its hashed content. State lives separately (see ActivationState). */
|
|
173
|
-
export interface SoulNode {
|
|
174
|
-
readonly hash: Hash;
|
|
175
|
-
readonly payload: NodePayload;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// ── Associative edges (mutable state, NOT hashed) ────────────────────────────
|
|
179
|
-
|
|
180
|
-
export type EdgeKind =
|
|
181
|
-
| "coactivation" // Hebbian: fired together in a successful interaction
|
|
182
|
-
| "tension" // opposed values frequently co-active (navigated, not resolved)
|
|
183
|
-
| "supersedes"; // one node mechanically overrode another
|
|
184
|
-
|
|
185
|
-
export interface AssocEdge {
|
|
186
|
-
readonly from: Hash;
|
|
187
|
-
readonly to: Hash;
|
|
188
|
-
readonly kind: EdgeKind;
|
|
189
|
-
/** Learned weight (counts / EMA). Mutable — never part of any content hash. */
|
|
190
|
-
weight: number;
|
|
191
|
-
updatedAt: number;
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
// ── Activation state (mutable, keyed by node hash) ───────────────────────────
|
|
195
|
-
|
|
196
|
-
/**
|
|
197
|
-
* The "weather" over a node. Reinforced on activation, decayed by time. Drives
|
|
198
|
-
* projection priority. Crucially separate from content so learning never churns
|
|
199
|
-
* the Merkle structure.
|
|
200
|
-
*/
|
|
201
|
-
export interface ActivationState {
|
|
202
|
-
/** EMA salience in [0, ∞); decays toward 0 without activation. */
|
|
203
|
-
salience: number;
|
|
204
|
-
/**
|
|
205
|
-
* Net evidence weight: positive when behavior expressing this node earns
|
|
206
|
-
* positive signals, negative when it earns corrections. Drives confidence.
|
|
207
|
-
*/
|
|
208
|
-
evidence: number;
|
|
209
|
-
/** Number of times activated. */
|
|
210
|
-
activations: number;
|
|
211
|
-
/** Last activation (unix ms) — used by the decay clock. */
|
|
212
|
-
lastActivatedAt: number;
|
|
213
|
-
/**
|
|
214
|
-
* Optional FSRS/DSR memory stability (ms-scale time constant). Present only
|
|
215
|
-
* under adaptive forgetting; grows with successful recall so well-established
|
|
216
|
-
* traits decay slowly and transients fade fast. When absent, decay is the
|
|
217
|
-
* fixed-half-life exponential.
|
|
218
|
-
*/
|
|
219
|
-
stability?: number;
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
// ── Commits (the version chain) ──────────────────────────────────────────────
|
|
223
|
-
|
|
224
|
-
/**
|
|
225
|
-
* A snapshot of identity. Mirrors git: a structural root plus a parent link,
|
|
226
|
-
* plus a digest of the mutable state at commit time (so a rollback restores both
|
|
227
|
-
* structure and weather). Commits form the identity's history.
|
|
228
|
-
*/
|
|
229
|
-
export interface SoulCommit {
|
|
230
|
-
/** Merkle root over all structural content at this version. */
|
|
231
|
-
readonly root: Hash;
|
|
232
|
-
/** Digest of the activation/edge state captured with this commit. */
|
|
233
|
-
readonly stateDigest: Hash;
|
|
234
|
-
readonly parent?: Hash;
|
|
235
|
-
readonly at: number;
|
|
236
|
-
/** Templated, human-readable summary of what changed (never model-written). */
|
|
237
|
-
readonly summary: string;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/** Default kernel tuning. Pure numbers — the whole "compiler" is arithmetic. */
|
|
241
|
-
export interface SoulConfig {
|
|
242
|
-
/** Salience reinforcement increment per activation. */
|
|
243
|
-
readonly reinforce: number;
|
|
244
|
-
/** Multiplicative decay applied per decay-clock tick. */
|
|
245
|
-
readonly decay: number;
|
|
246
|
-
/** Half-life of the decay clock in ms (informational; see salience.ts). */
|
|
247
|
-
readonly decayHalfLifeMs: number;
|
|
248
|
-
/**
|
|
249
|
-
* Per-kind decay half-life overrides (ms). Kinds without an entry fall back
|
|
250
|
-
* to `decayHalfLifeMs`. `Number.POSITIVE_INFINITY` disables decay for a kind
|
|
251
|
-
* entirely. The three node families have different temporal properties:
|
|
252
|
-
* reflexes are deliberately seeded behavioral blockers — correct by
|
|
253
|
-
* definition, they must never soften with disuse; evidence (corrections)
|
|
254
|
-
* should fade naturally; spine causal links are context-specific and should
|
|
255
|
-
* go stale faster than traits. See `halfLifeForKind`.
|
|
256
|
-
*/
|
|
257
|
-
readonly decayHalfLifeByKindMs?: Partial<Record<NodeKind, number>>;
|
|
258
|
-
/** Hebbian edge increment per co-activation. */
|
|
259
|
-
readonly hebbIncrement: number;
|
|
260
|
-
/** Cosine distance above which two embeddings are "different". */
|
|
261
|
-
readonly clusterDistance: number;
|
|
262
|
-
/** Distance from core centroid above which a mutation is "drift". */
|
|
263
|
-
readonly driftThreshold: number;
|
|
264
|
-
/** Token budget for the runtime projection surface. */
|
|
265
|
-
readonly runtimeBudgetTokens: number;
|
|
266
|
-
/**
|
|
267
|
-
* When true, use FSRS/DSR adaptive forgetting (per-node stability that grows
|
|
268
|
-
* with recall) instead of fixed-half-life exponential decay. Default false to
|
|
269
|
-
* preserve the simple baseline.
|
|
270
|
-
*/
|
|
271
|
-
readonly adaptiveForgetting?: boolean;
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
export const DEFAULT_SOUL_CONFIG: SoulConfig = {
|
|
275
|
-
reinforce: 1,
|
|
276
|
-
decay: 0.98,
|
|
277
|
-
decayHalfLifeMs: 1000 * 60 * 60 * 24 * 7, // one week
|
|
278
|
-
decayHalfLifeByKindMs: {
|
|
279
|
-
reflex: Number.POSITIVE_INFINITY, // behavioral blockers never soften
|
|
280
|
-
spine: 1000 * 60 * 60 * 24 * 3.5, // causal links go stale faster than traits
|
|
281
|
-
},
|
|
282
|
-
hebbIncrement: 1,
|
|
283
|
-
clusterDistance: 0.35,
|
|
284
|
-
driftThreshold: 0.55,
|
|
285
|
-
runtimeBudgetTokens: 1200,
|
|
286
|
-
};
|
|
287
|
-
|
|
288
|
-
/**
|
|
289
|
-
* Effective decay half-life for a node kind: the per-kind override when one is
|
|
290
|
-
* configured, otherwise the base `decayHalfLifeMs`. `Number.POSITIVE_INFINITY`
|
|
291
|
-
* means the kind never decays. Callers that don't know the kind (or operate on
|
|
292
|
-
* mixed sets) may omit it and get the base rate.
|
|
293
|
-
*/
|
|
294
|
-
export function halfLifeForKind(cfg: SoulConfig, kind?: NodeKind): number {
|
|
295
|
-
const override =
|
|
296
|
-
kind === undefined ? undefined : cfg.decayHalfLifeByKindMs?.[kind];
|
|
297
|
-
return override ?? cfg.decayHalfLifeMs;
|
|
298
|
-
}
|
package/src/core/soul/valence.ts
DELETED
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Soul Kernel — learned valence.
|
|
3
|
-
*
|
|
4
|
-
* Hardcoding "👍 = +1" is exactly the kind of over-programming the soul should
|
|
5
|
-
* outgrow. What a cue *means* is not a constant — it is whatever outcome reliably
|
|
6
|
-
* follows it. So valence is learned: when an interaction's downstream engagement
|
|
7
|
-
* resolves (the conversation continued, or died), every categorical cue that
|
|
8
|
-
* preceded it (the emoji used, say) is nudged toward that outcome. Over time the
|
|
9
|
-
* model discovers, from Talon's own data, that 👍 tends to precede continuation
|
|
10
|
-
* and 👎 tends to precede silence — or whatever is actually true here, which may
|
|
11
|
-
* differ from the prior.
|
|
12
|
-
*
|
|
13
|
-
* A weak prior (the old static table) seeds cold-start so early behavior is
|
|
14
|
-
* sane, but evidence overrides it: the effective valence is a count-weighted
|
|
15
|
-
* blend that converges to the observed mean as observations accumulate.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import { emojiValence } from "./signals.js";
|
|
19
|
-
|
|
20
|
-
interface CueStat {
|
|
21
|
-
sum: number;
|
|
22
|
-
count: number;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface ValenceSnapshot {
|
|
26
|
-
readonly priorWeight: number;
|
|
27
|
-
readonly stats: readonly (readonly [string, CueStat])[];
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export class ValenceModel {
|
|
31
|
-
private readonly stats = new Map<string, CueStat>();
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* @param prior cold-start valence for an unseen cue (default: the old
|
|
35
|
-
* emoji table — a fading prior, not a rule).
|
|
36
|
-
* @param priorWeight pseudo-count for the prior; evidence dominates past it.
|
|
37
|
-
*/
|
|
38
|
-
constructor(
|
|
39
|
-
private readonly prior: (cue: string) => number = emojiValence,
|
|
40
|
-
private readonly priorWeight = 2,
|
|
41
|
-
) {}
|
|
42
|
-
|
|
43
|
-
/** Record that `cue` was followed by `outcome` (e.g. +1 continued, -1 died). */
|
|
44
|
-
observe(cue: string, outcome: number): void {
|
|
45
|
-
const s = this.stats.get(cue) ?? { sum: 0, count: 0 };
|
|
46
|
-
s.sum += outcome;
|
|
47
|
-
s.count += 1;
|
|
48
|
-
this.stats.set(cue, s);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Effective learned valence: a count-weighted blend of the prior and the
|
|
53
|
-
* observed mean. With no observations it equals the prior; as observations
|
|
54
|
-
* accumulate it converges to sum/count.
|
|
55
|
-
*/
|
|
56
|
-
valence(cue: string): number {
|
|
57
|
-
const s = this.stats.get(cue);
|
|
58
|
-
const p = this.prior(cue);
|
|
59
|
-
if (!s || s.count === 0) return p;
|
|
60
|
-
return (this.priorWeight * p + s.sum) / (this.priorWeight + s.count);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/** How much evidence backs a cue (0 = pure prior). */
|
|
64
|
-
confidence(cue: string): number {
|
|
65
|
-
return this.stats.get(cue)?.count ?? 0;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
snapshot(): ValenceSnapshot {
|
|
69
|
-
return {
|
|
70
|
-
priorWeight: this.priorWeight,
|
|
71
|
-
stats: [...this.stats.entries()].map(([k, v]) => [k, { ...v }]),
|
|
72
|
-
};
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
static restore(
|
|
76
|
-
snap: ValenceSnapshot,
|
|
77
|
-
prior: (cue: string) => number = emojiValence,
|
|
78
|
-
): ValenceModel {
|
|
79
|
-
const model = new ValenceModel(prior, snap.priorWeight);
|
|
80
|
-
for (const [cue, stat] of snap.stats) model.stats.set(cue, { ...stat });
|
|
81
|
-
return model;
|
|
82
|
-
}
|
|
83
|
-
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|