talon-agent 4.6.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (225) hide show
  1. package/README.md +3 -3
  2. package/package.json +3 -2
  3. package/prompts/README.md +2 -2
  4. package/prompts/system/memory-recall.md +16 -0
  5. package/src/app.ts +19 -4
  6. package/src/backend/claude-sdk/handler.ts +28 -7
  7. package/src/backend/claude-sdk/one-shot.ts +1 -1
  8. package/src/backend/claude-sdk/options.ts +7 -6
  9. package/src/backend/claude-sdk/stream.ts +30 -3
  10. package/src/backend/claude-sdk/warm.ts +1 -1
  11. package/src/backend/codex/constants.ts +1 -1
  12. package/src/backend/codex/factory.ts +2 -2
  13. package/src/backend/codex/handler/events.ts +1 -1
  14. package/src/backend/codex/handler/message.ts +26 -12
  15. package/src/backend/codex/handler/rollout-accounting.ts +1 -1
  16. package/src/backend/codex/init.ts +1 -1
  17. package/src/backend/codex/mcp-config.ts +1 -1
  18. package/src/backend/codex/one-shot.ts +1 -1
  19. package/src/backend/kilo/handler/message.ts +4 -1
  20. package/src/backend/openai-agents/constants.ts +1 -1
  21. package/src/backend/openai-agents/factory.ts +2 -2
  22. package/src/backend/openai-agents/handler/events.ts +1 -1
  23. package/src/backend/openai-agents/handler/message.ts +8 -4
  24. package/src/backend/openai-agents/init.ts +1 -1
  25. package/src/backend/openai-agents/mcp-pool.ts +1 -1
  26. package/src/backend/opencode/handler/message.ts +4 -1
  27. package/src/backend/remote-server/chat-turn.ts +31 -24
  28. package/src/backend/remote-server/events.ts +3 -3
  29. package/src/backend/remote-server/factory.ts +6 -3
  30. package/src/backend/remote-server/index.ts +1 -1
  31. package/src/backend/remote-server/mcp.ts +1 -1
  32. package/src/backend/remote-server/one-shot.ts +1 -1
  33. package/src/backend/remote-server/server-bindings.ts +1 -1
  34. package/src/backend/remote-server/turn.ts +1 -1
  35. package/src/backend/runtime/cache/cache-metrics.ts +126 -0
  36. package/src/backend/{shared → runtime/cache}/cache-telemetry.ts +22 -2
  37. package/src/backend/{shared → runtime}/index.ts +32 -23
  38. package/src/backend/{shared → runtime/prompt}/delivery-contract.ts +1 -1
  39. package/src/backend/{shared → runtime/prompt}/prompt-format.ts +46 -2
  40. package/src/backend/{shared → runtime/prompt}/system-prompt.ts +4 -4
  41. package/src/backend/{shared → runtime/turn}/delivered-text.ts +1 -1
  42. package/src/backend/{shared → runtime/turn}/delivery.ts +2 -2
  43. package/src/backend/{shared → runtime/turn}/handle-retry.ts +5 -5
  44. package/src/backend/{shared → runtime/turn}/handler-to-events.ts +24 -10
  45. package/src/backend/{shared → runtime/turn}/handler-types.ts +7 -1
  46. package/src/backend/{shared → runtime/turn}/model-retry.ts +2 -2
  47. package/src/backend/{shared → runtime/turn}/result-events.ts +2 -2
  48. package/src/backend/{shared → runtime/turn}/stream-state.ts +2 -2
  49. package/src/backend/{shared → runtime/turn}/turn-interrupt.ts +2 -2
  50. package/src/backend/{shared → runtime/turn}/turn-phases.ts +6 -6
  51. package/src/bootstrap.ts +4 -19
  52. package/src/cli.ts +1 -1
  53. package/src/core/agent-runtime/capabilities.ts +11 -0
  54. package/src/core/agent-runtime/contract-tests.ts +92 -1
  55. package/src/core/agent-runtime/events.ts +1 -1
  56. package/src/core/background/{isolated-agent.ts → cron/isolated-agent.ts} +3 -3
  57. package/src/core/background/{job-health.ts → cron/job-health.ts} +1 -1
  58. package/src/core/background/{job-oneshot.ts → cron/job-oneshot.ts} +5 -5
  59. package/src/core/background/{job-prompt.ts → cron/job-prompt.ts} +1 -1
  60. package/src/core/background/{cron.ts → cron/scheduler.ts} +6 -6
  61. package/src/core/background/{cron-spec.ts → cron/spec.ts} +1 -1
  62. package/src/core/background/{dream.ts → dream/index.ts} +10 -15
  63. package/src/core/background/{plan-alerts.ts → pulse/plan-alerts.ts} +3 -3
  64. package/src/core/background/{pulse.ts → pulse/pulse.ts} +8 -5
  65. package/src/core/background/triggers/command.ts +1 -1
  66. package/src/core/config/index.ts +15 -11
  67. package/src/core/daemon/resource-sampler.ts +121 -0
  68. package/src/core/engine/dispatcher.ts +16 -0
  69. package/src/core/engine/gateway-actions/cron.ts +2 -2
  70. package/src/core/engine/gateway-actions/index.ts +3 -0
  71. package/src/core/engine/gateway-actions/memory.ts +335 -0
  72. package/src/core/engine/gateway.ts +0 -6
  73. package/src/core/memory/import.ts +3 -2
  74. package/src/core/memory/taps.ts +199 -0
  75. package/src/core/memory/turn-retrieval.ts +222 -0
  76. package/src/core/prompt/assemble.ts +2 -12
  77. package/src/core/prompt/index.ts +2 -2
  78. package/src/core/prompt/invalidation.ts +1 -1
  79. package/src/core/tasks/index.ts +1 -1
  80. package/src/core/tools/index.ts +2 -0
  81. package/src/core/tools/memory.ts +128 -0
  82. package/src/core/tools/types.ts +1 -0
  83. package/src/core/weaver/turn-cpu.ts +32 -0
  84. package/src/core/weaver/weaver.ts +36 -0
  85. package/src/frontend/discord/admin.ts +1 -1
  86. package/src/frontend/discord/callbacks/components/backend-select.ts +1 -1
  87. package/src/frontend/discord/callbacks/components/pulse.ts +1 -1
  88. package/src/frontend/discord/callbacks/components/settings.ts +1 -1
  89. package/src/frontend/discord/callbacks/modals.ts +4 -1
  90. package/src/frontend/discord/commands/admin.ts +1 -35
  91. package/src/frontend/discord/commands/definitions.ts +0 -11
  92. package/src/frontend/discord/commands/router.ts +0 -3
  93. package/src/frontend/discord/commands/session.ts +6 -0
  94. package/src/frontend/discord/commands/settings.ts +1 -1
  95. package/src/frontend/discord/middleware.ts +1 -25
  96. package/src/frontend/discord/runtime.ts +1 -2
  97. package/src/frontend/native/{auth.ts → bridge/auth.ts} +2 -2
  98. package/src/frontend/native/{discovery.ts → bridge/discovery.ts} +3 -3
  99. package/src/frontend/native/{routes → bridge/routes}/chats.ts +1 -1
  100. package/src/frontend/native/{routes → bridge/routes}/daemon.ts +1 -1
  101. package/src/frontend/native/{routes → bridge/routes}/host.ts +6 -3
  102. package/src/frontend/native/{routes → bridge/routes}/pre-auth.ts +1 -1
  103. package/src/frontend/native/{server.ts → bridge/server.ts} +3 -3
  104. package/src/frontend/native/{tls.ts → bridge/tls.ts} +2 -2
  105. package/src/frontend/native/{chat-lifecycle.ts → chats/chat-lifecycle.ts} +3 -3
  106. package/src/frontend/native/{chat-wire.ts → chats/chat-wire.ts} +4 -4
  107. package/src/frontend/native/{chats.ts → chats/chats.ts} +4 -4
  108. package/src/frontend/native/{empty-chat-sweep.ts → chats/empty-chat-sweep.ts} +4 -4
  109. package/src/frontend/native/{history.ts → chats/history.ts} +7 -7
  110. package/src/frontend/native/{reset.ts → chats/reset.ts} +7 -7
  111. package/src/frontend/native/index.ts +13 -10
  112. package/src/frontend/native/{media.ts → media/media.ts} +3 -3
  113. package/src/frontend/native/runtime.ts +1 -1
  114. package/src/frontend/native/{control.ts → surface/control.ts} +4 -4
  115. package/src/frontend/native/{extensions.ts → surface/extensions.ts} +12 -9
  116. package/src/frontend/native/{handlers.ts → surface/handlers.ts} +19 -14
  117. package/src/frontend/native/{logs.ts → surface/logs.ts} +2 -2
  118. package/src/frontend/native/{memory.ts → surface/memory.ts} +2 -2
  119. package/src/frontend/native/{models.ts → surface/models.ts} +17 -10
  120. package/src/frontend/native/{settings.ts → surface/settings.ts} +9 -9
  121. package/src/frontend/native/{status.ts → surface/status.ts} +3 -3
  122. package/src/frontend/native/{actions.ts → turn/actions.ts} +7 -4
  123. package/src/frontend/native/{context.ts → turn/context.ts} +8 -8
  124. package/src/frontend/native/{emit.ts → turn/emit.ts} +7 -7
  125. package/src/frontend/native/{queue.ts → turn/queue.ts} +3 -3
  126. package/src/frontend/native/{turn-meta.ts → turn/turn-meta.ts} +2 -2
  127. package/src/frontend/native/{turn.ts → turn/turn.ts} +9 -9
  128. package/src/frontend/shared/model-commands.ts +1 -1
  129. package/src/frontend/shared/session-status.ts +35 -8
  130. package/src/frontend/shared/status-context.ts +101 -2
  131. package/src/frontend/telegram/admin/background.ts +1 -1
  132. package/src/frontend/telegram/callbacks/model/backend.ts +1 -1
  133. package/src/frontend/telegram/callbacks/pulse.ts +1 -1
  134. package/src/frontend/telegram/callbacks/settings.ts +1 -1
  135. package/src/frontend/telegram/commands/admin.ts +2 -37
  136. package/src/frontend/telegram/commands/index.ts +1 -1
  137. package/src/frontend/telegram/commands/session.ts +6 -0
  138. package/src/frontend/telegram/commands/settings.ts +1 -1
  139. package/src/frontend/telegram/handlers/messages.ts +0 -10
  140. package/src/frontend/telegram/index.ts +1 -5
  141. package/src/frontend/telegram/middleware.ts +1 -15
  142. package/src/frontend/whatsapp/access.ts +2 -2
  143. package/src/frontend/whatsapp/actions/history.ts +2 -2
  144. package/src/frontend/whatsapp/actions/messaging.ts +2 -2
  145. package/src/frontend/whatsapp/actions/moderation.ts +1 -1
  146. package/src/frontend/whatsapp/actions/send.ts +1 -1
  147. package/src/frontend/whatsapp/commands.ts +8 -2
  148. package/src/frontend/whatsapp/{connection.ts → connection/connection.ts} +5 -5
  149. package/src/frontend/whatsapp/{pairing-service.ts → connection/pairing-service.ts} +3 -3
  150. package/src/frontend/whatsapp/{wa-logger.ts → connection/wa-logger.ts} +2 -2
  151. package/src/frontend/whatsapp/index.ts +4 -4
  152. package/src/frontend/whatsapp/{inbound.ts → messages/inbound.ts} +15 -15
  153. package/src/frontend/whatsapp/{media-store.ts → messages/media-store.ts} +4 -4
  154. package/src/frontend/whatsapp/{message-store.ts → messages/message-store.ts} +1 -1
  155. package/src/frontend/whatsapp/{turn-recovery.ts → messages/turn-recovery.ts} +4 -4
  156. package/src/frontend/whatsapp/registry.ts +1 -1
  157. package/src/frontend/whatsapp/runtime.ts +1 -1
  158. package/src/index.ts +1 -1
  159. package/src/storage/db.ts +6 -1
  160. package/src/storage/memory.ts +59 -8
  161. package/src/storage/metrics.ts +38 -0
  162. package/src/storage/repositories/goals-repo.ts +2 -1
  163. package/src/storage/repositories/sessions-repo.ts +6 -0
  164. package/src/storage/session-record.ts +13 -2
  165. package/src/storage/sessions.ts +12 -0
  166. package/src/storage/sql/db.sql +5 -0
  167. package/src/storage/sql/schema.sql +4 -0
  168. package/src/storage/sql/sessions.sql +3 -3
  169. package/src/storage/sql/statements.generated.ts +10 -3
  170. package/src/storage/sql/turn-meta.sql +1 -1
  171. package/src/util/boot-timer.ts +15 -1
  172. package/src/util/chat-id.ts +30 -0
  173. package/src/util/concurrency.ts +1 -1
  174. package/src/util/log.ts +1 -1
  175. package/src/util/paths.ts +0 -2
  176. package/src/core/soul/README.md +0 -110
  177. package/src/core/soul/RESEARCH.md +0 -98
  178. package/src/core/soul/associative.ts +0 -98
  179. package/src/core/soul/centrality.ts +0 -98
  180. package/src/core/soul/cluster.ts +0 -83
  181. package/src/core/soul/compiler.ts +0 -207
  182. package/src/core/soul/consolidate.ts +0 -179
  183. package/src/core/soul/critic.ts +0 -162
  184. package/src/core/soul/dag.ts +0 -265
  185. package/src/core/soul/delta.ts +0 -123
  186. package/src/core/soul/drift.ts +0 -99
  187. package/src/core/soul/embedder.ts +0 -129
  188. package/src/core/soul/emergent-critic.ts +0 -96
  189. package/src/core/soul/forgetting.ts +0 -131
  190. package/src/core/soul/governance.ts +0 -93
  191. package/src/core/soul/hash.ts +0 -97
  192. package/src/core/soul/hdc.ts +0 -154
  193. package/src/core/soul/kernel.ts +0 -540
  194. package/src/core/soul/lattice.ts +0 -103
  195. package/src/core/soul/lens.ts +0 -110
  196. package/src/core/soul/projector.ts +0 -240
  197. package/src/core/soul/reflect.ts +0 -170
  198. package/src/core/soul/reflex.ts +0 -164
  199. package/src/core/soul/retrieve.ts +0 -146
  200. package/src/core/soul/salience.ts +0 -146
  201. package/src/core/soul/service.ts +0 -204
  202. package/src/core/soul/settings.ts +0 -47
  203. package/src/core/soul/signals.ts +0 -117
  204. package/src/core/soul/talon-embedder.ts +0 -80
  205. package/src/core/soul/taps.ts +0 -199
  206. package/src/core/soul/types.ts +0 -298
  207. package/src/core/soul/valence.ts +0 -83
  208. /package/src/backend/{shared → runtime}/frontends.ts +0 -0
  209. /package/src/backend/{shared → runtime}/metrics.ts +0 -0
  210. /package/src/backend/{shared → runtime}/sleep.ts +0 -0
  211. /package/src/backend/{shared → runtime/turn}/flow-violation.ts +0 -0
  212. /package/src/backend/{shared → runtime}/usage.ts +0 -0
  213. /package/src/core/{scripting/lua-runner.ts → scripts/lua.ts} +0 -0
  214. /package/src/frontend/native/{routes → bridge/routes}/index.ts +0 -0
  215. /package/src/frontend/native/{routes → bridge/routes}/memory.ts +0 -0
  216. /package/src/frontend/native/{routes → bridge/routes}/mesh.ts +0 -0
  217. /package/src/frontend/native/{routes → bridge/routes}/models.ts +0 -0
  218. /package/src/frontend/native/{routes → bridge/routes}/params.ts +0 -0
  219. /package/src/frontend/native/{routes → bridge/routes}/table.ts +0 -0
  220. /package/src/frontend/native/{tool-result.ts → turn/tool-result.ts} +0 -0
  221. /package/src/frontend/whatsapp/{auth-state.ts → connection/auth-state.ts} +0 -0
  222. /package/src/frontend/whatsapp/{identity.ts → connection/identity.ts} +0 -0
  223. /package/src/frontend/whatsapp/{pairing-lock.ts → connection/pairing-lock.ts} +0 -0
  224. /package/src/frontend/whatsapp/{pairing.ts → connection/pairing.ts} +0 -0
  225. /package/src/frontend/whatsapp/{pins.ts → messages/pins.ts} +0 -0
package/README.md CHANGED
@@ -119,9 +119,9 @@ index.ts Composition root
119
119
  | +-- bus/ Typed pub-sub spine + event journal
120
120
  | +-- vfs/ The talon:// namespace (~/.talon/ns), FUSE-backed
121
121
  | +-- mesh/ Device mesh: presence, exec/fs channel, teleport
122
- | +-- soul/ Soul kernel — associative recall over memory
123
- | +-- scripts/ Run-to-completion execution of saved scripts
124
- | +-- scripting/ WASM-sandboxed Lua runner for trigger scripts
122
+ | +-- memory/ Typed memory store policy: core view, import, taps
123
+ | +-- scripts/ Run-to-completion execution of saved scripts +
124
+ | | the WASM-sandboxed Lua runner for trigger scripts
125
125
  | +-- daemon/ Start / stop / restart, pidfile, discovery
126
126
  | +-- plugin/ Plugin loader, registry, hot-reload
127
127
  | +-- update/ Self-update for git-checkout deployments
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "talon-agent",
3
- "version": "4.6.0",
3
+ "version": "5.0.0",
4
4
  "description": "Multi-frontend AI agent with full tool access, streaming, cron jobs, and plugin system",
5
5
  "author": "Dylan Neve",
6
6
  "license": "MIT",
@@ -80,10 +80,11 @@
80
80
  "test:watch": "vitest",
81
81
  "test:coverage": "vitest run --coverage",
82
82
  "typecheck": "tsc --noEmit",
83
- "lint": "oxlint src/",
83
+ "lint": "oxlint --max-warnings 0 src/",
84
84
  "depcruise": "node scripts/check-architecture.mjs",
85
85
  "ratchets": "node scripts/check-ratchets.mjs",
86
86
  "function-size": "node scripts/check-function-size.mjs",
87
+ "tree": "node scripts/check-tree.mjs",
87
88
  "knip": "knip",
88
89
  "format": "prettier --write src/ prompts/",
89
90
  "format:check": "prettier --check src/ prompts/",
package/prompts/README.md CHANGED
@@ -95,7 +95,7 @@ frontend, so it never belongs in the frontend `.md` files:
95
95
  The delivery TOOL NAMES are per-frontend (`end_turn`/`send`/`react` on
96
96
  telegram & discord, `end_turn`/`send_message`/`react` on native,
97
97
  `end_turn`/`send_message` on teams) and are injected into the templates
98
- by `src/backend/shared/delivery-contract.ts`, which is also where the
98
+ by `src/backend/runtime/prompt/delivery-contract.ts`, which is also where the
99
99
  frontend-aware `[FLOW VIOLATION]` reminder and the first-turn nudge
100
100
  (appended to the turn-0 user message) are built.
101
101
 
@@ -109,7 +109,7 @@ contradictions between backends.
109
109
  `heartbeat.md` and `dream.md` are not part of the chat system prompt — they
110
110
  are standalone prompts for the background heartbeat and memory-consolidation
111
111
  (dream) agents, loaded by `src/core/background/heartbeat.ts` /
112
- `src/core/background/dream.ts`.
112
+ `src/core/background/dream/index.ts`.
113
113
 
114
114
  ## Token budget
115
115
 
@@ -59,3 +59,19 @@ memory instead is what crowds a memory file with snapshots that were true for
59
59
  an hour.
60
60
 
61
61
  Memory updates should usually be quiet unless the user asks about them.
62
+
63
+ ### Remembering
64
+
65
+ When the `remember`, `recall`, and `forget` tools are available, they are the
66
+ canonical store — use them in preference to editing memory files by hand.
67
+
68
+ Call `remember` as you learn something durable: a stated preference, a
69
+ correction to something you had wrong, a fact about a person or a project the
70
+ user would expect you to still know next week. One claim per call.
71
+
72
+ Never store secrets or credentials, live operational status, or anything the
73
+ user asked you to keep off the record — and never store a claim as fact when
74
+ you are only guessing.
75
+
76
+ If a claim is already on file in a different wording, replace it rather than
77
+ adding a second copy; `forget` needs a reason, and drops are recoverable.
package/src/app.ts CHANGED
@@ -10,8 +10,11 @@ import { getFrontends } from "./core/config/index.js";
10
10
  import { startUploadCleanup, stopUploadCleanup } from "./util/workspace.js";
11
11
  import { flushDatabase } from "./storage/db.js";
12
12
  import { getActiveCount, stopAllTurns } from "./core/engine/dispatcher.js";
13
- import { startPulseTimer, stopPulseTimer } from "./core/background/pulse.js";
14
- import { stopPlanAlerts } from "./core/background/plan-alerts.js";
13
+ import {
14
+ startPulseTimer,
15
+ stopPulseTimer,
16
+ } from "./core/background/pulse/pulse.js";
17
+ import { stopPlanAlerts } from "./core/background/pulse/plan-alerts.js";
15
18
  import {
16
19
  startHeartbeatTimer,
17
20
  stopHeartbeatTimer,
@@ -21,7 +24,7 @@ import {
21
24
  startCronTimer,
22
25
  stopCronTimer,
23
26
  runStartupCatchup,
24
- } from "./core/background/cron.js";
27
+ } from "./core/background/cron/scheduler.js";
25
28
  import { shutdownTriggers } from "./core/background/triggers/index.js";
26
29
  import { pruneSettledTriggers } from "./storage/triggers.js";
27
30
  import { startWatchdog, stopWatchdog } from "./util/watchdog.js";
@@ -51,6 +54,11 @@ import {
51
54
  writePidRecord,
52
55
  removePidRecordIfOwnedBy,
53
56
  } from "./core/daemon/pidfile.js";
57
+ import {
58
+ recordBootMetrics,
59
+ startResourceSampler,
60
+ stopResourceSampler,
61
+ } from "./core/daemon/resource-sampler.js";
54
62
 
55
63
  const { config } = await bootPhase("bootstrap", () => bootstrap());
56
64
 
@@ -202,6 +210,7 @@ async function gracefulShutdown(signal: string): Promise<void> {
202
210
  });
203
211
  await shutdownStep("triggers", shutdownTriggers);
204
212
  await shutdownStep("watchdog", stopWatchdog);
213
+ await shutdownStep("resource sampler", stopResourceSampler);
205
214
  await shutdownStep("upload cleanup", stopUploadCleanup);
206
215
  await shutdownStep("mcp hub", async () => {
207
216
  const { shutdownHub } = await import("./core/mcp-hub/index.js");
@@ -311,7 +320,13 @@ async function main(): Promise<void> {
311
320
  await bootPhase("frontends start", () =>
312
321
  Promise.all(blockingFrontends.map((frontend) => frontend.start())),
313
322
  );
314
- log("bot", `Ready in ${bootReport()}`);
323
+ // Phase 0 accounting (docs/ts-migration-plan.md): the boot is over the
324
+ // moment the frontends are listening, so the totals are folded into the
325
+ // metrics store here, from the same uptime figure the log line prints.
326
+ const bootMs = Math.round(process.uptime() * 1000);
327
+ recordBootMetrics(bootMs);
328
+ startResourceSampler();
329
+ log("bot", `Ready in ${bootReport(bootMs)}`);
315
330
  for (const frontend of stdinFrontends) {
316
331
  void frontend
317
332
  .start()
@@ -6,7 +6,7 @@
6
6
  * (session expired / context overflow / model fallback via
7
7
  * `applyRetryDecisionStream`) and the post-result watchdog. The phases
8
8
  * after the stream — accounting, the trailing-prose contract, the result
9
- * events — are the shared ones in `backend/shared/turn-phases.ts`.
9
+ * events — are the shared ones in `backend/runtime/turn/turn-phases.ts`.
10
10
  *
11
11
  * The exported async generator `runChatTurn` is what the factory wires
12
12
  * onto `ChatBackend.runChatTurn` — no wrapper, no callback shim.
@@ -34,12 +34,13 @@ import {
34
34
  } from "../../core/agent-runtime/events.js";
35
35
  import type { ChatRunParams } from "../../core/agent-runtime/capabilities.js";
36
36
  import { makeBareModelRef } from "../../core/agent-runtime/model-ref.js";
37
- import { applyRetryDecisionStream } from "../shared/handle-retry.js";
37
+ import { applyRetryDecisionStream } from "../runtime/turn/handle-retry.js";
38
38
  import { getConfig } from "./state.js";
39
39
  import { buildSdkOptions, getActiveFrontends } from "./options.js";
40
40
  import { waitForMcpServersReady } from "./mcp-ready.js";
41
41
  import { invalidatePlanUsage } from "./plan-usage.js";
42
- import { frontendsForChat } from "../shared/frontends.js";
42
+ import { frontendsForChat } from "../runtime/frontends.js";
43
+ import { rollUpTurnCache } from "../runtime/cache/cache-metrics.js";
43
44
  import {
44
45
  createStreamState,
45
46
  isSystemInit,
@@ -47,10 +48,12 @@ import {
47
48
  isAssistant,
48
49
  isResult,
49
50
  isRateLimitEvent,
51
+ isCompactBoundary,
50
52
  isUserMessage,
51
53
  extractToolResults,
52
54
  processStreamDelta,
53
55
  processAssistantMessage,
56
+ processCompactBoundary,
54
57
  processResultMessage,
55
58
  type StreamState,
56
59
  } from "./stream.js";
@@ -74,7 +77,7 @@ import {
74
77
  enforceTrailingProse,
75
78
  buildResultEvents,
76
79
  turnUsageSnapshot,
77
- } from "../shared/index.js";
80
+ } from "../runtime/index.js";
78
81
 
79
82
  // ── Post-result watchdog ────────────────────────────────────────────────────
80
83
  // The SDK's PostToolBatch hook is the canonical loop-terminator — it returns
@@ -334,6 +337,12 @@ async function* consumeSdkStream(
334
337
  yield* translateToolResults(ctx, message);
335
338
  continue;
336
339
  }
340
+ // The SDK compacted the session mid-turn. Measured, not acted on: the
341
+ // turn carries on exactly as before.
342
+ if (isCompactBoundary(message)) {
343
+ processCompactBoundary(message, ctx.chatId);
344
+ continue;
345
+ }
337
346
  // The turn just moved the plan's usage — drop the cached windows so
338
347
  // the next /status reads them again instead of showing pre-turn
339
348
  // figures.
@@ -399,6 +408,7 @@ function buildTurnPrompt(
399
408
  senderHandle: params.senderHandle,
400
409
  isGroup: params.isGroup,
401
410
  messageId: params.messageId,
411
+ retrievedMemory: params.retrievedMemory,
402
412
  });
403
413
  if (frontend && previousTurns === 0 && !internal.flowRetries) {
404
414
  prompt += `\n\n${buildFirstTurnReminder(frontend)}`;
@@ -445,12 +455,23 @@ function accountFailedClaudeTurn(
445
455
  /**
446
456
  * The aggregate `cache=NN%` can't distinguish a turn that reused the
447
457
  * previous turn's prefix from one that re-wrote it — see
448
- * shared/cache-telemetry.ts. A lookback overflow only *predicts* a miss,
458
+ * runtime/cache/cache-telemetry.ts. A lookback overflow only *predicts* a miss,
449
459
  * so warn when this turn's verdict proves the previous turn's overflow
450
460
  * cost a prefix re-write, then record this turn's overflow for the next.
461
+ *
462
+ * Also the rollup seam: the verdict goes into `cache.first_request.*` (and
463
+ * `cache.session_start.*` on a chat's first turn) here rather than in
464
+ * `runChatTurn`, which is at its size limit. `turnsIncludingThis` is the
465
+ * session's turn count with this turn already counted — `incrementTurns`
466
+ * has run by the time this is called, so 1 means "this was the first".
451
467
  */
452
- function reportCacheVerdict(chatId: string, state: StreamState): void {
468
+ function reportCacheVerdict(
469
+ chatId: string,
470
+ state: StreamState,
471
+ turnsIncludingThis: number,
472
+ ): void {
453
473
  if (state.cacheStats) {
474
+ rollUpTurnCache(chatId, state.cacheStats, turnsIncludingThis);
454
475
  const overflow = priorLookbackOverflow(chatId);
455
476
  if (
456
477
  overflow !== undefined &&
@@ -647,7 +668,7 @@ export async function* runChatTurn(
647
668
  incrementTurns(chatId);
648
669
 
649
670
  state.allResponseText += state.currentBlockText;
650
- reportCacheVerdict(chatId, state);
671
+ reportCacheVerdict(chatId, state, session.turns);
651
672
 
652
673
  const usage = turnUsageSnapshot(state);
653
674
  log(
@@ -19,7 +19,7 @@ import { log, logWarn } from "../../util/log.js";
19
19
  import { ALLOWED_TOOLS_BACKGROUND } from "../../core/constants.js";
20
20
  import { EFFORT_MAP } from "./constants.js";
21
21
  import { buildMcpServers, buildPluginMcpServers } from "./options.js";
22
- import { warnIfBelowCacheMinimum } from "../shared/cache-telemetry.js";
22
+ import { warnIfBelowCacheMinimum } from "../runtime/cache/cache-telemetry.js";
23
23
 
24
24
  const DEFAULT_SUBPROCESS_KILL_GRACE_MS = 5 * 1000;
25
25
 
@@ -15,7 +15,7 @@ import type {
15
15
  HookCallback,
16
16
  HookJSONOutput,
17
17
  } from "@anthropic-ai/claude-agent-sdk";
18
- import type { PreparedSystemPrompt } from "../shared/system-prompt.js";
18
+ import type { PreparedSystemPrompt } from "../runtime/prompt/system-prompt.js";
19
19
  import { getSession } from "../../storage/sessions.js";
20
20
  import { getChatSettings } from "../../storage/chat-settings.js";
21
21
  import { resolveModelId } from "../../core/models/catalog.js";
@@ -25,11 +25,12 @@ import {
25
25
  pluginHubUrl,
26
26
  hubPluginServerNames,
27
27
  } from "../../core/mcp-hub/index.js";
28
- import { nonTerminalFrontends, frontendsForChat } from "../shared/frontends.js";
29
28
  import {
30
- noteToolFingerprint,
31
- toolFingerprint,
32
- } from "../shared/cache-telemetry.js";
29
+ nonTerminalFrontends,
30
+ frontendsForChat,
31
+ } from "../runtime/frontends.js";
32
+ import { toolFingerprint } from "../runtime/cache/cache-telemetry.js";
33
+ import { reportToolFingerprint } from "../runtime/cache/cache-metrics.js";
33
34
  import { log, logError } from "../../util/log.js";
34
35
  import { getConfig, getBridgePort } from "./state.js";
35
36
  import { ALLOWED_TOOLS_CHAT, EFFORT_MAP } from "./constants.js";
@@ -395,7 +396,7 @@ export function buildSdkOptions(
395
396
  // mid-session invalidates the system prompt and every cached message after
396
397
  // it — the most expensive cache event there is, and one the aggregate
397
398
  // hit-rate can't show. Plugin-provided MCP servers are the mutable part.
398
- noteToolFingerprint(
399
+ reportToolFingerprint(
399
400
  chatId,
400
401
  toolFingerprint(builtinTools, Object.keys(mcpServers)),
401
402
  );
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import type {
10
+ SDKCompactBoundaryMessage,
10
11
  SDKMessage,
11
12
  SDKSystemMessage,
12
13
  SDKPartialAssistantMessage,
@@ -22,7 +23,8 @@ import { checkModelDrift } from "./model-drift.js";
22
23
  import {
23
24
  turnCacheStats,
24
25
  type TurnCacheStats,
25
- } from "../shared/cache-telemetry.js";
26
+ } from "../runtime/cache/cache-telemetry.js";
27
+ import { recordCompactBoundary } from "../runtime/cache/cache-metrics.js";
26
28
 
27
29
  // ── Stream state accumulator ────────────────────────────────────────────────
28
30
 
@@ -46,7 +48,7 @@ export type StreamState = {
46
48
  * `usage.iterations`. Undefined when the provider reported none — the
47
49
  * aggregate totals above cannot distinguish a turn that read the previous
48
50
  * turn's prefix from one that re-wrote it, and that distinction is the
49
- * whole cost signal (see shared/cache-telemetry.ts).
51
+ * whole cost signal (see runtime/cache/cache-telemetry.ts).
50
52
  */
51
53
  cacheStats: TurnCacheStats | undefined;
52
54
  lastStreamUpdate: number;
@@ -156,6 +158,18 @@ export function isRateLimitEvent(msg: SDKMessage): boolean {
156
158
  return msg.type === "rate_limit_event";
157
159
  }
158
160
 
161
+ /**
162
+ * Emitted when the SDK compacts the session — the transcript before the
163
+ * boundary is replaced by a summary, so every cached block after the system
164
+ * prompt is gone and the next turn re-writes it. Shares `type: 'system'`
165
+ * with the init message, so the subtype is what tells them apart.
166
+ */
167
+ export function isCompactBoundary(
168
+ msg: SDKMessage,
169
+ ): msg is SDKCompactBoundaryMessage {
170
+ return msg.type === "system" && msg.subtype === "compact_boundary";
171
+ }
172
+
159
173
  // ── Message processors ──────────────────────────────────────────────────────
160
174
 
161
175
  /** Output of `processStreamDelta` when the throttle interval has elapsed. */
@@ -334,6 +348,19 @@ export function extractToolResults(msg: SDKUserMessage): ToolResultInfo[] {
334
348
  return results;
335
349
  }
336
350
 
351
+ /**
352
+ * Record a compaction boundary. Observational only: the turn proceeds
353
+ * exactly as it did before this message was recognised — what changes is
354
+ * that `session.compacted.*` now says how often the SDK compacts and how
355
+ * much it reclaims (docs/cache-economics.md, PR A step 3).
356
+ */
357
+ export function processCompactBoundary(
358
+ msg: SDKCompactBoundaryMessage,
359
+ chatId: string,
360
+ ): void {
361
+ recordCompactBoundary(chatId, msg.compact_metadata);
362
+ }
363
+
337
364
  /** Context fill of the last API iteration + whether the turn's first request hit the cache. */
338
365
  function readContextFill(
339
366
  state: StreamState,
@@ -450,4 +477,4 @@ export function processResultMessage(
450
477
  export {
451
478
  normalizeForDedupe,
452
479
  isDuplicateOfDelivered,
453
- } from "../shared/delivered-text.js";
480
+ } from "../runtime/turn/delivered-text.js";
@@ -11,7 +11,7 @@ import { getSession } from "../../storage/sessions.js";
11
11
  import { log, logWarn } from "../../util/log.js";
12
12
  import { getConfig } from "./state.js";
13
13
  import { buildSdkOptions } from "./options.js";
14
- import { prepareSystemPrompt } from "../shared/index.js";
14
+ import { prepareSystemPrompt } from "../runtime/index.js";
15
15
 
16
16
  export async function warmSession(chatId: string): Promise<void> {
17
17
  // Guard against being called before initAgent()
@@ -2,7 +2,7 @@
2
2
  * Codex backend constants.
3
3
  */
4
4
 
5
- import { buildDeliveryContract } from "../shared/delivery-contract.js";
5
+ import { buildDeliveryContract } from "../runtime/prompt/delivery-contract.js";
6
6
 
7
7
  /**
8
8
  * System-prompt suffix appended to the user-configured system prompt.
@@ -10,8 +10,8 @@ import { registerBackend } from "../../core/agent-runtime/backend-registry.js";
10
10
  import { codexDoctorChecks } from "./doctor.js";
11
11
  import type { BackendFactory } from "../../core/agent-runtime/backend-registry.js";
12
12
  import { log } from "../../util/log.js";
13
- import { handlerToEvents } from "../shared/handler-to-events.js";
14
- import { interruptChatTurn } from "../shared/turn-interrupt.js";
13
+ import { handlerToEvents } from "../runtime/turn/handler-to-events.js";
14
+ import { interruptChatTurn } from "../runtime/turn/turn-interrupt.js";
15
15
  import {
16
16
  composeBackend,
17
17
  type ChatBackend,
@@ -16,7 +16,7 @@ import {
16
16
  recordToolUse,
17
17
  recordToolCall,
18
18
  type StreamState,
19
- } from "../../shared/index.js";
19
+ } from "../../runtime/index.js";
20
20
 
21
21
  export interface HandleEventContext {
22
22
  state: StreamState;
@@ -7,11 +7,14 @@
7
7
  * `codex.resumeThread(id)`, the rollout-JSONL live/settle usage accounting
8
8
  * (`rollout-accounting.ts`), and the ChatGPT-OAuth model-mismatch recovery
9
9
  * ladder. The post-stream phases are the shared ones in
10
- * `backend/shared/turn-phases.ts`.
10
+ * `backend/runtime/turn/turn-phases.ts`.
11
11
  */
12
12
 
13
13
  import type { Thread, Usage } from "@openai/codex-sdk";
14
- import type { QueryParams, QueryResult } from "../../shared/handler-types.js";
14
+ import type {
15
+ QueryParams,
16
+ QueryResult,
17
+ } from "../../runtime/turn/handler-types.js";
15
18
  import {
16
19
  getSession,
17
20
  incrementTurns,
@@ -37,7 +40,7 @@ import {
37
40
  nameSessionFromFirstMessage,
38
41
  finishCallbackTurn,
39
42
  type StreamState,
40
- } from "../../shared/index.js";
43
+ } from "../../runtime/index.js";
41
44
 
42
45
  import {
43
46
  codexSystemPromptSuffix,
@@ -48,7 +51,7 @@ import {
48
51
  import {
49
52
  frontendsForChat,
50
53
  nonTerminalFrontends,
51
- } from "../../shared/frontends.js";
54
+ } from "../../runtime/frontends.js";
52
55
  import { getState } from "../state.js";
53
56
  import { ensureCodex, getCodexAuthInfo } from "../init.js";
54
57
  import {
@@ -213,6 +216,23 @@ async function maybeFallbackForChatGptMismatch(
213
216
  * ChatGPT-OAuth account is swapped pre-emptively rather than letting the
214
217
  * first turn fail.
215
218
  */
219
+ /**
220
+ * The turn's user prompt: the shared framing every backend emits, plus
221
+ * whatever this turn's memory retrieval produced. `formatUserPrompt` is
222
+ * the one place `retrievedMemory` is rendered — see
223
+ * `backend/runtime/prompt/prompt-format.ts`.
224
+ */
225
+ function buildTurnPrompt(params: QueryParams): string {
226
+ return formatUserPrompt({
227
+ text: params.text,
228
+ senderName: params.senderName ?? "user",
229
+ senderHandle: params.senderHandle,
230
+ isGroup: params.isGroup,
231
+ messageId: params.messageId,
232
+ retrievedMemory: params.retrievedMemory,
233
+ });
234
+ }
235
+
216
236
  function resolveCodexModel(chatId: string, requested: string | undefined) {
217
237
  const authInfo = getCodexAuthInfo();
218
238
  const authAwareDefault =
@@ -409,7 +429,7 @@ export async function handleMessage(
409
429
  }
410
430
  const codex = ensureCodex(params.chatId);
411
431
 
412
- const { chatId, text, senderName, senderHandle, isGroup, messageId } = params;
432
+ const { chatId, text, senderName, isGroup } = params;
413
433
  const t0 = Date.now();
414
434
  const session = getSession(chatId);
415
435
  const previousTurns = session.turns;
@@ -432,13 +452,7 @@ export async function handleMessage(
432
452
  sessionEpoch: session.createdAt,
433
453
  });
434
454
 
435
- const prompt = formatUserPrompt({
436
- text,
437
- senderName: senderName ?? "user",
438
- senderHandle,
439
- isGroup,
440
- messageId,
441
- });
455
+ const prompt = buildTurnPrompt(params);
442
456
 
443
457
  log("agent", `[${chatId}] <- (${text.length} chars)`);
444
458
  traceMessage(chatId, "in", text, { senderName, isGroup });
@@ -19,7 +19,7 @@ import {
19
19
  pushLiveUsage,
20
20
  recordTokens,
21
21
  type StreamState,
22
- } from "../../shared/index.js";
22
+ } from "../../runtime/index.js";
23
23
  import { CODEX_LIVE_POLL_INTERVAL_MS } from "../constants.js";
24
24
  import {
25
25
  readLastRolloutSnapshot,
@@ -25,7 +25,7 @@ import { Codex } from "@openai/codex-sdk";
25
25
  import type { TalonConfig } from "../../core/config/index.js";
26
26
  import type { FrontendName } from "../../core/agent-runtime/backend-registry.js";
27
27
  import { log, logWarn } from "../../util/log.js";
28
- import { nonTerminalFrontends } from "../shared/frontends.js";
28
+ import { nonTerminalFrontends } from "../runtime/frontends.js";
29
29
  import { getState } from "./state.js";
30
30
  import { asCodexConfig, buildCodexMcpServers } from "./mcp-config.js";
31
31
  import { detectCodexAuth, type CodexAuthInfo } from "./auth.js";
@@ -30,7 +30,7 @@ import {
30
30
  hubPluginServerNames,
31
31
  } from "../../core/mcp-hub/index.js";
32
32
  import type { ToolExclusionConfig } from "../../core/tools/mcp-env.js";
33
- import { frontendsForChat } from "../shared/frontends.js";
33
+ import { frontendsForChat } from "../runtime/frontends.js";
34
34
 
35
35
  /**
36
36
  * AppToolApproval values accepted by Codex's `mcp_servers.<name>` table.
@@ -19,7 +19,7 @@
19
19
 
20
20
  import type { OneShotAgentParams, OneShotUsage } from "../../core/types.js";
21
21
  import { log, logWarn } from "../../util/log.js";
22
- import { appendBackendSuffix } from "../shared/index.js";
22
+ import { appendBackendSuffix } from "../runtime/index.js";
23
23
  import { ensureCodex, getCodexAuthInfo } from "./init.js";
24
24
  import {
25
25
  CODEX_SYSTEM_PROMPT_SUFFIX,
@@ -3,7 +3,10 @@
3
3
  * Kilo's server bindings.
4
4
  */
5
5
 
6
- import type { QueryParams, QueryResult } from "../../shared/handler-types.js";
6
+ import type {
7
+ QueryParams,
8
+ QueryResult,
9
+ } from "../../runtime/turn/handler-types.js";
7
10
  import { runRemoteChatTurn } from "../../remote-server/chat-turn.js";
8
11
  import {
9
12
  ensureServer,
@@ -10,7 +10,7 @@
10
10
  * orchestration) with MCP servers wired from the plugin system.
11
11
  */
12
12
 
13
- import { buildDeliveryContract } from "../shared/delivery-contract.js";
13
+ import { buildDeliveryContract } from "../runtime/prompt/delivery-contract.js";
14
14
 
15
15
  /**
16
16
  * Agents-specific addendum to the shared tool-only delivery contract:
@@ -13,8 +13,8 @@ import { registerBackend } from "../../core/agent-runtime/backend-registry.js";
13
13
  import { openAIAgentsDoctorChecks } from "./doctor.js";
14
14
  import type { BackendFactory } from "../../core/agent-runtime/backend-registry.js";
15
15
  import { log } from "../../util/log.js";
16
- import { handlerToEvents } from "../shared/handler-to-events.js";
17
- import { interruptChatTurn } from "../shared/turn-interrupt.js";
16
+ import { handlerToEvents } from "../runtime/turn/handler-to-events.js";
17
+ import { interruptChatTurn } from "../runtime/turn/turn-interrupt.js";
18
18
  import {
19
19
  composeBackend,
20
20
  type ChatBackend,
@@ -9,7 +9,7 @@ import {
9
9
  createStreamState,
10
10
  recordToolUse,
11
11
  recordToolCall,
12
- } from "../../shared/index.js";
12
+ } from "../../runtime/index.js";
13
13
 
14
14
  export interface HandleRunItemContext {
15
15
  state: ReturnType<typeof createStreamState>;
@@ -6,11 +6,14 @@
6
6
  * MCP bundle, iterating the `StreamedRunResult` (see `events.ts`), and
7
7
  * `.cancel()` on terminator. The post-stream phases — accounting, the
8
8
  * trailing-prose flow-violation retry, the result — are the shared ones in
9
- * `backend/shared/turn-phases.ts`.
9
+ * `backend/runtime/turn/turn-phases.ts`.
10
10
  */
11
11
 
12
12
  import { Agent, run } from "@openai/agents";
13
- import type { QueryParams, QueryResult } from "../../shared/handler-types.js";
13
+ import type {
14
+ QueryParams,
15
+ QueryResult,
16
+ } from "../../runtime/turn/handler-types.js";
14
17
  import { getSession, incrementTurns } from "../../../storage/sessions.js";
15
18
  import { getChatSettings } from "../../../storage/chat-settings.js";
16
19
  import { log, logError, logWarn } from "../../../util/log.js";
@@ -33,7 +36,7 @@ import {
33
36
  enforceTrailingProse,
34
37
  finishCallbackTurn,
35
38
  type StreamState,
36
- } from "../../shared/index.js";
39
+ } from "../../runtime/index.js";
37
40
 
38
41
  import {
39
42
  buildOpenAiAgentsSuffix,
@@ -43,7 +46,7 @@ import {
43
46
  } from "../constants.js";
44
47
  import { getState, getOrCreateSession } from "../state.js";
45
48
  import { getActiveFrontends } from "../init.js";
46
- import { frontendsForChat } from "../../shared/frontends.js";
49
+ import { frontendsForChat } from "../../runtime/frontends.js";
47
50
  import { getOrCreateBundle } from "../mcp-pool.js";
48
51
  import { OPENAI_AGENTS_BUILTIN_TOOLS } from "../builtins.js";
49
52
  import { activeAborts } from "./state.js";
@@ -105,6 +108,7 @@ function buildTurnPrompt(
105
108
  senderHandle: params.senderHandle,
106
109
  isGroup: params.isGroup,
107
110
  messageId: params.messageId,
111
+ retrievedMemory: params.retrievedMemory,
108
112
  });
109
113
  if (frontend && previousTurns === 0 && !isRetry) {
110
114
  prompt += `\n\n${buildFirstTurnReminder(frontend)}`;
@@ -46,7 +46,7 @@ import { setDefaultOpenAIClient, setOpenAIAPI } from "@openai/agents";
46
46
  import type { TalonConfig } from "../../core/config/index.js";
47
47
  import type { FrontendName } from "../../core/agent-runtime/backend-registry.js";
48
48
  import { log, logWarn } from "../../util/log.js";
49
- import { nonTerminalFrontends } from "../shared/frontends.js";
49
+ import { nonTerminalFrontends } from "../runtime/frontends.js";
50
50
  import { getState } from "./state.js";
51
51
  import { startDiscovery } from "./discovery.js";
52
52
 
@@ -31,7 +31,7 @@ import {
31
31
  hubPluginServerNames,
32
32
  } from "../../core/mcp-hub/index.js";
33
33
  import type { ToolExclusionConfig } from "../../core/tools/mcp-env.js";
34
- import { frontendsForChat } from "../shared/frontends.js";
34
+ import { frontendsForChat } from "../runtime/frontends.js";
35
35
  import { log, logWarn } from "../../util/log.js";
36
36
 
37
37
  /**
@@ -3,7 +3,10 @@
3
3
  * bound to OpenCode's server bindings.
4
4
  */
5
5
 
6
- import type { QueryParams, QueryResult } from "../../shared/handler-types.js";
6
+ import type {
7
+ QueryParams,
8
+ QueryResult,
9
+ } from "../../runtime/turn/handler-types.js";
7
10
  import { runRemoteChatTurn } from "../../remote-server/chat-turn.js";
8
11
  import {
9
12
  ensureServer,