@namzu/sdk 55.0.0 → 56.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 (219) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/compaction/serializer.d.ts.map +1 -1
  3. package/dist/compaction/serializer.js +27 -15
  4. package/dist/compaction/serializer.js.map +1 -1
  5. package/dist/compaction/verifier.d.ts.map +1 -1
  6. package/dist/compaction/verifier.js +3 -2
  7. package/dist/compaction/verifier.js.map +1 -1
  8. package/dist/constants/compaction/index.d.ts +3 -1
  9. package/dist/constants/compaction/index.d.ts.map +1 -1
  10. package/dist/constants/compaction/index.js +3 -1
  11. package/dist/constants/compaction/index.js.map +1 -1
  12. package/dist/{manager/resident/learning-cycle.d.ts → learning/cycle.d.ts} +45 -36
  13. package/dist/learning/cycle.d.ts.map +1 -0
  14. package/dist/{manager/resident/learning-cycle.js → learning/cycle.js} +52 -20
  15. package/dist/learning/cycle.js.map +1 -0
  16. package/dist/learning/deprecated.d.ts +86 -0
  17. package/dist/learning/deprecated.d.ts.map +1 -0
  18. package/dist/learning/deprecated.js +22 -0
  19. package/dist/learning/deprecated.js.map +1 -0
  20. package/dist/learning/experience-store.d.ts +35 -0
  21. package/dist/learning/experience-store.d.ts.map +1 -0
  22. package/dist/learning/experience-store.js +143 -0
  23. package/dist/learning/experience-store.js.map +1 -0
  24. package/dist/learning/experience.d.ts +150 -0
  25. package/dist/learning/experience.d.ts.map +1 -0
  26. package/dist/learning/experience.js +146 -0
  27. package/dist/learning/experience.js.map +1 -0
  28. package/dist/learning/generator.d.ts +118 -0
  29. package/dist/learning/generator.d.ts.map +1 -0
  30. package/dist/learning/generator.js +275 -0
  31. package/dist/learning/generator.js.map +1 -0
  32. package/dist/learning/leak-tripwire.d.ts +66 -0
  33. package/dist/learning/leak-tripwire.d.ts.map +1 -0
  34. package/dist/learning/leak-tripwire.js +208 -0
  35. package/dist/learning/leak-tripwire.js.map +1 -0
  36. package/dist/{manager/resident/learning-observation.d.ts → learning/observation.d.ts} +4 -4
  37. package/dist/learning/observation.d.ts.map +1 -0
  38. package/dist/{manager/resident/learning-observation.js → learning/observation.js} +3 -3
  39. package/dist/learning/observation.js.map +1 -0
  40. package/dist/{manager/resident/learning.d.ts → learning/skill.d.ts} +29 -29
  41. package/dist/learning/skill.d.ts.map +1 -0
  42. package/dist/{manager/resident/learning.js → learning/skill.js} +27 -27
  43. package/dist/learning/skill.js.map +1 -0
  44. package/dist/{manager/resident/learning-store.d.ts → learning/store.d.ts} +38 -31
  45. package/dist/learning/store.d.ts.map +1 -0
  46. package/dist/{manager/resident/learning-store.js → learning/store.js} +52 -20
  47. package/dist/learning/store.js.map +1 -0
  48. package/dist/manager/resident/activity.d.ts +30 -6
  49. package/dist/manager/resident/activity.d.ts.map +1 -1
  50. package/dist/manager/resident/activity.js.map +1 -1
  51. package/dist/manager/resident/agenda.d.ts +42 -12
  52. package/dist/manager/resident/agenda.d.ts.map +1 -1
  53. package/dist/manager/resident/agenda.js +9 -7
  54. package/dist/manager/resident/agenda.js.map +1 -1
  55. package/dist/manager/resident/consumption.d.ts +27 -5
  56. package/dist/manager/resident/consumption.d.ts.map +1 -1
  57. package/dist/manager/resident/consumption.js +2 -0
  58. package/dist/manager/resident/consumption.js.map +1 -1
  59. package/dist/manager/resident/delivery-window.d.ts +7 -1
  60. package/dist/manager/resident/delivery-window.d.ts.map +1 -1
  61. package/dist/manager/resident/delivery-window.js +2 -0
  62. package/dist/manager/resident/delivery-window.js.map +1 -1
  63. package/dist/manager/resident/evidence-recall.d.ts +7 -1
  64. package/dist/manager/resident/evidence-recall.d.ts.map +1 -1
  65. package/dist/manager/resident/evidence-recall.js +2 -0
  66. package/dist/manager/resident/evidence-recall.js.map +1 -1
  67. package/dist/manager/resident/history.d.ts +45 -9
  68. package/dist/manager/resident/history.d.ts.map +1 -1
  69. package/dist/manager/resident/history.js.map +1 -1
  70. package/dist/manager/resident/host.d.ts +44 -10
  71. package/dist/manager/resident/host.d.ts.map +1 -1
  72. package/dist/manager/resident/host.js +4 -2
  73. package/dist/manager/resident/host.js.map +1 -1
  74. package/dist/manager/resident/initiative.d.ts +32 -6
  75. package/dist/manager/resident/initiative.d.ts.map +1 -1
  76. package/dist/manager/resident/initiative.js +2 -0
  77. package/dist/manager/resident/initiative.js.map +1 -1
  78. package/dist/manager/resident/loop.d.ts +22 -4
  79. package/dist/manager/resident/loop.d.ts.map +1 -1
  80. package/dist/manager/resident/loop.js +7 -1
  81. package/dist/manager/resident/loop.js.map +1 -1
  82. package/dist/manager/resident/outbox.d.ts +42 -8
  83. package/dist/manager/resident/outbox.d.ts.map +1 -1
  84. package/dist/manager/resident/outbox.js +2 -0
  85. package/dist/manager/resident/outbox.js.map +1 -1
  86. package/dist/manager/resident/proposal.d.ts +17 -3
  87. package/dist/manager/resident/proposal.d.ts.map +1 -1
  88. package/dist/manager/resident/proposal.js +2 -0
  89. package/dist/manager/resident/proposal.js.map +1 -1
  90. package/dist/manager/resident/store.d.ts +27 -5
  91. package/dist/manager/resident/store.d.ts.map +1 -1
  92. package/dist/manager/resident/store.js +7 -1
  93. package/dist/manager/resident/store.js.map +1 -1
  94. package/dist/manager/resident/tool-evidence.d.ts +45 -9
  95. package/dist/manager/resident/tool-evidence.d.ts.map +1 -1
  96. package/dist/manager/resident/tool-evidence.js +5 -1
  97. package/dist/manager/resident/tool-evidence.js.map +1 -1
  98. package/dist/prompt/resident-learning.d.ts +12 -2
  99. package/dist/prompt/resident-learning.d.ts.map +1 -1
  100. package/dist/prompt/resident-learning.js +6 -5
  101. package/dist/prompt/resident-learning.js.map +1 -1
  102. package/dist/prompt/resident-step.d.ts +10 -4
  103. package/dist/prompt/resident-step.d.ts.map +1 -1
  104. package/dist/prompt/resident-step.js +6 -4
  105. package/dist/prompt/resident-step.js.map +1 -1
  106. package/dist/public-runtime.d.ts +9 -3
  107. package/dist/public-runtime.d.ts.map +1 -1
  108. package/dist/public-runtime.js +10 -3
  109. package/dist/public-runtime.js.map +1 -1
  110. package/dist/public-types.d.ts +9 -4
  111. package/dist/public-types.d.ts.map +1 -1
  112. package/dist/runtime/query/file-evidence-context.d.ts +4 -0
  113. package/dist/runtime/query/file-evidence-context.d.ts.map +1 -1
  114. package/dist/runtime/query/file-evidence-context.js +5 -1
  115. package/dist/runtime/query/file-evidence-context.js.map +1 -1
  116. package/dist/runtime/query/iteration/index.d.ts +2 -0
  117. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  118. package/dist/runtime/query/iteration/index.js +19 -11
  119. package/dist/runtime/query/iteration/index.js.map +1 -1
  120. package/dist/runtime/query/iteration/phases/per-call-review.d.ts +9 -0
  121. package/dist/runtime/query/iteration/phases/per-call-review.d.ts.map +1 -1
  122. package/dist/runtime/query/iteration/phases/per-call-review.js +19 -0
  123. package/dist/runtime/query/iteration/phases/per-call-review.js.map +1 -1
  124. package/dist/runtime/query/iteration/phases/tool-review.d.ts.map +1 -1
  125. package/dist/runtime/query/iteration/phases/tool-review.js +4 -1
  126. package/dist/runtime/query/iteration/phases/tool-review.js.map +1 -1
  127. package/dist/runtime/query/iteration/step-context.d.ts +42 -0
  128. package/dist/runtime/query/iteration/step-context.d.ts.map +1 -1
  129. package/dist/runtime/query/iteration/step-context.js +114 -0
  130. package/dist/runtime/query/iteration/step-context.js.map +1 -1
  131. package/dist/runtime/query/iteration/step-shaping.d.ts +9 -1
  132. package/dist/runtime/query/iteration/step-shaping.d.ts.map +1 -1
  133. package/dist/runtime/query/iteration/step-shaping.js +21 -11
  134. package/dist/runtime/query/iteration/step-shaping.js.map +1 -1
  135. package/dist/runtime/query/steering.d.ts +13 -0
  136. package/dist/runtime/query/steering.d.ts.map +1 -1
  137. package/dist/runtime/query/steering.js +42 -0
  138. package/dist/runtime/query/steering.js.map +1 -1
  139. package/dist/scheduler/completion-inbox.js +1 -1
  140. package/dist/scheduler/completion-inbox.js.map +1 -1
  141. package/dist/tools/builtins/bash.d.ts +3 -5
  142. package/dist/tools/builtins/bash.d.ts.map +1 -1
  143. package/dist/tools/builtins/bash.js +14 -15
  144. package/dist/tools/builtins/bash.js.map +1 -1
  145. package/dist/tools/builtins/edit.js +1 -1
  146. package/dist/tools/builtins/edit.js.map +1 -1
  147. package/dist/tools/env-scrub.d.ts +8 -0
  148. package/dist/tools/env-scrub.d.ts.map +1 -1
  149. package/dist/tools/env-scrub.js +13 -0
  150. package/dist/tools/env-scrub.js.map +1 -1
  151. package/dist/tools/resident-history.d.ts +2 -0
  152. package/dist/tools/resident-history.d.ts.map +1 -1
  153. package/dist/tools/resident-history.js +2 -0
  154. package/dist/tools/resident-history.js.map +1 -1
  155. package/dist/tools/resident-tool-evidence.d.ts +5 -1
  156. package/dist/tools/resident-tool-evidence.d.ts.map +1 -1
  157. package/dist/tools/resident-tool-evidence.js +5 -1
  158. package/dist/tools/resident-tool-evidence.js.map +1 -1
  159. package/dist/turn/evidence-recall.d.ts +15 -3
  160. package/dist/turn/evidence-recall.d.ts.map +1 -1
  161. package/dist/turn/evidence-recall.js +116 -27
  162. package/dist/turn/evidence-recall.js.map +1 -1
  163. package/dist/types/message/notice-frame.d.ts +1 -1
  164. package/dist/types/message/notice-frame.d.ts.map +1 -1
  165. package/dist/types/message/notice-frame.js +1 -0
  166. package/dist/types/message/notice-frame.js.map +1 -1
  167. package/package.json +1 -1
  168. package/src/compaction/serializer.ts +29 -17
  169. package/src/compaction/verifier.ts +3 -2
  170. package/src/constants/compaction/index.ts +3 -1
  171. package/src/{manager/resident/learning-cycle.ts → learning/cycle.ts} +130 -78
  172. package/src/learning/deprecated.ts +124 -0
  173. package/src/learning/experience-store.ts +202 -0
  174. package/src/learning/experience.ts +238 -0
  175. package/src/learning/generator.ts +388 -0
  176. package/src/learning/leak-tripwire.ts +280 -0
  177. package/src/{manager/resident/learning-observation.ts → learning/observation.ts} +5 -5
  178. package/src/{manager/resident/learning.ts → learning/skill.ts} +64 -67
  179. package/src/{manager/resident/learning-store.ts → learning/store.ts} +97 -67
  180. package/src/manager/resident/activity.ts +30 -6
  181. package/src/manager/resident/agenda.ts +63 -33
  182. package/src/manager/resident/consumption.ts +27 -5
  183. package/src/manager/resident/delivery-window.ts +7 -1
  184. package/src/manager/resident/evidence-recall.ts +7 -1
  185. package/src/manager/resident/history.ts +45 -9
  186. package/src/manager/resident/host.ts +45 -11
  187. package/src/manager/resident/initiative.ts +32 -6
  188. package/src/manager/resident/loop.ts +22 -4
  189. package/src/manager/resident/outbox.ts +42 -8
  190. package/src/manager/resident/proposal.ts +17 -3
  191. package/src/manager/resident/store.ts +27 -5
  192. package/src/manager/resident/tool-evidence.ts +45 -9
  193. package/src/prompt/resident-learning.ts +16 -7
  194. package/src/prompt/resident-step.ts +19 -13
  195. package/src/public-runtime.ts +35 -5
  196. package/src/public-types.ts +96 -34
  197. package/src/runtime/query/file-evidence-context.ts +8 -1
  198. package/src/runtime/query/iteration/index.ts +27 -13
  199. package/src/runtime/query/iteration/phases/per-call-review.ts +19 -0
  200. package/src/runtime/query/iteration/phases/tool-review.ts +3 -0
  201. package/src/runtime/query/iteration/step-context.ts +138 -0
  202. package/src/runtime/query/iteration/step-shaping.ts +39 -13
  203. package/src/runtime/query/steering.ts +51 -0
  204. package/src/scheduler/completion-inbox.ts +1 -1
  205. package/src/tools/builtins/bash.ts +14 -14
  206. package/src/tools/builtins/edit.ts +1 -1
  207. package/src/tools/env-scrub.ts +13 -0
  208. package/src/tools/resident-history.ts +2 -0
  209. package/src/tools/resident-tool-evidence.ts +5 -1
  210. package/src/turn/evidence-recall.ts +125 -30
  211. package/src/types/message/notice-frame.ts +3 -1
  212. package/dist/manager/resident/learning-cycle.d.ts.map +0 -1
  213. package/dist/manager/resident/learning-cycle.js.map +0 -1
  214. package/dist/manager/resident/learning-observation.d.ts.map +0 -1
  215. package/dist/manager/resident/learning-observation.js.map +0 -1
  216. package/dist/manager/resident/learning-store.d.ts.map +0 -1
  217. package/dist/manager/resident/learning-store.js.map +0 -1
  218. package/dist/manager/resident/learning.d.ts.map +0 -1
  219. package/dist/manager/resident/learning.js.map +0 -1
@@ -29,6 +29,25 @@ const NOT_THIS_QUESTION =
29
29
  'That answer was not an answer to a question about a tool call, so this call was not run.'
30
30
 
31
31
  /** The audit trail keeps a person's reason, cut short and without control characters. */
32
+ /**
33
+ * Whether a tool result is the answer to a call that never ran because the
34
+ * review did not let it: the person's No, a refusal that ended the turn, or a
35
+ * question that went unanswered. Such a result records a choice or an
36
+ * interruption, not a tool that failed, so a host counting failures leaves it
37
+ * out. Policy refusals and failed runs are not matched.
38
+ */
39
+ export function isReviewRefusalResult(output: string): boolean {
40
+ if (!output.startsWith('Error: Tool "')) return false
41
+ return [
42
+ DECLINED_TOOL_CALL_FEEDBACK,
43
+ REFUSED_WITH_THE_TURN,
44
+ UNANSWERED_CALL,
45
+ EDIT_NOT_OFFERED,
46
+ PAUSED_CALL,
47
+ NOT_THIS_QUESTION,
48
+ ].some((reason) => output.endsWith(` was not executed. ${reason}`) || output.endsWith(reason))
49
+ }
50
+
32
51
  const AUDIT_NOTE_MAX_CHARS = 500
33
52
 
34
53
  /**
@@ -28,6 +28,7 @@ import {
28
28
  formatMonitorNote,
29
29
  takeNoticeMessage,
30
30
  takeSteeringMessage,
31
+ takeWithheldEnvNotice,
31
32
  } from '../../steering.js'
32
33
  import { type IterationContext, awaitDecisionDurably } from './context.js'
33
34
  import { PerCallReview } from './per-call-review.js'
@@ -295,6 +296,8 @@ export async function* runToolReview(
295
296
  },
296
297
  )
297
298
  if (monitorNotice) ctx.recorder.pushMessage(monitorNotice)
299
+ const credentialNotice = takeWithheldEnvNotice(ctx.recorder.messages, batch.observations)
300
+ if (credentialNotice) ctx.recorder.pushMessage(credentialNotice)
298
301
  if (jobNoticeDelivered) ctx.onJobNoticeDelivered?.()
299
302
  if (monitorNoticeDelivered) ctx.onMonitorNoticeDelivered?.()
300
303
  // The complete tool-result batch is already in history before host policy
@@ -44,3 +44,141 @@ export function placeBeforeLatestOperatorMessage(history: Message[], blocks: rea
44
44
  }
45
45
  history.splice(at, 0, ...blocks)
46
46
  }
47
+
48
+ interface PlacedBlock {
49
+ readonly text: string
50
+ /** The operator message this block precedes, or the history message it follows. */
51
+ at: { readonly before: Message } | { readonly after: Message | undefined }
52
+ }
53
+
54
+ function textOf(message: Message): string {
55
+ return typeof message.content === 'string' ? message.content : JSON.stringify(message.content)
56
+ }
57
+
58
+ /** The operator message the model is about to answer, when there is one. */
59
+ function pendingOperatorMessage(history: readonly Message[]): Message | undefined {
60
+ for (let i = history.length - 1; i >= 0; i--) {
61
+ const message = history[i] as Message
62
+ if (isOperatorUserMessage(message)) return message
63
+ if (message.role === 'assistant' || message.role === 'tool') return undefined
64
+ }
65
+ return undefined
66
+ }
67
+
68
+ /** A block whose own text says that a later one replaces it, under the name of what it reports. */
69
+ export interface ReplaceableBlock {
70
+ readonly key: string
71
+ readonly message: Message
72
+ }
73
+
74
+ /** How many superseded versions of a replaceable block still go with the latest. */
75
+ export const EARLIER_VERSIONS_KEPT = 2
76
+
77
+ interface VersionedBlock extends PlacedBlock {
78
+ readonly message: Message
79
+ }
80
+
81
+ /**
82
+ * Where a turn's request-only data sits, so it stays put from call to call.
83
+ *
84
+ * What a block says is the step's own business: a block it offers again with
85
+ * the same text is the same block, and it keeps the place it first had; a
86
+ * block it no longer offers is withdrawn, so corrected data never lingers.
87
+ * A block seen for the first time goes before the operator's message when that
88
+ * message is what the model is answering, and otherwise after the history of
89
+ * that moment. So the requests of a turn share their opening up to the first
90
+ * block that changed, and a caching driver keeps reading the same prefix
91
+ * instead of re-reading the turn from the operator's message on.
92
+ *
93
+ * A block that says of itself that a later one replaces it (the file
94
+ * evidence) is never taken back. When its text changes, the earlier text stays
95
+ * where it went and the new text follows the history of that moment, which is
96
+ * how the block itself tells the model to read it; when it stops holding, a
97
+ * short block says so. Only the latest version and the last
98
+ * `EARLIER_VERSIONS_KEPT` before it are carried, and an earlier version whose
99
+ * place a compaction rewrote is dropped, so the stale text stays small. Until
100
+ * a version is dropped no request of the turn rewrites the one before it.
101
+ *
102
+ * One carry per turn. A position whose message has left the history (a
103
+ * compaction rewrote it) is given up and the block follows the history.
104
+ */
105
+ export class TurnContextCarry {
106
+ private placed = new Map<string, PlacedBlock>()
107
+ private readonly versions = new Map<string, VersionedBlock[]>()
108
+
109
+ /**
110
+ * Put `blocks` and the `replaceable` ones into `request`, the projected copy
111
+ * of `history` (same length and order). `end` is the last message of the
112
+ * turn's own history. `retractions` says, per replaceable key, what to
113
+ * write when an earlier block of that key no longer holds.
114
+ */
115
+ place(
116
+ request: Message[],
117
+ history: readonly Message[],
118
+ end: Message | undefined,
119
+ blocks: readonly Message[],
120
+ replaceable: readonly ReplaceableBlock[] = [],
121
+ retractions: Readonly<Record<string, Message>> = {},
122
+ ): void {
123
+ const index = (message: Message | undefined) =>
124
+ message ? history.lastIndexOf(message) : history.length - 1
125
+ const operator = pendingOperatorMessage(history)
126
+ const positionOf = (slot: PlacedBlock) =>
127
+ 'before' in slot.at ? index(slot.at.before) : index(slot.at.after) + 1
128
+ const anchorOf = (slot: PlacedBlock) => ('before' in slot.at ? slot.at.before : slot.at.after)
129
+ const next = new Map<string, PlacedBlock>()
130
+ const inserts: { at: number; message: Message }[] = []
131
+ for (const message of blocks) {
132
+ const text = textOf(message)
133
+ if (next.has(text)) continue
134
+ let slot: PlacedBlock | undefined = this.placed.get(text)
135
+ if (slot) {
136
+ const anchor = anchorOf(slot)
137
+ if (anchor && index(anchor) < 0) slot = { text, at: { after: end } }
138
+ } else {
139
+ slot = { text, at: operator ? { before: operator } : { after: end } }
140
+ }
141
+ next.set(text, slot)
142
+ inserts.push({ at: positionOf(slot), message })
143
+ }
144
+ this.placed = next
145
+
146
+ const offered = new Map(replaceable.map((block) => [block.key, block.message]))
147
+ for (const key of new Set([...this.versions.keys(), ...offered.keys()])) {
148
+ const held = this.versions.get(key) ?? []
149
+ const wanted = offered.get(key) ?? retractions[key]
150
+ const last = held.at(-1)
151
+ if (wanted !== undefined && (last === undefined || last.text !== textOf(wanted)))
152
+ held.push({
153
+ text: textOf(wanted),
154
+ message: wanted,
155
+ // The first version goes where the other blocks go; a later one
156
+ // follows the history, after the one it replaces.
157
+ at: last === undefined && operator ? { before: operator } : { after: end },
158
+ })
159
+ // An earlier version whose place a compaction rewrote is given up: the
160
+ // request is rewritten at that point anyway, and stacking stale copies
161
+ // at the end would only add bulk. The latest version is never given up.
162
+ const kept = held.filter((version, i) => {
163
+ if (i === held.length - 1) return true
164
+ const anchor = anchorOf(version)
165
+ return !anchor || index(anchor) >= 0
166
+ })
167
+ // Only the latest and a few earlier versions are carried, so a long turn
168
+ // does not grow by a full copy per change.
169
+ kept.splice(0, Math.max(0, kept.length - (EARLIER_VERSIONS_KEPT + 1)))
170
+ this.versions.set(key, kept)
171
+ for (const version of kept) {
172
+ const anchor = anchorOf(version)
173
+ if (anchor && index(anchor) < 0) version.at = { after: end }
174
+ inserts.push({ at: positionOf(version), message: version.message })
175
+ }
176
+ }
177
+
178
+ // Back to front, so an earlier position is not shifted by a later one;
179
+ // blocks at one position keep the order they were offered in.
180
+ const order = inserts.map((entry, position) => ({ ...entry, position }))
181
+ order.sort((a, b) => b.at - a.at || b.position - a.position)
182
+ for (const entry of order) request.splice(entry.at, 0, entry.message)
183
+ }
184
+ }
@@ -20,7 +20,11 @@ import { toErrorMessage } from '../../../utils/error.js'
20
20
  import { createCallbackInference } from '../callback-inference.js'
21
21
  import { measureContext } from './phases/compaction.js'
22
22
  import type { IterationContext } from './phases/index.js'
23
- import { placeBeforeLatestOperatorMessage, stepContextMessage } from './step-context.js'
23
+ import {
24
+ type ReplaceableBlock,
25
+ placeBeforeLatestOperatorMessage,
26
+ stepContextMessage,
27
+ } from './step-context.js'
24
28
 
25
29
  /**
26
30
  * How the next request is shaped: admission, preparation, and the context
@@ -51,28 +55,50 @@ export function appendWorkContext(
51
55
  stepNumber: number,
52
56
  prepared: PrepareStepResult,
53
57
  ): void {
58
+ // Per-turn data goes in front of the operator's new message while that is
59
+ // what the model is answering, like the other blocks of request-only data.
60
+ placeBeforeLatestOperatorMessage(
61
+ messages,
62
+ workContextBlocks(shaping, messages, stepNumber, prepared).map((block) => block.message),
63
+ )
64
+ }
65
+
66
+ /** What the two work-context blocks report; each tells the model a later one replaces it. */
67
+ export const OWNED_WORK_KEY = 'owned-work'
68
+ export const FILE_EVIDENCE_KEY = 'file-evidence'
69
+
70
+ /**
71
+ * The work-context blocks for a request whose projected messages are
72
+ * `messages`, each under the name of what it reports.
73
+ */
74
+ export function workContextBlocks(
75
+ shaping: StepShaping,
76
+ messages: readonly Message[],
77
+ stepNumber: number,
78
+ prepared: PrepareStepResult,
79
+ ): ReplaceableBlock[] {
54
80
  const { ctx } = shaping
55
81
 
56
- const contributions = [
57
- ctx.completionInbox?.describeOwnedWork(),
58
- ctx.toolExecutor.describeFileEvidence(messages),
59
- ].filter((content): content is string => Boolean(content))
60
- if (contributions.length === 0) return
61
- const placed: Message[] = []
82
+ const contributions = (
83
+ [
84
+ [OWNED_WORK_KEY, ctx.completionInbox?.describeOwnedWork()],
85
+ [FILE_EVIDENCE_KEY, ctx.toolExecutor.describeFileEvidence(messages)],
86
+ ] as [string, string | undefined][]
87
+ ).filter((entry): entry is [string, string] => Boolean(entry[1]))
88
+ if (contributions.length === 0) return []
89
+ const placed: ReplaceableBlock[] = []
62
90
  let room = stepContext(shaping, stepNumber, prepared).contextBudget?.remainingTokens ?? 0
63
91
  // Leave room for the actual task; admit whole contributions, never dangling partial references.
64
- if (room < 1_500) return
65
- for (const content of contributions) {
92
+ if (room < 1_500) return []
93
+ for (const [key, content] of contributions) {
66
94
  if (!content || content.length > 8_000) continue
67
95
  const message = stepContextMessage(content)
68
96
  const tokens = estimateMessageTokens(message)
69
97
  if (tokens > Math.min(2_000, room - 1_000)) continue
70
- placed.push(message)
98
+ placed.push({ key, message })
71
99
  room -= tokens
72
100
  }
73
- // Per-turn data goes in front of the operator's new message while that is
74
- // what the model is answering, like the other blocks of request-only data.
75
- placeBeforeLatestOperatorMessage(messages, placed)
101
+ return placed
76
102
  }
77
103
 
78
104
  export function stepContext(
@@ -1,3 +1,4 @@
1
+ import { withheldEnvNoticeText } from '../../tools/env-scrub.js'
1
2
  import { createRuntimeContextMessage } from '../../types/message/index.js'
2
3
  import type { Message, UserMessage } from '../../types/message/index.js'
3
4
  import { NOTICE_TAG, frameNotice, readOperatorNotice } from '../../types/message/notice-frame.js'
@@ -377,3 +378,53 @@ export function attachSteering(
377
378
  onDelivered?.(guidance)
378
379
  return next
379
380
  }
381
+
382
+ /**
383
+ * The count of credential-shaped variables a settled batch's shell results
384
+ * report as removed, or 0. The count rides in a result's `data`, never in the
385
+ * text the model reads.
386
+ */
387
+ function withheldEnvCount(
388
+ observations: readonly { readonly result: { readonly data?: unknown } }[],
389
+ ) {
390
+ let count = 0
391
+ for (const { result } of observations) {
392
+ const data = result.data
393
+ if (typeof data !== 'object' || data === null) continue
394
+ const n = (data as { withheldEnv?: unknown }).withheldEnv
395
+ if (typeof n === 'number' && Number.isInteger(n) && n > count) count = n
396
+ }
397
+ return count
398
+ }
399
+
400
+ /**
401
+ * The credential notice for a settled tool batch, as a user-role message of its
402
+ * own, or `undefined` when the batch removed nothing or the conversation
403
+ * already carries this very notice. It is sent once, and again only when the
404
+ * number of removed variables changes, so a run of failing commands does not
405
+ * repeat it. The conversation is the record, so a resumed or compacted one
406
+ * needs no counter of its own.
407
+ */
408
+ export function takeWithheldEnvNotice(
409
+ history: readonly Message[],
410
+ observations: readonly { readonly result: { readonly data?: unknown } }[],
411
+ ): UserMessage | undefined {
412
+ const count = withheldEnvCount(observations)
413
+ if (count === 0) return undefined
414
+ const body = withheldEnvNoticeText(count)
415
+ const said = history.some(
416
+ (message) =>
417
+ message.role === 'user' &&
418
+ message.source?.type === 'runtime-context' &&
419
+ message.source.kind === 'reminder' &&
420
+ typeof message.content === 'string' &&
421
+ message.content.startsWith(`<${NOTICE_TAG}-`) &&
422
+ message.content.includes(' kind="credentials">') &&
423
+ message.content.includes(body),
424
+ )
425
+ if (said) return undefined
426
+ return createRuntimeContextMessage(
427
+ frameNotice('credentials', '[Command environment]', body),
428
+ 'reminder',
429
+ )
430
+ }
@@ -479,7 +479,7 @@ export class CompletionInbox {
479
479
  stillRunningNotShown > 0
480
480
  ? ` ${shownRunning.length} running tasks are shown below, and ${stillRunningNotShown} more still running.`
481
481
  : ''
482
- return `Owned delegated work (current scheduler observations).${overflow} Result delivery is not proof that you answered the original request. Answer the latest operator message and complete the requested synthesis of available results unless the operator cancelled or changed that request. Reuse delivered results when sufficient; do not relaunch completed work. If a delivered result is no longer visible, retrieve it by task ID. A terminal state with a non-end_turn stop reason is not successful task completion. Do not repeat a synthesis already given.\n${JSON.stringify({ tasks, omitted: this.ours.size - ids.length })}`
482
+ return `Owned delegated work (current scheduler observations; a later block with this heading replaces this one).${overflow} Result delivery is not proof that you answered the original request. Answer the latest operator message and complete the requested synthesis of available results unless the operator cancelled or changed that request. Reuse delivered results when sufficient; do not relaunch completed work. If a delivered result is no longer visible, retrieve it by task ID. A terminal state with a non-end_turn stop reason is not successful task completion. Do not repeat a synthesis already given.\n${JSON.stringify({ tasks, omitted: this.ours.size - ids.length })}`
483
483
  }
484
484
 
485
485
  /**
@@ -20,7 +20,7 @@ import {
20
20
  withoutBashStartup,
21
21
  } from '../command-shell.js'
22
22
  import { defineTool } from '../defineTool.js'
23
- import { scrubInheritedEnv } from '../env-scrub.js'
23
+ import { scrubInheritedEnv, withheldEnvNoticeText } from '../env-scrub.js'
24
24
  import { createHostOutputDecoder } from '../host-output.js'
25
25
  import { looksBinary, windowText } from '../text-window.js'
26
26
  import { judgeExit } from './exit-semantics.js'
@@ -802,10 +802,10 @@ const BashToolBase = defineTool({
802
802
  failure.stdoutTruncated ? 'stdout' : '',
803
803
  failure.stderrTruncated ? 'stderr' : '',
804
804
  ].filter(Boolean)
805
- // Only on the failure path. A successful command did not need to know,
806
- // and appending this to every result would make the common case noisy
807
- // to buy nothing. A failing one is exactly where "authentication
808
- // failed" has to be distinguishable from "the variable was withheld".
805
+ // Only the failure path reports removed variables (in `data`, for the
806
+ // kernel's notice). A successful command did not need to know; a failing
807
+ // one is where "authentication failed" has to be distinguishable from
808
+ // "the variable was withheld".
809
809
  // A status the command uses to answer (no match, files differ) is a
810
810
  // result, not a failure; the model sees it as an ordinary call.
811
811
  const judged =
@@ -816,7 +816,6 @@ const BashToolBase = defineTool({
816
816
  clipped.length > 0
817
817
  ? `[${clipped.join(' and ')} ${clipped.length === 1 ? 'was' : 'were'} truncated by the host output cap. The omitted output is unavailable in this result.]`
818
818
  : '',
819
- answered ? '' : describeWithheldEnv(inherited.dropped),
820
819
  ]
821
820
  .filter(Boolean)
822
821
  .join('\n\n')
@@ -827,6 +826,11 @@ const BashToolBase = defineTool({
827
826
  data: {
828
827
  ...(exitCode !== undefined ? { exitCode } : {}),
829
828
  ...(judged?.note ? { exitNote: judged.note } : {}),
829
+ // How many variables were removed, for the kernel's own notice; the
830
+ // model's result stays what the command printed.
831
+ ...(!answered && inherited.dropped.length > 0
832
+ ? { withheldEnv: inherited.dropped.length }
833
+ : {}),
830
834
  ...(leaveSandbox ? { sandboxed: false, sandboxEscape: true } : {}),
831
835
  timedOut,
832
836
  stdoutTruncated: failure.stdoutTruncated ?? false,
@@ -904,16 +908,12 @@ function formatShellOutput(stdout: string | undefined, stderr: string | undefine
904
908
  }
905
909
 
906
910
  /**
907
- * Say that credential-shaped variables were left out of this command's
908
- * environment. The count is enough: the model needs to know a missing
909
- * credential is deliberate and who to ask, and the names are the user's data.
910
- * A command that failed because it wanted one otherwise reports an
911
- * authentication error pointing nowhere.
911
+ * The notice text for the credential-shaped variables left out of a command's
912
+ * environment (see {@link withheldEnvNoticeText}). It is sent as a notice after
913
+ * the tool batch, never written into the command's result.
912
914
  */
913
915
  export function describeWithheldEnv(dropped: readonly string[]): string {
914
- if (dropped.length === 0) return ''
915
- const one = dropped.length === 1
916
- return `NOTE: ${dropped.length} credential-shaped environment ${one ? 'variable was' : 'variables were'} removed from this command's environment, so ${one ? 'it reads' : 'they read'} as unset. Ask the user if the command needs ${one ? 'it' : 'one'}.`
916
+ return withheldEnvNoticeText(dropped.length)
917
917
  }
918
918
 
919
919
  /**
@@ -428,7 +428,7 @@ function refusedEdit(result: EditApplyFailure, path: string) {
428
428
  export const EditTool = defineTool({
429
429
  name: 'edit',
430
430
  description:
431
- 'Makes targeted edits to a file using exact string find-and-replace or line insertion. To change an existing file, use `edit`: `write` overwrites the whole body and discards earlier work on partial failure, while `edit` keeps the rest of the file byte-for-byte intact and is recoverable: if a single edit fails (old_string/oldStr ambiguous, broader restructuring needed), follow up with another `edit` instead of re-emitting the entire file via `write`. The old_string/oldStr must be unique in the file unless replace_all is true. For insertions, pass insertLine plus new_string/newStr; use insertLine: "end" to extend a file at the end. Self-budget new_string/newStr under 12000 characters before emitting the tool call; use repeated bounded edits for long sections. Preserves file formatting and indentation. To make several changes to one file that only make sense together, send them as `edits`: a list of {old_string, new_string} applied in order and committed as one write — if any of them does not apply, nothing is written and the reply names which one, so the file is never left half-changed.',
431
+ 'Makes targeted edits to a file using exact string find-and-replace or line insertion. To change an existing file, use `edit`: `write` overwrites the whole body and discards earlier work on partial failure, while `edit` keeps the rest of the file byte-for-byte intact and is recoverable: if a single edit fails (old_string/oldStr ambiguous, broader restructuring needed), follow up with another `edit` instead of re-emitting the entire file via `write`. The old_string/oldStr must be unique in the file unless replace_all is true. For insertions, pass insertLine plus new_string/newStr; use insertLine: "end" to extend a file at the end. Keep new_string/newStr under 12000 characters, because a longer payload can be cut off mid-call and fail; use repeated bounded edits for long sections. Preserves file formatting and indentation. To make several changes to one file that only make sense together, send them as `edits`: a list of {old_string, new_string} applied in order and committed as one write — if any of them does not apply, nothing is written and the reply names which one, so the file is never left half-changed.',
432
432
  inputSchema,
433
433
  modelInputSchema,
434
434
  enforceModelInput: true,
@@ -91,3 +91,16 @@ export function scrubInheritedEnv(source: NodeJS.ProcessEnv = process.env): Scru
91
91
  dropped.sort()
92
92
  return { env, dropped }
93
93
  }
94
+
95
+ /**
96
+ * What the model is told, once, when shell commands run with credential-shaped
97
+ * variables removed. The count is enough: the model needs to know a missing
98
+ * credential is deliberate and who to ask, and the names are the user's data.
99
+ * The kernel sends this as a notice of its own after the tool batch; a command's
100
+ * result carries only what the command printed.
101
+ */
102
+ export function withheldEnvNoticeText(count: number): string {
103
+ if (count <= 0) return ''
104
+ const one = count === 1
105
+ return `A shell command that just ran had ${count} credential-shaped environment ${one ? 'variable' : 'variables'} removed from its environment, so ${one ? 'it reads' : 'they read'} as unset. When a failure looks like a missing credential, ask the user to provide ${one ? 'it' : 'one'}, because the user decides what a command may see.`
106
+ }
@@ -8,6 +8,8 @@ import { defineTool } from './defineTool.js'
8
8
  * Read-only tools over an explicitly authorized historical source. The host
9
9
  * resolves and checks the executing turn's scope; models choose no paths,
10
10
  * tenant, resident, pursuit or upper-history boundary.
11
+ *
12
+ * @deprecated Residents are deprecated and removed in the next major release. Learning (`runLearningCycle` and the other `Learning*` names) keeps working, but `runLearningCycle` still takes a resident agenda until learning gets its own activation target in the next major release; for long-running work use scheduled tasks (`LocalTaskScheduler`), session goals and background agents.
11
13
  */
12
14
  export function buildResidentHistoryTools(
13
15
  resolveSource: (context: ToolContext) => ResidentHistorySource,
@@ -3,7 +3,11 @@ import type { ResidentToolEvidenceSource } from '../manager/resident/tool-eviden
3
3
  import type { ToolContext, ToolDefinition } from '../types/tool/index.js'
4
4
  import { defineTool } from './defineTool.js'
5
5
 
6
- /** @experimental The host must authorize each executing turn before returning its bound source. */
6
+ /**
7
+ * @experimental The host must authorize each executing turn before returning its bound source.
8
+ *
9
+ * @deprecated Residents are deprecated and removed in the next major release. Learning (`runLearningCycle` and the other `Learning*` names) keeps working, but `runLearningCycle` still takes a resident agenda until learning gets its own activation target in the next major release; for long-running work use scheduled tasks (`LocalTaskScheduler`), session goals and background agents.
10
+ */
7
11
  export function buildResidentToolEvidenceTools(
8
12
  resolveSource: (context: ToolContext) => ResidentToolEvidenceSource,
9
13
  ): ToolDefinition[] {