@namzu/sdk 61.3.0 → 62.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 (307) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/dist/agents/QueryAgent.d.ts.map +1 -1
  3. package/dist/agents/QueryAgent.js +2 -0
  4. package/dist/agents/QueryAgent.js.map +1 -1
  5. package/dist/bridge/acp/permission.d.ts +8 -0
  6. package/dist/bridge/acp/permission.d.ts.map +1 -1
  7. package/dist/bridge/acp/permission.js.map +1 -1
  8. package/dist/bridge/acp/server.d.ts.map +1 -1
  9. package/dist/bridge/acp/server.js +8 -1
  10. package/dist/bridge/acp/server.js.map +1 -1
  11. package/dist/constants/tools/index.d.ts +2 -0
  12. package/dist/constants/tools/index.d.ts.map +1 -1
  13. package/dist/constants/tools/index.js +23 -0
  14. package/dist/constants/tools/index.js.map +1 -1
  15. package/dist/eval/harness-protection.js +1 -1
  16. package/dist/eval/harness-protection.js.map +1 -1
  17. package/dist/learning/cycle.d.ts +5 -3
  18. package/dist/learning/cycle.d.ts.map +1 -1
  19. package/dist/learning/cycle.js +11 -13
  20. package/dist/learning/cycle.js.map +1 -1
  21. package/dist/learning/guidance.d.ts +74 -0
  22. package/dist/learning/guidance.d.ts.map +1 -0
  23. package/dist/learning/guidance.js +259 -0
  24. package/dist/learning/guidance.js.map +1 -0
  25. package/dist/learning/skill.d.ts +2 -2
  26. package/dist/learning/skill.d.ts.map +1 -1
  27. package/dist/learning/skill.js +6 -6
  28. package/dist/learning/skill.js.map +1 -1
  29. package/dist/learning/store.d.ts.map +1 -1
  30. package/dist/learning/store.js +6 -6
  31. package/dist/learning/store.js.map +1 -1
  32. package/dist/pals/communication/ingress-schema.d.ts +8 -8
  33. package/dist/pals/communication/schema.d.ts +2 -2
  34. package/dist/peers/client.d.ts +2 -3
  35. package/dist/peers/client.d.ts.map +1 -1
  36. package/dist/peers/client.js +2 -3
  37. package/dist/peers/client.js.map +1 -1
  38. package/dist/peers/endpoint.d.ts +1 -2
  39. package/dist/peers/endpoint.d.ts.map +1 -1
  40. package/dist/peers/endpoint.js +1 -2
  41. package/dist/peers/endpoint.js.map +1 -1
  42. package/dist/peers/protocol.d.ts.map +1 -1
  43. package/dist/peers/protocol.js +1 -0
  44. package/dist/peers/protocol.js.map +1 -1
  45. package/dist/prompt/coding-agent-doctrine.d.ts +4 -4
  46. package/dist/prompt/coding-agent-doctrine.d.ts.map +1 -1
  47. package/dist/prompt/coding-agent-doctrine.js +5 -3
  48. package/dist/prompt/coding-agent-doctrine.js.map +1 -1
  49. package/dist/prompt/index.d.ts +0 -4
  50. package/dist/prompt/index.d.ts.map +1 -1
  51. package/dist/prompt/index.js +0 -2
  52. package/dist/prompt/index.js.map +1 -1
  53. package/dist/provider/fallback.d.ts.map +1 -1
  54. package/dist/provider/fallback.js +3 -0
  55. package/dist/provider/fallback.js.map +1 -1
  56. package/dist/provider/idle-timeout.d.ts.map +1 -1
  57. package/dist/provider/idle-timeout.js +5 -0
  58. package/dist/provider/idle-timeout.js.map +1 -1
  59. package/dist/provider/model-window-suffix.d.ts.map +1 -1
  60. package/dist/provider/model-window-suffix.js +1 -0
  61. package/dist/provider/model-window-suffix.js.map +1 -1
  62. package/dist/provider/retry.d.ts.map +1 -1
  63. package/dist/provider/retry.js +5 -0
  64. package/dist/provider/retry.js.map +1 -1
  65. package/dist/provider/step-stopped.d.ts +19 -0
  66. package/dist/provider/step-stopped.d.ts.map +1 -0
  67. package/dist/provider/step-stopped.js +25 -0
  68. package/dist/provider/step-stopped.js.map +1 -0
  69. package/dist/provider/token-budget.d.ts.map +1 -1
  70. package/dist/provider/token-budget.js +6 -1
  71. package/dist/provider/token-budget.js.map +1 -1
  72. package/dist/public-runtime.d.ts +4 -15
  73. package/dist/public-runtime.d.ts.map +1 -1
  74. package/dist/public-runtime.js +3 -14
  75. package/dist/public-runtime.js.map +1 -1
  76. package/dist/public-tools.d.ts +0 -2
  77. package/dist/public-tools.d.ts.map +1 -1
  78. package/dist/public-tools.js +0 -2
  79. package/dist/public-tools.js.map +1 -1
  80. package/dist/public-types.d.ts +3 -21
  81. package/dist/public-types.d.ts.map +1 -1
  82. package/dist/runtime/query/iteration/index.d.ts +17 -0
  83. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  84. package/dist/runtime/query/iteration/index.js +168 -5
  85. package/dist/runtime/query/iteration/index.js.map +1 -1
  86. package/dist/runtime/query/iteration/phases/per-call-review.d.ts.map +1 -1
  87. package/dist/runtime/query/iteration/phases/per-call-review.js +4 -5
  88. package/dist/runtime/query/iteration/phases/per-call-review.js.map +1 -1
  89. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  90. package/dist/runtime/query/iteration/phases/tool-review.js +6 -3
  91. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  92. package/dist/runtime/query/iteration/stream-turn.d.ts +9 -1
  93. package/dist/runtime/query/iteration/stream-turn.d.ts.map +1 -1
  94. package/dist/runtime/query/iteration/stream-turn.js +48 -2
  95. package/dist/runtime/query/iteration/stream-turn.js.map +1 -1
  96. package/dist/runtime/query/resume-pending.d.ts.map +1 -1
  97. package/dist/runtime/query/resume-pending.js +7 -4
  98. package/dist/runtime/query/resume-pending.js.map +1 -1
  99. package/dist/runtime/query/review-policy.d.ts +40 -8
  100. package/dist/runtime/query/review-policy.d.ts.map +1 -1
  101. package/dist/runtime/query/review-policy.js +139 -24
  102. package/dist/runtime/query/review-policy.js.map +1 -1
  103. package/dist/runtime/query/turn/completion-audit.d.ts +32 -0
  104. package/dist/runtime/query/turn/completion-audit.d.ts.map +1 -0
  105. package/dist/runtime/query/turn/completion-audit.js +56 -0
  106. package/dist/runtime/query/turn/completion-audit.js.map +1 -0
  107. package/dist/runtime/query/turn/stall-monitor.d.ts +78 -0
  108. package/dist/runtime/query/turn/stall-monitor.d.ts.map +1 -0
  109. package/dist/runtime/query/turn/stall-monitor.js +101 -0
  110. package/dist/runtime/query/turn/stall-monitor.js.map +1 -0
  111. package/dist/session/paths.d.ts +1 -2
  112. package/dist/session/paths.d.ts.map +1 -1
  113. package/dist/session/paths.js +1 -4
  114. package/dist/session/paths.js.map +1 -1
  115. package/dist/tools/builtins/bash.d.ts +2 -1
  116. package/dist/tools/builtins/bash.d.ts.map +1 -1
  117. package/dist/tools/builtins/bash.js +5 -2
  118. package/dist/tools/builtins/bash.js.map +1 -1
  119. package/dist/tools/builtins/computer-use.d.ts +80 -0
  120. package/dist/tools/builtins/computer-use.d.ts.map +1 -1
  121. package/dist/tools/builtins/computer-use.js +172 -9
  122. package/dist/tools/builtins/computer-use.js.map +1 -1
  123. package/dist/tools/builtins/content-fingerprint.d.ts +13 -0
  124. package/dist/tools/builtins/content-fingerprint.d.ts.map +1 -1
  125. package/dist/tools/builtins/content-fingerprint.js +25 -0
  126. package/dist/tools/builtins/content-fingerprint.js.map +1 -1
  127. package/dist/tools/builtins/edit.d.ts.map +1 -1
  128. package/dist/tools/builtins/edit.js +7 -2
  129. package/dist/tools/builtins/edit.js.map +1 -1
  130. package/dist/tools/builtins/shell-read-credit.d.ts +15 -0
  131. package/dist/tools/builtins/shell-read-credit.d.ts.map +1 -0
  132. package/dist/tools/builtins/shell-read-credit.js +161 -0
  133. package/dist/tools/builtins/shell-read-credit.js.map +1 -0
  134. package/dist/tools/paths.d.ts +15 -2
  135. package/dist/tools/paths.d.ts.map +1 -1
  136. package/dist/tools/paths.js +17 -2
  137. package/dist/tools/paths.js.map +1 -1
  138. package/dist/turn/evidence-recall.d.ts +1 -1
  139. package/dist/turn/evidence-recall.d.ts.map +1 -1
  140. package/dist/turn/evidence-recall.js +5 -14
  141. package/dist/turn/evidence-recall.js.map +1 -1
  142. package/dist/types/acp/index.d.ts +6 -0
  143. package/dist/types/acp/index.d.ts.map +1 -1
  144. package/dist/types/agent/base.d.ts +4 -0
  145. package/dist/types/agent/base.d.ts.map +1 -1
  146. package/dist/types/hitl/index.d.ts +8 -0
  147. package/dist/types/hitl/index.d.ts.map +1 -1
  148. package/dist/types/hitl/index.js.map +1 -1
  149. package/dist/types/ids/index.d.ts +1 -1
  150. package/dist/types/message/index.d.ts +1 -1
  151. package/dist/types/message/index.d.ts.map +1 -1
  152. package/dist/types/message/index.js +1 -0
  153. package/dist/types/message/index.js.map +1 -1
  154. package/dist/types/provider/interface.d.ts +12 -0
  155. package/dist/types/provider/interface.d.ts.map +1 -1
  156. package/dist/types/session/config.d.ts +25 -0
  157. package/dist/types/session/config.d.ts.map +1 -1
  158. package/dist/types/session/records.d.ts.map +1 -1
  159. package/dist/types/session/records.js +2 -0
  160. package/dist/types/session/records.js.map +1 -1
  161. package/dist/types/session/turn.d.ts +1 -1
  162. package/dist/types/session/turn.d.ts.map +1 -1
  163. package/dist/types/session/turn.js.map +1 -1
  164. package/package.json +1 -1
  165. package/src/agents/QueryAgent.ts +2 -0
  166. package/src/bridge/acp/permission.ts +8 -0
  167. package/src/bridge/acp/server.ts +7 -1
  168. package/src/constants/tools/index.ts +27 -0
  169. package/src/eval/harness-protection.ts +1 -1
  170. package/src/learning/cycle.ts +15 -16
  171. package/src/learning/guidance.ts +339 -0
  172. package/src/learning/skill.ts +7 -7
  173. package/src/learning/store.ts +5 -6
  174. package/src/peers/client.ts +2 -3
  175. package/src/peers/endpoint.ts +1 -2
  176. package/src/peers/protocol.ts +1 -0
  177. package/src/prompt/coding-agent-doctrine.ts +5 -3
  178. package/src/prompt/index.ts +0 -4
  179. package/src/provider/fallback.ts +7 -0
  180. package/src/provider/idle-timeout.ts +8 -0
  181. package/src/provider/model-window-suffix.ts +1 -0
  182. package/src/provider/retry.ts +8 -0
  183. package/src/provider/step-stopped.ts +27 -0
  184. package/src/provider/token-budget.ts +6 -1
  185. package/src/public-runtime.ts +10 -28
  186. package/src/public-tools.ts +0 -3
  187. package/src/public-types.ts +5 -137
  188. package/src/runtime/query/iteration/index.ts +207 -5
  189. package/src/runtime/query/iteration/phases/per-call-review.ts +7 -5
  190. package/src/runtime/query/iteration/phases/tool-review.ts +5 -3
  191. package/src/runtime/query/iteration/stream-turn.ts +51 -1
  192. package/src/runtime/query/resume-pending.ts +11 -4
  193. package/src/runtime/query/review-policy.ts +157 -24
  194. package/src/runtime/query/turn/completion-audit.ts +71 -0
  195. package/src/runtime/query/turn/stall-monitor.ts +158 -0
  196. package/src/session/paths.ts +2 -6
  197. package/src/tools/builtins/bash.ts +6 -2
  198. package/src/tools/builtins/computer-use.ts +245 -7
  199. package/src/tools/builtins/content-fingerprint.ts +25 -0
  200. package/src/tools/builtins/edit.ts +11 -2
  201. package/src/tools/builtins/shell-read-credit.ts +159 -0
  202. package/src/tools/paths.ts +18 -2
  203. package/src/turn/evidence-recall.ts +7 -15
  204. package/src/types/acp/index.ts +6 -0
  205. package/src/types/agent/base.ts +4 -0
  206. package/src/types/hitl/index.ts +8 -0
  207. package/src/types/ids/index.ts +1 -1
  208. package/src/types/message/index.ts +1 -0
  209. package/src/types/provider/interface.ts +13 -0
  210. package/src/types/session/config.ts +27 -0
  211. package/src/types/session/records.ts +2 -0
  212. package/src/types/session/turn.ts +2 -1
  213. package/dist/learning/deprecated.d.ts +0 -86
  214. package/dist/learning/deprecated.d.ts.map +0 -1
  215. package/dist/learning/deprecated.js +0 -22
  216. package/dist/learning/deprecated.js.map +0 -1
  217. package/dist/manager/resident/activity.d.ts +0 -74
  218. package/dist/manager/resident/activity.d.ts.map +0 -1
  219. package/dist/manager/resident/activity.js +0 -125
  220. package/dist/manager/resident/activity.js.map +0 -1
  221. package/dist/manager/resident/agenda.d.ts +0 -167
  222. package/dist/manager/resident/agenda.d.ts.map +0 -1
  223. package/dist/manager/resident/agenda.js +0 -588
  224. package/dist/manager/resident/agenda.js.map +0 -1
  225. package/dist/manager/resident/consumption.d.ts +0 -98
  226. package/dist/manager/resident/consumption.d.ts.map +0 -1
  227. package/dist/manager/resident/consumption.js +0 -237
  228. package/dist/manager/resident/consumption.js.map +0 -1
  229. package/dist/manager/resident/delivery-window.d.ts +0 -28
  230. package/dist/manager/resident/delivery-window.d.ts.map +0 -1
  231. package/dist/manager/resident/delivery-window.js +0 -80
  232. package/dist/manager/resident/delivery-window.js.map +0 -1
  233. package/dist/manager/resident/evidence-recall.d.ts +0 -30
  234. package/dist/manager/resident/evidence-recall.d.ts.map +0 -1
  235. package/dist/manager/resident/evidence-recall.js +0 -297
  236. package/dist/manager/resident/evidence-recall.js.map +0 -1
  237. package/dist/manager/resident/history-disk.d.ts +0 -10
  238. package/dist/manager/resident/history-disk.d.ts.map +0 -1
  239. package/dist/manager/resident/history-disk.js +0 -50
  240. package/dist/manager/resident/history-disk.js.map +0 -1
  241. package/dist/manager/resident/history.d.ts +0 -115
  242. package/dist/manager/resident/history.d.ts.map +0 -1
  243. package/dist/manager/resident/history.js +0 -203
  244. package/dist/manager/resident/history.js.map +0 -1
  245. package/dist/manager/resident/host.d.ts +0 -112
  246. package/dist/manager/resident/host.d.ts.map +0 -1
  247. package/dist/manager/resident/host.js +0 -254
  248. package/dist/manager/resident/host.js.map +0 -1
  249. package/dist/manager/resident/initiative.d.ts +0 -86
  250. package/dist/manager/resident/initiative.d.ts.map +0 -1
  251. package/dist/manager/resident/initiative.js +0 -131
  252. package/dist/manager/resident/initiative.js.map +0 -1
  253. package/dist/manager/resident/loop.d.ts +0 -49
  254. package/dist/manager/resident/loop.d.ts.map +0 -1
  255. package/dist/manager/resident/loop.js +0 -75
  256. package/dist/manager/resident/loop.js.map +0 -1
  257. package/dist/manager/resident/outbox.d.ts +0 -124
  258. package/dist/manager/resident/outbox.d.ts.map +0 -1
  259. package/dist/manager/resident/outbox.js +0 -216
  260. package/dist/manager/resident/outbox.js.map +0 -1
  261. package/dist/manager/resident/proposal.d.ts +0 -65
  262. package/dist/manager/resident/proposal.d.ts.map +0 -1
  263. package/dist/manager/resident/proposal.js +0 -77
  264. package/dist/manager/resident/proposal.js.map +0 -1
  265. package/dist/manager/resident/store.d.ts +0 -108
  266. package/dist/manager/resident/store.d.ts.map +0 -1
  267. package/dist/manager/resident/store.js +0 -205
  268. package/dist/manager/resident/store.js.map +0 -1
  269. package/dist/manager/resident/tool-evidence.d.ts +0 -112
  270. package/dist/manager/resident/tool-evidence.d.ts.map +0 -1
  271. package/dist/manager/resident/tool-evidence.js +0 -289
  272. package/dist/manager/resident/tool-evidence.js.map +0 -1
  273. package/dist/prompt/resident-learning.d.ts +0 -29
  274. package/dist/prompt/resident-learning.d.ts.map +0 -1
  275. package/dist/prompt/resident-learning.js +0 -126
  276. package/dist/prompt/resident-learning.js.map +0 -1
  277. package/dist/prompt/resident-step.d.ts +0 -42
  278. package/dist/prompt/resident-step.d.ts.map +0 -1
  279. package/dist/prompt/resident-step.js +0 -137
  280. package/dist/prompt/resident-step.js.map +0 -1
  281. package/dist/tools/resident-history.d.ts +0 -12
  282. package/dist/tools/resident-history.d.ts.map +0 -1
  283. package/dist/tools/resident-history.js +0 -82
  284. package/dist/tools/resident-history.js.map +0 -1
  285. package/dist/tools/resident-tool-evidence.d.ts +0 -9
  286. package/dist/tools/resident-tool-evidence.d.ts.map +0 -1
  287. package/dist/tools/resident-tool-evidence.js +0 -61
  288. package/dist/tools/resident-tool-evidence.js.map +0 -1
  289. package/src/learning/deprecated.ts +0 -124
  290. package/src/manager/resident/activity.ts +0 -211
  291. package/src/manager/resident/agenda.ts +0 -937
  292. package/src/manager/resident/consumption.ts +0 -335
  293. package/src/manager/resident/delivery-window.ts +0 -99
  294. package/src/manager/resident/evidence-recall.ts +0 -369
  295. package/src/manager/resident/history-disk.ts +0 -63
  296. package/src/manager/resident/history.ts +0 -348
  297. package/src/manager/resident/host.ts +0 -390
  298. package/src/manager/resident/initiative.ts +0 -231
  299. package/src/manager/resident/loop.ts +0 -119
  300. package/src/manager/resident/outbox.ts +0 -341
  301. package/src/manager/resident/proposal.ts +0 -119
  302. package/src/manager/resident/store.ts +0 -279
  303. package/src/manager/resident/tool-evidence.ts +0 -455
  304. package/src/prompt/resident-learning.ts +0 -152
  305. package/src/prompt/resident-step.ts +0 -182
  306. package/src/tools/resident-history.ts +0 -88
  307. package/src/tools/resident-tool-evidence.ts +0 -72
@@ -9,8 +9,7 @@ import {
9
9
  type HarnessVerificationBatch,
10
10
  reviewHarnessCandidate,
11
11
  } from '../eval/harness-verification.js'
12
- import type { DiskResidentAgenda } from '../manager/resident/agenda.js'
13
- import { ResidentConflictError } from '../manager/resident/store.js'
12
+ import { type DiskLearningGuidance, LearningGuidanceConflictError } from './guidance.js'
14
13
  import {
15
14
  LearningLeakError,
16
15
  type LearningLeakFinding,
@@ -113,7 +112,8 @@ export interface LearningCycleOptions {
113
112
  readonly purpose?: 'task' | 'exploration'
114
113
  /** Fixed before generation; both rounds must preserve every named task. */
115
114
  readonly protection: HarnessProtectionPlan
116
- readonly agenda: Pick<DiskResidentAgenda, 'read' | 'promoteSkill'>
115
+ /** Where the active guidance is read and where an accepted candidate is activated. */
116
+ readonly guidance: Pick<DiskLearningGuidance, 'read' | 'promoteSkill'>
117
117
  readonly skillName: string
118
118
  readonly failure: { readonly evidence: LearningEvidence; readonly trace: string }
119
119
  readonly parentCycleId?: string
@@ -158,7 +158,8 @@ export interface LearningCycleResult {
158
158
  readonly candidate?: LearningSkillCandidate
159
159
  readonly candidateRevision?: string
160
160
  readonly baselineRevision?: string
161
- readonly agendaRevision?: number
161
+ /** The guidance revision this activation created. */
162
+ readonly guidanceRevision?: number
162
163
  readonly review?: HarnessReview
163
164
  /** Present when the cycle was refused as a leak: where it was seen, never the text. */
164
165
  readonly leak?: readonly LearningLeakFinding[]
@@ -239,7 +240,7 @@ export async function runLearningCycle(
239
240
  ...(baselineRevision ? { baselineRevision } : {}),
240
241
  ...(review ? { review } : {}),
241
242
  ...(leak ? { leak } : {}),
242
- ...(activatedRevision !== undefined ? { agendaRevision: activatedRevision } : {}),
243
+ ...(activatedRevision !== undefined ? { guidanceRevision: activatedRevision } : {}),
243
244
  consumption: usage(),
244
245
  auditComplete,
245
246
  })
@@ -270,10 +271,8 @@ export async function runLearningCycle(
270
271
  },
271
272
  { subject: 'failure.trace', text: failure.trace },
272
273
  )
273
- const snapshot = structuredClone(await options.agenda.read())
274
- if (!snapshot) throw new Error('No resident agenda exists for this learning cycle.')
275
- if (snapshot.paused || snapshot.pursuits.some((p) => p.state.phase === 'running'))
276
- throw new Error('Resident learning requires an unpaused agenda without running pursuits.')
274
+ const snapshot = structuredClone(await options.guidance.read())
275
+ if (!snapshot) throw new Error('No learning guidance exists for this learning cycle.')
277
276
  const current = snapshot.learning?.skills.find((s) => s.name === skillName)
278
277
  if (current && (current.purpose ?? 'task') !== purpose)
279
278
  throw new Error(
@@ -294,7 +293,7 @@ export async function runLearningCycle(
294
293
  await append('started', {
295
294
  tenantId: snapshot.tenantId,
296
295
  agentKey: snapshot.agentKey,
297
- agendaRevision: snapshot.revision,
296
+ guidanceRevision: snapshot.revision,
298
297
  baselineRevision,
299
298
  skillName,
300
299
  ...purposeContext,
@@ -306,7 +305,7 @@ export async function runLearningCycle(
306
305
  })
307
306
  const check = async () => {
308
307
  options.signal.throwIfAborted()
309
- const latest = await options.agenda.read()
308
+ const latest = await options.guidance.read()
310
309
  options.signal.throwIfAborted()
311
310
  if (
312
311
  !latest ||
@@ -314,7 +313,7 @@ export async function runLearningCycle(
314
313
  latest.agentKey !== snapshot.agentKey ||
315
314
  latest.revision !== snapshot.revision
316
315
  )
317
- throw new ResidentConflictError()
316
+ throw new LearningGuidanceConflictError()
318
317
  }
319
318
  const stage = async <T extends { usageComplete: boolean }>(
320
319
  name: LearningStage,
@@ -497,7 +496,7 @@ export async function runLearningCycle(
497
496
  await check()
498
497
  const evidence: LearningEvidence = {
499
498
  key: cycleId,
500
- source: `resident-learning-cycle:${cycleId}`,
499
+ source: `learning-cycle:${cycleId}`,
501
500
  reason: `Paired verification and fresh confirmation of guidance derived from evidence ${failure.evidence.key}.`,
502
501
  }
503
502
  // Validate all existing activation contracts before entering the ambiguous
@@ -511,11 +510,11 @@ export async function runLearningCycle(
511
510
  await append('activation-requested', {
512
511
  baselineRevision,
513
512
  candidateRevision,
514
- agendaRevision: snapshot.revision,
513
+ guidanceRevision: snapshot.revision,
515
514
  })
516
515
  options.signal.throwIfAborted()
517
516
  activationRequested = true
518
- const updated = await options.agenda.promoteSkill(
517
+ const updated = await options.guidance.promoteSkill(
519
518
  snapshot,
520
519
  candidate,
521
520
  { verification, confirmation, protection },
@@ -532,7 +531,7 @@ export async function runLearningCycle(
532
531
  outcome = result(
533
532
  error instanceof LearningLeakError
534
533
  ? 'failed'
535
- : error instanceof ResidentConflictError
534
+ : error instanceof LearningGuidanceConflictError
536
535
  ? 'conflict'
537
536
  : activationRequested
538
537
  ? 'activation-unknown'
@@ -0,0 +1,339 @@
1
+ import { createHash } from 'node:crypto'
2
+ import { readdir } from 'node:fs/promises'
3
+ import { join } from 'node:path'
4
+ import { isDeepStrictEqual } from 'node:util'
5
+ import { z } from 'zod'
6
+ import { DiskRecordStore } from '../store/kv/record-store.js'
7
+ import { DiskRevisionRecordStore, revisionFileSegment } from '../store/kv/revision-record-store.js'
8
+ import { defineSchema } from '../store/schema.js'
9
+ import type { TenantId } from '../types/ids/index.js'
10
+ import { asTenantId } from '../utils/id.js'
11
+ import {
12
+ type LearningEvidence,
13
+ type LearningProfileUpdate,
14
+ type LearningSkillCandidate,
15
+ type LearningSkillEvaluation,
16
+ type LearningState,
17
+ freezeLearning,
18
+ learningEvidenceSchema,
19
+ learningSchema,
20
+ promoteLearningSkill,
21
+ restoreLearningSkill,
22
+ reviseLearningProfile,
23
+ } from './skill.js'
24
+
25
+ /**
26
+ * @experimental One immutable revision of the guidance a learning target has
27
+ * earned. `learning` is absent until the first profile change or activation.
28
+ */
29
+ export interface LearningGuidanceState {
30
+ readonly tenantId: string
31
+ readonly agentKey: string
32
+ readonly revision: number
33
+ readonly learning?: LearningState
34
+ }
35
+
36
+ /** @experimental A competing writer advanced the guidance revision; read it again before retrying. */
37
+ export class LearningGuidanceConflictError extends Error {
38
+ constructor() {
39
+ super('Learning guidance changed; read the current guidance before retrying.')
40
+ this.name = 'LearningGuidanceConflictError'
41
+ }
42
+ }
43
+
44
+ const guidanceSchema = z.object({
45
+ tenantId: z.string().uuid(),
46
+ agentKey: z.string().min(1).max(200),
47
+ revision: z.number().int().positive().safe(),
48
+ learning: learningSchema.optional(),
49
+ })
50
+
51
+ const guidanceRecordSchema = defineSchema({
52
+ kind: 'learning-guidance',
53
+ current: 1,
54
+ migrations: {},
55
+ })
56
+
57
+ /**
58
+ * The record format learning guidance was kept in before it had a store of its
59
+ * own. Its `kind` is the identifier stamped into those files, so it keeps its
60
+ * spelling; only the fields learning used are read from it.
61
+ */
62
+ const earlierRecordSchema = defineSchema({
63
+ kind: 'resident-agenda',
64
+ current: 6,
65
+ migrations: {
66
+ 1: (record) => record,
67
+ 2: (record) => record,
68
+ 3: (record) => record,
69
+ 4: (record) => record,
70
+ 5: (record) => record,
71
+ },
72
+ })
73
+
74
+ const earlierFieldsSchema = z.object({
75
+ tenantId: z.string().uuid(),
76
+ agentKey: z.string().min(1).max(200),
77
+ revision: z.number().int().positive().safe(),
78
+ learning: learningSchema.optional(),
79
+ })
80
+
81
+ /** Marker written beside the revisions once an earlier store has been carried over. */
82
+ const ADOPTED_MARKER = 'adopted.json'
83
+ const REVISION_FILE = /^([1-9][0-9]{0,15})\.json$/
84
+
85
+ /** A key as one bounded path segment, the way the earlier store addressed it. */
86
+ function keySegment(key: string): string {
87
+ const encoded = revisionFileSegment(key)
88
+ return encoded.length <= 255
89
+ ? encoded
90
+ : `~sha256-${createHash('sha256').update(encoded).digest('hex')}`
91
+ }
92
+
93
+ /**
94
+ * Where guidance was kept before it had a store of its own, for one scope:
95
+ * `<root>/<tenant>/<key>/agenda`. Pass the result as `adoptFrom` to carry it over.
96
+ */
97
+ export function earlierGuidanceDirectory(
98
+ root: string,
99
+ scope: { readonly tenantId: string; readonly agentKey: string },
100
+ ): string {
101
+ return join(root, asTenantId(scope.tenantId), keySegment(scope.agentKey), 'agenda')
102
+ }
103
+
104
+ /** @experimental Where a guidance store lives and what it may carry over on first use. */
105
+ export interface DiskLearningGuidanceOptions {
106
+ /**
107
+ * A directory holding guidance in the earlier record format
108
+ * (see {@link earlierGuidanceDirectory}). On the first use of this store,
109
+ * every revision found there is copied into it with its revision number, and
110
+ * a marker is written beside them. The earlier files are only read, never
111
+ * changed or removed, so an older version keeps working on them.
112
+ */
113
+ readonly adoptFrom?: string
114
+ }
115
+
116
+ /**
117
+ * @experimental Revisioned, evaluated guidance for one tenant and one scope key,
118
+ * on a trusted local filesystem. Every change is an immutable revision published
119
+ * exclusively, so two processes cannot both commit the same one, and an older
120
+ * revision stays readable to roll one skill back.
121
+ */
122
+ export class DiskLearningGuidance {
123
+ private readonly records = new DiskRevisionRecordStore<LearningGuidanceState>(
124
+ guidanceRecordSchema,
125
+ 'learning guidance',
126
+ (record) => record.revision,
127
+ )
128
+ private readonly historyRecords = new DiskRecordStore<LearningGuidanceState>(guidanceRecordSchema)
129
+ private readonly location
130
+ private readonly tenantId: TenantId
131
+ private readonly agentKey: string
132
+ private readonly directory: string
133
+ private readonly adoptFrom: string | undefined
134
+ private adopting: Promise<void> | undefined
135
+
136
+ constructor(
137
+ directory: string,
138
+ scope: { tenantId: TenantId | string; agentKey: string },
139
+ options: DiskLearningGuidanceOptions = {},
140
+ ) {
141
+ this.tenantId = asTenantId(scope.tenantId)
142
+ this.agentKey = z.string().min(1).max(200).parse(scope.agentKey)
143
+ this.directory = directory
144
+ this.adoptFrom = options.adoptFrom
145
+ this.location = {
146
+ legacyPath: join(directory, 'state.json'),
147
+ revisionsDir: join(directory, 'revisions'),
148
+ publishLegacyProjection: false,
149
+ }
150
+ }
151
+
152
+ private checked(record: LearningGuidanceState): LearningGuidanceState {
153
+ const state = guidanceSchema.parse(record)
154
+ if (state.tenantId !== this.tenantId || state.agentKey !== this.agentKey)
155
+ throw new Error('Guidance does not match the bound tenant and scope key.')
156
+ return Object.freeze({
157
+ ...state,
158
+ ...(state.learning ? { learning: freezeLearning(state.learning) } : {}),
159
+ })
160
+ }
161
+
162
+ /** Carry an earlier store over once per process, and once ever per directory. */
163
+ private ready(): Promise<void> {
164
+ if (!this.adoptFrom) return Promise.resolve()
165
+ this.adopting ??= this.adopt(this.adoptFrom).catch((error) => {
166
+ this.adopting = undefined
167
+ throw error
168
+ })
169
+ return this.adopting
170
+ }
171
+
172
+ private async adopt(from: string): Promise<void> {
173
+ const marker = new DiskRecordStore<unknown>(guidanceRecordSchema)
174
+ if ((await marker.read(join(this.directory, ADOPTED_MARKER))) !== null) return
175
+ const earlier = new DiskRecordStore<unknown>(earlierRecordSchema)
176
+ let names: string[]
177
+ try {
178
+ names = await readdir(join(from, 'revisions'))
179
+ } catch (error) {
180
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') return
181
+ throw error
182
+ }
183
+ const revisions = names
184
+ .flatMap((name) => {
185
+ const match = REVISION_FILE.exec(name)
186
+ return match ? [Number(match[1])] : []
187
+ })
188
+ .sort((a, b) => a - b)
189
+ if (!revisions.length) return
190
+ const head = revisions[revisions.length - 1] as number
191
+ if (revisions.length !== head)
192
+ throw new Error(
193
+ `Earlier guidance in ${from} has gaps between revisions 1 and ${head}; it was not carried over.`,
194
+ )
195
+ for (const revision of revisions) {
196
+ const current = await this.records.read(this.location)
197
+ if ((current?.revision ?? 0) >= revision) continue
198
+ const raw = await earlier.read(join(from, 'revisions', `${revision}.json`))
199
+ const fields = earlierFieldsSchema.parse(raw)
200
+ if (
201
+ fields.revision !== revision ||
202
+ fields.tenantId !== this.tenantId ||
203
+ fields.agentKey !== this.agentKey
204
+ )
205
+ throw new Error(
206
+ `Earlier guidance revision ${revision} in ${from} does not match this tenant and scope key; it was not carried over.`,
207
+ )
208
+ const record = this.checked({
209
+ tenantId: fields.tenantId,
210
+ agentKey: fields.agentKey,
211
+ revision,
212
+ ...(fields.learning ? { learning: fields.learning } : {}),
213
+ })
214
+ try {
215
+ await this.records.transact(this.location, (latest) => {
216
+ if ((latest?.revision ?? 0) !== revision - 1) throw new LearningGuidanceConflictError()
217
+ return { record, result: undefined }
218
+ })
219
+ } catch (error) {
220
+ // A concurrent process carrying over the same revision wins identically.
221
+ const after = await this.records.read(this.location)
222
+ if ((after?.revision ?? 0) < revision) throw error
223
+ }
224
+ }
225
+ await marker.write(join(this.directory, ADOPTED_MARKER), {
226
+ from,
227
+ revisions: head,
228
+ adoptedAt: new Date().toISOString(),
229
+ })
230
+ }
231
+
232
+ async read(): Promise<LearningGuidanceState | null> {
233
+ await this.ready()
234
+ const record = await this.records.read(this.location)
235
+ return record === null ? null : this.checked(record)
236
+ }
237
+
238
+ /** One immutable revision, as it was committed. */
239
+ async readRevision(revision: number): Promise<LearningGuidanceState | null> {
240
+ z.number().int().positive().safe().parse(revision)
241
+ await this.ready()
242
+ const record = await this.historyRecords.read(
243
+ join(this.location.revisionsDir, `${revision}.json`),
244
+ )
245
+ if (record === null) return null
246
+ const state = this.checked(record)
247
+ if (state.revision !== revision)
248
+ throw new Error('Guidance revision filename and body disagree.')
249
+ return state
250
+ }
251
+
252
+ /** Create the empty first revision. A second creator gets a conflict. */
253
+ async create(): Promise<LearningGuidanceState> {
254
+ await this.ready()
255
+ const record = this.checked({
256
+ tenantId: this.tenantId,
257
+ agentKey: this.agentKey,
258
+ revision: 1,
259
+ })
260
+ return this.records.transact(this.location, (current) => {
261
+ if (current !== null) throw new LearningGuidanceConflictError()
262
+ return { record, result: record }
263
+ })
264
+ }
265
+
266
+ /** The current guidance, created empty when none exists yet. Safe to race. */
267
+ async open(): Promise<LearningGuidanceState> {
268
+ const existing = await this.read()
269
+ if (existing) return existing
270
+ try {
271
+ return await this.create()
272
+ } catch (error) {
273
+ const raced = await this.read()
274
+ if (!raced) throw error
275
+ return raced
276
+ }
277
+ }
278
+
279
+ private async learn(
280
+ expected: LearningGuidanceState,
281
+ learning: LearningState,
282
+ ): Promise<LearningGuidanceState> {
283
+ await this.ready()
284
+ const snapshot = this.checked(expected)
285
+ return this.records.transact(this.location, (record) => {
286
+ if (record === null) throw new LearningGuidanceConflictError()
287
+ const current = this.checked(record)
288
+ if (current.revision !== snapshot.revision) throw new LearningGuidanceConflictError()
289
+ if (!isDeepStrictEqual(current.learning, snapshot.learning))
290
+ throw new LearningGuidanceConflictError()
291
+ const next = this.checked({ ...current, learning, revision: current.revision + 1 })
292
+ return { record: next, result: next }
293
+ })
294
+ }
295
+
296
+ /** Append a host-evidenced correction to identity or preferences. */
297
+ async updateProfile(
298
+ expected: LearningGuidanceState,
299
+ input: LearningProfileUpdate,
300
+ ): Promise<LearningGuidanceState> {
301
+ const snapshot = this.checked(expected)
302
+ return this.learn(snapshot, reviseLearningProfile(snapshot.learning, input))
303
+ }
304
+
305
+ /** Bind evaluated guidance to the current learning version. */
306
+ async promoteSkill(
307
+ expected: LearningGuidanceState,
308
+ candidate: LearningSkillCandidate,
309
+ evaluation: LearningSkillEvaluation,
310
+ evidence: LearningEvidence,
311
+ ): Promise<LearningGuidanceState> {
312
+ const snapshot = this.checked(expected)
313
+ return this.learn(
314
+ snapshot,
315
+ promoteLearningSkill(snapshot.learning, candidate, evaluation, evidence),
316
+ )
317
+ }
318
+
319
+ /** Restore one skill from an immutable earlier revision while keeping the current profile. */
320
+ async rollbackSkill(
321
+ expected: LearningGuidanceState,
322
+ name: string,
323
+ fromRevision: number,
324
+ evidence: LearningEvidence,
325
+ ): Promise<LearningGuidanceState> {
326
+ const snapshot = this.checked(expected)
327
+ const checkedEvidence = learningEvidenceSchema.parse(evidence)
328
+ z.number().int().positive().safe().max(snapshot.revision).parse(fromRevision)
329
+ z.string()
330
+ .regex(/^[a-z0-9][a-z0-9_-]{0,63}$/)
331
+ .parse(name)
332
+ const historical = await this.readRevision(fromRevision)
333
+ if (!historical) throw new Error('Skill rollback revision is missing.')
334
+ return this.learn(
335
+ snapshot,
336
+ restoreLearningSkill(snapshot.learning, historical.learning, name, checkedEvidence),
337
+ )
338
+ }
339
+ }
@@ -119,11 +119,11 @@ export const learningSchema = z
119
119
  )
120
120
  context.addIssue({
121
121
  code: z.ZodIssueCode.custom,
122
- message: 'Invalid resident learning identity or content digest.',
122
+ message: 'Invalid learning identity or content digest.',
123
123
  })
124
124
  })
125
125
 
126
- /** @experimental Versioned behavioral overlay; the original resident mandate remains unchanged. */
126
+ /** @experimental Versioned behavioral overlay; the original identity remains unchanged. */
127
127
  export interface LearningState {
128
128
  readonly revision: number
129
129
  readonly identity?: { readonly text: string; readonly evidence: LearningEvidence }
@@ -219,7 +219,7 @@ function base(current: LearningState | undefined, evidence: LearningEvidence): L
219
219
  }
220
220
  }
221
221
 
222
- /** Internal pure profile transition; storage owns exact agenda CAS and idle admission. */
222
+ /** Internal pure profile transition; storage owns the exact-revision check. */
223
223
  export function reviseLearningProfile(
224
224
  current: LearningState | undefined,
225
225
  input: LearningProfileUpdate,
@@ -306,7 +306,7 @@ export function promoteLearningSkill(
306
306
  batch.candidate.length > 128 ||
307
307
  batch.attributions.length > 128
308
308
  )
309
- throw new Error('Resident skill evaluation is bounded to 64 tasks per round.')
309
+ throw new Error('Skill evaluation is bounded to 64 tasks per round.')
310
310
  // Recorded evidence must consistently identify success and complete measurements.
311
311
  for (const trial of [...batch.baseline, ...batch.candidate]) {
312
312
  if (
@@ -322,7 +322,7 @@ export function promoteLearningSkill(
322
322
  protection,
323
323
  )
324
324
  if (review.decision !== 'accept')
325
- throw new Error(`Resident skill promotion ${review.decision}: ${review.reason}`)
325
+ throw new Error(`Skill promotion ${review.decision}: ${review.reason}`)
326
326
  const skill: LearnedSkill = {
327
327
  ...candidate,
328
328
  hash,
@@ -358,7 +358,7 @@ export function restoreLearningSkill(
358
358
  ): LearningState {
359
359
  name.parse(skillName)
360
360
  if (!current?.skills.some((s) => s.name === skillName))
361
- throw new Error('No active resident skill to roll back.')
361
+ throw new Error('No active skill to roll back.')
362
362
  const next = base(current, evidenceFor(current, inputEvidence))
363
363
  const previous = historical
364
364
  ? freezeLearning(historical).skills.find((s) => s.name === skillName)
@@ -398,7 +398,7 @@ export function projectLearning(
398
398
  options: LearningProjectionOptions,
399
399
  ): LearningProjection {
400
400
  if (!Number.isSafeInteger(options.maxChars) || options.maxChars < 0 || options.maxChars > 64_000)
401
- throw new TypeError('Resident learning context requires maxChars between 0 and 64000.')
401
+ throw new TypeError('Learning context requires maxChars between 0 and 64000.')
402
402
  const selected = z.array(name).max(16).parse(options.skillNames)
403
403
  const purpose = z.enum(['task', 'exploration']).parse(options.purpose ?? 'task')
404
404
  if (new Set(selected).size !== selected.length) throw new Error('Duplicate selected skill names.')
@@ -286,7 +286,7 @@ export class SqliteLearningStore {
286
286
  row.project_id !== this.scope.projectId ||
287
287
  row.agent_key !== this.scope.agentKey
288
288
  )
289
- throw new Error('Learning cycle does not belong to this tenant, project and resident.')
289
+ throw new Error('Learning cycle does not belong to this tenant, project and scope key.')
290
290
  return row
291
291
  }
292
292
 
@@ -524,7 +524,7 @@ export class SqliteLearningStore {
524
524
  (event.data.tenantId !== this.scope.tenantId ||
525
525
  event.data.agentKey !== this.scope.agentKey)
526
526
  )
527
- throw new Error('Learning start does not match the bound resident.')
527
+ throw new Error('Learning start does not match the bound scope key.')
528
528
  const parent =
529
529
  event.data.parentCycleId === undefined ? null : uuid.parse(event.data.parentCycleId)
530
530
  if (parent && !this.owned(db, parent))
@@ -843,11 +843,10 @@ export async function runStoredLearningFromObservations(
843
843
  options: LearningDiscoveryOptions,
844
844
  ): Promise<LearningDiscoveryResult> {
845
845
  options.signal.throwIfAborted()
846
- const agenda = await options.agenda.read()
847
- if (!agenda || agenda.paused || agenda.pursuits.some((p) => p.state.phase === 'running'))
848
- throw new Error('Learning discovery requires an unpaused agenda without running pursuits.')
846
+ const guidance = await options.guidance.read()
847
+ if (!guidance) throw new Error('Learning discovery requires existing learning guidance.')
849
848
  const targets = options.evaluators.map((target) => {
850
- const skill = agenda.learning?.skills.find((s) => s.name === target.skillName)
849
+ const skill = guidance.learning?.skills.find((s) => s.name === target.skillName)
851
850
  return { ...target, baselineRevision: skill ? hashLearningSkill(skill) : 'none' }
852
851
  })
853
852
  const observation = await store.selectObservation(targets)
@@ -1,9 +1,8 @@
1
1
  /**
2
2
  * A client for one connection of `namzu-peer/1`: connect, write one request
3
3
  * line, read one response line, close. Mirrors the shape of this
4
- * repository's other local control clients (`queryRunner` in
5
- * `packages/cli/src/integrations/resident/runner-control.ts`,
6
- * `callEndpoint` in `packages/cli/src/schedule/daemon/endpoint.ts`): an
4
+ * repository's other local control clients (`callEndpoint` in
5
+ * `packages/cli/src/schedule/daemon/endpoint.ts`): an
7
6
  * authenticated response PROVES responsiveness, and its absence never proves
8
7
  * the peer is dead — only that this attempt could not reach it.
9
8
  *
@@ -3,8 +3,7 @@
3
3
  * session, newline-delimited JSON framing, a closed op set (design §1.1,
4
4
  * §1.3). Mirrors the shape of this repository's other local control
5
5
  * endpoints (`startEndpoint` in
6
- * `packages/cli/src/schedule/daemon/endpoint.ts`, `createRunnerControlServer`
7
- * in `packages/cli/src/integrations/resident/runner-control.ts`): a total
6
+ * `packages/cli/src/schedule/daemon/endpoint.ts`): a total
8
7
  * read deadline per connection (not an inactivity timer a slow sender can
9
8
  * reset), a bounded connection count, and a destroyed socket rather than an
10
9
  * error response for anything malformed — request bytes and tokens are
@@ -36,6 +36,7 @@ export const DEFAULT_PEER_READ_DEADLINE_MS = 2_000
36
36
  /** Concurrent connections a `createPeerEndpoint` server accepts before refusing new ones. */
37
37
  export const MAX_PEER_CONNECTIONS = 16
38
38
 
39
+ // 'resident' is no longer announced; it stays so an older peer's record still parses.
39
40
  export const PeerSessionKindSchema = z.enum(['tui', 'exec', 'resident', 'scheduled'])
40
41
  export type PeerSessionKind = z.infer<typeof PeerSessionKindSchema>
41
42
 
@@ -21,7 +21,7 @@
21
21
  * `CODING_AGENT_WORKING_DOCTRINE` names only the builtins and is meant for the
22
22
  * parent and for every delegated sub-agent, because a delegated task edits the
23
23
  * same repository under the same rules. `CODING_AGENT_DELEGATION_DOCTRINE`
24
- * names `task_create` and `Agent`, which a sub-agent does not have, and so is
24
+ * names `Agent` and speaks of the task list, which a sub-agent does not have, and so is
25
25
  * for the parent only. An adversarial review caught a draft ordering children
26
26
  * to plan with a tool they could not call; a rule about a tool the reader does
27
27
  * not have is not guidance, it is an instruction to fail.
@@ -38,6 +38,7 @@ export const CODING_AGENT_WORKING_DOCTRINE = `## How you work
38
38
 
39
39
  ### Delivering work
40
40
  - Act when you have enough information. Take established facts and the user's decisions as settled.
41
+ - Begin editing once you know the first change, and refine as you go. If you stop before changing anything, the next step starts from the same code.
41
42
  - The requested scope is the deliverable: deliver it at the size and on the subject it was asked. Read ambiguity the way a careful colleague would; make the routine judgment calls yourself and check in only when different readings would produce materially different work.
42
43
  - Finish the whole task, not just the easy parts. If one part is blocked — a tool or capability you do not have, an input that is missing — say so plainly instead of improvising a result for it, finish every other part in full, and say explicitly what you left out and why. Scaling the work down is the user's call.
43
44
  - If you find a real problem with the task as stated, say so in a sentence or two, then keep going under a stated assumption. Reserve blocking questions for cases where proceeding under any assumption would be unsafe or would make the work useless if wrong.
@@ -59,6 +60,7 @@ export const CODING_AGENT_WORKING_DOCTRINE = `## How you work
59
60
  - Before editing, use the relevant file content already available from a successful read or write; if it is unavailable or may be stale, read the needed portion. Read additional surrounding code when needed to match its naming and idiom. Respect project instructions and tool-specific prerequisites even when they require a fresh read.
60
61
  - Prefer the dedicated tools over shell equivalents (\`read\` over \`cat\`, \`grep\` over a shell grep, \`glob\` over \`find\`, \`edit\` over \`sed -i\`, \`write\` over a shell redirect), because their structured inputs and focused results are easier to check. Use \`bash\` when the task needs a shell; permissions apply the same way.
61
62
  - Independent tool calls go in one response: reading three files, or running a grep and a glob, does not need three turns. Wait for a mutation to finish before an observation or another action that depends on it; runtime ordering and concurrency limits remain authoritative.
63
+ - For a defect, reproduce it first with a small script or a failing test, and keep it. Make the change, then rerun that reproduction. Afterwards run the project's tests and its lint or format check, add tests for behaviour you introduced, and say where any throwaway file you made is. Say in your report what you ran and what you did not run.
62
64
  - Match verification to the task and its risk. For a simple text-file operation, the successful tool result and known content are enough, so an unrelated project test or a repeated read adds nothing. For code changes, run the relevant tests, typecheck or lint that would catch a mistake, and follow required project checks. Claim only the checks you ran.
63
65
  - Describe the state you leave the working tree in: say where scratch files are, and which files outside the request you touched.
64
66
 
@@ -74,11 +76,11 @@ export const CODING_AGENT_WORKING_DOCTRINE = `## How you work
74
76
  - Keep the final reply short and specific: what changed, what was verified, what is left.`
75
77
 
76
78
  /**
77
- * Parent-only: rules about `task_create` / `task_update` and `Agent`, which a
79
+ * Parent-only: rules about the task list and `Agent`, which a
78
80
  * delegated sub-agent does not have. See the module comment for the split.
79
81
  */
80
82
  export const CODING_AGENT_DELEGATION_DOCTRINE = `### Planning and delegating
81
- - For work of three or more steps — several files, several distinct stages, anything you would write a checklist for — open a task list with \`task_create\` and keep it current with \`task_update\`, marking each step done when it is done rather than at the end. Skip it for a single inspect-edit-test cycle or a two-step job: the list is for the user to follow, and a list of one or two items tells them nothing.
83
+ - A task list is optional. For work of three or more distinct steps — several files, several separate stages — you may keep one: exactly one step marked in progress at a time, each marked done when it is done rather than at the end, and the list cleared away once everything is done. Do not make one for a single inspect-edit-test cycle or a two-step job; a list of one or two items tells the user nothing. Keeping a list is not progress on the task itself.
82
84
  - When the \`Agent\` tool is available, delegate independent work through it and give each delegation a short, specific description; the user watches those descriptions, not the prompts. Brief each sub-agent with four things: the objective; what is out of bounds; which tools and sources to use; and the shape of the reply you want, such as a short summary with a file path for anything long. A fresh sub-agent starts with none of your context and returns what you ask for.
83
85
  - When the \`Agent\` tool lists a \`fork\` type, use \`subagent_type: "fork"\` when a side task would fill your context with output you will not need again, such as an open-ended survey. A fork starts from this conversation under your own instructions, so its prompt is only the directive: what to do and where its scope ends, without the background. Never write or guess what a fork will find before its report arrives. A fork cannot start forks of its own.
84
86
  - When delegated agents finish, tell the user what each one found in your next message. Their results reach you and not the user, so a result you do not pass on stays unseen.
@@ -23,7 +23,3 @@ export {
23
23
  codingAgentDoctrineContribution,
24
24
  } from './coding-agent-doctrine.js'
25
25
  export type { CodingAgentDoctrineOptions } from './coding-agent-doctrine.js'
26
- export { createResidentStepContributions } from './resident-step.js'
27
- export type { ResidentStepPromptOptions } from './resident-step.js'
28
- export { createResidentStepContext } from './resident-learning.js'
29
- export type { ResidentStepContextOptions, ResidentStepContextBundle } from './resident-learning.js'
@@ -554,6 +554,13 @@ export function withProviderFallback(
554
554
  const serving = members[cursor] ?? first
555
555
  return serving.provider.supportsImageInputFor?.(serving.model ?? model) ?? true
556
556
  },
557
+ // A chain takes a forced choice only if every member does: the request may
558
+ // be served by any of them, and the one that refuses would fail the step.
559
+ acceptsForcedToolChoiceFor: (model: string, thinking?: ThinkingConfig) =>
560
+ members.every(
561
+ (member) =>
562
+ member.provider.acceptsForcedToolChoiceFor?.(member.model ?? model, thinking) ?? true,
563
+ ),
557
564
  reasoningEffortLevelsFor: (model: string, thinking?: ThinkingConfig) =>
558
565
  commonReasoningEffortLevels(members, model, thinking),
559
566
  // Present on the chain wrapper for the same reason the menu method is:
@@ -223,6 +223,14 @@ export function withStreamIdleTimeout(
223
223
  supportsImageInputFor: (model: string) => provider.supportsImageInputFor?.(model) ?? true,
224
224
  }
225
225
  : {}),
226
+ ...(provider.acceptsForcedToolChoiceFor
227
+ ? {
228
+ acceptsForcedToolChoiceFor: (
229
+ model: string,
230
+ thinking?: Parameters<NonNullable<LLMProvider['acceptsForcedToolChoiceFor']>>[1],
231
+ ) => provider.acceptsForcedToolChoiceFor?.(model, thinking) ?? true,
232
+ }
233
+ : {}),
226
234
  ...(provider.reasoningEffortLevelsFor
227
235
  ? {
228
236
  reasoningEffortLevelsFor: (
@@ -18,6 +18,7 @@ const MODEL_FIRST_METHODS: ReadonlySet<string> = new Set([
18
18
  'supportsImageInputFor',
19
19
  'reasoningEffortLevelsFor',
20
20
  'reasoningEffortDefaultFor',
21
+ 'acceptsForcedToolChoiceFor',
21
22
  'effortLevelsFor',
22
23
  'resolveContextWindow',
23
24
  'refusalFallbackFor',