@namzu/sdk 7.0.0 → 8.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 (128) hide show
  1. package/CHANGELOG.md +437 -0
  2. package/dist/agents/SupervisorAgent.d.ts.map +1 -1
  3. package/dist/agents/SupervisorAgent.js +172 -162
  4. package/dist/agents/SupervisorAgent.js.map +1 -1
  5. package/dist/agents/__tests__/supervisor-inbox-scope.test.d.ts +2 -0
  6. package/dist/agents/__tests__/supervisor-inbox-scope.test.d.ts.map +1 -0
  7. package/dist/agents/__tests__/supervisor-inbox-scope.test.js +125 -0
  8. package/dist/agents/__tests__/supervisor-inbox-scope.test.js.map +1 -0
  9. package/dist/bridge/a2a/mapper.d.ts.map +1 -1
  10. package/dist/bridge/a2a/mapper.js +4 -0
  11. package/dist/bridge/a2a/mapper.js.map +1 -1
  12. package/dist/bridge/sse/mapper.d.ts.map +1 -1
  13. package/dist/bridge/sse/mapper.js +24 -0
  14. package/dist/bridge/sse/mapper.js.map +1 -1
  15. package/dist/contracts/api.d.ts +1 -1
  16. package/dist/contracts/api.d.ts.map +1 -1
  17. package/dist/gateway/__tests__/completion-inbox.test.js +292 -2
  18. package/dist/gateway/__tests__/completion-inbox.test.js.map +1 -1
  19. package/dist/gateway/completion-inbox.d.ts +94 -6
  20. package/dist/gateway/completion-inbox.d.ts.map +1 -1
  21. package/dist/gateway/completion-inbox.js +235 -15
  22. package/dist/gateway/completion-inbox.js.map +1 -1
  23. package/dist/manager/run/persistence.d.ts +8 -0
  24. package/dist/manager/run/persistence.d.ts.map +1 -1
  25. package/dist/manager/run/persistence.js +12 -0
  26. package/dist/manager/run/persistence.js.map +1 -1
  27. package/dist/run/reporter.d.ts.map +1 -1
  28. package/dist/run/reporter.js +11 -0
  29. package/dist/run/reporter.js.map +1 -1
  30. package/dist/runtime/query/__tests__/completion-does-not-erase-the-answer.test.d.ts +2 -0
  31. package/dist/runtime/query/__tests__/completion-does-not-erase-the-answer.test.d.ts.map +1 -0
  32. package/dist/runtime/query/__tests__/completion-does-not-erase-the-answer.test.js +142 -0
  33. package/dist/runtime/query/__tests__/completion-does-not-erase-the-answer.test.js.map +1 -0
  34. package/dist/runtime/query/__tests__/completion-notification.test.js +414 -32
  35. package/dist/runtime/query/__tests__/completion-notification.test.js.map +1 -1
  36. package/dist/runtime/query/__tests__/context-size-on-the-wire.test.d.ts +2 -0
  37. package/dist/runtime/query/__tests__/context-size-on-the-wire.test.d.ts.map +1 -0
  38. package/dist/runtime/query/__tests__/context-size-on-the-wire.test.js +100 -0
  39. package/dist/runtime/query/__tests__/context-size-on-the-wire.test.js.map +1 -0
  40. package/dist/runtime/query/guard.d.ts +28 -0
  41. package/dist/runtime/query/guard.d.ts.map +1 -1
  42. package/dist/runtime/query/guard.js +31 -0
  43. package/dist/runtime/query/guard.js.map +1 -1
  44. package/dist/runtime/query/iteration/__tests__/settle-grace.test.d.ts +2 -0
  45. package/dist/runtime/query/iteration/__tests__/settle-grace.test.d.ts.map +1 -0
  46. package/dist/runtime/query/iteration/__tests__/settle-grace.test.js +226 -0
  47. package/dist/runtime/query/iteration/__tests__/settle-grace.test.js.map +1 -0
  48. package/dist/runtime/query/iteration/index.d.ts +92 -0
  49. package/dist/runtime/query/iteration/index.d.ts.map +1 -1
  50. package/dist/runtime/query/iteration/index.js +814 -566
  51. package/dist/runtime/query/iteration/index.js.map +1 -1
  52. package/dist/runtime/query/iteration/phases/__tests__/compaction-declined.test.d.ts +2 -0
  53. package/dist/runtime/query/iteration/phases/__tests__/compaction-declined.test.d.ts.map +1 -0
  54. package/dist/runtime/query/iteration/phases/__tests__/compaction-declined.test.js +95 -0
  55. package/dist/runtime/query/iteration/phases/__tests__/compaction-declined.test.js.map +1 -0
  56. package/dist/runtime/query/iteration/phases/compaction.d.ts +34 -0
  57. package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
  58. package/dist/runtime/query/iteration/phases/compaction.js +61 -4
  59. package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
  60. package/dist/tools/__tests__/untrusted-envelope.test.js +16 -0
  61. package/dist/tools/__tests__/untrusted-envelope.test.js.map +1 -1
  62. package/dist/tools/coordinator/__tests__/completion-delivery.test.js +117 -0
  63. package/dist/tools/coordinator/__tests__/completion-delivery.test.js.map +1 -1
  64. package/dist/tools/coordinator/__tests__/task-list.test.js +57 -0
  65. package/dist/tools/coordinator/__tests__/task-list.test.js.map +1 -1
  66. package/dist/tools/coordinator/index.d.ts.map +1 -1
  67. package/dist/tools/coordinator/index.js +109 -11
  68. package/dist/tools/coordinator/index.js.map +1 -1
  69. package/dist/tools/untrusted-envelope.d.ts.map +1 -1
  70. package/dist/tools/untrusted-envelope.js +9 -1
  71. package/dist/tools/untrusted-envelope.js.map +1 -1
  72. package/dist/types/agent/gateway.d.ts +18 -0
  73. package/dist/types/agent/gateway.d.ts.map +1 -1
  74. package/dist/types/provider/interface.d.ts +35 -0
  75. package/dist/types/provider/interface.d.ts.map +1 -1
  76. package/dist/types/run/entity.d.ts +16 -0
  77. package/dist/types/run/entity.d.ts.map +1 -1
  78. package/dist/types/run/events.d.ts +75 -0
  79. package/dist/types/run/events.d.ts.map +1 -1
  80. package/dist/types/run/events.js.map +1 -1
  81. package/dist/types/run/prepare-step.d.ts +17 -2
  82. package/dist/types/run/prepare-step.d.ts.map +1 -1
  83. package/dist/types/verification/index.d.ts +98 -0
  84. package/dist/types/verification/index.d.ts.map +1 -1
  85. package/dist/types/verification/index.js +10 -0
  86. package/dist/types/verification/index.js.map +1 -1
  87. package/dist/verification/__tests__/argument-pattern.test.d.ts +2 -0
  88. package/dist/verification/__tests__/argument-pattern.test.d.ts.map +1 -0
  89. package/dist/verification/__tests__/argument-pattern.test.js +122 -0
  90. package/dist/verification/__tests__/argument-pattern.test.js.map +1 -0
  91. package/dist/verification/gate.d.ts.map +1 -1
  92. package/dist/verification/gate.js +44 -0
  93. package/dist/verification/gate.js.map +1 -1
  94. package/dist/verification/rules.d.ts.map +1 -1
  95. package/dist/verification/rules.js +27 -0
  96. package/dist/verification/rules.js.map +1 -1
  97. package/package.json +1 -1
  98. package/src/agents/SupervisorAgent.ts +175 -166
  99. package/src/agents/__tests__/supervisor-inbox-scope.test.ts +149 -0
  100. package/src/bridge/a2a/mapper.ts +4 -0
  101. package/src/bridge/sse/mapper.ts +25 -0
  102. package/src/contracts/api.ts +1 -0
  103. package/src/gateway/__tests__/completion-inbox.test.ts +348 -2
  104. package/src/gateway/completion-inbox.ts +248 -16
  105. package/src/manager/run/persistence.ts +12 -0
  106. package/src/run/reporter.ts +12 -0
  107. package/src/runtime/query/__tests__/completion-does-not-erase-the-answer.test.ts +163 -0
  108. package/src/runtime/query/__tests__/completion-notification.test.ts +486 -34
  109. package/src/runtime/query/__tests__/context-size-on-the-wire.test.ts +122 -0
  110. package/src/runtime/query/guard.ts +32 -0
  111. package/src/runtime/query/iteration/__tests__/settle-grace.test.ts +265 -0
  112. package/src/runtime/query/iteration/index.ts +902 -636
  113. package/src/runtime/query/iteration/phases/__tests__/compaction-declined.test.ts +124 -0
  114. package/src/runtime/query/iteration/phases/compaction.ts +83 -10
  115. package/src/tools/__tests__/untrusted-envelope.test.ts +23 -0
  116. package/src/tools/coordinator/__tests__/completion-delivery.test.ts +147 -0
  117. package/src/tools/coordinator/__tests__/task-list.test.ts +72 -0
  118. package/src/tools/coordinator/index.ts +122 -15
  119. package/src/tools/untrusted-envelope.ts +9 -1
  120. package/src/types/agent/gateway.ts +18 -0
  121. package/src/types/provider/interface.ts +39 -0
  122. package/src/types/run/entity.ts +17 -0
  123. package/src/types/run/events.ts +75 -0
  124. package/src/types/run/prepare-step.ts +17 -2
  125. package/src/types/verification/index.ts +61 -0
  126. package/src/verification/__tests__/argument-pattern.test.ts +158 -0
  127. package/src/verification/gate.ts +45 -0
  128. package/src/verification/rules.ts +28 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,442 @@
1
1
  # Changelog
2
2
 
3
+ ## 8.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - 9ac8dd4: A delegate's output is framed as untrusted material on every path the model reads it, and it can no longer end the frame early
8
+
9
+ Blocking `create_task` and `wait_for_task` wrap a worker's text in the
10
+ `<namzu-untrusted>` envelope. Two other paths carried the same bytes and did
11
+ not: the completion notification injected into the transcript, and
12
+ `agent_task_list`'s rendered output. So whether a worker's words arrived as
13
+ material or as the parent's own reasoning depended on how the model happened to
14
+ fetch them — and the two unframed paths are the ones reached when a wait was
15
+ abandoned, which is when a run is already off its expected course.
16
+
17
+ Worse, the notification's own delimiter was forgeable. Measured: worker output
18
+ containing `</task-notification>` produced two closing tags in one message, with
19
+ attacker-controlled text sitting outside the first — reading as ordinary
20
+ transcript rather than as a delegate's material.
21
+
22
+ **What changed on the wire the model sees.**
23
+
24
+ - The notification now nests a `<namzu-untrusted kind="agent-result">` block
25
+ inside `<task-notification>`. Kernel metadata (`task_id`, `agent`, `state`,
26
+ `duration_ms`) stays OUTSIDE it — framing this kernel's own statements as
27
+ untrusted would tell the model to discount the only part of the message it
28
+ can rely on — and so does the truncation notice, which is an instruction
29
+ about how to fetch the rest.
30
+ - `agent_task_list` wraps each finished task's output the same way, with the
31
+ same `agent` and `task` attributes the blocking path uses.
32
+ - Both delimiters are defanged inside worker text, case-insensitively. The
33
+ replacements (`task_notification`, `namzu_untrusted`) share no substring with
34
+ the tokens they replace — a replacement that still contains the token is found
35
+ again by a second pass or by any looser matcher downstream.
36
+ - A notification is 257 characters longer than before — measured, both for a
37
+ five-character result and for a truncated 4 kB one, so the cost is fixed
38
+ rather than proportional to the output. It grows only with the length of the
39
+ agent id and task id, which appear in the envelope's attributes.
40
+
41
+ `data.result` on both tools is unchanged, so a host reading results
42
+ programmatically is unaffected. If you match on the model-facing text of either
43
+ tool, expect the envelope.
44
+
45
+ - 9ac8dd4: A completion inbox hears only about the tasks its own run launched, and a supervisor releases the gateway it borrowed
46
+
47
+ `TaskGateway.onTaskCompleted` is a broadcast and `TaskHandle` carries no run id,
48
+ so every inbox attached to a gateway was handed every completion on it.
49
+ Measured: two inboxes on one gateway, one run launches a task, and the OTHER
50
+ run drains it — it would have been told "a task you launched has finished", a
51
+ false statement, over another run's worker output. A shared gateway is not an
52
+ abuse of the API: `SupervisorAgentConfig.gateway` takes one, and a host that
53
+ owns a gateway reuses it.
54
+
55
+ Separately, nothing ever called `CompletionInbox.close()`. Three sequential
56
+ `SupervisorAgent` runs against one host gateway left three live subscriptions,
57
+ each still holding its run's handles, and the set only grew.
58
+
59
+ **Breaking, and what to do.**
60
+
61
+ - `CompletionInbox` now ignores a completion for a task it was not told about.
62
+ If you drive `buildCoordinatorTools` there is nothing to do — `create_task`
63
+ declares every launch, blocking and background alike. If you launch tasks
64
+ some other way and expect notifications, call `inbox.launched(taskId)` after
65
+ the launch. `inbox.expect(taskId)` already implies it.
66
+ - `SupervisorAgent` closes the inbox it created when the run ends, including
67
+ when setup throws. An inbox you construct yourself is still yours to close.
68
+ - `close()` now clears what the inbox owned and claimed as well as what it
69
+ queued, so a closed inbox cannot be re-armed through a stale reference.
70
+
71
+ The ordering that would otherwise turn this into lost results is handled in
72
+ two layers. `gateway.createTask` resolves one microtask before its caller can
73
+ say who owns the task, so a worker that finishes inside that window is
74
+ announced first. An unowned announcement is therefore BUFFERED rather than
75
+ dropped, and ownership may be claimed retroactively; the buffer is bounded at
76
+ 32 entries so that on a shared gateway it cannot accumulate every other run's
77
+ worker output, and an eviction is logged at WARN so a dropped completion is
78
+ never inferable only from an absence. Where the buffer could not hold an entry,
79
+ `launched()` also asks `gateway.getTask` — an assumption that a just-settled
80
+ task is still findable, now stated on `TaskGateway.getTask` itself so a host
81
+ that cannot meet it knows it is the one paying.
82
+
83
+ - 9ac8dd4: `create_task` offers `background: true` only when there is somewhere for the result to arrive
84
+
85
+ A background launch returns a task id and tells the model its result will come
86
+ "later, as a task notification". The `CompletionInbox` is the only thing that
87
+ delivers one — it holds the run open for the outstanding worker and puts the
88
+ completion into the transcript. `buildCoordinatorTools` mounted the parameter
89
+ whether or not it was given an inbox, so a host without one had a tool
90
+ advertising a channel that did not exist. Nothing failed loudly, because the
91
+ launch itself succeeded; the result simply never arrived.
92
+
93
+ Without a `completionInbox`, `create_task` no longer declares `background` and
94
+ its description no longer mentions it. Everything else is unchanged: the
95
+ blocking path, `wait_for_task`, `cancel_task` and `agent_task_list` are all
96
+ still mounted. Pass a `completionInbox` — to `buildCoordinatorTools` **and** to
97
+ `drainQuery` — to get background launching back. `SupervisorAgent` does both
98
+ already, so a host using it sees no change.
99
+
100
+ A `background: true` that reaches `execute` some other way — a directly
101
+ constructed definition — is REFUSED, naming the missing piece, rather than
102
+ quietly turned into a blocking call: the caller asked for something that
103
+ returns immediately, and giving them a different thing is accepting work whose
104
+ stated terms cannot be met. The abandoned-wait messages on `create_task` and
105
+ `wait_for_task` no longer promise a notification either, and
106
+ `agent_task_list` stops telling the model to avoid the listing when the
107
+ listing is the only route left.
108
+
109
+ The parameter is withheld rather than refused per call, and rather than thrown
110
+ at construction. A parameter the model is never shown costs nothing; one it is shown and then
111
+ denied costs prompt-prefix tokens plus an iteration per attempt. And a throw
112
+ would break a caller doing something legitimate — an inbox-less coordinator
113
+ surface is a supported configuration. This is the same reasoning that made an
114
+ empty roster withhold `create_task` rather than refuse to build.
115
+
116
+ ### Minor Changes
117
+
118
+ - a39c2ed: A compaction pass now reports both of its outcomes.
119
+
120
+ Two gaps, in opposite directions, in the same function.
121
+
122
+ **A compaction that sheds nothing was invisible to everyone.** All three decline
123
+ paths — the reducer throws, it returns no fewer messages than it was given, or
124
+ its result splits a `tool_use` from its `tool_result` and is refused wholesale —
125
+ reached a log line and stopped there. A host that silences its logger, which
126
+ every command-line entry point does, made a failed compaction invisible to the
127
+ user, to the host _and_ to the model at once. The run then continued at full
128
+ context toward a provider rejection several turns later that named none of this.
129
+ A shed that did not happen is exactly as consequential as one that did, and only
130
+ one of them was on the wire.
131
+
132
+ New `compaction_failed` event (wire: `compaction.failed`) carrying `cause`
133
+ (`reducer_threw` | `shed_nothing` | `split_tool_pair`), the unchanged message
134
+ count, and the reducer's error where there was one. The cause is on the event
135
+ because the three want different responses: one may succeed next pass, one will
136
+ decline identically every time, and one is a reducer bug that `findSafeTrimIndex`
137
+ exists to prevent.
138
+
139
+ **And a compaction that succeeded was invisible on the path most hosts take.**
140
+ `compaction_completed` was emitted only from the structured working-state path.
141
+ The reducer path — taken by any host-supplied `contextReducer` and by
142
+ `strategy: 'sliding-window'` — emitted nothing at all, so the event whose own
143
+ documentation says it exists because "a host could not show the user that context
144
+ was dropped" never reached the hosts most likely to need it. It is emitted from
145
+ both paths now.
146
+
147
+ That second one was found by a test written for the first: asserting that a
148
+ successful compaction does _not_ report a failure is what showed it reported
149
+ nothing.
150
+
151
+ **If you switch exhaustively over `RunEvent`, you need a case for
152
+ `compaction_failed`.** Nothing else changes: no existing event's shape moved, and
153
+ a host that ignores unknown events is unaffected. The A2A bridge deliberately
154
+ does not forward either compaction event — a peer models a task lifecycle and
155
+ cannot act on how this runtime manages its own context.
156
+
157
+ - f6e0594: `token_usage_updated` now carries the current context size and the window it is measured against.
158
+
159
+ A host built a context indicator, and it could not have been right. The event
160
+ carried `usage` — **cumulative run spend**, summed over every turn, monotonically
161
+ increasing and untouched by compaction — and nothing about the size of the
162
+ conversation being sent. So the host divided cumulative spend by a context
163
+ window guessed from a substring of the model name, and rendered the result as an
164
+ unqualified percentage, continuously.
165
+
166
+ Both terms were wrong, and the numerator was the worse of the two. A guessed
167
+ window is wrong by a bounded factor. Cumulative spend has three properties that
168
+ make it not merely imprecise but actively misleading:
169
+
170
+ - It **never decreases**, by explicit design — the accumulator is documented as
171
+ monotone so it can never under-report a bill. Compaction does not reduce it.
172
+ - It grows **superlinearly in turn count**, because every turn re-sends the whole
173
+ history and counts those prompt tokens again. Ten turns over a 50k context
174
+ accumulate roughly 500k.
175
+ - It measures **spend**, which is the right quantity for cost and the wrong one
176
+ for occupancy.
177
+
178
+ So an indicator built on it saturates at full long before the context is, and it
179
+ is **anti-correlated with what it claims in exactly the regime a user cares
180
+ about**: a long conversation reads FULL while the real context may be a fifth of
181
+ the window. That alarms people into compacting or restarting when they have
182
+ room — worse than showing nothing, because silence does not tell you something
183
+ false in red. In the other direction, a driver that reports no usage shows 0%
184
+ for a conversation that is really there.
185
+
186
+ The kernel already computed the right numbers on every iteration and kept them
187
+ to itself. `measureContext()` is now exported, and the event carries four new
188
+ optional fields: `contextTokens`, `contextMeasuredBy` (`'provider' | 'estimate'`),
189
+ `contextWindowTokens` and `windowSource` (`'config' | 'model-table' | 'default'`).
190
+ They are named apart from the cumulative figures beside them deliberately —
191
+ reaching for the wrong one should be a visible mistake, not a plausible guess.
192
+
193
+ **They are absent when the run has no compaction configuration**, because nothing
194
+ then resolves a window and inventing one would be the guess this replaces. A
195
+ surface should show what it can name rather than a fraction it cannot ground.
196
+
197
+ **A fraction is only as honest as the weaker of its terms.** `contextMeasuredBy`
198
+ and `windowSource` exist so a surface can pass that on rather than presenting an
199
+ estimate as a measurement. Nothing existing changes: `usage` and `cost` are
200
+ untouched, and the new fields are additive and optional.
201
+
202
+ - a39c2ed: A verification rule can name one tool and one argument.
203
+
204
+ Every pattern rule an operator could write was one of two wrong things.
205
+
206
+ `custom_pattern` carries no tool scope, so a rule written about `bash` decided
207
+ `edit` calls as well — `target: 'both'` prefixes the tool name to the subject
208
+ rather than requiring it, which is not a scope. And `target: 'args'` tests
209
+ `JSON.stringify(toolInput)`, so the subject is the JSON _text_ of the whole
210
+ argument object: the natural, anchored thing to write, `^git push`, is tested
211
+ against `{"command":"git push origin main"}` and can never match. The rule then
212
+ decides nothing, silently. Pinning the tool cost the anchor; anchoring cost the
213
+ tool scope.
214
+
215
+ New `argument_pattern` rule — `toolNames`, `argument`, `pattern`, `decision` —
216
+ whose subject is the named argument's own value, so an anchored pattern means
217
+ what it looks like it means. The refusal names the argument as well as the
218
+ pattern, which is what tells a model whether a different value could get through.
219
+
220
+ It deliberately decides nothing in three cases: the tool was not called, the
221
+ argument is absent, or the argument holds an object or an array. No string a
222
+ pattern could match says anything true about a structured value, and serialising
223
+ one to try would put this rule back where `custom_pattern` already is. To refuse
224
+ a tool over the _shape_ of its input, deny it by name. Numbers and booleans are
225
+ matched rather than skipped — they render unambiguously, and a rule about a
226
+ numeric argument is a reasonable thing to write.
227
+
228
+ `custom_pattern` is unchanged and not deprecated: matching anywhere in the
229
+ serialised input without caring where is a real use, and it is now documented as
230
+ being that rather than reading as something it never was. The trap was the name,
231
+ not the behaviour.
232
+
233
+ - 9ac8dd4: A run that ends any way other than a plain final answer no longer throws away a finished worker's output
234
+
235
+ The iteration loop consulted its completion inbox at exactly one place: the
236
+ branch where the model stops calling tools and answers. It leaves by eight other
237
+ routes, and three of them are ordinary ways for a run to END — a tool the author
238
+ marked `terminal`, a captured `structured_output`, and the host's `stopWhen`.
239
+ A background or abandoned worker that finished while any of those was deciding
240
+ had its result dropped: the gateway held it, the run closed, nothing read it.
241
+ Measured before the fix — terminal-tool exit and `stopWhen` exit both delivered
242
+ nothing; the final-answer exit delivered in 44 ms.
243
+
244
+ Delivery now happens in a `finally` around the loop, so it does not depend on
245
+ each exit remembering — including the two `return`s and a generator abandoned by
246
+ its consumer, which no post-loop statement reaches.
247
+
248
+ **What you may observe.** On those exits `Run.messages` can now end with a
249
+ `task-notification` user message after the assistant's last message. The answer
250
+ is on `Run.result`, as before. If you were reading the answer off the last
251
+ element of `Run.messages`, that assumption was already unsafe whenever a
252
+ notification landed mid-run; it is now unsafe in three more places.
253
+
254
+ **Which exits wait, and which only deliver.** A hold buys the model a turn in
255
+ which to use a result, so it is only worth paying where a turn can still
256
+ happen. A terminal tool and a captured `structured_output` have decided the
257
+ answer, so those deliver what arrived and stop. `stopWhen` is a programmable
258
+ halt that says nothing about whether the answer is complete, so it now HOLDS
259
+ like the ordinary final-answer exit — a precedence rule chosen here, not
260
+ something `stopWhen` implies — and costs exactly one extra turn, after which
261
+ the predicate fires again with nothing pending.
262
+
263
+ The stop reason survives that extra turn. `stopWhen` is consulted only after a
264
+ tool batch, so when the extra turn is prose the predicate is never asked again
265
+ and the run leaves by the ordinary route — which would have reported
266
+ `stopReason: 'end_turn'`, naming the shape of the last message rather than the
267
+ host's decision. A run that ends because a host said stop now reports
268
+ `'stop_condition'` whether or not a delegated result delayed it by a turn. If
269
+ the extra turn instead runs more tools, the predicate is asked again and
270
+ answers for itself.
271
+
272
+ A run that ends with a worker still running now says so on
273
+ `Run.abandonedTaskIds` rather than leaving the impression the result arrived.
274
+
275
+ - 9ac8dd4: A run that ends over a still-running worker says so, and the untrusted envelope's own label can no longer close it
276
+
277
+ Three things an adversarial review of the completion path found.
278
+
279
+ **`Run.abandonedTaskIds`.** A run can settle while a worker it launched is
280
+ still going — the model answered, a terminal tool decided the result, a
281
+ `stopWhen` fired. Until now nothing said so, which left the impression the
282
+ worker's result had been delivered. The run now names those task ids.
283
+
284
+ They are **named, not cancelled**, and that is the decision: giving up on a
285
+ wait is a statement about the waiter, not about the work — the rule this
286
+ subsystem already applies to `wait_for_task` — and "the parent answered early"
287
+ is a weaker warrant for killing a child than "the clock ran out", not a
288
+ stronger one. A worker mid-write is not the kernel's to judge. A host that
289
+ wants the work stopped has `cancel_task` and the run's abort controller, and
290
+ now has the ids to use them on.
291
+
292
+ **`wrapUntrusted` neutralises its own delimiter inside `provenance`.** The body
293
+ was defanged and the attributes escaped; the provenance line was interpolated
294
+ raw, and every caller in the SDK builds it from a value it did not author — an
295
+ agent id from a roster, a server name from a connector manifest. A provenance
296
+ carrying `</namzu-untrusted>` ended the block before the content it was
297
+ introducing. This affects the blocking `create_task`, `wait_for_task` and the
298
+ `Agent` tool as well as the two paths framed in this release.
299
+
300
+ **`background: true` with no inbox is refused, not silently made blocking**,
301
+ and the sentences match. The abandoned-wait messages on `create_task` and
302
+ `wait_for_task` promised "its result will arrive separately as a task
303
+ notification" unconditionally — false with no inbox, and a model told to expect
304
+ a message waits for it. They now say where the result actually is. The
305
+ `agent_task_list` description likewise stops telling the model not to use the
306
+ listing when, without an inbox, the listing is the only route left to an
307
+ abandoned launch's output.
308
+
309
+ `CompletionInbox` gains `outstandingTaskIds`, which reads the ids and cancels
310
+ nothing.
311
+
312
+ - 9ac8dd4: A run holding for a background worker waits a share of its own budget, not a fixed two minutes
313
+
314
+ `BACKGROUND_TASK_GRACE_MS = 120_000` was unrelated to the run it bounded, and
315
+ wrong in both directions at once. Measured: a run configured `timeoutMs: 20_000`
316
+ was held open for **120,267 ms** — six times its own budget — because the hold
317
+ sits inside an iteration and the run guard only checks between them, so nothing
318
+ could interrupt it. In the other direction, on a run with hours left the same
319
+ two minutes abandoned delegated workers observed at 4m21s, 5m58s and 8m04s, all
320
+ comfortably inside the hour `DELEGATION_TIMEOUT_MS` already declares.
321
+
322
+ The hold is now `min(remainingBeforeFinalize × 0.5, DELEGATION_TIMEOUT_MS)`,
323
+ where `remainingBeforeFinalize` is the time left before the run guard stops
324
+ asking for more work and asks for a closing summary (90% of `timeoutMs`), less
325
+ what the run has spent — carried across a resume, so a checkpointed run sizes
326
+ the hold from what is left of the RUN rather than of the process now hosting
327
+ it, and read when the wait starts rather than at the top of the iteration.
328
+
329
+ - **Half, not all.** The hold exists to put a worker's result where the model
330
+ can read it, and reading it costs a turn. Spending everything remaining would
331
+ deliver a notification into a run with no turn left to act on it — the same
332
+ failure the mechanism exists to prevent.
333
+ - **Bounded against the boundary that binds.** Measuring to the DEADLINE was
334
+ the first attempt and it looked safe: a hold cannot outlive the deadline
335
+ either way. But half of the time-to-deadline, started just under the warning
336
+ threshold, ends at 95% of the budget — so the slice the guard keeps for the
337
+ run to produce a closing answer is half spent waiting for the result that
338
+ answer was supposed to use. Against the finalize point the hold cannot reach
339
+ that reserve at all, which is what makes the guard's inability to interrupt
340
+ a hold a non-issue rather than a smaller issue.
341
+ - **A floor of zero, deliberately.** A run with no time left before it must
342
+ start finishing has no turn in which to read a notification. Nothing is
343
+ dropped by it: the wait returns before it looks at its timer when a
344
+ completion is already in hand.
345
+
346
+ **What changes for you.** A run with a short `timeoutMs` finishes when it said
347
+ it would instead of overrunning by minutes. A run with a long one keeps its
348
+ worker instead of abandoning it. If you were relying on a fixed two-minute
349
+ settle regardless of run configuration, set `timeoutMs` to about four and a half minutes to
350
+ get the same hold.
351
+
352
+ - 585a592: A caller can ask which effort levels a model accepts.
353
+
354
+ The answer existed, was modelled carefully, and was reachable only from inside
355
+ one driver. That matters because effort is **refused, not clamped**: a level a
356
+ model does not have makes the vendor reject the request, so a control offering
357
+ the wrong one produces a run that fails at the start rather than a quieter one.
358
+
359
+ Every option open to a caller without the answer was bad. Offering all five
360
+ breaks some models. Offering the intersection hides `xhigh` and `max` from every
361
+ model that has them, which is most of the reason to build such a control. And
362
+ copying the table looks fine and is worst: the ceiling has moved twice already,
363
+ so a copy goes stale on the next model and goes stale **silently**, surfacing as
364
+ a vendor rejection rather than a failing build.
365
+
366
+ **New optional `LLMProvider.effortLevelsFor(model, thinking?)`.** Three states,
367
+ each meaning something different: the method absent means the driver has no
368
+ effort concept at all and setting one will be refused; an empty array means the
369
+ driver implements effort and this model has none; a non-empty array is the set
370
+ to offer.
371
+
372
+ **`thinking` is a parameter, and that is the point.** At least one model family
373
+ accepts a narrower set while thinking is disabled than while it is on — so an
374
+ API returning two sibling arrays invites a caller to render a picker from one
375
+ and send the other, a combination the vendor rejects, on exactly one family.
376
+ Passing the configuration you will actually send makes that unspellable: there
377
+ is one answer and it is the one for your request.
378
+
379
+ The driver's implementation shares the same two resolution steps the request
380
+ path uses, so a caller's picker and the request it produces cannot disagree.
381
+
382
+ `@namzu/anthropic` also now exports `resolveThinkingCapability`,
383
+ `resolveThinkingBody`, `resolveEffort` and their types, for a caller that needs
384
+ the fuller picture — whether thinking can be switched off at all, not only which
385
+ effort levels apply. Prefer `effortLevelsFor` where it suffices: it is
386
+ provider-agnostic and cannot return the wrong one of the two sets.
387
+
388
+ Separately, the live wire-contract suite now retries a transient status rather
389
+ than reporting it as a contract failure. A 529 says the service is busy and
390
+ answers nothing about whether a schema is expressible — so a test named "every
391
+ shipped tool is expressible on this wire" was claiming something the run had not
392
+ established. That cost two manual re-runs in one day to discover the wire had no
393
+ opinion.
394
+
395
+ ### Patch Changes
396
+
397
+ - 9ac8dd4: A background task whose completion arrived early no longer holds the run open forever
398
+
399
+ `CompletionInbox.drain()` handed the completion over and marked it claimed, but
400
+ left the task on the OUTSTANDING set. That set is meant to hold ids that are
401
+ still running, and only the gateway's completion listener takes an id off it —
402
+ so if the listener ran BEFORE the launching call said `expect()`, the id was
403
+ added to a set nothing would ever clear.
404
+
405
+ That order is reachable rather than theoretical: `expect()` runs one microtask
406
+ after `gateway.createTask()` resolves, and a worker that finishes fast is
407
+ announced in between. The result of it was `hasPendingWork === true` for the
408
+ rest of the run, with an empty inbox — so every attempt to settle waited out the
409
+ full background grace period for a result that was already in the transcript,
410
+ and did it again on the next turn, and the next.
411
+
412
+ Nothing to do on upgrade. If you were seeing runs pause for two minutes before
413
+ their final answer with no background work outstanding, this was why.
414
+
415
+ - 3d4315e: `PrepareStepResult.activeTools` documented the opposite of what it does.
416
+
417
+ Its comment promised that unregistered names are dropped so a phase list
418
+ outliving a tool rename would "narrow the surface, not kill the agent mid-run".
419
+ Since the list began bounding what may RUN rather than only what the model is
420
+ shown, dropping every name leaves the step able to call nothing — so the code
421
+ and its own documentation had said different things.
422
+
423
+ **The behaviour is right and the comment was wrong.** This list means "only
424
+ these": when a rename outlives it, the only set satisfying "only the tools that
425
+ no longer exist" is the empty one. Widening back to the run's list would grant
426
+ precisely the tools the caller asked to exclude, on the grounds that their own
427
+ list failed — a control that stops applying because it was aged out, which is
428
+ worse than a step that answers from what it already has. The run continues
429
+ either way; nothing crashes.
430
+
431
+ The warning now distinguishes the two cases, because they have different
432
+ consequences: some names dropped narrows the step, and all of them dropped
433
+ leaves it unable to call anything. "Ignoring them" was accurate for the first
434
+ and misleading for the second.
435
+
436
+ **Worth knowing if you rely on this:** the warning goes to the logger, so a host
437
+ that silences its logger sees a phase quietly stop doing anything. That is a real
438
+ gap and it is named here rather than papered over.
439
+
3
440
  ## 7.0.0
4
441
 
5
442
  ### Major Changes
@@ -1 +1 @@
1
- {"version":3,"file":"SupervisorAgent.d.ts","sourceRoot":"","sources":["../../src/agents/SupervisorAgent.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAe,UAAU,EAAE,MAAM,2BAA2B,CAAA;AACxE,OAAO,KAAK,EACX,UAAU,EACV,aAAa,EACb,eAAe,EACf,qBAAqB,EACrB,qBAAqB,EACrB,MAAM,yBAAyB,CAAA;AAEhC,OAAO,KAAK,EAAW,KAAK,EAAU,MAAM,uBAAuB,CAAA;AAEnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAG7D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAElD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CACpC,WAAW,EAAE,SAAS,UAAU,EAAE,EAClC,KAAK,EAAE,KAAK,EACZ,GAAG,GAAE,MAAmB,GACtB,eAAe,EAAE,CAcnB;AAED,4DAA4D;AAC5D,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,SAAS,eAAe,EAAE,GAAG,MAAM,CAEnF;AAED,qBAAa,eAAgB,SAAQ,aAAa,CAAC,qBAAqB,EAAE,qBAAqB,CAAC;IAC/F,QAAQ,CAAC,IAAI,EAAG,YAAY,CAAS;gBAEzB,QAAQ,EAAE,IAAI,CAAC,aAAa,EAAE,MAAM,GAAG,cAAc,CAAC;IAalE;;;;;;;;OAQG;IACG,GAAG,CACR,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,qBAAqB,EAC7B,QAAQ,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,qBAAqB,CAAC;YAMnB,YAAY;CAwQ1B"}
1
+ {"version":3,"file":"SupervisorAgent.d.ts","sourceRoot":"","sources":["../../src/agents/SupervisorAgent.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAe,UAAU,EAAE,MAAM,2BAA2B,CAAA;AACxE,OAAO,KAAK,EACX,UAAU,EACV,aAAa,EACb,eAAe,EACf,qBAAqB,EACrB,qBAAqB,EACrB,MAAM,yBAAyB,CAAA;AAEhC,OAAO,KAAK,EAAW,KAAK,EAAU,MAAM,uBAAuB,CAAA;AAEnE,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAG7D,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAA;AAElD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CACpC,WAAW,EAAE,SAAS,UAAU,EAAE,EAClC,KAAK,EAAE,KAAK,EACZ,GAAG,GAAE,MAAmB,GACtB,eAAe,EAAE,CAcnB;AAED,4DAA4D;AAC5D,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,SAAS,eAAe,EAAE,GAAG,MAAM,CAEnF;AAED,qBAAa,eAAgB,SAAQ,aAAa,CAAC,qBAAqB,EAAE,qBAAqB,CAAC;IAC/F,QAAQ,CAAC,IAAI,EAAG,YAAY,CAAS;gBAEzB,QAAQ,EAAE,IAAI,CAAC,aAAa,EAAE,MAAM,GAAG,cAAc,CAAC;IAalE;;;;;;;;OAQG;IACG,GAAG,CACR,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,qBAAqB,EAC7B,QAAQ,CAAC,EAAE,gBAAgB,GACzB,OAAO,CAAC,qBAAqB,CAAC;YAMnB,YAAY;CAiR1B"}