@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
@@ -6,37 +6,85 @@ import type {
6
6
  MemorySearchResult,
7
7
  } from '../../types/memory/index.js'
8
8
 
9
- export class InMemoryMemoryIndex implements MemoryIndex {
10
- private entries = new Map<string, MemoryIndexEntry>()
11
-
12
- search(params: MemorySearchParams): MemorySearchResult {
13
- let results = Array.from(this.entries.values())
14
-
15
- if (params.query) {
16
- const q = params.query.toLowerCase()
17
- results = results.filter(
18
- (e) => e.title.toLowerCase().includes(q) || e.summary.toLowerCase().includes(q),
19
- )
20
- }
21
-
22
- if (params.tags && params.tags.length > 0) {
23
- const requiredTags = params.tags
24
- results = results.filter((e) => requiredTags.every((tag) => e.tags.includes(tag)))
25
- }
9
+ function terms(text: string): Set<string> {
10
+ return new Set(
11
+ text
12
+ .normalize('NFKC')
13
+ .toLowerCase()
14
+ .match(/[\p{L}\p{N}]+/gu) ?? [],
15
+ )
16
+ }
26
17
 
27
- if (params.status) {
28
- results = results.filter((e) => e.status === params.status)
29
- }
18
+ /** Exact normalized word membership; no substring, stemming or alias inference. */
19
+ export function matchesMemoryIdentifier(text: string, identifiers: ReadonlySet<string>): boolean {
20
+ for (const match of text
21
+ .normalize('NFKC')
22
+ .toLowerCase()
23
+ .matchAll(/[\p{L}\p{N}_]+/gu)) {
24
+ if (identifiers.has(match[0])) return true
25
+ }
26
+ return false
27
+ }
30
28
 
31
- results.sort((a, b) => b.updatedAt - a.updatedAt)
29
+ /** Shared lexical ranking; a caller-owned index can omit body content. */
30
+ export function searchMemoryEntries(
31
+ entries: readonly MemoryIndexEntry[],
32
+ params: MemorySearchParams,
33
+ contentOf: (id: MemoryId) => string = () => '',
34
+ ): MemorySearchResult {
35
+ const query = terms(params.query ?? '')
36
+ const identifiers = new Set(
37
+ params.requiredIdentifiers?.map((id) => id.normalize('NFKC').toLowerCase()),
38
+ )
39
+ if (params.query?.trim() && query.size === 0) return { entries: [], totalCount: 0 }
40
+ const ranked = entries
41
+ .filter(
42
+ (entry) =>
43
+ (!params.status || entry.status === params.status) &&
44
+ (!params.tags?.length || params.tags.every((tag) => entry.tags.includes(tag))),
45
+ )
46
+ .filter(
47
+ (entry) =>
48
+ !identifiers.size ||
49
+ [entry.id, entry.title, entry.summary, contentOf(entry.id)].some((text) =>
50
+ matchesMemoryIdentifier(text, identifiers),
51
+ ),
52
+ )
53
+ .map((entry) => {
54
+ let coverage = 0
55
+ let score = 0
56
+ if (query.size > 0) {
57
+ const title = terms(entry.title)
58
+ const summary = terms(entry.summary)
59
+ const body = terms(contentOf(entry.id))
60
+ for (const term of query) {
61
+ const weight =
62
+ (title.has(term) ? 8 : 0) + (summary.has(term) ? 4 : 0) + (body.has(term) ? 1 : 0)
63
+ if (weight > 0) coverage++
64
+ score += weight
65
+ }
66
+ }
67
+ return { entry, coverage, score }
68
+ })
69
+ .filter(({ coverage }) => query.size === 0 || coverage > 0)
70
+ .sort(
71
+ (a, b) =>
72
+ b.coverage - a.coverage ||
73
+ b.score - a.score ||
74
+ b.entry.updatedAt - a.entry.updatedAt ||
75
+ (a.entry.id < b.entry.id ? -1 : a.entry.id > b.entry.id ? 1 : 0),
76
+ )
77
+ return {
78
+ entries: ranked.slice(0, params.limit ?? ranked.length).map(({ entry }) => entry),
79
+ totalCount: ranked.length,
80
+ }
81
+ }
32
82
 
33
- const totalCount = results.length
34
- const limit = params.limit ?? totalCount
83
+ export class InMemoryMemoryIndex implements MemoryIndex {
84
+ private entries = new Map<string, MemoryIndexEntry>()
35
85
 
36
- return {
37
- entries: results.slice(0, limit),
38
- totalCount,
39
- }
86
+ search(params: MemorySearchParams): MemorySearchResult {
87
+ return searchMemoryEntries([...this.entries.values()], params)
40
88
  }
41
89
 
42
90
  getEntry(id: MemoryId): MemoryIndexEntry | undefined {
@@ -3,12 +3,15 @@ import type {
3
3
  CreateMemoryParams,
4
4
  MemoryContent,
5
5
  MemoryIndexEntry,
6
+ MemoryRecord,
6
7
  MemorySearchParams,
7
8
  MemorySearchResult,
8
9
  MemoryStore,
10
+ UpdateMemoryParams,
9
11
  } from '../../types/memory/index.js'
12
+ import { assertMemoryStatus } from '../../types/memory/index.js'
10
13
  import { generateMemoryId } from '../../utils/id.js'
11
- import { InMemoryMemoryIndex } from './index.js'
14
+ import { InMemoryMemoryIndex, searchMemoryEntries } from './index.js'
12
15
 
13
16
  export class InMemoryMemoryStore implements MemoryStore {
14
17
  private content = new Map<string, MemoryContent>()
@@ -47,10 +50,14 @@ export class InMemoryMemoryStore implements MemoryStore {
47
50
  return this.content.get(id)
48
51
  }
49
52
 
50
- async update(
51
- id: MemoryId,
52
- updates: Partial<CreateMemoryParams>,
53
- ): Promise<MemoryIndexEntry | undefined> {
53
+ async getRecord(id: MemoryId): Promise<MemoryRecord | undefined> {
54
+ const entry = this.index.getEntry(id)
55
+ const content = this.content.get(id)
56
+ return entry && content ? structuredClone({ entry, content }) : undefined
57
+ }
58
+
59
+ async update(id: MemoryId, updates: UpdateMemoryParams): Promise<MemoryIndexEntry | undefined> {
60
+ if (updates.status !== undefined) assertMemoryStatus(updates.status)
54
61
  const existing = this.index.getEntry(id)
55
62
  if (!existing) return undefined
56
63
 
@@ -61,6 +68,7 @@ export class InMemoryMemoryStore implements MemoryStore {
61
68
  title: updates.title ?? existing.title,
62
69
  summary: updates.summary ?? existing.summary,
63
70
  tags: updates.tags ? [...updates.tags] : existing.tags,
71
+ status: updates.status ?? existing.status,
64
72
  updatedAt: now,
65
73
  }
66
74
 
@@ -94,7 +102,11 @@ export class InMemoryMemoryStore implements MemoryStore {
94
102
  }
95
103
 
96
104
  async list(params?: MemorySearchParams): Promise<MemorySearchResult> {
97
- return this.index.search(params ?? {})
105
+ return searchMemoryEntries(
106
+ this.index.allEntries(),
107
+ params ?? {},
108
+ (id) => this.content.get(id)?.content ?? '',
109
+ )
98
110
  }
99
111
 
100
112
  getIndex(): InMemoryMemoryIndex {
@@ -0,0 +1,99 @@
1
+ import { randomUUID } from 'node:crypto'
2
+ import { type FileHandle, lstat, open, readFile, unlink } from 'node:fs/promises'
3
+ import { setTimeout as delay } from 'node:timers/promises'
4
+
5
+ import { NamzuError } from '../../types/errors/index.js'
6
+
7
+ /** Wait bound, not a lease: a slow or crashed owner is never silently replaced. */
8
+ export const DEFAULT_MEMORY_LOCK_TIMEOUT_MS = 10_000
9
+
10
+ export function validateMemoryLockTimeout(timeoutMs: number): void {
11
+ if (!Number.isSafeInteger(timeoutMs) || timeoutMs <= 0) {
12
+ throw new NamzuError({
13
+ code: 'invalid_config',
14
+ message: 'DiskMemoryStore lockTimeoutMs must be a positive safe integer.',
15
+ retryable: false,
16
+ })
17
+ }
18
+ }
19
+
20
+ function lockError(path: string, reason: string, timeoutMs: number): NamzuError {
21
+ return new NamzuError({
22
+ code: 'storage_error',
23
+ message: `Memory operation lock ${path}: ${reason}. Stop all processes using this memory store and inspect the lock owner before removing a stale lock; it is never broken automatically.`,
24
+ details: { lockPath: path, timeoutMs },
25
+ retryable: false,
26
+ })
27
+ }
28
+
29
+ /**
30
+ * Local-filesystem exclusion for cooperating processes. Atomic rename of the
31
+ * index alone cannot serialize the index/content read-modify-write transaction.
32
+ * An abandoned lock fails closed; guessing a lease expired would permit two
33
+ * live writers to overwrite each other again.
34
+ */
35
+ export async function acquireMemoryOperationLock(
36
+ path: string,
37
+ timeoutMs: number,
38
+ ): Promise<() => Promise<void>> {
39
+ const deadline = performance.now() + timeoutMs
40
+ let attempted = false
41
+ for (;;) {
42
+ if (attempted && performance.now() >= deadline) {
43
+ throw lockError(path, `acquisition timed out after ${timeoutMs} ms`, timeoutMs)
44
+ }
45
+ attempted = true
46
+ let handle: FileHandle
47
+ try {
48
+ handle = await open(path, 'wx', 0o600)
49
+ } catch (error) {
50
+ if ((error as NodeJS.ErrnoException).code !== 'EEXIST') throw error
51
+ const existing = await lstat(path).catch((statError: NodeJS.ErrnoException) => {
52
+ if (statError.code === 'ENOENT') return undefined
53
+ throw statError
54
+ })
55
+ if (!existing) continue
56
+ if (!existing.isFile() || existing.isSymbolicLink()) {
57
+ throw lockError(path, 'the occupied lock is not a regular file', timeoutMs)
58
+ }
59
+ if (performance.now() >= deadline) {
60
+ throw lockError(path, `acquisition timed out after ${timeoutMs} ms`, timeoutMs)
61
+ }
62
+ await delay(Math.min(25, Math.max(1, deadline - performance.now())))
63
+ continue
64
+ }
65
+
66
+ const owner = `${JSON.stringify({ pid: process.pid, token: randomUUID(), acquiredAt: Date.now() })}\n`
67
+ const identity = await handle.stat().catch(async (error: unknown) => {
68
+ await handle.close()
69
+ // Exclusive create happened, but ownership could not be established.
70
+ // Leave the path for explicit recovery rather than unlink an unknown inode.
71
+ throw error
72
+ })
73
+ try {
74
+ await handle.writeFile(owner, 'utf8')
75
+ await handle.sync()
76
+ } catch (error) {
77
+ await handle.close()
78
+ const current = await lstat(path).catch(() => undefined)
79
+ if (current?.dev === identity.dev && current.ino === identity.ino) {
80
+ await unlink(path).catch(() => undefined)
81
+ }
82
+ throw error
83
+ }
84
+ await handle.close()
85
+ return async () => {
86
+ const current = await lstat(path)
87
+ if (
88
+ !current.isFile() ||
89
+ current.isSymbolicLink() ||
90
+ current.dev !== identity.dev ||
91
+ current.ino !== identity.ino ||
92
+ (await readFile(path, 'utf8')) !== owner
93
+ ) {
94
+ throw lockError(path, 'the lock owner changed before release', timeoutMs)
95
+ }
96
+ await unlink(path)
97
+ }
98
+ }
99
+ }
@@ -63,7 +63,7 @@ const pointModelInputSchema = {
63
63
  additionalProperties: false,
64
64
  } as const
65
65
 
66
- const modelInputSchema: Record<string, unknown> = {
66
+ const modelInputSchema = {
67
67
  type: 'object',
68
68
  properties: {
69
69
  type: {
@@ -136,16 +136,17 @@ function requiredCapability(type: ComputerUseAction['type']): keyof ComputerUseC
136
136
 
137
137
  function buildDescription(host: ComputerUseHost): string {
138
138
  const caps = host.capabilities
139
- const available: string[] = []
140
- if (caps.screenshot) available.push('screenshot')
141
- if (caps.cursorPosition) available.push('cursor_position')
142
- if (caps.mouse) available.push('mouse_move, mouse_click, mouse_drag, scroll')
143
- if (caps.keyboard) available.push('type_text, key')
139
+ const available = availableActions(caps)
144
140
  const unavailable: string[] = []
145
141
  if (!caps.screenshot) unavailable.push('screenshot')
146
142
  if (!caps.cursorPosition) unavailable.push('cursor_position')
147
143
  if (!caps.mouse) unavailable.push('mouse')
148
144
  if (!caps.keyboard) unavailable.push('keyboard')
145
+ if (caps.supportedActions) {
146
+ for (const action of actionSchema.options.map((option) => option.shape.type.value)) {
147
+ if (!available.includes(action)) unavailable.push(action)
148
+ }
149
+ }
149
150
 
150
151
  const lines = [
151
152
  `Controls the user's desktop on a ${caps.displayServer} host. Use to take screenshots and drive mouse/keyboard input for GUI tasks.`,
@@ -161,9 +162,42 @@ function buildDescription(host: ComputerUseHost): string {
161
162
  lines.push(
162
163
  'Coordinates are in logical pixels from the top-left of the primary display. Call getDisplayGeometry through screenshot output before clicking to confirm bounds.',
163
164
  )
165
+ if (caps.mouseClickButtons && available.includes('mouse_click'))
166
+ lines.push(`Click buttons: ${caps.mouseClickButtons.join(', ') || 'none'}.`)
167
+ if (caps.mouseDragButtons && available.includes('mouse_drag'))
168
+ lines.push(`Drag buttons: ${caps.mouseDragButtons.join(', ') || 'none'}.`)
164
169
  return lines.join(' ')
165
170
  }
166
171
 
172
+ function availableActions(caps: ComputerUseCapabilities): ComputerUseAction['type'][] {
173
+ return actionSchema.options
174
+ .map((option) => option.shape.type.value)
175
+ .filter((action) => {
176
+ const required = requiredCapability(action)
177
+ return (
178
+ (required === null || caps[required] === true) &&
179
+ (caps.supportedActions === undefined || caps.supportedActions.includes(action)) &&
180
+ (action !== 'mouse_click' || caps.mouseClickButtons?.length !== 0) &&
181
+ (action !== 'mouse_drag' || caps.mouseDragButtons?.length !== 0)
182
+ )
183
+ })
184
+ }
185
+
186
+ function hostModelSchema(caps: ComputerUseCapabilities): Record<string, unknown> {
187
+ const schema = structuredClone(modelInputSchema)
188
+ const actions = availableActions(caps)
189
+ // An unavailable host remains a diagnostic tool. Avoid invalid empty enums
190
+ // on provider wires; every execution is still refused before host access.
191
+ if (actions.length > 0) schema.properties.type.enum = actions
192
+ const buttons = new Set<string>()
193
+ if (actions.includes('mouse_click'))
194
+ for (const button of caps.mouseClickButtons ?? ['left', 'right', 'middle']) buttons.add(button)
195
+ if (actions.includes('mouse_drag'))
196
+ for (const button of caps.mouseDragButtons ?? ['left', 'right', 'middle']) buttons.add(button)
197
+ if (buttons.size > 0) schema.properties.button.enum = [...buttons]
198
+ return schema
199
+ }
200
+
167
201
  function pointLabel(point: { readonly x: number; readonly y: number }): string {
168
202
  return `(${point.x}, ${point.y})`
169
203
  }
@@ -283,7 +317,7 @@ export function createComputerUseTool(host: ComputerUseHost): ToolDefinition<Act
283
317
  name: COMPUTER_USE_TOOL_NAME,
284
318
  description: buildDescription(host),
285
319
  inputSchema: actionSchema,
286
- modelInputSchema: structuredClone(modelInputSchema),
320
+ modelInputSchema: hostModelSchema(host.capabilities),
287
321
  validationErrorHint:
288
322
  'Action requirements: mouse_move needs "to"; mouse_click needs "at" and "button"; mouse_drag needs "from", "to", and "button"; scroll needs "at", "direction", and positive "amount"; type_text needs "text"; key needs "keys".',
289
323
  category: 'custom',
@@ -310,6 +344,26 @@ export function createComputerUseTool(host: ComputerUseHost): ToolDefinition<Act
310
344
  error: `computer_use: action "${input.type}" requires capability "${required}" which is not available on this host (displayServer=${host.capabilities.displayServer}).${host.capabilities.unavailableReason ? ` ${host.capabilities.unavailableReason} Do not retry; tell the user.` : ''}`,
311
345
  }
312
346
  }
347
+ if (!availableActions(host.capabilities).includes(input.type)) {
348
+ return {
349
+ success: false,
350
+ output: '',
351
+ error: `computer_use: action "${input.type}" is not supported on this host.`,
352
+ }
353
+ }
354
+ const buttons =
355
+ input.type === 'mouse_click'
356
+ ? host.capabilities.mouseClickButtons
357
+ : input.type === 'mouse_drag'
358
+ ? host.capabilities.mouseDragButtons
359
+ : undefined
360
+ if (buttons && 'button' in input && !buttons.includes(input.button)) {
361
+ return {
362
+ success: false,
363
+ output: '',
364
+ error: `computer_use: action "${input.type}" does not support button "${input.button}" on this host.`,
365
+ }
366
+ }
313
367
  try {
314
368
  const result = await host.execute(input as ComputerUseAction)
315
369
  return resultToToolResult(result)
@@ -49,6 +49,12 @@ export interface RunCodeToolOptions {
49
49
  readonly runtime?: CodeRuntime
50
50
  readonly timeoutMs?: number
51
51
  readonly maxOutputBytes?: number
52
+ /**
53
+ * What a successful `call()` resolves to. Default: `text` (ToolResult.output).
54
+ * `structured` returns `{ output, data? }`, preserving JSON tool data for
55
+ * local filtering. Failures still reject; authority and dispatch are unchanged.
56
+ */
57
+ readonly toolResultMode?: 'text' | 'structured'
52
58
  }
53
59
 
54
60
  const DEFAULT_TIMEOUT_MS = 30_000
@@ -59,8 +65,11 @@ export function buildRunCodeTool(options: RunCodeToolOptions = {}) {
59
65
 
60
66
  return defineTool({
61
67
  name: RUN_CODE_TOOL_NAME,
62
- description:
63
- "Runs a short JavaScript program that can call this run's own tools in a loop. Use it when the same tool would otherwise be called many times in a row — filtering, retrying, fanning out — and not for a single call.",
68
+ description: `Runs a short JavaScript program that can call this run's own tools in a loop. Use it when the same tool would otherwise be called many times in a row — filtering, retrying, fanning out — and not for a single call. ${
69
+ options.toolResultMode === 'structured'
70
+ ? 'Each successful call() returns { output: string, data?: JSON value }. Filter data locally and return only the needed result. Failed calls reject.'
71
+ : 'Each successful call() returns the tool output string. Failed calls reject.'
72
+ }`,
64
73
  inputSchema,
65
74
  category: 'custom',
66
75
  permissions: [],
@@ -108,9 +117,19 @@ export function buildRunCodeTool(options: RunCodeToolOptions = {}) {
108
117
  signal: operation.signal,
109
118
  runtimeToolCallId: operation.runtimeToolCallId,
110
119
  })
111
- return toolResult.success
112
- ? { ok: true, value: toolResult.output }
113
- : { ok: false, error: toolResult.error ?? 'the tool failed' }
120
+ if (!toolResult.success) {
121
+ return { ok: false, error: toolResult.error ?? 'the tool failed' }
122
+ }
123
+ return {
124
+ ok: true,
125
+ value:
126
+ options.toolResultMode === 'structured'
127
+ ? {
128
+ output: toolResult.output,
129
+ ...(toolResult.data !== undefined ? { data: toolResult.data } : {}),
130
+ }
131
+ : toolResult.output,
132
+ }
114
133
  } catch (err) {
115
134
  return {
116
135
  ok: false,
@@ -38,8 +38,7 @@ export const SearchToolsTool = defineTool({
38
38
  }
39
39
  }
40
40
 
41
- const allowed =
42
- context.allowedTools && context.allowedTools.length > 0 ? new Set(context.allowedTools) : null
41
+ const allowed = context.allowedTools !== undefined ? new Set(context.allowedTools) : null
43
42
  // `searchDeferred` returns a ranked list (score-descending), so slicing
44
43
  // the head is a true top-k activation, not an arbitrary subset.
45
44
  const ranked = context.toolRegistry
@@ -47,9 +46,25 @@ export const SearchToolsTool = defineTool({
47
46
  .filter((tool) => !allowed || allowed.has(tool.name))
48
47
 
49
48
  if (ranked.length === 0) {
49
+ const active = context.toolRegistry
50
+ .searchActive?.(input.query)
51
+ .filter(
52
+ (tool) =>
53
+ (!allowed || allowed.has(tool.name)) &&
54
+ context.toolRegistry?.getAvailability(tool.name) === 'active',
55
+ )
56
+ const activeReceipt =
57
+ active === undefined
58
+ ? 'This registry cannot search active tools.'
59
+ : active.length === 0
60
+ ? 'No matching active tools were found.'
61
+ : `Already active matching tools (up to ${ACTIVATION_TOP_K}):\n${active
62
+ .slice(0, ACTIVATION_TOP_K)
63
+ .map((tool) => `- ${tool.name}`)
64
+ .join('\n')}`
50
65
  return {
51
66
  success: true,
52
- output: `No deferred tools matching "${input.query}". All matching tools are already active.`,
67
+ output: `No deferred tools matching "${input.query}". ${activeReceipt}`,
53
68
  }
54
69
  }
55
70
 
@@ -20,6 +20,8 @@ export interface DefineToolOptions<S extends z.ZodType> {
20
20
  readOnly: boolean
21
21
  destructive: boolean | ((input: z.infer<S>) => boolean)
22
22
  concurrencySafe: boolean
23
+ /** Batch ordering boundary; see {@link ToolDefinition.executionBarrier}. */
24
+ executionBarrier?: boolean
23
25
  tier?: string
24
26
  /**
25
27
  * How this tool's call and result should be shown; see
@@ -81,6 +83,9 @@ export function defineTool<S extends z.ZodType>(
81
83
  ...(options.presentResult ? { presentResult: options.presentResult } : {}),
82
84
  ...(options.outputSchema !== undefined ? { outputSchema: options.outputSchema } : {}),
83
85
  ...(options.terminal !== undefined ? { terminal: options.terminal } : {}),
86
+ ...(options.executionBarrier !== undefined
87
+ ? { executionBarrier: options.executionBarrier }
88
+ : {}),
84
89
  category: options.category,
85
90
  permissions: options.permissions,
86
91
  isReadOnly: () => options.readOnly,
@@ -0,0 +1,29 @@
1
+ import { z } from 'zod'
2
+ import type { MemoryStore } from '../../types/memory/index.js'
3
+ import type { ToolDefinition } from '../../types/tool/index.js'
4
+ import { asMemoryId } from '../../utils/id.js'
5
+ import { defineTool } from '../defineTool.js'
6
+
7
+ export function buildDeleteMemoryTool(store: MemoryStore): ToolDefinition {
8
+ return defineTool({
9
+ name: 'delete_memory',
10
+ description:
11
+ 'Permanently delete a stored memory by ID. Use update_memory with status archived when the record should remain available for inspection. Deletion does not erase past conversation transcripts.',
12
+ inputSchema: z.object({
13
+ id: z.string().describe('Memory ID to permanently delete'),
14
+ }),
15
+ category: 'custom',
16
+ permissions: [],
17
+ readOnly: false,
18
+ destructive: true,
19
+ concurrencySafe: true,
20
+ async execute({ id }) {
21
+ const removed = await store.delete(asMemoryId(id))
22
+ return {
23
+ success: removed,
24
+ output: removed ? `Memory deleted: ${id}.` : `Memory ${id} not found.`,
25
+ ...(removed ? {} : { error: 'Memory not found' }),
26
+ }
27
+ },
28
+ })
29
+ }
@@ -1,8 +1,10 @@
1
1
  import type { MemoryIndex, MemoryStore } from '../../types/memory/index.js'
2
2
  import type { ToolDefinition } from '../../types/tool/index.js'
3
+ import { buildDeleteMemoryTool } from './delete.js'
3
4
  import { buildReadMemoryTool } from './read.js'
4
5
  import { buildSaveMemoryTool } from './save.js'
5
6
  import { buildSearchMemoryTool, buildStoreSearchMemoryTool } from './search.js'
7
+ import { buildUpdateMemoryTool } from './update.js'
6
8
 
7
9
  /**
8
10
  * Build memory tools over one authoritative store.
@@ -28,9 +30,13 @@ export function buildMemoryTools(store: MemoryStore, index?: MemoryIndex): ToolD
28
30
  index ? buildSearchMemoryTool(index) : buildStoreSearchMemoryTool(store),
29
31
  buildReadMemoryTool(store),
30
32
  buildSaveMemoryTool(store),
33
+ buildUpdateMemoryTool(store),
34
+ buildDeleteMemoryTool(store),
31
35
  ]
32
36
  }
33
37
 
34
38
  export { buildSearchMemoryTool } from './search.js'
35
39
  export { buildReadMemoryTool } from './read.js'
36
40
  export { buildSaveMemoryTool } from './save.js'
41
+ export { buildUpdateMemoryTool } from './update.js'
42
+ export { buildDeleteMemoryTool } from './delete.js'
@@ -6,7 +6,8 @@ import { defineTool } from '../defineTool.js'
6
6
  export function buildSaveMemoryTool(store: MemoryStore): ToolDefinition {
7
7
  return defineTool({
8
8
  name: 'save_memory',
9
- description: 'Save a new memory with title, summary, and full content for future reference.',
9
+ description:
10
+ 'Save a new historical claim for future reference. Include the actual useful fact and its evidence in the summary and content. For corrections to an existing record use update_memory instead of creating contradictory duplicates.',
10
11
  inputSchema: z.object({
11
12
  title: z.string().min(1).describe('Short descriptive title'),
12
13
  summary: z.string().min(1).describe('Brief summary (1-2 sentences)'),
@@ -18,12 +19,13 @@ export function buildSaveMemoryTool(store: MemoryStore): ToolDefinition {
18
19
  readOnly: false,
19
20
  destructive: false,
20
21
  concurrencySafe: true,
21
- async execute({ title, summary, content, tags }) {
22
+ async execute({ title, summary, content, tags }, context) {
22
23
  const { entry } = await store.create({
23
24
  title,
24
25
  summary,
25
26
  content,
26
27
  tags,
28
+ metadata: { source: 'agent-memory', runId: context.runId },
27
29
  })
28
30
 
29
31
  return {
@@ -14,19 +14,26 @@ function defineSearchMemoryTool(search: SearchMemory): ToolDefinition {
14
14
  return defineTool({
15
15
  name: 'search_memory',
16
16
  description:
17
- 'Search stored memories by query, tags, or status. Returns titles and summaries only — use read_memory for full content.',
17
+ 'Search active stored memories by relevant words or tags. The built-in store ranks matches in titles, summaries and full content. Returns titles and summaries; use read_memory for evidence. Set status to archived to inspect obsolete records.',
18
18
  inputSchema: z.object({
19
- query: z.string().optional().describe('Search query to match against titles and summaries'),
19
+ query: z.string().optional().describe('Relevant words or identifiers to search'),
20
20
  tags: z.array(z.string()).optional().describe('Filter by tags (all must match)'),
21
- limit: z.number().positive().default(10).describe('Maximum results to return'),
21
+ status: z.enum(['active', 'archived']).default('active'),
22
+ limit: z
23
+ .number()
24
+ .int()
25
+ .min(1)
26
+ .max(50)
27
+ .default(10)
28
+ .describe('Maximum results to return (1–50)'),
22
29
  }),
23
30
  category: 'analysis',
24
31
  permissions: [],
25
32
  readOnly: true,
26
33
  destructive: false,
27
34
  concurrencySafe: true,
28
- async execute({ query, tags, limit }) {
29
- const result = await search({ query, tags, limit })
35
+ async execute({ query, tags, status, limit }) {
36
+ const result = await search({ query, tags, status, limit })
30
37
 
31
38
  if (result.entries.length === 0) {
32
39
  return {
@@ -0,0 +1,50 @@
1
+ import { z } from 'zod'
2
+ import type { MemoryStore } from '../../types/memory/index.js'
3
+ import type { ToolDefinition } from '../../types/tool/index.js'
4
+ import { asMemoryId } from '../../utils/id.js'
5
+ import { defineTool } from '../defineTool.js'
6
+
7
+ export function buildUpdateMemoryTool(store: MemoryStore): ToolDefinition {
8
+ return defineTool({
9
+ name: 'update_memory',
10
+ description:
11
+ 'Correct an existing memory in place, or archive an obsolete claim so it is no longer recalled. Use the ID from search_memory; current evidence should determine corrections.',
12
+ inputSchema: z.object({
13
+ id: z.string().describe('Memory ID to correct or archive'),
14
+ title: z.string().min(1).optional(),
15
+ summary: z.string().min(1).optional(),
16
+ content: z.string().min(1).optional(),
17
+ tags: z.array(z.string()).optional(),
18
+ status: z.enum(['active', 'archived']).optional(),
19
+ }),
20
+ category: 'custom',
21
+ permissions: [],
22
+ readOnly: false,
23
+ destructive: false,
24
+ concurrencySafe: true,
25
+ async execute({ id, ...updates }) {
26
+ if (Object.values(updates).every((value) => value === undefined)) {
27
+ return {
28
+ success: false,
29
+ output: 'Provide a correction or status to update.',
30
+ error: 'No memory update supplied',
31
+ }
32
+ }
33
+ const memoryId = asMemoryId(id)
34
+ // Patch only requested fields in one store operation. A read/merge of
35
+ // provenance here would overwrite metadata from a concurrent writer.
36
+ const entry = await store.update(memoryId, updates)
37
+ if (!entry)
38
+ return {
39
+ success: false,
40
+ output: `Memory ${id} no longer exists.`,
41
+ error: 'Memory not found',
42
+ }
43
+ return {
44
+ success: true,
45
+ output: `Memory updated: ${id} — ${entry.title} (${entry.status}).`,
46
+ data: { id, status: entry.status },
47
+ }
48
+ },
49
+ })
50
+ }
@@ -29,6 +29,12 @@ export interface TaskHandle {
29
29
  export type SiblingFailurePolicy = 'continue' | 'cancel-siblings'
30
30
 
31
31
  export interface CreateTaskOptions {
32
+ /**
33
+ * Revalidate host authority at actual admission, including after a capacity wait.
34
+ * Queue retries may invoke this more than once; checks must tolerate repeated calls.
35
+ */
36
+ readonly beforeStart?: () => Promise<void>
37
+
32
38
  /**
33
39
  * Observe events from this one delegated run when the scheduler can expose
34
40
  * them.