talon-agent 3.7.0 → 3.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/README.md +16 -0
  2. package/package.json +2 -2
  3. package/prompts/README.md +10 -2
  4. package/prompts/dream.md +5 -3
  5. package/prompts/heartbeat.md +1 -1
  6. package/prompts/identity.md +1 -1
  7. package/prompts/mem0.md +7 -5
  8. package/prompts/mempalace.md +7 -5
  9. package/prompts/system/memory-recall.md +49 -0
  10. package/prompts/system/workspace.md +2 -2
  11. package/src/backend/claude-sdk/one-shot.ts +31 -1
  12. package/src/backend/codex/effort.ts +30 -0
  13. package/src/backend/codex/handler/message.ts +12 -22
  14. package/src/backend/codex/one-shot.ts +20 -0
  15. package/src/backend/opencode/handler/message.ts +7 -13
  16. package/src/backend/remote-server/one-shot.ts +6 -0
  17. package/src/backend/shared/handler-to-events.ts +0 -1
  18. package/src/backend/shared/handler-types.ts +0 -8
  19. package/src/backend/shared/index.ts +1 -6
  20. package/src/backend/shared/prompt-format.ts +0 -69
  21. package/src/bootstrap.ts +2 -0
  22. package/src/core/agent-runtime/capabilities.ts +5 -49
  23. package/src/core/background/dream.ts +27 -2
  24. package/src/core/background/effort.ts +80 -0
  25. package/src/core/background/heartbeat/agent.ts +22 -1
  26. package/src/core/background/heartbeat/state.ts +7 -0
  27. package/src/core/engine/model-audit.ts +69 -2
  28. package/src/core/errors.ts +107 -3
  29. package/src/core/models/reasoning-levels.ts +14 -3
  30. package/src/core/prompt/assemble.ts +8 -5
  31. package/src/core/prompt/embedded-prompts.ts +16 -14
  32. package/src/core/types.ts +10 -0
  33. package/src/core/weaver/index.ts +0 -1
  34. package/src/core/weaver/weaver.ts +1 -24
  35. package/src/frontend/telegram/callbacks/index.ts +8 -0
  36. package/src/frontend/telegram/callbacks/metrics.ts +36 -0
  37. package/src/frontend/telegram/commands/admin.ts +9 -13
  38. package/src/frontend/telegram/commands/info.ts +3 -44
  39. package/src/frontend/telegram/helpers/diagnostics.ts +190 -70
  40. package/src/util/config.ts +34 -19
  41. package/src/core/memory/retrieval.ts +0 -92
  42. package/src/core/weaver/memory-prefetch.ts +0 -65
@@ -5,6 +5,7 @@ import { dirs, files as pathFiles } from "./paths.js";
5
5
  import { hardenTalonPermissions } from "./harden.js";
6
6
  import { setTimezone } from "./time.js";
7
7
  import { BACKEND_IDS } from "../core/agent-runtime/model-ref.js";
8
+ import { REASONING_LEVEL_ORDER } from "../core/models/reasoning-levels.js";
8
9
  import {
9
10
  assembleSystemPrompt,
10
11
  joinSystemPromptParts,
@@ -28,6 +29,20 @@ const BACKEND_ID_ENUM = [...BACKEND_IDS] as [
28
29
  ...(typeof BACKEND_IDS)[number][],
29
30
  ];
30
31
 
32
+ /**
33
+ * Reasoning-effort literal source for the background-agent knobs
34
+ * (`heartbeatEffort` / `dreamEffort`). Same trick as `BACKEND_ID_ENUM`:
35
+ * reuse the single source of truth (`REASONING_LEVEL_ORDER`) so adding a
36
+ * level to the vocabulary doesn't need a second edit here.
37
+ *
38
+ * There is no `"adaptive"` member — leaving the field unset IS adaptive
39
+ * (the backend/model default), matching how per-chat `effort` behaves.
40
+ */
41
+ const REASONING_EFFORT_ENUM = [...REASONING_LEVEL_ORDER] as [
42
+ (typeof REASONING_LEVEL_ORDER)[number],
43
+ ...(typeof REASONING_LEVEL_ORDER)[number][],
44
+ ];
45
+
31
46
  // ── Config schema ───────────────────────────────────────────────────────────
32
47
 
33
48
  /** Path-based Talon plugin (loaded as a Node module). */
@@ -241,23 +256,6 @@ const mem0SettingsSchema = z.object({
241
256
  userId: z.string().min(1).optional(),
242
257
  });
243
258
 
244
- /**
245
- * Memory pre-retrieval (Phase B) — automatic per-turn palace retrieval.
246
- * Ships INERT: the flag gates wiring a real retriever into the dispatcher.
247
- * With `enabled: false` (default) prompts are byte-identical to previous
248
- * behavior. See docs in core/memory/retrieval.ts for the trust policy.
249
- */
250
- const memoryPreRetrievalSchema = z.object({
251
- /** Master switch. Default off — plumbing merges disabled. */
252
- enabled: z.boolean().default(false),
253
- /** Maximum retrieved items injected per turn. */
254
- maxResults: z.number().int().min(1).max(10).default(3),
255
- /** Hard cap on injected characters, provenance labels included. */
256
- maxChars: z.number().int().min(200).max(20000).default(3000),
257
- /** In groups, filter private-user drawers before injection. */
258
- groupPrivateUserFiltering: z.boolean().default(true),
259
- });
260
-
261
259
  const configSchema = z.object({
262
260
  frontend: z.union([frontendEnum, z.array(frontendEnum)]).default("telegram"),
263
261
  botToken: z.string().optional(),
@@ -317,6 +315,15 @@ const configSchema = z.object({
317
315
  */
318
316
  backendDefaults: z.record(z.string(), z.string()).optional(),
319
317
  dreamModel: z.string().optional(), // Model used for background memory consolidation (defaults to main model)
318
+ /**
319
+ * Reasoning effort for the dream / memory-consolidation agent. Unset =
320
+ * the backend/model default. Pair with `dreamModel` when you want a
321
+ * cheap model that still thinks hard (or an expensive one that doesn't).
322
+ *
323
+ * Honoured by backends with a reasoning knob (Claude SDK, Codex);
324
+ * silently ignored by Kilo / OpenCode, which have none.
325
+ */
326
+ dreamEffort: z.enum(REASONING_EFFORT_ENUM).optional(),
320
327
  maxMessageLength: z.number().int().min(100).default(4000),
321
328
  concurrency: z.number().int().min(1).max(20).default(1),
322
329
  apiId: z.number().int().optional(),
@@ -327,8 +334,6 @@ const configSchema = z.object({
327
334
  pulseIntervalMs: z.number().int().min(60000).default(300000),
328
335
  /** Background memory-consolidation (dream) runs. Mirrors `pulse`/`heartbeat`. */
329
336
  dream: z.boolean().default(true),
330
- /** Memory pre-retrieval (Phase B). Optional; absent = disabled. */
331
- memoryPreRetrieval: memoryPreRetrievalSchema.optional(),
332
337
  /**
333
338
  * Periodic background agent (default: on, hourly). Advances open
334
339
  * goals, runs user-defined maintenance, and proactively messages
@@ -339,6 +344,16 @@ const configSchema = z.object({
339
344
  heartbeat: z.boolean().default(true),
340
345
  heartbeatIntervalMinutes: z.number().int().min(5).default(60),
341
346
  heartbeatModel: z.string().optional(), // Model for heartbeat agent (defaults to main model)
347
+ /**
348
+ * Reasoning effort for the heartbeat agent. Unset = the backend/model
349
+ * default. Pair with `heartbeatModel` — e.g. `"high"` so unattended
350
+ * goal work reasons harder than a chat turn, or `"low"` to keep hourly
351
+ * runs cheap.
352
+ *
353
+ * Honoured by backends with a reasoning knob (Claude SDK, Codex);
354
+ * silently ignored by Kilo / OpenCode, which have none.
355
+ */
356
+ heartbeatEffort: z.enum(REASONING_EFFORT_ENUM).optional(),
342
357
  braveApiKey: z.string().optional(),
343
358
  /**
344
359
  * Codex-specific OpenAI API key. Prefer this, CODEX_API_KEY, or
@@ -1,92 +0,0 @@
1
- /**
2
- * Memory pre-retrieval (Phase B) — injectable retriever boundary.
3
- *
4
- * Phase A made `memory.md` the compact always-loaded layer; Phase B closes the
5
- * recall gap by handing each chat turn a small, relevant palace slice without
6
- * depending on the model to call `mempalace_search` itself. This module owns
7
- * the CORE side of that boundary: a typed retriever dependency the Weaver can
8
- * call before `runChatTurn(...)`, returning plain data
9
- * (`RetrievedMemory | undefined`) that backends fold into the live user
10
- * prompt via `formatPromptWithRetrievedMemory`.
11
- *
12
- * Deliberate constraints (see docs/memory-phase-b-pre-retrieval.md):
13
- *
14
- * - The retriever receives a small context object and returns plain data.
15
- * It never imports backend handlers, and retrieval output never enters
16
- * `prepareSystemPrompt()` / frozen prompt snapshots — cache safety first.
17
- * - Fail closed: a broken retriever must never block chat delivery. The
18
- * Weaver catches errors, logs one warning, and runs the turn without
19
- * injected memory.
20
- * - The PRODUCTION retriever is intentionally absent in this first pass.
21
- * There is no clean in-process MemPalace call surface yet (the plugin is
22
- * an MCP server, not a typed module), and shelling out per message from
23
- * the dispatch hot path is explicitly forbidden. Until a deliberate
24
- * bridge exists, deployments get `noopMemoryRetriever` — plumbing lands
25
- * inert, prompts stay byte-identical.
26
- * - Trust-aware injection (#373): when a real adapter lands, only items
27
- * with `trustLevel` in `AUTO_INJECT_TRUST_LEVELS` may be auto-injected.
28
- * `user_claim` / `group_chat` content stays pull-only, or a poisoned
29
- * drawer becomes a persistent prompt injection in every session.
30
- */
31
-
32
- import type {
33
- RetrievedMemory,
34
- RetrievedMemoryTrustLevel,
35
- } from "../agent-runtime/capabilities.js";
36
-
37
- // ── Retriever contract ──────────────────────────────────────────────────────
38
-
39
- /** Context handed to a retriever for one chat turn. */
40
- export type MemoryRetrievalContext = {
41
- runKind: "chat";
42
- chatId: string;
43
- /** The raw incoming message text (retrievers trim/cap it themselves). */
44
- text: string;
45
- senderName: string;
46
- isGroup?: boolean;
47
- };
48
-
49
- /**
50
- * A memory retriever: small context in, bounded plain data out.
51
- * `undefined` means "inject nothing" — the turn proceeds unchanged.
52
- */
53
- export type MemoryRetriever = (
54
- context: MemoryRetrievalContext,
55
- ) => Promise<RetrievedMemory | undefined>;
56
-
57
- // ── Trust policy (#373) ─────────────────────────────────────────────────────
58
-
59
- /**
60
- * Trust levels eligible for automatic injection. Anything else (including an
61
- * absent `trustLevel`) must be filtered by adapters before returning items.
62
- */
63
- export const AUTO_INJECT_TRUST_LEVELS: ReadonlySet<RetrievedMemoryTrustLevel> =
64
- new Set(["dylan_direct", "bot_inferred", "heartbeat_synthesis"]);
65
-
66
- /** True when an item's trust level allows automatic injection. */
67
- export function isAutoInjectTrusted(
68
- trustLevel: RetrievedMemoryTrustLevel | undefined,
69
- ): boolean {
70
- return trustLevel !== undefined && AUTO_INJECT_TRUST_LEVELS.has(trustLevel);
71
- }
72
-
73
- /**
74
- * Enforce the #373 trust policy on a retrieval result: drop every item that
75
- * is not explicitly auto-inject trusted. Adapters should call this as their
76
- * last step, and the Weaver's `prefetchMemory` applies it again to whatever
77
- * a retriever returns — a buggy adapter cannot leak low-trust items into
78
- * the prompt.
79
- */
80
- export function filterAutoInjectable(
81
- memory: RetrievedMemory | undefined,
82
- ): RetrievedMemory | undefined {
83
- if (!memory) return undefined;
84
- const items = memory.items.filter((i) => isAutoInjectTrusted(i.trustLevel));
85
- if (items.length === 0) return undefined;
86
- return { ...memory, items };
87
- }
88
-
89
- // ── Default (inert) retriever ───────────────────────────────────────────────
90
-
91
- /** The no-op retriever: Phase B plumbing present, injection disabled. */
92
- export const noopMemoryRetriever: MemoryRetriever = async () => undefined;
@@ -1,65 +0,0 @@
1
- /**
2
- * Memory prefetch (Phase B) — optional pre-retrieval of palace memory
3
- * for live user messages, strictly fail-closed: a broken palace must
4
- * never block chat delivery. The result is dynamic turn context passed
5
- * through ChatRunParams; it never touches the frozen prompt.
6
- *
7
- * The #373 trust policy is enforced HERE, not just in adapters: whatever a
8
- * retriever returns is passed through `filterAutoInjectable` before it can
9
- * reach the prompt, so a buggy or future adapter cannot leak `user_claim` /
10
- * `group_chat` items into auto-injection.
11
- */
12
-
13
- import type { RetrievedMemory } from "../agent-runtime/capabilities.js";
14
- import type { MemoryRetriever } from "../memory/retrieval.js";
15
- import { filterAutoInjectable } from "../memory/retrieval.js";
16
- import { logDebug, logWarn } from "../../util/log.js";
17
-
18
- export type PrefetchMemoryInput = {
19
- chatId: string;
20
- text: string;
21
- senderName: string;
22
- isGroup: boolean;
23
- /** Request id for log correlation. */
24
- reqId: string;
25
- };
26
-
27
- export async function prefetchMemory(
28
- retrieve: MemoryRetriever,
29
- input: PrefetchMemoryInput,
30
- ): Promise<RetrievedMemory | undefined> {
31
- try {
32
- const raw = await retrieve({
33
- runKind: "chat",
34
- chatId: input.chatId,
35
- text: input.text,
36
- senderName: input.senderName,
37
- isGroup: input.isGroup,
38
- });
39
- const retrieved = filterAutoInjectable(raw);
40
- const droppedCount =
41
- (raw?.items.length ?? 0) - (retrieved?.items.length ?? 0);
42
- if (droppedCount > 0) {
43
- logWarn(
44
- "dispatcher",
45
- `[${input.reqId}] memory pre-retrieval: dropped ${droppedCount} ` +
46
- `low-trust item(s) the retriever failed to filter (#373)`,
47
- );
48
- }
49
- if (retrieved) {
50
- logDebug(
51
- "dispatcher",
52
- `[${input.reqId}] memory pre-retrieval: ${retrieved.items.length} item(s), ` +
53
- `${retrieved.items.reduce((n, i) => n + i.text.length, 0)} chars`,
54
- );
55
- }
56
- return retrieved;
57
- } catch (err) {
58
- logWarn(
59
- "dispatcher",
60
- `[${input.reqId}] memory pre-retrieval failed (running turn without it): ` +
61
- (err instanceof Error ? err.message : String(err)),
62
- );
63
- return undefined;
64
- }
65
- }