@namzu/sdk 36.0.0 → 37.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 (308) hide show
  1. package/CHANGELOG.md +180 -0
  2. package/README.md +68 -41
  3. package/dist/bridge/a2a/mapper.d.ts.map +1 -1
  4. package/dist/bridge/a2a/mapper.js +2 -0
  5. package/dist/bridge/a2a/mapper.js.map +1 -1
  6. package/dist/bridge/sse/mapper.d.ts.map +1 -1
  7. package/dist/bridge/sse/mapper.js +2 -0
  8. package/dist/bridge/sse/mapper.js.map +1 -1
  9. package/dist/compaction/extractor.d.ts +1 -1
  10. package/dist/compaction/extractor.d.ts.map +1 -1
  11. package/dist/compaction/extractor.js +15 -7
  12. package/dist/compaction/extractor.js.map +1 -1
  13. package/dist/compaction/manager.d.ts +2 -0
  14. package/dist/compaction/manager.d.ts.map +1 -1
  15. package/dist/compaction/manager.js +12 -1
  16. package/dist/compaction/manager.js.map +1 -1
  17. package/dist/compaction/verifier.d.ts.map +1 -1
  18. package/dist/compaction/verifier.js +82 -15
  19. package/dist/compaction/verifier.js.map +1 -1
  20. package/dist/contracts/schemas.d.ts +10 -10
  21. package/dist/eval/harness-verification.d.ts +63 -0
  22. package/dist/eval/harness-verification.d.ts.map +1 -0
  23. package/dist/eval/harness-verification.js +165 -0
  24. package/dist/eval/harness-verification.js.map +1 -0
  25. package/dist/eval/index.d.ts +2 -0
  26. package/dist/eval/index.d.ts.map +1 -1
  27. package/dist/eval/index.js +1 -0
  28. package/dist/eval/index.js.map +1 -1
  29. package/dist/execution/code-runtime/json-value.d.ts +10 -0
  30. package/dist/execution/code-runtime/json-value.d.ts.map +1 -0
  31. package/dist/execution/code-runtime/json-value.js +89 -0
  32. package/dist/execution/code-runtime/json-value.js.map +1 -0
  33. package/dist/execution/code-runtime/worker-program.d.ts +8 -0
  34. package/dist/execution/code-runtime/worker-program.d.ts.map +1 -0
  35. package/dist/execution/code-runtime/worker-program.js +240 -0
  36. package/dist/execution/code-runtime/worker-program.js.map +1 -0
  37. package/dist/execution/code-runtime/worker.d.ts +22 -0
  38. package/dist/execution/code-runtime/worker.d.ts.map +1 -1
  39. package/dist/execution/code-runtime/worker.js +148 -111
  40. package/dist/execution/code-runtime/worker.js.map +1 -1
  41. package/dist/manager/agent/lifecycle.d.ts +12 -0
  42. package/dist/manager/agent/lifecycle.d.ts.map +1 -1
  43. package/dist/manager/agent/lifecycle.js +306 -37
  44. package/dist/manager/agent/lifecycle.js.map +1 -1
  45. package/dist/manager/run/persistence.d.ts +2 -0
  46. package/dist/manager/run/persistence.d.ts.map +1 -1
  47. package/dist/manager/run/persistence.js +6 -0
  48. package/dist/manager/run/persistence.js.map +1 -1
  49. package/dist/provider/capabilities.d.ts +3 -1
  50. package/dist/provider/capabilities.d.ts.map +1 -1
  51. package/dist/provider/capabilities.js +13 -0
  52. package/dist/provider/capabilities.js.map +1 -1
  53. package/dist/provider/fallback.d.ts.map +1 -1
  54. package/dist/provider/fallback.js +2 -0
  55. package/dist/provider/fallback.js.map +1 -1
  56. package/dist/public-runtime.d.ts +3 -1
  57. package/dist/public-runtime.d.ts.map +1 -1
  58. package/dist/public-runtime.js +2 -1
  59. package/dist/public-runtime.js.map +1 -1
  60. package/dist/public-tools.d.ts +1 -1
  61. package/dist/public-tools.d.ts.map +1 -1
  62. package/dist/public-tools.js +1 -1
  63. package/dist/public-tools.js.map +1 -1
  64. package/dist/public-types.d.ts +1 -0
  65. package/dist/public-types.d.ts.map +1 -1
  66. package/dist/registry/tool/execute.d.ts +3 -0
  67. package/dist/registry/tool/execute.d.ts.map +1 -1
  68. package/dist/registry/tool/execute.js +11 -80
  69. package/dist/registry/tool/execute.js.map +1 -1
  70. package/dist/run/command-gate.d.ts.map +1 -1
  71. package/dist/run/command-gate.js +65 -12
  72. package/dist/run/command-gate.js.map +1 -1
  73. package/dist/run/index.d.ts +2 -0
  74. package/dist/run/index.d.ts.map +1 -1
  75. package/dist/run/index.js +1 -0
  76. package/dist/run/index.js.map +1 -1
  77. package/dist/run/memory-promoter.d.ts +3 -35
  78. package/dist/run/memory-promoter.d.ts.map +1 -1
  79. package/dist/run/memory-promoter.js +32 -40
  80. package/dist/run/memory-promoter.js.map +1 -1
  81. package/dist/run/memory-recall.d.ts +25 -0
  82. package/dist/run/memory-recall.d.ts.map +1 -0
  83. package/dist/run/memory-recall.js +228 -0
  84. package/dist/run/memory-recall.js.map +1 -0
  85. package/dist/run/reporter.d.ts.map +1 -1
  86. package/dist/run/reporter.js +1 -0
  87. package/dist/run/reporter.js.map +1 -1
  88. package/dist/run/token-budget.d.ts +2 -0
  89. package/dist/run/token-budget.d.ts.map +1 -1
  90. package/dist/run/token-budget.js +4 -0
  91. package/dist/run/token-budget.js.map +1 -1
  92. package/dist/run/workspace-fingerprint.d.ts +10 -3
  93. package/dist/run/workspace-fingerprint.d.ts.map +1 -1
  94. package/dist/run/workspace-fingerprint.js +17 -8
  95. package/dist/run/workspace-fingerprint.js.map +1 -1
  96. package/dist/runtime/query/checkpoint.d.ts +14 -0
  97. package/dist/runtime/query/checkpoint.d.ts.map +1 -1
  98. package/dist/runtime/query/checkpoint.js +82 -1
  99. package/dist/runtime/query/checkpoint.js.map +1 -1
  100. package/dist/runtime/query/executor.d.ts +5 -0
  101. package/dist/runtime/query/executor.d.ts.map +1 -1
  102. package/dist/runtime/query/executor.js +70 -14
  103. package/dist/runtime/query/executor.js.map +1 -1
  104. package/dist/runtime/query/index.d.ts +5 -8
  105. package/dist/runtime/query/index.d.ts.map +1 -1
  106. package/dist/runtime/query/index.js +54 -7
  107. package/dist/runtime/query/index.js.map +1 -1
  108. package/dist/runtime/query/iteration/index.d.ts +10 -0
  109. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  110. package/dist/runtime/query/iteration/index.js +325 -36
  111. package/dist/runtime/query/iteration/index.js.map +1 -1
  112. package/dist/runtime/query/iteration/native-output.d.ts +10 -0
  113. package/dist/runtime/query/iteration/native-output.d.ts.map +1 -0
  114. package/dist/runtime/query/iteration/native-output.js +32 -0
  115. package/dist/runtime/query/iteration/native-output.js.map +1 -0
  116. package/dist/runtime/query/iteration/phases/advisory.d.ts.map +1 -1
  117. package/dist/runtime/query/iteration/phases/advisory.js +18 -13
  118. package/dist/runtime/query/iteration/phases/advisory.js.map +1 -1
  119. package/dist/runtime/query/iteration/phases/compaction.d.ts +2 -0
  120. package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
  121. package/dist/runtime/query/iteration/phases/compaction.js +32 -11
  122. package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
  123. package/dist/runtime/query/iteration/phases/context.d.ts +9 -0
  124. package/dist/runtime/query/iteration/phases/context.d.ts.map +1 -1
  125. package/dist/runtime/query/iteration/phases/context.js.map +1 -1
  126. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  127. package/dist/runtime/query/iteration/phases/tool-review.js +1 -1
  128. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  129. package/dist/runtime/query/iteration/phases/working-memory.d.ts +1 -1
  130. package/dist/runtime/query/iteration/phases/working-memory.d.ts.map +1 -1
  131. package/dist/runtime/query/iteration/phases/working-memory.js +23 -6
  132. package/dist/runtime/query/iteration/phases/working-memory.js.map +1 -1
  133. package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
  134. package/dist/runtime/query/iteration/stream-turn.js +2 -0
  135. package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
  136. package/dist/runtime/query/steering.d.ts +6 -2
  137. package/dist/runtime/query/steering.d.ts.map +1 -1
  138. package/dist/runtime/query/steering.js +17 -2
  139. package/dist/runtime/query/steering.js.map +1 -1
  140. package/dist/runtime/query/tool-call-budget.d.ts +19 -0
  141. package/dist/runtime/query/tool-call-budget.d.ts.map +1 -0
  142. package/dist/runtime/query/tool-call-budget.js +110 -0
  143. package/dist/runtime/query/tool-call-budget.js.map +1 -0
  144. package/dist/runtime/query/tooling.d.ts +2 -0
  145. package/dist/runtime/query/tooling.d.ts.map +1 -1
  146. package/dist/runtime/query/tooling.js +4 -0
  147. package/dist/runtime/query/tooling.js.map +1 -1
  148. package/dist/scheduler/local.d.ts.map +1 -1
  149. package/dist/scheduler/local.js +1 -0
  150. package/dist/scheduler/local.js.map +1 -1
  151. package/dist/session/handoff/capacity.d.ts +2 -2
  152. package/dist/session/handoff/capacity.d.ts.map +1 -1
  153. package/dist/session/handoff/capacity.js +3 -2
  154. package/dist/session/handoff/capacity.js.map +1 -1
  155. package/dist/store/memory/disk.d.ts +6 -2
  156. package/dist/store/memory/disk.d.ts.map +1 -1
  157. package/dist/store/memory/disk.js +42 -25
  158. package/dist/store/memory/disk.js.map +1 -1
  159. package/dist/store/memory/index.d.ts +4 -0
  160. package/dist/store/memory/index.d.ts.map +1 -1
  161. package/dist/store/memory/index.js +55 -19
  162. package/dist/store/memory/index.js.map +1 -1
  163. package/dist/store/memory/memory.d.ts +3 -2
  164. package/dist/store/memory/memory.d.ts.map +1 -1
  165. package/dist/store/memory/memory.js +11 -2
  166. package/dist/store/memory/memory.js.map +1 -1
  167. package/dist/store/memory/operation-lock.d.ts +11 -0
  168. package/dist/store/memory/operation-lock.d.ts.map +1 -0
  169. package/dist/store/memory/operation-lock.js +94 -0
  170. package/dist/store/memory/operation-lock.js.map +1 -0
  171. package/dist/tools/builtins/computer-use.d.ts.map +1 -1
  172. package/dist/tools/builtins/computer-use.js +60 -10
  173. package/dist/tools/builtins/computer-use.js.map +1 -1
  174. package/dist/tools/builtins/run-code.d.ts +6 -0
  175. package/dist/tools/builtins/run-code.d.ts.map +1 -1
  176. package/dist/tools/builtins/run-code.js +15 -4
  177. package/dist/tools/builtins/run-code.js.map +1 -1
  178. package/dist/tools/builtins/search-tools.d.ts.map +1 -1
  179. package/dist/tools/builtins/search-tools.js +14 -2
  180. package/dist/tools/builtins/search-tools.js.map +1 -1
  181. package/dist/tools/defineTool.d.ts +2 -0
  182. package/dist/tools/defineTool.d.ts.map +1 -1
  183. package/dist/tools/defineTool.js +3 -0
  184. package/dist/tools/defineTool.js.map +1 -1
  185. package/dist/tools/memory/delete.d.ts +4 -0
  186. package/dist/tools/memory/delete.d.ts.map +1 -0
  187. package/dist/tools/memory/delete.js +26 -0
  188. package/dist/tools/memory/delete.js.map +1 -0
  189. package/dist/tools/memory/index.d.ts +2 -0
  190. package/dist/tools/memory/index.d.ts.map +1 -1
  191. package/dist/tools/memory/index.js +6 -0
  192. package/dist/tools/memory/index.js.map +1 -1
  193. package/dist/tools/memory/save.d.ts.map +1 -1
  194. package/dist/tools/memory/save.js +3 -2
  195. package/dist/tools/memory/save.js.map +1 -1
  196. package/dist/tools/memory/search.d.ts.map +1 -1
  197. package/dist/tools/memory/search.js +12 -5
  198. package/dist/tools/memory/search.js.map +1 -1
  199. package/dist/tools/memory/update.d.ts +4 -0
  200. package/dist/tools/memory/update.d.ts.map +1 -0
  201. package/dist/tools/memory/update.js +47 -0
  202. package/dist/tools/memory/update.js.map +1 -0
  203. package/dist/types/agent/scheduler.d.ts +5 -0
  204. package/dist/types/agent/scheduler.d.ts.map +1 -1
  205. package/dist/types/agent/task.d.ts +10 -0
  206. package/dist/types/agent/task.d.ts.map +1 -1
  207. package/dist/types/computer-use/index.d.ts +6 -0
  208. package/dist/types/computer-use/index.d.ts.map +1 -1
  209. package/dist/types/computer-use/index.js.map +1 -1
  210. package/dist/types/errors/catalog.js +1 -1
  211. package/dist/types/errors/catalog.js.map +1 -1
  212. package/dist/types/hitl/index.d.ts +11 -1
  213. package/dist/types/hitl/index.d.ts.map +1 -1
  214. package/dist/types/hitl/index.js.map +1 -1
  215. package/dist/types/memory/index.d.ts +14 -1
  216. package/dist/types/memory/index.d.ts.map +1 -1
  217. package/dist/types/project/entity.d.ts +1 -0
  218. package/dist/types/project/entity.d.ts.map +1 -1
  219. package/dist/types/provider/config.d.ts +2 -0
  220. package/dist/types/provider/config.d.ts.map +1 -1
  221. package/dist/types/provider/model.d.ts +4 -0
  222. package/dist/types/provider/model.d.ts.map +1 -1
  223. package/dist/types/run/answer-review.d.ts +2 -0
  224. package/dist/types/run/answer-review.d.ts.map +1 -1
  225. package/dist/types/run/events.d.ts +9 -0
  226. package/dist/types/run/events.d.ts.map +1 -1
  227. package/dist/types/run/events.js.map +1 -1
  228. package/dist/types/run/prepare-step.d.ts +19 -1
  229. package/dist/types/run/prepare-step.d.ts.map +1 -1
  230. package/dist/types/structured-output/index.d.ts +8 -2
  231. package/dist/types/structured-output/index.d.ts.map +1 -1
  232. package/dist/types/tool/index.d.ts +15 -0
  233. package/dist/types/tool/index.d.ts.map +1 -1
  234. package/dist/types/tool/index.js.map +1 -1
  235. package/dist/utils/json-snapshot.d.ts +3 -0
  236. package/dist/utils/json-snapshot.d.ts.map +1 -0
  237. package/dist/utils/json-snapshot.js +79 -0
  238. package/dist/utils/json-snapshot.js.map +1 -0
  239. package/package.json +4 -2
  240. package/src/bridge/a2a/mapper.ts +2 -0
  241. package/src/bridge/sse/mapper.ts +2 -0
  242. package/src/compaction/extractor.ts +21 -8
  243. package/src/compaction/manager.ts +11 -1
  244. package/src/compaction/verifier.ts +80 -18
  245. package/src/eval/harness-verification.ts +270 -0
  246. package/src/eval/index.ts +10 -0
  247. package/src/execution/code-runtime/json-value.ts +82 -0
  248. package/src/execution/code-runtime/worker-program.ts +239 -0
  249. package/src/execution/code-runtime/worker.ts +164 -111
  250. package/src/manager/agent/lifecycle.ts +340 -45
  251. package/src/manager/run/persistence.ts +7 -0
  252. package/src/provider/capabilities.ts +25 -1
  253. package/src/provider/fallback.ts +2 -0
  254. package/src/public-runtime.ts +4 -0
  255. package/src/public-tools.ts +5 -1
  256. package/src/public-types.ts +1 -0
  257. package/src/registry/tool/execute.ts +17 -79
  258. package/src/run/command-gate.ts +61 -13
  259. package/src/run/index.ts +2 -0
  260. package/src/run/memory-promoter.ts +32 -40
  261. package/src/run/memory-recall.ts +254 -0
  262. package/src/run/reporter.ts +1 -0
  263. package/src/run/token-budget.ts +4 -0
  264. package/src/run/workspace-fingerprint.ts +17 -8
  265. package/src/runtime/query/checkpoint.ts +96 -2
  266. package/src/runtime/query/executor.ts +77 -14
  267. package/src/runtime/query/index.ts +73 -16
  268. package/src/runtime/query/iteration/index.ts +377 -44
  269. package/src/runtime/query/iteration/native-output.ts +35 -0
  270. package/src/runtime/query/iteration/phases/advisory.ts +21 -11
  271. package/src/runtime/query/iteration/phases/compaction.ts +36 -14
  272. package/src/runtime/query/iteration/phases/context.ts +9 -0
  273. package/src/runtime/query/iteration/phases/tool-review.ts +5 -1
  274. package/src/runtime/query/iteration/phases/working-memory.ts +23 -5
  275. package/src/runtime/query/iteration/stream-turn.ts +2 -0
  276. package/src/runtime/query/steering.ts +19 -2
  277. package/src/runtime/query/tool-call-budget.ts +115 -0
  278. package/src/runtime/query/tooling.ts +6 -0
  279. package/src/scheduler/local.ts +1 -0
  280. package/src/session/handoff/capacity.ts +7 -3
  281. package/src/store/memory/disk.ts +52 -30
  282. package/src/store/memory/index.ts +75 -27
  283. package/src/store/memory/memory.ts +18 -6
  284. package/src/store/memory/operation-lock.ts +99 -0
  285. package/src/tools/builtins/computer-use.ts +61 -7
  286. package/src/tools/builtins/run-code.ts +24 -5
  287. package/src/tools/builtins/search-tools.ts +18 -3
  288. package/src/tools/defineTool.ts +5 -0
  289. package/src/tools/memory/delete.ts +29 -0
  290. package/src/tools/memory/index.ts +6 -0
  291. package/src/tools/memory/save.ts +4 -2
  292. package/src/tools/memory/search.ts +12 -5
  293. package/src/tools/memory/update.ts +50 -0
  294. package/src/types/agent/scheduler.ts +6 -0
  295. package/src/types/agent/task.ts +13 -0
  296. package/src/types/computer-use/index.ts +6 -0
  297. package/src/types/errors/catalog.ts +1 -1
  298. package/src/types/hitl/index.ts +11 -1
  299. package/src/types/memory/index.ts +16 -1
  300. package/src/types/project/entity.ts +1 -0
  301. package/src/types/provider/config.ts +2 -0
  302. package/src/types/provider/model.ts +4 -0
  303. package/src/types/run/answer-review.ts +2 -0
  304. package/src/types/run/events.ts +10 -0
  305. package/src/types/run/prepare-step.ts +19 -1
  306. package/src/types/structured-output/index.ts +15 -2
  307. package/src/types/tool/index.ts +16 -0
  308. package/src/utils/json-snapshot.ts +76 -0
@@ -5,6 +5,7 @@ import { toolResultToText } from '../../../../types/message/content.js'
5
5
  import { createRuntimeContextMessage } from '../../../../types/message/index.js'
6
6
  import type { ChatCompletionResponse } from '../../../../types/provider/index.js'
7
7
  import { toErrorMessage } from '../../../../utils/error.js'
8
+ import { activeContextWindow, measureContext } from './compaction.js'
8
9
  import type { IterationContext } from './context.js'
9
10
 
10
11
  function countToolCalls(ctx: IterationContext): number {
@@ -18,9 +19,8 @@ function countToolCalls(ctx: IterationContext): number {
18
19
  }
19
20
 
20
21
  function estimateContextWindowPercent(ctx: IterationContext): number {
21
- const budget = ctx.runConfig.tokenBudget
22
- if (budget <= 0) return 0
23
- return (ctx.runMgr.tokenUsage.totalTokens / budget) * 100
22
+ const window = activeContextWindow(ctx)
23
+ return (measureContext(ctx).tokens / window.tokens) * 100
24
24
  }
25
25
 
26
26
  function computeCostBudgetPercent(ctx: IterationContext): number | undefined {
@@ -67,7 +67,7 @@ export async function runAdvisoryPhase(
67
67
  contextWindowPercent: estimateContextWindowPercent(ctx),
68
68
  totalCostUsd: ctx.runMgr.costInfo.totalCost,
69
69
  costBudgetPercent: computeCostBudgetPercent(ctx),
70
- lastError: extractLastErrorFromMessages(ctx),
70
+ lastError: extractCurrentToolErrors(ctx, response),
71
71
  lastToolCategory: extractLastToolCategory(ctx, response),
72
72
  advisoryCallCount: advisoryCtx.callHistory.length,
73
73
  }
@@ -190,16 +190,26 @@ export async function runAdvisoryPhase(
190
190
  }
191
191
  }
192
192
 
193
- function extractLastErrorFromMessages(ctx: IterationContext): string | undefined {
193
+ function extractCurrentToolErrors(
194
+ ctx: IterationContext,
195
+ response: ChatCompletionResponse,
196
+ ): string | undefined {
197
+ const calls = new Map(response.message.toolCalls?.map((call) => [call.id, call.function.name]))
198
+ if (calls.size === 0) return undefined
194
199
  const messages = ctx.runMgr.messages
200
+ const errors: string[] = []
195
201
  for (let i = messages.length - 1; i >= 0; i--) {
196
202
  const msg = messages[i]
197
- if (msg?.role === 'tool') {
198
- // Tool results can now carry content blocks; the error sentinel is
199
- // a text convention, so flatten before matching it.
200
- const text = toolResultToText(msg.content)
201
- if (text.startsWith('Error:')) return text
203
+ // The current assistant turn starts this batch. Never resurrect an old
204
+ // failure, even if a provider reused a call ID in another iteration.
205
+ if (msg?.role === 'assistant') break
206
+ if (msg?.role === 'tool' && msg.isError === true && calls.has(msg.toolCallId)) {
207
+ errors.push(
208
+ toolResultToText(msg.content).trim() ||
209
+ `Tool ${calls.get(msg.toolCallId)} reported an error.`,
210
+ )
202
211
  }
203
212
  }
204
- return undefined
213
+ // A successful sibling does not erase failed results in this same batch.
214
+ return errors.length > 0 ? errors.reverse().join('\n') : undefined
205
215
  }
@@ -28,6 +28,16 @@ import { isWorkingMemoryMessage } from './working-memory.js'
28
28
  // because `runtime/query/index.ts` already imports it from this path.
29
29
  export { isCompactionMessage } from '../../../../compaction/summary.js'
30
30
 
31
+ /** The selected request model owns the denominator, including overflow recovery. */
32
+ export function activeContextWindow(ctx: IterationContext) {
33
+ const model = ctx.contextModel ?? ctx.runConfig.model
34
+ return resolveContextWindow(
35
+ ctx.compactionConfig?.contextWindowTokens,
36
+ model,
37
+ model === ctx.runConfig.model ? ctx.providerContextWindow : ctx.activeProviderContextWindow,
38
+ )
39
+ }
40
+
31
41
  /**
32
42
  * How much a forced pass must shed before the turn is worth retrying.
33
43
  *
@@ -147,7 +157,12 @@ async function commitToolResultClear(
147
157
  plan: ToolResultClearPlan,
148
158
  contextWindowTokens: number,
149
159
  windowSource: 'config' | 'provider' | 'model-table' | 'default',
160
+ reason: 'threshold' | 'overflow',
150
161
  ): Promise<void> {
162
+ // A shorter replacement also removes evidence. Preserve its original
163
+ // content in the same archive used by whole-message compaction before
164
+ // changing the live history or publishing the clear.
165
+ await recordShed(ctx, [...ctx.runMgr.messages], plan.messages, reason)
151
166
  installMessages(ctx.runMgr.messages, plan.messages)
152
167
  // The provider counted the pre-edit prompt. Leaving that measurement live
153
168
  // makes the next trigger compare the old prompt against the new history.
@@ -547,11 +562,7 @@ async function runCompactionCheckInner(
547
562
  // number. Nothing in the estate ever set `contextWindowTokens`, so the
548
563
  // fallback WAS the behavior, and the shipped CLI's 1M budget put the
549
564
  // trigger at ~700k. See `compaction/context-window.ts`.
550
- const window = resolveContextWindow(
551
- config.contextWindowTokens,
552
- ctx.runConfig.model,
553
- ctx.providerContextWindow,
554
- )
565
+ const window = activeContextWindow(ctx)
555
566
  const budget = window.tokens
556
567
 
557
568
  const usage = estimatedTokens / budget
@@ -659,7 +670,7 @@ async function runCompactionCheckInner(
659
670
  ...(openTasks ? { openTasks } : {}),
660
671
  })
661
672
  if (working.reclaimedTokens > 0) {
662
- await commitToolResultClear(ctx, working, budget, window.source)
673
+ await commitToolResultClear(ctx, working, budget, window.source, pass.reason)
663
674
  }
664
675
  // Under the trigger, the working set was the whole pass: the summary
665
676
  // path is for the trigger, not for the soft target.
@@ -684,7 +695,7 @@ async function runCompactionCheckInner(
684
695
  // the NEXT turn will correct it, and an over-eager summarization is
685
696
  // far more costly than one late pass.
686
697
  if (clearPlan.reliefWasEnough) {
687
- await commitToolResultClear(ctx, clearPlan, budget, window.source)
698
+ await commitToolResultClear(ctx, clearPlan, budget, window.source, pass.reason)
688
699
  return
689
700
  }
690
701
  }
@@ -736,7 +747,7 @@ async function runCompactionCheckInner(
736
747
  // There is no opaque provider wait on this path, so publish that single
737
748
  // complete edit now rather than discarding it.
738
749
  if (stagedClear) {
739
- await commitToolResultClear(ctx, stagedClear, budget, window.source)
750
+ await commitToolResultClear(ctx, stagedClear, budget, window.source, pass.reason)
740
751
  }
741
752
  return
742
753
  }
@@ -823,6 +834,20 @@ async function runCompactionCheckInner(
823
834
 
824
835
  const live = ctx.runMgr.messages
825
836
  const oldCount = live.length
837
+ // Retained history can survive in full while a summary adds another copy
838
+ // of its facts. Even an unretained span may be smaller than the cumulative
839
+ // summary. Compare both candidates with the same estimator, rather than
840
+ // comparing provider usage against an estimate or trusting message count.
841
+ // When a clear was staged, require the summary itself to improve on it;
842
+ // otherwise publish the useful clear without consuming its savings.
843
+ if (estimateMessagesTokens(newMessages) >= estimateMessagesTokens(messages)) {
844
+ if (stagedClear && stagedClear.reclaimedTokens > 0) {
845
+ await commitToolResultClear(ctx, stagedClear, budget, window.source, pass.reason)
846
+ } else {
847
+ await declined(ctx, 'shed_nothing', oldCount)
848
+ }
849
+ return
850
+ }
826
851
  await recordShed(ctx, [...live], newMessages, options?.force ? 'overflow' : 'threshold')
827
852
 
828
853
  installMessages(live, newMessages)
@@ -840,12 +865,9 @@ async function runCompactionCheckInner(
840
865
  // edits are installed and no verifier can still fail between them.
841
866
  if (stagedClear) await emitToolResultClear(ctx, stagedClear)
842
867
 
843
- // Hysteresis. A pass that only gets the context from 0.72 to 0.71 of the
844
- // window leaves the trigger armed, so the next iteration compacts again
845
- // — paying a summarization call and busting the prompt-cache prefix each
846
- // time, for nothing. Report the shortfall rather than repeating a move
847
- // that demonstrably does not work; `resetThreshold` was declared and
848
- // CLI-set but read by nothing until now.
868
+ // Report remaining pressure after a useful edit. This diagnostic does not
869
+ // suppress future passes: appended evidence may become reclaimable even
870
+ // when the protected floor and recent history remain above the target.
849
871
  const reachedReset = newEstimate / budget <= config.resetThreshold
850
872
  if (!reachedReset) {
851
873
  ctx.log.warn('Compaction did not reach its reset threshold — context may still be tight', {
@@ -131,6 +131,8 @@ export interface IterationContext {
131
131
  * drained and no tool result is extended.
132
132
  */
133
133
  readonly steering?: SteeringChannel
134
+ /** Records operator intent only after guidance was accepted by a tool result. */
135
+ readonly onSteeringDelivered?: (text: string) => void
134
136
  /** Exit notices for the run's background jobs, drained into the next tool result. */
135
137
  readonly jobNotices?: SteeringChannel
136
138
  readonly checkpointMgr: CheckpointManager
@@ -179,6 +181,11 @@ export interface IterationContext {
179
181
  * through to the table.
180
182
  */
181
183
  readonly providerContextWindow?: number
184
+ /** Selected request model; its window must not reuse another model's metadata. */
185
+ contextModel?: string
186
+ activeProviderContextWindow?: number
187
+ /** Bounded, run-cached provider metadata lookup for a newly selected model. */
188
+ readonly resolveModelContextWindow?: (model: string) => Promise<number | undefined>
182
189
 
183
190
  /**
184
191
  * Text queued for this run since its last turn.
@@ -188,6 +195,8 @@ export interface IterationContext {
188
195
  * after tool results and before the next turn.
189
196
  */
190
197
  readonly inboundMessages?: () => readonly import('../../../../types/message/index.js').Message[]
198
+ /** Known topic-queue arrivals appended after the restored checkpoint history. */
199
+ readonly resumedInput?: readonly import('../../../../types/message/index.js').Message[]
191
200
  /** Observes pending input without draining it; abort releases the waiter. */
192
201
  readonly waitForInbound?: (signal: AbortSignal) => Promise<void>
193
202
 
@@ -146,7 +146,11 @@ export async function* runToolReview(
146
146
  // user message wedged between them is rejected by the provider. Same
147
147
  // delivery a denial already uses, without the refusal.
148
148
  for (const msg of attachRepeatNotice(
149
- attachNotice(attachSteering(batch.messages, ctx.steering), ctx.jobNotices, formatJobNote),
149
+ attachNotice(
150
+ attachSteering(batch.messages, ctx.steering, ctx.onSteeringDelivered),
151
+ ctx.jobNotices,
152
+ formatJobNote,
153
+ ),
150
154
  notices,
151
155
  )) {
152
156
  ctx.runMgr.pushMessage(msg)
@@ -14,6 +14,10 @@ import type { IterationContext } from './context.js'
14
14
  export const WORKING_MEMORY_HEADER =
15
15
  '[WORKING MEMORY] Authoritative state for this conversation — you produced these.'
16
16
 
17
+ // Stored in the message, so ownership survives checkpoint JSON. Unmarked
18
+ // inherited blocks may be opaque host ledgers and must never be guessed away.
19
+ const TOOL_PINS_HEADER = `${WORKING_MEMORY_HEADER}\n[Tool-managed pins]`
20
+
17
21
  /**
18
22
  * True when `content` is the pinned working-memory slot (header identity only).
19
23
  * Shared with `compaction.ts` so the survival guard re-pins by the SAME rule.
@@ -34,16 +38,15 @@ export function isWorkingMemoryMessage(content: string | null | undefined): bool
34
38
  *
35
39
  * Failure-isolated (the `web-search` seam rule): a throwing/slow provider
36
40
  * degrades to "no refresh this turn" (keeps the prior slot), never breaks the
37
- * run. Early-returns when no provider is configured ⇒ byte-identical run path.
41
+ * run. With no provider or tool-owned slot, the history is left unchanged.
38
42
  */
39
43
  export async function refreshWorkingMemory(ctx: IterationContext): Promise<void> {
40
44
  const provider = ctx.workingMemoryProvider
41
45
  // What the tools pinned joins the slot beside what the host provides: a
42
46
  // pin exists to be in front of the model every iteration, and until this
43
47
  // the working state reached the history only through a compaction
44
- // summary. No provider and no pins leaves the history untouched.
48
+ // summary. When the final pin is removed, retire only a tool-owned slot.
45
49
  const pinned = ctx.workingStateManager ? renderPins(ctx.workingStateManager.getState()) : null
46
- if (!provider && !pinned) return
47
50
 
48
51
  let block = ''
49
52
  if (provider) {
@@ -80,17 +83,32 @@ export async function refreshWorkingMemory(ctx: IterationContext): Promise<void>
80
83
  }
81
84
  }
82
85
 
86
+ if (!provider && !pinned) {
87
+ if (!ctx.workingStateManager) return
88
+ const prior = msgs[idx]?.content
89
+ if (typeof prior !== 'string' || !prior.startsWith(TOOL_PINS_HEADER)) return
90
+ }
91
+
83
92
  if (!block.trim()) {
84
93
  // Empty block ⇒ remove the slot (byte-identical-when-empty).
85
- if (idx >= 0) msgs.splice(idx, 1)
94
+ if (idx >= 0) {
95
+ msgs.splice(idx, 1)
96
+ ctx.runMgr.clearLastPromptTokens?.()
97
+ }
86
98
  return
87
99
  }
88
100
 
89
- const wm = createSystemMessage(`${WORKING_MEMORY_HEADER}\n\n${block}`, 'ephemeral')
101
+ const header = provider ? WORKING_MEMORY_HEADER : TOOL_PINS_HEADER
102
+ const content = `${header}\n\n${block}`
103
+ if (idx >= 0 && msgs[idx]?.content === content) return
104
+ const wm = createSystemMessage(content, 'ephemeral')
90
105
  if (idx >= 0) {
91
106
  msgs[idx] = wm
92
107
  } else {
93
108
  // Insert as the LAST leading system message (after static + dynamic).
94
109
  msgs.splice(leadEnd, 0, wm)
95
110
  }
111
+ // The old provider reading measured a different prefix, and an insertion
112
+ // also moves its tail watermark. Re-estimate until the next request reports.
113
+ ctx.runMgr.clearLastPromptTokens?.()
96
114
  }
@@ -1,4 +1,5 @@
1
1
  import { type Span, SpanStatusCode } from '@opentelemetry/api'
2
+ import { assertNativeStructuredOutputSupported } from '../../../provider/capabilities.js'
2
3
  import { isProviderRequestError } from '../../../provider/errors.js'
3
4
  import { GENAI, NAMZU, chatSpanName, parentContext } from '../../../telemetry/attributes.js'
4
5
  import {
@@ -153,6 +154,7 @@ export async function* streamProviderTurn(
153
154
  readonly onAccepted: (identity: RequestImageIdentity) => Promise<void>
154
155
  },
155
156
  ): AsyncGenerator<RunEvent, StreamingTurnResult> {
157
+ assertNativeStructuredOutputSupported(provider, params)
156
158
  // The `chat {model}` span the GenAI conventions require. There was none:
157
159
  // `chatSpanName` existed with zero call sites, so a trace carried no LLM
158
160
  // latency at all and the token counts landed on the iteration span
@@ -1,4 +1,15 @@
1
- import type { Message } from '../../types/message/index.js'
1
+ import type { Message, UserMessage } from '../../types/message/index.js'
2
+
3
+ /** User-role runtime reports are context, not new operator intent. */
4
+ export function isOperatorUserMessage(message: Message): message is UserMessage {
5
+ if (message.role !== 'user') return false
6
+ const source = message.source
7
+ return (
8
+ !source ||
9
+ source.type === 'goal-round' ||
10
+ (source.type === 'runtime-context' && source.kind === 'steering')
11
+ )
12
+ }
2
13
 
3
14
  /**
4
15
  * Guidance a host hands to a turn that is already running.
@@ -134,6 +145,8 @@ export function formatSteeringNote(text: string): string {
134
145
  export function attachSteering(
135
146
  messages: readonly Message[],
136
147
  channel: SteeringChannel | undefined,
148
+ /** Receives accepted operator text, never inferred from a tool's output. */
149
+ onDelivered?: (text: string) => void,
137
150
  ): readonly Message[] {
138
151
  if (!channel?.pending) return messages
139
152
 
@@ -162,6 +175,10 @@ export function attachSteering(
162
175
  }
163
176
 
164
177
  const next = [...messages]
165
- next[lastToolIndex] = { ...target, content: target.content + formatSteeringNote(guidance) }
178
+ next[lastToolIndex] = {
179
+ ...target,
180
+ content: target.content + formatSteeringNote(guidance),
181
+ }
182
+ onDelivered?.(guidance)
166
183
  return next
167
184
  }
@@ -0,0 +1,115 @@
1
+ import type { RunId } from '../../types/ids/index.js'
2
+ import type { RunEvent } from '../../types/run/index.js'
3
+
4
+ export function assertMaxToolCalls(limit: number | undefined): void {
5
+ if (limit !== undefined && (!Number.isSafeInteger(limit) || limit < 0)) {
6
+ throw new RangeError('maxToolCalls must be a nonnegative safe integer.')
7
+ }
8
+ }
9
+
10
+ /** A run-owned admission ledger; durability belongs to the run's event store. */
11
+ export class ToolCallBudget {
12
+ private used = 0
13
+ private initialized?: Promise<void>
14
+ private chain: Promise<void> = Promise.resolve()
15
+ private failed: unknown
16
+
17
+ constructor(
18
+ private readonly limit: number,
19
+ private readonly runId: RunId,
20
+ private readonly emit: (event: RunEvent) => Promise<void>,
21
+ private readonly read?: () => Promise<readonly RunEvent[]>,
22
+ ) {
23
+ assertMaxToolCalls(limit)
24
+ }
25
+
26
+ private async initialize(): Promise<void> {
27
+ const events = (await this.read?.()) ?? []
28
+ if (events.length > 100_000) throw new Error('Tool-call budget recovery exceeds 100000 events.')
29
+ let initialized = false
30
+ let expectedSeq = 0
31
+ for (const event of events) {
32
+ // A replayed transcript must be complete and belong to this run.
33
+ if (event.runId !== this.runId || event.seq !== ++expectedSeq)
34
+ throw new Error('Tool-call budget recovery requires a complete run event log.')
35
+ if (event.type === 'tool_calls_admitted') {
36
+ if (
37
+ !Number.isSafeInteger(event.count) ||
38
+ event.count < 0 ||
39
+ !Number.isSafeInteger(event.used) ||
40
+ event.used !== this.used + event.count ||
41
+ !Number.isSafeInteger(event.limit) ||
42
+ event.limit < event.used ||
43
+ !['initialize', 'batch', 'nested', 'retry'].includes(event.kind) ||
44
+ (event.kind === 'initialize'
45
+ ? initialized || event.count !== 0
46
+ : !initialized || event.count === 0)
47
+ )
48
+ throw new Error('Invalid tool-call admission ledger.')
49
+ initialized = true
50
+ this.used = event.used
51
+ } else if (
52
+ !initialized &&
53
+ (event.type === 'tool_executing' || event.type === 'tool_completed')
54
+ ) {
55
+ throw new Error('Cannot establish tool-call usage for an earlier unbudgeted execution.')
56
+ }
57
+ }
58
+ if (!initialized) {
59
+ await this.emit({
60
+ type: 'tool_calls_admitted',
61
+ runId: this.runId,
62
+ kind: 'initialize',
63
+ count: 0,
64
+ used: 0,
65
+ limit: this.limit,
66
+ })
67
+ }
68
+ }
69
+
70
+ /** Reserve before work, never refund. A refused group consumes no new slots. */
71
+ async admit(
72
+ count: number,
73
+ kind: 'batch' | 'nested' | 'retry',
74
+ signal: AbortSignal,
75
+ ): Promise<string | undefined> {
76
+ signal.throwIfAborted()
77
+ const previous = this.chain
78
+ let release!: () => void
79
+ this.chain = new Promise<void>((resolve) => {
80
+ release = resolve
81
+ })
82
+ try {
83
+ await previous
84
+ signal.throwIfAborted()
85
+ if (this.failed !== undefined) throw this.failed
86
+ this.initialized ??= this.initialize()
87
+ await this.initialized
88
+ signal.throwIfAborted()
89
+ if (count > Math.max(0, this.limit - this.used)) {
90
+ return `Tool-call budget exhausted: this ${kind} needs ${count} slots; ${Math.max(0, this.limit - this.used)} remain of maxToolCalls=${this.limit}. No calls in this admission were executed.`
91
+ }
92
+ if (count === 0) return undefined
93
+ const used = this.used + count
94
+ try {
95
+ await this.emit({
96
+ type: 'tool_calls_admitted',
97
+ runId: this.runId,
98
+ kind,
99
+ count,
100
+ used,
101
+ limit: this.limit,
102
+ })
103
+ } catch (error) {
104
+ // An uncertain write must not be retried as a fresh allowance.
105
+ this.failed = error ?? new Error('Tool-call admission persistence failed.')
106
+ throw this.failed
107
+ }
108
+ this.used = used
109
+ signal.throwIfAborted()
110
+ return undefined
111
+ } finally {
112
+ release()
113
+ }
114
+ }
115
+ }
@@ -43,6 +43,8 @@ export interface ToolingBootstrapConfig {
43
43
  web?: import('../../types/tool/index.js').ToolContext['web']
44
44
  toolRetryBackoff?: Partial<BackoffPolicy>
45
45
  maxToolConcurrency?: number
46
+ maxToolCalls?: number
47
+ readToolCallBudgetEvents?: () => Promise<readonly RunEvent[]>
46
48
  maxToolOutputChars?: number
47
49
  maxToolContentBytes?: number
48
50
  toolOutputDir?: string
@@ -84,6 +86,10 @@ export class ToolingBootstrap {
84
86
  ...(config.toolRetryBackoff !== undefined
85
87
  ? { toolRetryBackoff: config.toolRetryBackoff }
86
88
  : {}),
89
+ ...(config.maxToolCalls !== undefined ? { maxToolCalls: config.maxToolCalls } : {}),
90
+ ...(config.readToolCallBudgetEvents
91
+ ? { readToolCallBudgetEvents: config.readToolCallBudgetEvents }
92
+ : {}),
87
93
  ...(config.maxToolConcurrency !== undefined
88
94
  ? { maxToolConcurrency: config.maxToolConcurrency }
89
95
  : {}),
@@ -111,6 +111,7 @@ export class LocalTaskScheduler implements TaskScheduler {
111
111
  const task = await this.agentManager.sendMessage(
112
112
  {
113
113
  agentId: options.agentId,
114
+ beforeStart: options.beforeStart,
114
115
  input: {
115
116
  messages: [createUserMessage(options.prompt)],
116
117
  workingDirectory: options.workingDirectory,
@@ -62,7 +62,7 @@ export interface CapacityValidator {
62
62
  ): Promise<void>
63
63
 
64
64
  /**
65
- * Asserts `existingDirectChildren + pendingNewChildren ≤ projectMaxWidth`
65
+ * Asserts `liveDirectChildren + pendingNewChildren ≤ projectMaxWidth`
66
66
  * under `parentSessionId`. The width cap applies to a single spawn call —
67
67
  * a broadcast of N recipients passes `pendingNewChildren = N`.
68
68
  */
@@ -77,7 +77,7 @@ export interface CapacityValidator {
77
77
  /**
78
78
  * Default validator backed by {@link SessionStore}. Uses `getAncestry` for
79
79
  * depth (root-to-self chain length) and `getChildren` for width (count of
80
- * existing direct sub-sessions).
80
+ * pending or active direct sub-sessions). Historical edges stay queryable.
81
81
  */
82
82
  export class DefaultCapacityValidator implements CapacityValidator {
83
83
  constructor(private readonly store: SessionStore) {}
@@ -108,7 +108,11 @@ export class DefaultCapacityValidator implements CapacityValidator {
108
108
  tenantId: TenantId,
109
109
  ): Promise<void> {
110
110
  const existing = await this.store.getChildren(parentSessionId, tenantId)
111
- const total = existing.length + pendingNewChildren
111
+ const live = existing.filter(
112
+ (child) =>
113
+ child.status !== 'idle' && child.status !== 'failed' && child.status !== 'archived',
114
+ )
115
+ const total = live.length + pendingNewChildren
112
116
  if (total > projectMaxWidth) {
113
117
  throw new DelegationCapacityExceeded({
114
118
  dimension: 'width',
@@ -6,16 +6,24 @@ import type {
6
6
  CreateMemoryParams,
7
7
  MemoryContent,
8
8
  MemoryIndexEntry,
9
+ MemoryRecord,
9
10
  MemorySearchParams,
10
11
  MemorySearchResult,
11
12
  MemoryStore,
13
+ UpdateMemoryParams,
12
14
  } from '../../types/memory/index.js'
15
+ import { assertMemoryStatus } from '../../types/memory/index.js'
13
16
  import { generateMemoryId, isEntityId } from '../../utils/id.js'
14
17
  import { SCOPE_ATTRIBUTE } from '../../utils/log/types.js'
15
18
  import { type Logger, resolveLogger } from '../../utils/logger.js'
16
19
  import { DiskRecordStore } from '../kv/record-store.js'
17
20
  import { defineSchema } from '../schema.js'
18
- import { InMemoryMemoryIndex } from './index.js'
21
+ import { InMemoryMemoryIndex, searchMemoryEntries } from './index.js'
22
+ import {
23
+ DEFAULT_MEMORY_LOCK_TIMEOUT_MS,
24
+ acquireMemoryOperationLock,
25
+ validateMemoryLockTimeout,
26
+ } from './operation-lock.js'
19
27
 
20
28
  /**
21
29
  * This store's on-disk format, versioned as a unit — which is how a
@@ -32,27 +40,6 @@ interface StorageLocation {
32
40
  readonly contentDir: string
33
41
  }
34
42
 
35
- const operationTails = new Map<string, Promise<void>>()
36
-
37
- /** Serialize every operation over one canonical disk projection in this process. */
38
- async function withMemoryOperationLock<T>(key: string, operation: () => Promise<T>): Promise<T> {
39
- const previous = operationTails.get(key) ?? Promise.resolve()
40
- let release = () => {}
41
- const current = new Promise<void>((resolveCurrent) => {
42
- release = resolveCurrent
43
- })
44
- const tail = previous.then(() => current)
45
- operationTails.set(key, tail)
46
-
47
- await previous
48
- try {
49
- return await operation()
50
- } finally {
51
- release()
52
- if (operationTails.get(key) === tail) operationTails.delete(key)
53
- }
54
- }
55
-
56
43
  function invalidIndex(
57
44
  reason: string,
58
45
  details: { entryIndex?: number; field?: string } = {},
@@ -163,11 +150,14 @@ function assertMemoryContent(value: unknown, expectedId: MemoryId): asserts valu
163
150
  export interface DiskMemoryStoreConfig {
164
151
  baseDir: string
165
152
  logger?: Logger
153
+ /** Maximum wait for another process's operation; default 10 seconds. Never breaks stale locks. */
154
+ lockTimeoutMs?: number
166
155
  }
167
156
 
168
157
  export class DiskMemoryStore implements MemoryStore {
169
158
  private readonly baseDir: string
170
159
  private readonly log: Logger
160
+ private readonly lockTimeoutMs: number
171
161
  private readonly index = new InMemoryMemoryIndex()
172
162
  private location?: StorageLocation
173
163
  // Two instances of one primitive rather than two copies of its body.
@@ -180,6 +170,8 @@ export class DiskMemoryStore implements MemoryStore {
180
170
  constructor(config: DiskMemoryStoreConfig) {
181
171
  this.baseDir = join(config.baseDir, 'memory')
182
172
  this.log = resolveLogger(config.logger).child({ [SCOPE_ATTRIBUTE]: 'store/memory/disk' })
173
+ this.lockTimeoutMs = config.lockTimeoutMs ?? DEFAULT_MEMORY_LOCK_TIMEOUT_MS
174
+ validateMemoryLockTimeout(this.lockTimeoutMs)
183
175
  }
184
176
 
185
177
  private get contentDir(): string {
@@ -235,10 +227,16 @@ export class DiskMemoryStore implements MemoryStore {
235
227
  operation: (location: StorageLocation) => Promise<T>,
236
228
  ): Promise<T> {
237
229
  const location = await this.storageLocation()
238
- return withMemoryOperationLock(location.indexPath, async () => {
230
+ const release = await acquireMemoryOperationLock(
231
+ join(dirname(location.indexPath), 'operation.lock'),
232
+ this.lockTimeoutMs,
233
+ )
234
+ try {
239
235
  await this.reloadIndex(location)
240
- return operation(location)
241
- })
236
+ return await operation(location)
237
+ } finally {
238
+ await release()
239
+ }
242
240
  }
243
241
 
244
242
  private async readContent(location: StorageLocation, id: MemoryId): Promise<MemoryContent> {
@@ -307,10 +305,17 @@ export class DiskMemoryStore implements MemoryStore {
307
305
  })
308
306
  }
309
307
 
310
- async update(
311
- id: MemoryId,
312
- updates: Partial<CreateMemoryParams>,
313
- ): Promise<MemoryIndexEntry | undefined> {
308
+ async getRecord(id: MemoryId): Promise<MemoryRecord | undefined> {
309
+ return this.withAuthoritativeIndex(async (location) => {
310
+ assertStorageMemoryId(id, (reason) => invalidContent(id, reason, { field: 'id' }))
311
+ const entry = this.index.getEntry(id)
312
+ if (!entry) return undefined
313
+ return structuredClone({ entry, content: await this.readContent(location, id) })
314
+ })
315
+ }
316
+
317
+ async update(id: MemoryId, updates: UpdateMemoryParams): Promise<MemoryIndexEntry | undefined> {
318
+ if (updates.status !== undefined) assertMemoryStatus(updates.status)
314
319
  return this.withAuthoritativeIndex(async (location) => {
315
320
  assertStorageMemoryId(id, (reason) => invalidContent(id, reason, { field: 'id' }))
316
321
  const existing = this.index.getEntry(id)
@@ -321,6 +326,7 @@ export class DiskMemoryStore implements MemoryStore {
321
326
  title: updates.title ?? existing.title,
322
327
  summary: updates.summary ?? existing.summary,
323
328
  tags: updates.tags ? [...updates.tags] : existing.tags,
329
+ status: updates.status ?? existing.status,
324
330
  updatedAt: Date.now(),
325
331
  }
326
332
  const updatesContent =
@@ -367,7 +373,23 @@ export class DiskMemoryStore implements MemoryStore {
367
373
  }
368
374
 
369
375
  async list(params?: MemorySearchParams): Promise<MemorySearchResult> {
370
- return this.withAuthoritativeIndex(async () => this.index.search(params ?? {}))
376
+ return this.withAuthoritativeIndex(async (location) => {
377
+ if (!params?.query?.trim() && !params?.requiredIdentifiers?.length)
378
+ return this.index.search(params ?? {})
379
+ // Search the current bodies inside the same operation as the index
380
+ // snapshot. No retained cache can hide another process's update.
381
+ const candidates = this.index.search({
382
+ ...params,
383
+ query: undefined,
384
+ limit: undefined,
385
+ requiredIdentifiers: undefined,
386
+ })
387
+ const contents = new Map<MemoryId, string>()
388
+ for (const entry of candidates.entries) {
389
+ contents.set(entry.id, (await this.readContent(location, entry.id)).content)
390
+ }
391
+ return searchMemoryEntries(candidates.entries, params, (id) => contents.get(id) ?? '')
392
+ })
371
393
  }
372
394
 
373
395
  getIndex(): InMemoryMemoryIndex {