@namzu/sdk 7.0.0 → 9.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.
- package/CHANGELOG.md +652 -0
- package/dist/agents/SupervisorAgent.d.ts.map +1 -1
- package/dist/agents/SupervisorAgent.js +186 -163
- package/dist/agents/SupervisorAgent.js.map +1 -1
- package/dist/agents/__tests__/a-policy-nobody-can-select.test.d.ts +2 -0
- package/dist/agents/__tests__/a-policy-nobody-can-select.test.d.ts.map +1 -0
- package/dist/agents/__tests__/a-policy-nobody-can-select.test.js +177 -0
- package/dist/agents/__tests__/a-policy-nobody-can-select.test.js.map +1 -0
- package/dist/agents/__tests__/supervisor-inbox-scope.test.d.ts +2 -0
- package/dist/agents/__tests__/supervisor-inbox-scope.test.d.ts.map +1 -0
- package/dist/agents/__tests__/supervisor-inbox-scope.test.js +125 -0
- package/dist/agents/__tests__/supervisor-inbox-scope.test.js.map +1 -0
- package/dist/bridge/a2a/mapper.d.ts.map +1 -1
- package/dist/bridge/a2a/mapper.js +4 -0
- package/dist/bridge/a2a/mapper.js.map +1 -1
- package/dist/bridge/sse/mapper.d.ts.map +1 -1
- package/dist/bridge/sse/mapper.js +24 -0
- package/dist/bridge/sse/mapper.js.map +1 -1
- package/dist/contracts/api.d.ts +1 -1
- package/dist/contracts/api.d.ts.map +1 -1
- package/dist/gateway/__tests__/a-knob-nobody-can-turn.test.d.ts +2 -0
- package/dist/gateway/__tests__/a-knob-nobody-can-turn.test.d.ts.map +1 -0
- package/dist/gateway/__tests__/a-knob-nobody-can-turn.test.js +114 -0
- package/dist/gateway/__tests__/a-knob-nobody-can-turn.test.js.map +1 -0
- package/dist/gateway/__tests__/completion-inbox.test.js +292 -2
- package/dist/gateway/__tests__/completion-inbox.test.js.map +1 -1
- package/dist/gateway/completion-inbox.d.ts +94 -6
- package/dist/gateway/completion-inbox.d.ts.map +1 -1
- package/dist/gateway/completion-inbox.js +235 -15
- package/dist/gateway/completion-inbox.js.map +1 -1
- package/dist/gateway/local.d.ts.map +1 -1
- package/dist/gateway/local.js +19 -6
- package/dist/gateway/local.js.map +1 -1
- package/dist/manager/agent/__tests__/lifecycle.test.js +56 -0
- package/dist/manager/agent/__tests__/lifecycle.test.js.map +1 -1
- package/dist/manager/agent/lifecycle.d.ts.map +1 -1
- package/dist/manager/agent/lifecycle.js +62 -33
- package/dist/manager/agent/lifecycle.js.map +1 -1
- package/dist/manager/plan/__tests__/a-plan-that-succeeded-is-not-failed.test.d.ts +2 -0
- package/dist/manager/plan/__tests__/a-plan-that-succeeded-is-not-failed.test.d.ts.map +1 -0
- package/dist/manager/plan/__tests__/a-plan-that-succeeded-is-not-failed.test.js +73 -0
- package/dist/manager/plan/__tests__/a-plan-that-succeeded-is-not-failed.test.js.map +1 -0
- package/dist/manager/plan/lifecycle.d.ts +48 -0
- package/dist/manager/plan/lifecycle.d.ts.map +1 -1
- package/dist/manager/plan/lifecycle.js +56 -0
- package/dist/manager/plan/lifecycle.js.map +1 -1
- package/dist/manager/run/persistence.d.ts +8 -0
- package/dist/manager/run/persistence.d.ts.map +1 -1
- package/dist/manager/run/persistence.js +12 -0
- package/dist/manager/run/persistence.js.map +1 -1
- package/dist/run/reporter.d.ts.map +1 -1
- package/dist/run/reporter.js +11 -0
- package/dist/run/reporter.js.map +1 -1
- package/dist/runtime/query/__tests__/completion-does-not-erase-the-answer.test.d.ts +2 -0
- package/dist/runtime/query/__tests__/completion-does-not-erase-the-answer.test.d.ts.map +1 -0
- package/dist/runtime/query/__tests__/completion-does-not-erase-the-answer.test.js +142 -0
- package/dist/runtime/query/__tests__/completion-does-not-erase-the-answer.test.js.map +1 -0
- package/dist/runtime/query/__tests__/completion-notification.test.js +414 -32
- package/dist/runtime/query/__tests__/completion-notification.test.js.map +1 -1
- package/dist/runtime/query/__tests__/context-size-on-the-wire.test.d.ts +2 -0
- package/dist/runtime/query/__tests__/context-size-on-the-wire.test.d.ts.map +1 -0
- package/dist/runtime/query/__tests__/context-size-on-the-wire.test.js +100 -0
- package/dist/runtime/query/__tests__/context-size-on-the-wire.test.js.map +1 -0
- package/dist/runtime/query/__tests__/the-plan-graph-reaches-a-host.test.d.ts +2 -0
- package/dist/runtime/query/__tests__/the-plan-graph-reaches-a-host.test.d.ts.map +1 -0
- package/dist/runtime/query/__tests__/the-plan-graph-reaches-a-host.test.js +62 -0
- package/dist/runtime/query/__tests__/the-plan-graph-reaches-a-host.test.js.map +1 -0
- package/dist/runtime/query/events.d.ts.map +1 -1
- package/dist/runtime/query/events.js +5 -0
- package/dist/runtime/query/events.js.map +1 -1
- package/dist/runtime/query/guard.d.ts +28 -0
- package/dist/runtime/query/guard.d.ts.map +1 -1
- package/dist/runtime/query/guard.js +31 -0
- package/dist/runtime/query/guard.js.map +1 -1
- package/dist/runtime/query/iteration/__tests__/settle-grace.test.d.ts +2 -0
- package/dist/runtime/query/iteration/__tests__/settle-grace.test.d.ts.map +1 -0
- package/dist/runtime/query/iteration/__tests__/settle-grace.test.js +226 -0
- package/dist/runtime/query/iteration/__tests__/settle-grace.test.js.map +1 -0
- package/dist/runtime/query/iteration/index.d.ts +92 -0
- package/dist/runtime/query/iteration/index.d.ts.map +1 -1
- package/dist/runtime/query/iteration/index.js +814 -566
- package/dist/runtime/query/iteration/index.js.map +1 -1
- package/dist/runtime/query/iteration/phases/__tests__/compaction-declined.test.d.ts +2 -0
- package/dist/runtime/query/iteration/phases/__tests__/compaction-declined.test.d.ts.map +1 -0
- package/dist/runtime/query/iteration/phases/__tests__/compaction-declined.test.js +95 -0
- package/dist/runtime/query/iteration/phases/__tests__/compaction-declined.test.js.map +1 -0
- package/dist/runtime/query/iteration/phases/compaction.d.ts +34 -0
- package/dist/runtime/query/iteration/phases/compaction.d.ts.map +1 -1
- package/dist/runtime/query/iteration/phases/compaction.js +61 -4
- package/dist/runtime/query/iteration/phases/compaction.js.map +1 -1
- package/dist/store/task/disk.d.ts.map +1 -1
- package/dist/store/task/disk.js +14 -0
- package/dist/store/task/disk.js.map +1 -1
- package/dist/store/task/memory.d.ts.map +1 -1
- package/dist/store/task/memory.js +14 -0
- package/dist/store/task/memory.js.map +1 -1
- package/dist/tools/__tests__/untrusted-envelope.test.js +16 -0
- package/dist/tools/__tests__/untrusted-envelope.test.js.map +1 -1
- package/dist/tools/coordinator/__tests__/a-failed-worker-is-not-an-answer.test.d.ts +2 -0
- package/dist/tools/coordinator/__tests__/a-failed-worker-is-not-an-answer.test.d.ts.map +1 -0
- package/dist/tools/coordinator/__tests__/a-failed-worker-is-not-an-answer.test.js +101 -0
- package/dist/tools/coordinator/__tests__/a-failed-worker-is-not-an-answer.test.js.map +1 -0
- package/dist/tools/coordinator/__tests__/completion-delivery.test.js +117 -0
- package/dist/tools/coordinator/__tests__/completion-delivery.test.js.map +1 -1
- package/dist/tools/coordinator/__tests__/task-list.test.js +57 -0
- package/dist/tools/coordinator/__tests__/task-list.test.js.map +1 -1
- package/dist/tools/coordinator/__tests__/the-plan-a-human-approves.test.d.ts +2 -0
- package/dist/tools/coordinator/__tests__/the-plan-a-human-approves.test.d.ts.map +1 -0
- package/dist/tools/coordinator/__tests__/the-plan-a-human-approves.test.js +99 -0
- package/dist/tools/coordinator/__tests__/the-plan-a-human-approves.test.js.map +1 -0
- package/dist/tools/coordinator/agent.d.ts.map +1 -1
- package/dist/tools/coordinator/agent.js +17 -23
- package/dist/tools/coordinator/agent.js.map +1 -1
- package/dist/tools/coordinator/index.d.ts.map +1 -1
- package/dist/tools/coordinator/index.js +142 -14
- package/dist/tools/coordinator/index.js.map +1 -1
- package/dist/tools/coordinator/outcome.d.ts +60 -0
- package/dist/tools/coordinator/outcome.d.ts.map +1 -0
- package/dist/tools/coordinator/outcome.js +68 -0
- package/dist/tools/coordinator/outcome.js.map +1 -0
- package/dist/tools/untrusted-envelope.d.ts.map +1 -1
- package/dist/tools/untrusted-envelope.js +9 -1
- package/dist/tools/untrusted-envelope.js.map +1 -1
- package/dist/types/agent/gateway.d.ts +37 -2
- package/dist/types/agent/gateway.d.ts.map +1 -1
- package/dist/types/agent/supervisor.d.ts +38 -1
- package/dist/types/agent/supervisor.d.ts.map +1 -1
- package/dist/types/plan/index.d.ts +26 -0
- package/dist/types/plan/index.d.ts.map +1 -1
- package/dist/types/provider/interface.d.ts +35 -0
- package/dist/types/provider/interface.d.ts.map +1 -1
- package/dist/types/run/entity.d.ts +16 -0
- package/dist/types/run/entity.d.ts.map +1 -1
- package/dist/types/run/events.d.ts +92 -0
- package/dist/types/run/events.d.ts.map +1 -1
- package/dist/types/run/events.js.map +1 -1
- package/dist/types/run/prepare-step.d.ts +17 -2
- package/dist/types/run/prepare-step.d.ts.map +1 -1
- package/dist/types/task/index.d.ts +19 -1
- package/dist/types/task/index.d.ts.map +1 -1
- package/dist/types/task/index.js +10 -1
- package/dist/types/task/index.js.map +1 -1
- package/dist/types/verification/index.d.ts +98 -0
- package/dist/types/verification/index.d.ts.map +1 -1
- package/dist/types/verification/index.js +10 -0
- package/dist/types/verification/index.js.map +1 -1
- package/dist/verification/__tests__/argument-pattern.test.d.ts +2 -0
- package/dist/verification/__tests__/argument-pattern.test.d.ts.map +1 -0
- package/dist/verification/__tests__/argument-pattern.test.js +122 -0
- package/dist/verification/__tests__/argument-pattern.test.js.map +1 -0
- package/dist/verification/gate.d.ts.map +1 -1
- package/dist/verification/gate.js +44 -0
- package/dist/verification/gate.js.map +1 -1
- package/dist/verification/rules.d.ts.map +1 -1
- package/dist/verification/rules.js +27 -0
- package/dist/verification/rules.js.map +1 -1
- package/package.json +1 -1
- package/src/agents/SupervisorAgent.ts +189 -167
- package/src/agents/__tests__/a-policy-nobody-can-select.test.ts +202 -0
- package/src/agents/__tests__/supervisor-inbox-scope.test.ts +149 -0
- package/src/bridge/a2a/mapper.ts +4 -0
- package/src/bridge/sse/mapper.ts +25 -0
- package/src/contracts/api.ts +1 -0
- package/src/gateway/__tests__/a-knob-nobody-can-turn.test.ts +139 -0
- package/src/gateway/__tests__/completion-inbox.test.ts +348 -2
- package/src/gateway/completion-inbox.ts +248 -16
- package/src/gateway/local.ts +19 -7
- package/src/manager/agent/__tests__/lifecycle.test.ts +74 -0
- package/src/manager/agent/lifecycle.ts +72 -42
- package/src/manager/plan/__tests__/a-plan-that-succeeded-is-not-failed.test.ts +88 -0
- package/src/manager/plan/lifecycle.ts +61 -0
- package/src/manager/run/persistence.ts +12 -0
- package/src/run/reporter.ts +12 -0
- package/src/runtime/query/__tests__/completion-does-not-erase-the-answer.test.ts +163 -0
- package/src/runtime/query/__tests__/completion-notification.test.ts +486 -34
- package/src/runtime/query/__tests__/context-size-on-the-wire.test.ts +122 -0
- package/src/runtime/query/__tests__/the-plan-graph-reaches-a-host.test.ts +79 -0
- package/src/runtime/query/events.ts +5 -0
- package/src/runtime/query/guard.ts +32 -0
- package/src/runtime/query/iteration/__tests__/settle-grace.test.ts +265 -0
- package/src/runtime/query/iteration/index.ts +902 -636
- package/src/runtime/query/iteration/phases/__tests__/compaction-declined.test.ts +124 -0
- package/src/runtime/query/iteration/phases/compaction.ts +83 -10
- package/src/store/task/disk.ts +15 -0
- package/src/store/task/memory.ts +15 -0
- package/src/tools/__tests__/untrusted-envelope.test.ts +23 -0
- package/src/tools/coordinator/__tests__/a-failed-worker-is-not-an-answer.test.ts +133 -0
- package/src/tools/coordinator/__tests__/completion-delivery.test.ts +147 -0
- package/src/tools/coordinator/__tests__/task-list.test.ts +72 -0
- package/src/tools/coordinator/__tests__/the-plan-a-human-approves.test.ts +121 -0
- package/src/tools/coordinator/agent.ts +17 -25
- package/src/tools/coordinator/index.ts +155 -18
- package/src/tools/coordinator/outcome.ts +70 -0
- package/src/tools/untrusted-envelope.ts +9 -1
- package/src/types/agent/gateway.ts +37 -2
- package/src/types/agent/supervisor.ts +40 -1
- package/src/types/plan/index.ts +29 -0
- package/src/types/provider/interface.ts +39 -0
- package/src/types/run/entity.ts +17 -0
- package/src/types/run/events.ts +92 -0
- package/src/types/run/prepare-step.ts +17 -2
- package/src/types/task/index.ts +22 -3
- package/src/types/verification/index.ts +61 -0
- package/src/verification/__tests__/argument-pattern.test.ts +158 -0
- package/src/verification/gate.ts +45 -0
- package/src/verification/rules.ts +28 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,657 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 9.0.0
|
|
4
|
+
|
|
5
|
+
### Major Changes
|
|
6
|
+
|
|
7
|
+
- 16dc634: A failed worker is reported as a failure, and a plan task can say it failed.
|
|
8
|
+
|
|
9
|
+
**`create_task` reported a failed worker as a success.** Two layers can disagree
|
|
10
|
+
about whether a delegated run succeeded: the gateway's `TaskHandle.state`, and
|
|
11
|
+
the run's own `BaseAgentResult.status`. The kernel's `finalizeChild` always calls
|
|
12
|
+
`markCompleted`, so `state === 'completed'` holds for a child that ran and
|
|
13
|
+
returned `status: 'failed'` — and `create_task` asked only that layer. The model
|
|
14
|
+
received the failure text as an answer, the tool result carried
|
|
15
|
+
`isError: false`, and the plan task was written closed as though the work had
|
|
16
|
+
been done.
|
|
17
|
+
|
|
18
|
+
The correct two-authority predicate was already written, twenty lines away, in
|
|
19
|
+
the canonical `Agent` tool — put there because a review caught it on that site,
|
|
20
|
+
and nothing carried the answer to the other one. It now lives in one place both
|
|
21
|
+
reach.
|
|
22
|
+
|
|
23
|
+
**`TaskStatus` gains `failed`, and that is the breaking part.** A unit that did
|
|
24
|
+
not succeed had nowhere to say so, which is why a failed delegation was recorded
|
|
25
|
+
as `completed` with the failure encoded as prose in `description`: a reader
|
|
26
|
+
scanning statuses saw work that had been done, and a dependent unit had no way
|
|
27
|
+
to tell at all.
|
|
28
|
+
|
|
29
|
+
If you switch exhaustively over `TaskStatus`, or hold a `Record<TaskStatus, T>`,
|
|
30
|
+
you need a `failed` arm. `isTerminalTaskStatus` now returns `true` for it —
|
|
31
|
+
terminal means "will not change on its own", not "succeeded", and a unit blocked
|
|
32
|
+
on something that failed would otherwise wait forever for a status that will
|
|
33
|
+
never arrive. In the store's transition ranking `failed` sits alongside
|
|
34
|
+
`completed` rather than after it, so `in_progress → failed` is allowed and
|
|
35
|
+
`completed → failed` is not.
|
|
36
|
+
|
|
37
|
+
**Two smaller repairs ride along.** A background launch refused for want of a
|
|
38
|
+
completion inbox now marks its plan task failed rather than leaving it in
|
|
39
|
+
progress with no worker behind it — nothing later closes a task whose launch
|
|
40
|
+
never happened. And the `Agent` tool passes `parentSpan` when creating its
|
|
41
|
+
child, so a delegated run joins the turn that asked for it instead of starting
|
|
42
|
+
its own root trace; `create_task` has done this all along.
|
|
43
|
+
|
|
44
|
+
- a743c7e: A delegated run is built with the config its caller asked for, and a supervisor can select its sibling-failure policy.
|
|
45
|
+
|
|
46
|
+
Two capabilities were declared, documented, typed, and unreachable. Both are the
|
|
47
|
+
same defect: a knob wired to nothing, which reads to a caller as a knob that
|
|
48
|
+
works.
|
|
49
|
+
|
|
50
|
+
**`CreateTaskOptions.configOverrides` was accepted and dropped.**
|
|
51
|
+
`LocalTaskGateway.createTask` built its own `configOverrides` object out of
|
|
52
|
+
`parentSpan` alone and never read the field, so a caller pinning a delegated run
|
|
53
|
+
to a cheaper model, or capping its iterations, got the agent's defaults and no
|
|
54
|
+
indication anything had been ignored. It is forwarded now. A caller who sets
|
|
55
|
+
both the field and the dedicated `parentSpan` option gets the dedicated one for
|
|
56
|
+
the span — that is the specific field for the job — and keeps every other
|
|
57
|
+
override alongside it.
|
|
58
|
+
|
|
59
|
+
**`siblingFailurePolicy` could not be selected by any host.**
|
|
60
|
+
`LocalTaskGateway` has honoured it since it was written and the cancellation
|
|
61
|
+
machinery behind `'cancel-siblings'` is complete — but it was the fifth
|
|
62
|
+
constructor argument of a gateway `SupervisorAgent` builds itself, and the
|
|
63
|
+
supervisor passed four. Every host in existence ran `'continue'`, and the only
|
|
64
|
+
route to the other value was to construct the gateway by hand and pass it as
|
|
65
|
+
`config.gateway`. It is now `SupervisorAgentConfig.siblingFailurePolicy`.
|
|
66
|
+
|
|
67
|
+
`'continue'` remains the default and deliberately so: partial results are
|
|
68
|
+
usually worth having, and tearing down healthy siblings on any failure lets one
|
|
69
|
+
flaky child waste four good ones. `'cancel-siblings'` is for a fan-out whose
|
|
70
|
+
parts only mean something together. The choice is now expressible; the answer
|
|
71
|
+
has not changed. The field is ignored when the host supplies its own `gateway`,
|
|
72
|
+
which owns its policy.
|
|
73
|
+
|
|
74
|
+
**Breaking:** `CreateTaskOptions.configOverrides` is now typed
|
|
75
|
+
`Partial<BaseAgentConfig>` instead of `Record<string, unknown>`. It lands on
|
|
76
|
+
`SendMessageOptions.configOverrides`, which is already that shape, and the loose
|
|
77
|
+
type let a misspelled key type-check and then silently do nothing — the same
|
|
78
|
+
silence the field was already producing. If you pass a key that is not on
|
|
79
|
+
`BaseAgentConfig`, it will now fail to compile; that key was never being applied.
|
|
80
|
+
|
|
81
|
+
**Also:** the two-authority failure check in `LocalTaskGateway` moves to
|
|
82
|
+
`taskFailed` in `tools/coordinator/outcome.ts`, next to `taskSucceeded`. It is
|
|
83
|
+
deliberately _not_ the negation of that predicate — a task that is still running
|
|
84
|
+
satisfies neither, and cancelling a fan-out on `!taskSucceeded` would tear down
|
|
85
|
+
siblings the moment the first child had merely not finished yet. The gateway's
|
|
86
|
+
copy was correct; a rule each caller has to remember is one a caller eventually
|
|
87
|
+
forgets, which is what happened to `taskSucceeded` before it was consolidated.
|
|
88
|
+
|
|
89
|
+
- 529b343: `PlanManager.completePlan` refuses an unreported step instead of scoring it a failure.
|
|
90
|
+
|
|
91
|
+
**A plan that fully succeeded was reported as failed.** `completePlan` asked one
|
|
92
|
+
question — "is every step `completed` or `skipped`?" — and everything that was
|
|
93
|
+
not fell to the same branch. A step still `pending` therefore produced
|
|
94
|
+
`status: 'failed'`, indistinguishable from a step that genuinely failed. Since
|
|
95
|
+
`addStep` defaults every step to `pending`, a host that added steps, did the
|
|
96
|
+
work, and settled the plan without calling `updateStepStatus` on each one got
|
|
97
|
+
`failed` for a plan where nothing had gone wrong. That is the path of least
|
|
98
|
+
effort through this API, not an unusual one.
|
|
99
|
+
|
|
100
|
+
The two cases are different facts and deserve different answers. A step that
|
|
101
|
+
FAILED is an outcome: report the plan failed. A step nobody reported on is not
|
|
102
|
+
an outcome at all — it says the caller and the plan disagree about whether the
|
|
103
|
+
work is over, and answering `failed` settles that disagreement by inventing a
|
|
104
|
+
result.
|
|
105
|
+
|
|
106
|
+
**What changes for you.** `completePlan()` now throws when any step is still
|
|
107
|
+
`pending` or `running`. The message names the unfinished steps and both ways
|
|
108
|
+
forward, because a caller in this position either forgot to report progress or
|
|
109
|
+
called too early, and only they know which:
|
|
110
|
+
|
|
111
|
+
- report each step with `updateStepStatus` — `'skipped'` is a valid outcome for
|
|
112
|
+
work that was planned and then not needed; or
|
|
113
|
+
- call `failPlan` if the plan is being abandoned, which marks unfinished steps
|
|
114
|
+
`skipped` and settles the plan as failed.
|
|
115
|
+
|
|
116
|
+
Behaviour is unchanged once every step has reported: all `completed` or
|
|
117
|
+
`skipped` still yields `completed`, and any `failed` still yields `failed`.
|
|
118
|
+
No code in this repository called `completePlan`, so nothing inside the kernel
|
|
119
|
+
changes behaviour; the affected callers are hosts.
|
|
120
|
+
|
|
121
|
+
**`PlanManager` now says which half of it the kernel drives.** The kernel builds
|
|
122
|
+
a plan, gates it, translates its events, and settles it on failure — it never
|
|
123
|
+
reports a step outcome and never settles a plan that succeeded. That is a
|
|
124
|
+
deliberate split, since `drainQuery` hands the manager to the host through
|
|
125
|
+
`onContextCreated` for exactly this purpose, and a search for callers inside the
|
|
126
|
+
package finds none because the callers are outside it. The absence had already
|
|
127
|
+
been read once as a dead layer and proposed for deletion; what that would have
|
|
128
|
+
deleted is a working human-in-the-loop approval gate. It is written down now.
|
|
129
|
+
|
|
130
|
+
### Minor Changes
|
|
131
|
+
|
|
132
|
+
- e355049: The plan a human approves names the agent the model chose.
|
|
133
|
+
|
|
134
|
+
`approve_plan` asks the model for an `agent_id` per step — "which agent handles
|
|
135
|
+
this" — and reduced the answer to a boolean. The step got
|
|
136
|
+
`toolName: 'create_task'` when any agent was named and nothing when not, so the
|
|
137
|
+
name was dropped between the model saying it and the human being shown the plan.
|
|
138
|
+
|
|
139
|
+
The approval is the one moment where that difference can still be acted on.
|
|
140
|
+
Approving "delegate this step" is not the same as approving "delegate this step
|
|
141
|
+
to the agent with shell access", and a reviewer who cannot see which agent was
|
|
142
|
+
chosen cannot withhold approval from the wrong one. Two delegated steps reached
|
|
143
|
+
the approver identical in every field.
|
|
144
|
+
|
|
145
|
+
`PlanStep` gains `agentId?: string`, populated by `approve_plan` from the
|
|
146
|
+
model's choice. A host rendering a plan approval can show it directly. Absent
|
|
147
|
+
still means the step is the orchestrator's own work, which is what omitting
|
|
148
|
+
`agent_id` says — so absent stays absent rather than becoming a placeholder.
|
|
149
|
+
|
|
150
|
+
Typed rather than folded into the existing `estimatedInput`, which is `unknown`:
|
|
151
|
+
an approval gate's whole job is being readable, and a field a host must cast
|
|
152
|
+
before it can render is one a host renders wrong or not at all. `estimatedInput`
|
|
153
|
+
is now documented as having no producer and no reader, since that is what it
|
|
154
|
+
has, and it is left in place because it is on the published typings.
|
|
155
|
+
|
|
156
|
+
- 16dc634: A host can see the fan-out gate, and the plan graph the model is already keeping.
|
|
157
|
+
|
|
158
|
+
**`SupervisorAgentConfig` gains `maxToolConcurrency`.** The kernel has honoured
|
|
159
|
+
it all along and `ReactiveAgent` forwards it — it was missing on the one agent
|
|
160
|
+
whose entire job is delegation. So the agent that fans out could not set the gate
|
|
161
|
+
that bounds a fan-out, while the agent that does not fan out could, and a host
|
|
162
|
+
wanting a narrower one had to reach past the supervisor to `drainQuery`.
|
|
163
|
+
|
|
164
|
+
Note what it bounds: how many delegated children run **concurrently**, not how
|
|
165
|
+
many a turn may launch. A model emitting twenty `create_task` blocks still
|
|
166
|
+
launches twenty; they queue.
|
|
167
|
+
|
|
168
|
+
**`task_created` and `task_updated` carry `blockedBy`.** The task store
|
|
169
|
+
maintains a full dependency graph — `blocks` and `blockedBy` mirrored on both
|
|
170
|
+
ends, written under a lock, deadlock-avoided — and none of it reached the wire.
|
|
171
|
+
A host could draw a flat list of units and nothing about their order, while the
|
|
172
|
+
model was already maintaining the order.
|
|
173
|
+
|
|
174
|
+
Absent rather than empty when a unit depends on nothing, so a reader can tell
|
|
175
|
+
"no dependencies" from an emitter that predates the field.
|
|
176
|
+
|
|
177
|
+
**And `block()` announced nothing at all.** Both stores wrote the edge and
|
|
178
|
+
emitted no event, so the graph was observable only by polling: a listener saw a
|
|
179
|
+
unit created and never learned that something now waits on it. Both stores now
|
|
180
|
+
announce **both ends**, because both changed — a host tracking one side would
|
|
181
|
+
draw half the edge. The disk store announces only when something actually
|
|
182
|
+
changed, so re-establishing an existing edge stays silent.
|
|
183
|
+
|
|
184
|
+
That second half is the one worth knowing about if you consume these events: the
|
|
185
|
+
field alone would have been useless, because the moment a dependency is created
|
|
186
|
+
was never on the wire in the first place.
|
|
187
|
+
|
|
188
|
+
### Patch Changes
|
|
189
|
+
|
|
190
|
+
- 16dc634: A concurrent fan-out no longer allocates more budget than the parent has.
|
|
191
|
+
|
|
192
|
+
`sendMessage` read the parent's remaining budget at the top and debited it after
|
|
193
|
+
`provisionSpawn` — putting the two halves of a read-modify-write on either side
|
|
194
|
+
of an await, with the only critical section in between. So siblings launched
|
|
195
|
+
from one assistant turn all read the same undebited number and each took a
|
|
196
|
+
fraction of it. Measured: \*\*four concurrent children were handed 50 000 + 50 000
|
|
197
|
+
|
|
198
|
+
- 50 000 + 50 000 from a pool of 100 000.\*\*
|
|
199
|
+
|
|
200
|
+
`create_task`'s own description instructs exactly the shape that triggers it —
|
|
201
|
+
_"'fan out 8 specialists' is one assistant message with 8 create_task blocks"_ —
|
|
202
|
+
so the documented usage was the reproduction.
|
|
203
|
+
|
|
204
|
+
The read, the refusal when an allocation floors to zero, and the debit now all
|
|
205
|
+
happen inside the per-parent spawn lock. That keeps the property the debit's
|
|
206
|
+
placement was chosen for — a spawn this call rejects burns no allocation — while
|
|
207
|
+
closing the race that placement opened. It was introduced by a correct fix to a
|
|
208
|
+
different bug: moving the debit after the provisioning put it outside the lock.
|
|
209
|
+
|
|
210
|
+
**Nothing pinned it**, and the reason is worth knowing if you write tests here:
|
|
211
|
+
the existing concurrency test builds a fresh context per call, so each spawn got
|
|
212
|
+
its own tracker — it measures width, not budget. The sequential tests pass
|
|
213
|
+
because a refund makes the arithmetic close. The regression test holds its
|
|
214
|
+
children open, because a settled child refunds and the refund restores a
|
|
215
|
+
plausible number; a test that measures after settle sees a healthy total and
|
|
216
|
+
reports nothing.
|
|
217
|
+
|
|
218
|
+
## 8.0.0
|
|
219
|
+
|
|
220
|
+
### Major Changes
|
|
221
|
+
|
|
222
|
+
- 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
|
|
223
|
+
|
|
224
|
+
Blocking `create_task` and `wait_for_task` wrap a worker's text in the
|
|
225
|
+
`<namzu-untrusted>` envelope. Two other paths carried the same bytes and did
|
|
226
|
+
not: the completion notification injected into the transcript, and
|
|
227
|
+
`agent_task_list`'s rendered output. So whether a worker's words arrived as
|
|
228
|
+
material or as the parent's own reasoning depended on how the model happened to
|
|
229
|
+
fetch them — and the two unframed paths are the ones reached when a wait was
|
|
230
|
+
abandoned, which is when a run is already off its expected course.
|
|
231
|
+
|
|
232
|
+
Worse, the notification's own delimiter was forgeable. Measured: worker output
|
|
233
|
+
containing `</task-notification>` produced two closing tags in one message, with
|
|
234
|
+
attacker-controlled text sitting outside the first — reading as ordinary
|
|
235
|
+
transcript rather than as a delegate's material.
|
|
236
|
+
|
|
237
|
+
**What changed on the wire the model sees.**
|
|
238
|
+
|
|
239
|
+
- The notification now nests a `<namzu-untrusted kind="agent-result">` block
|
|
240
|
+
inside `<task-notification>`. Kernel metadata (`task_id`, `agent`, `state`,
|
|
241
|
+
`duration_ms`) stays OUTSIDE it — framing this kernel's own statements as
|
|
242
|
+
untrusted would tell the model to discount the only part of the message it
|
|
243
|
+
can rely on — and so does the truncation notice, which is an instruction
|
|
244
|
+
about how to fetch the rest.
|
|
245
|
+
- `agent_task_list` wraps each finished task's output the same way, with the
|
|
246
|
+
same `agent` and `task` attributes the blocking path uses.
|
|
247
|
+
- Both delimiters are defanged inside worker text, case-insensitively. The
|
|
248
|
+
replacements (`task_notification`, `namzu_untrusted`) share no substring with
|
|
249
|
+
the tokens they replace — a replacement that still contains the token is found
|
|
250
|
+
again by a second pass or by any looser matcher downstream.
|
|
251
|
+
- A notification is 257 characters longer than before — measured, both for a
|
|
252
|
+
five-character result and for a truncated 4 kB one, so the cost is fixed
|
|
253
|
+
rather than proportional to the output. It grows only with the length of the
|
|
254
|
+
agent id and task id, which appear in the envelope's attributes.
|
|
255
|
+
|
|
256
|
+
`data.result` on both tools is unchanged, so a host reading results
|
|
257
|
+
programmatically is unaffected. If you match on the model-facing text of either
|
|
258
|
+
tool, expect the envelope.
|
|
259
|
+
|
|
260
|
+
- 9ac8dd4: A completion inbox hears only about the tasks its own run launched, and a supervisor releases the gateway it borrowed
|
|
261
|
+
|
|
262
|
+
`TaskGateway.onTaskCompleted` is a broadcast and `TaskHandle` carries no run id,
|
|
263
|
+
so every inbox attached to a gateway was handed every completion on it.
|
|
264
|
+
Measured: two inboxes on one gateway, one run launches a task, and the OTHER
|
|
265
|
+
run drains it — it would have been told "a task you launched has finished", a
|
|
266
|
+
false statement, over another run's worker output. A shared gateway is not an
|
|
267
|
+
abuse of the API: `SupervisorAgentConfig.gateway` takes one, and a host that
|
|
268
|
+
owns a gateway reuses it.
|
|
269
|
+
|
|
270
|
+
Separately, nothing ever called `CompletionInbox.close()`. Three sequential
|
|
271
|
+
`SupervisorAgent` runs against one host gateway left three live subscriptions,
|
|
272
|
+
each still holding its run's handles, and the set only grew.
|
|
273
|
+
|
|
274
|
+
**Breaking, and what to do.**
|
|
275
|
+
|
|
276
|
+
- `CompletionInbox` now ignores a completion for a task it was not told about.
|
|
277
|
+
If you drive `buildCoordinatorTools` there is nothing to do — `create_task`
|
|
278
|
+
declares every launch, blocking and background alike. If you launch tasks
|
|
279
|
+
some other way and expect notifications, call `inbox.launched(taskId)` after
|
|
280
|
+
the launch. `inbox.expect(taskId)` already implies it.
|
|
281
|
+
- `SupervisorAgent` closes the inbox it created when the run ends, including
|
|
282
|
+
when setup throws. An inbox you construct yourself is still yours to close.
|
|
283
|
+
- `close()` now clears what the inbox owned and claimed as well as what it
|
|
284
|
+
queued, so a closed inbox cannot be re-armed through a stale reference.
|
|
285
|
+
|
|
286
|
+
The ordering that would otherwise turn this into lost results is handled in
|
|
287
|
+
two layers. `gateway.createTask` resolves one microtask before its caller can
|
|
288
|
+
say who owns the task, so a worker that finishes inside that window is
|
|
289
|
+
announced first. An unowned announcement is therefore BUFFERED rather than
|
|
290
|
+
dropped, and ownership may be claimed retroactively; the buffer is bounded at
|
|
291
|
+
32 entries so that on a shared gateway it cannot accumulate every other run's
|
|
292
|
+
worker output, and an eviction is logged at WARN so a dropped completion is
|
|
293
|
+
never inferable only from an absence. Where the buffer could not hold an entry,
|
|
294
|
+
`launched()` also asks `gateway.getTask` — an assumption that a just-settled
|
|
295
|
+
task is still findable, now stated on `TaskGateway.getTask` itself so a host
|
|
296
|
+
that cannot meet it knows it is the one paying.
|
|
297
|
+
|
|
298
|
+
- 9ac8dd4: `create_task` offers `background: true` only when there is somewhere for the result to arrive
|
|
299
|
+
|
|
300
|
+
A background launch returns a task id and tells the model its result will come
|
|
301
|
+
"later, as a task notification". The `CompletionInbox` is the only thing that
|
|
302
|
+
delivers one — it holds the run open for the outstanding worker and puts the
|
|
303
|
+
completion into the transcript. `buildCoordinatorTools` mounted the parameter
|
|
304
|
+
whether or not it was given an inbox, so a host without one had a tool
|
|
305
|
+
advertising a channel that did not exist. Nothing failed loudly, because the
|
|
306
|
+
launch itself succeeded; the result simply never arrived.
|
|
307
|
+
|
|
308
|
+
Without a `completionInbox`, `create_task` no longer declares `background` and
|
|
309
|
+
its description no longer mentions it. Everything else is unchanged: the
|
|
310
|
+
blocking path, `wait_for_task`, `cancel_task` and `agent_task_list` are all
|
|
311
|
+
still mounted. Pass a `completionInbox` — to `buildCoordinatorTools` **and** to
|
|
312
|
+
`drainQuery` — to get background launching back. `SupervisorAgent` does both
|
|
313
|
+
already, so a host using it sees no change.
|
|
314
|
+
|
|
315
|
+
A `background: true` that reaches `execute` some other way — a directly
|
|
316
|
+
constructed definition — is REFUSED, naming the missing piece, rather than
|
|
317
|
+
quietly turned into a blocking call: the caller asked for something that
|
|
318
|
+
returns immediately, and giving them a different thing is accepting work whose
|
|
319
|
+
stated terms cannot be met. The abandoned-wait messages on `create_task` and
|
|
320
|
+
`wait_for_task` no longer promise a notification either, and
|
|
321
|
+
`agent_task_list` stops telling the model to avoid the listing when the
|
|
322
|
+
listing is the only route left.
|
|
323
|
+
|
|
324
|
+
The parameter is withheld rather than refused per call, and rather than thrown
|
|
325
|
+
at construction. A parameter the model is never shown costs nothing; one it is shown and then
|
|
326
|
+
denied costs prompt-prefix tokens plus an iteration per attempt. And a throw
|
|
327
|
+
would break a caller doing something legitimate — an inbox-less coordinator
|
|
328
|
+
surface is a supported configuration. This is the same reasoning that made an
|
|
329
|
+
empty roster withhold `create_task` rather than refuse to build.
|
|
330
|
+
|
|
331
|
+
### Minor Changes
|
|
332
|
+
|
|
333
|
+
- a39c2ed: A compaction pass now reports both of its outcomes.
|
|
334
|
+
|
|
335
|
+
Two gaps, in opposite directions, in the same function.
|
|
336
|
+
|
|
337
|
+
**A compaction that sheds nothing was invisible to everyone.** All three decline
|
|
338
|
+
paths — the reducer throws, it returns no fewer messages than it was given, or
|
|
339
|
+
its result splits a `tool_use` from its `tool_result` and is refused wholesale —
|
|
340
|
+
reached a log line and stopped there. A host that silences its logger, which
|
|
341
|
+
every command-line entry point does, made a failed compaction invisible to the
|
|
342
|
+
user, to the host _and_ to the model at once. The run then continued at full
|
|
343
|
+
context toward a provider rejection several turns later that named none of this.
|
|
344
|
+
A shed that did not happen is exactly as consequential as one that did, and only
|
|
345
|
+
one of them was on the wire.
|
|
346
|
+
|
|
347
|
+
New `compaction_failed` event (wire: `compaction.failed`) carrying `cause`
|
|
348
|
+
(`reducer_threw` | `shed_nothing` | `split_tool_pair`), the unchanged message
|
|
349
|
+
count, and the reducer's error where there was one. The cause is on the event
|
|
350
|
+
because the three want different responses: one may succeed next pass, one will
|
|
351
|
+
decline identically every time, and one is a reducer bug that `findSafeTrimIndex`
|
|
352
|
+
exists to prevent.
|
|
353
|
+
|
|
354
|
+
**And a compaction that succeeded was invisible on the path most hosts take.**
|
|
355
|
+
`compaction_completed` was emitted only from the structured working-state path.
|
|
356
|
+
The reducer path — taken by any host-supplied `contextReducer` and by
|
|
357
|
+
`strategy: 'sliding-window'` — emitted nothing at all, so the event whose own
|
|
358
|
+
documentation says it exists because "a host could not show the user that context
|
|
359
|
+
was dropped" never reached the hosts most likely to need it. It is emitted from
|
|
360
|
+
both paths now.
|
|
361
|
+
|
|
362
|
+
That second one was found by a test written for the first: asserting that a
|
|
363
|
+
successful compaction does _not_ report a failure is what showed it reported
|
|
364
|
+
nothing.
|
|
365
|
+
|
|
366
|
+
**If you switch exhaustively over `RunEvent`, you need a case for
|
|
367
|
+
`compaction_failed`.** Nothing else changes: no existing event's shape moved, and
|
|
368
|
+
a host that ignores unknown events is unaffected. The A2A bridge deliberately
|
|
369
|
+
does not forward either compaction event — a peer models a task lifecycle and
|
|
370
|
+
cannot act on how this runtime manages its own context.
|
|
371
|
+
|
|
372
|
+
- f6e0594: `token_usage_updated` now carries the current context size and the window it is measured against.
|
|
373
|
+
|
|
374
|
+
A host built a context indicator, and it could not have been right. The event
|
|
375
|
+
carried `usage` — **cumulative run spend**, summed over every turn, monotonically
|
|
376
|
+
increasing and untouched by compaction — and nothing about the size of the
|
|
377
|
+
conversation being sent. So the host divided cumulative spend by a context
|
|
378
|
+
window guessed from a substring of the model name, and rendered the result as an
|
|
379
|
+
unqualified percentage, continuously.
|
|
380
|
+
|
|
381
|
+
Both terms were wrong, and the numerator was the worse of the two. A guessed
|
|
382
|
+
window is wrong by a bounded factor. Cumulative spend has three properties that
|
|
383
|
+
make it not merely imprecise but actively misleading:
|
|
384
|
+
|
|
385
|
+
- It **never decreases**, by explicit design — the accumulator is documented as
|
|
386
|
+
monotone so it can never under-report a bill. Compaction does not reduce it.
|
|
387
|
+
- It grows **superlinearly in turn count**, because every turn re-sends the whole
|
|
388
|
+
history and counts those prompt tokens again. Ten turns over a 50k context
|
|
389
|
+
accumulate roughly 500k.
|
|
390
|
+
- It measures **spend**, which is the right quantity for cost and the wrong one
|
|
391
|
+
for occupancy.
|
|
392
|
+
|
|
393
|
+
So an indicator built on it saturates at full long before the context is, and it
|
|
394
|
+
is **anti-correlated with what it claims in exactly the regime a user cares
|
|
395
|
+
about**: a long conversation reads FULL while the real context may be a fifth of
|
|
396
|
+
the window. That alarms people into compacting or restarting when they have
|
|
397
|
+
room — worse than showing nothing, because silence does not tell you something
|
|
398
|
+
false in red. In the other direction, a driver that reports no usage shows 0%
|
|
399
|
+
for a conversation that is really there.
|
|
400
|
+
|
|
401
|
+
The kernel already computed the right numbers on every iteration and kept them
|
|
402
|
+
to itself. `measureContext()` is now exported, and the event carries four new
|
|
403
|
+
optional fields: `contextTokens`, `contextMeasuredBy` (`'provider' | 'estimate'`),
|
|
404
|
+
`contextWindowTokens` and `windowSource` (`'config' | 'model-table' | 'default'`).
|
|
405
|
+
They are named apart from the cumulative figures beside them deliberately —
|
|
406
|
+
reaching for the wrong one should be a visible mistake, not a plausible guess.
|
|
407
|
+
|
|
408
|
+
**They are absent when the run has no compaction configuration**, because nothing
|
|
409
|
+
then resolves a window and inventing one would be the guess this replaces. A
|
|
410
|
+
surface should show what it can name rather than a fraction it cannot ground.
|
|
411
|
+
|
|
412
|
+
**A fraction is only as honest as the weaker of its terms.** `contextMeasuredBy`
|
|
413
|
+
and `windowSource` exist so a surface can pass that on rather than presenting an
|
|
414
|
+
estimate as a measurement. Nothing existing changes: `usage` and `cost` are
|
|
415
|
+
untouched, and the new fields are additive and optional.
|
|
416
|
+
|
|
417
|
+
- a39c2ed: A verification rule can name one tool and one argument.
|
|
418
|
+
|
|
419
|
+
Every pattern rule an operator could write was one of two wrong things.
|
|
420
|
+
|
|
421
|
+
`custom_pattern` carries no tool scope, so a rule written about `bash` decided
|
|
422
|
+
`edit` calls as well — `target: 'both'` prefixes the tool name to the subject
|
|
423
|
+
rather than requiring it, which is not a scope. And `target: 'args'` tests
|
|
424
|
+
`JSON.stringify(toolInput)`, so the subject is the JSON _text_ of the whole
|
|
425
|
+
argument object: the natural, anchored thing to write, `^git push`, is tested
|
|
426
|
+
against `{"command":"git push origin main"}` and can never match. The rule then
|
|
427
|
+
decides nothing, silently. Pinning the tool cost the anchor; anchoring cost the
|
|
428
|
+
tool scope.
|
|
429
|
+
|
|
430
|
+
New `argument_pattern` rule — `toolNames`, `argument`, `pattern`, `decision` —
|
|
431
|
+
whose subject is the named argument's own value, so an anchored pattern means
|
|
432
|
+
what it looks like it means. The refusal names the argument as well as the
|
|
433
|
+
pattern, which is what tells a model whether a different value could get through.
|
|
434
|
+
|
|
435
|
+
It deliberately decides nothing in three cases: the tool was not called, the
|
|
436
|
+
argument is absent, or the argument holds an object or an array. No string a
|
|
437
|
+
pattern could match says anything true about a structured value, and serialising
|
|
438
|
+
one to try would put this rule back where `custom_pattern` already is. To refuse
|
|
439
|
+
a tool over the _shape_ of its input, deny it by name. Numbers and booleans are
|
|
440
|
+
matched rather than skipped — they render unambiguously, and a rule about a
|
|
441
|
+
numeric argument is a reasonable thing to write.
|
|
442
|
+
|
|
443
|
+
`custom_pattern` is unchanged and not deprecated: matching anywhere in the
|
|
444
|
+
serialised input without caring where is a real use, and it is now documented as
|
|
445
|
+
being that rather than reading as something it never was. The trap was the name,
|
|
446
|
+
not the behaviour.
|
|
447
|
+
|
|
448
|
+
- 9ac8dd4: A run that ends any way other than a plain final answer no longer throws away a finished worker's output
|
|
449
|
+
|
|
450
|
+
The iteration loop consulted its completion inbox at exactly one place: the
|
|
451
|
+
branch where the model stops calling tools and answers. It leaves by eight other
|
|
452
|
+
routes, and three of them are ordinary ways for a run to END — a tool the author
|
|
453
|
+
marked `terminal`, a captured `structured_output`, and the host's `stopWhen`.
|
|
454
|
+
A background or abandoned worker that finished while any of those was deciding
|
|
455
|
+
had its result dropped: the gateway held it, the run closed, nothing read it.
|
|
456
|
+
Measured before the fix — terminal-tool exit and `stopWhen` exit both delivered
|
|
457
|
+
nothing; the final-answer exit delivered in 44 ms.
|
|
458
|
+
|
|
459
|
+
Delivery now happens in a `finally` around the loop, so it does not depend on
|
|
460
|
+
each exit remembering — including the two `return`s and a generator abandoned by
|
|
461
|
+
its consumer, which no post-loop statement reaches.
|
|
462
|
+
|
|
463
|
+
**What you may observe.** On those exits `Run.messages` can now end with a
|
|
464
|
+
`task-notification` user message after the assistant's last message. The answer
|
|
465
|
+
is on `Run.result`, as before. If you were reading the answer off the last
|
|
466
|
+
element of `Run.messages`, that assumption was already unsafe whenever a
|
|
467
|
+
notification landed mid-run; it is now unsafe in three more places.
|
|
468
|
+
|
|
469
|
+
**Which exits wait, and which only deliver.** A hold buys the model a turn in
|
|
470
|
+
which to use a result, so it is only worth paying where a turn can still
|
|
471
|
+
happen. A terminal tool and a captured `structured_output` have decided the
|
|
472
|
+
answer, so those deliver what arrived and stop. `stopWhen` is a programmable
|
|
473
|
+
halt that says nothing about whether the answer is complete, so it now HOLDS
|
|
474
|
+
like the ordinary final-answer exit — a precedence rule chosen here, not
|
|
475
|
+
something `stopWhen` implies — and costs exactly one extra turn, after which
|
|
476
|
+
the predicate fires again with nothing pending.
|
|
477
|
+
|
|
478
|
+
The stop reason survives that extra turn. `stopWhen` is consulted only after a
|
|
479
|
+
tool batch, so when the extra turn is prose the predicate is never asked again
|
|
480
|
+
and the run leaves by the ordinary route — which would have reported
|
|
481
|
+
`stopReason: 'end_turn'`, naming the shape of the last message rather than the
|
|
482
|
+
host's decision. A run that ends because a host said stop now reports
|
|
483
|
+
`'stop_condition'` whether or not a delegated result delayed it by a turn. If
|
|
484
|
+
the extra turn instead runs more tools, the predicate is asked again and
|
|
485
|
+
answers for itself.
|
|
486
|
+
|
|
487
|
+
A run that ends with a worker still running now says so on
|
|
488
|
+
`Run.abandonedTaskIds` rather than leaving the impression the result arrived.
|
|
489
|
+
|
|
490
|
+
- 9ac8dd4: A run that ends over a still-running worker says so, and the untrusted envelope's own label can no longer close it
|
|
491
|
+
|
|
492
|
+
Three things an adversarial review of the completion path found.
|
|
493
|
+
|
|
494
|
+
**`Run.abandonedTaskIds`.** A run can settle while a worker it launched is
|
|
495
|
+
still going — the model answered, a terminal tool decided the result, a
|
|
496
|
+
`stopWhen` fired. Until now nothing said so, which left the impression the
|
|
497
|
+
worker's result had been delivered. The run now names those task ids.
|
|
498
|
+
|
|
499
|
+
They are **named, not cancelled**, and that is the decision: giving up on a
|
|
500
|
+
wait is a statement about the waiter, not about the work — the rule this
|
|
501
|
+
subsystem already applies to `wait_for_task` — and "the parent answered early"
|
|
502
|
+
is a weaker warrant for killing a child than "the clock ran out", not a
|
|
503
|
+
stronger one. A worker mid-write is not the kernel's to judge. A host that
|
|
504
|
+
wants the work stopped has `cancel_task` and the run's abort controller, and
|
|
505
|
+
now has the ids to use them on.
|
|
506
|
+
|
|
507
|
+
**`wrapUntrusted` neutralises its own delimiter inside `provenance`.** The body
|
|
508
|
+
was defanged and the attributes escaped; the provenance line was interpolated
|
|
509
|
+
raw, and every caller in the SDK builds it from a value it did not author — an
|
|
510
|
+
agent id from a roster, a server name from a connector manifest. A provenance
|
|
511
|
+
carrying `</namzu-untrusted>` ended the block before the content it was
|
|
512
|
+
introducing. This affects the blocking `create_task`, `wait_for_task` and the
|
|
513
|
+
`Agent` tool as well as the two paths framed in this release.
|
|
514
|
+
|
|
515
|
+
**`background: true` with no inbox is refused, not silently made blocking**,
|
|
516
|
+
and the sentences match. The abandoned-wait messages on `create_task` and
|
|
517
|
+
`wait_for_task` promised "its result will arrive separately as a task
|
|
518
|
+
notification" unconditionally — false with no inbox, and a model told to expect
|
|
519
|
+
a message waits for it. They now say where the result actually is. The
|
|
520
|
+
`agent_task_list` description likewise stops telling the model not to use the
|
|
521
|
+
listing when, without an inbox, the listing is the only route left to an
|
|
522
|
+
abandoned launch's output.
|
|
523
|
+
|
|
524
|
+
`CompletionInbox` gains `outstandingTaskIds`, which reads the ids and cancels
|
|
525
|
+
nothing.
|
|
526
|
+
|
|
527
|
+
- 9ac8dd4: A run holding for a background worker waits a share of its own budget, not a fixed two minutes
|
|
528
|
+
|
|
529
|
+
`BACKGROUND_TASK_GRACE_MS = 120_000` was unrelated to the run it bounded, and
|
|
530
|
+
wrong in both directions at once. Measured: a run configured `timeoutMs: 20_000`
|
|
531
|
+
was held open for **120,267 ms** — six times its own budget — because the hold
|
|
532
|
+
sits inside an iteration and the run guard only checks between them, so nothing
|
|
533
|
+
could interrupt it. In the other direction, on a run with hours left the same
|
|
534
|
+
two minutes abandoned delegated workers observed at 4m21s, 5m58s and 8m04s, all
|
|
535
|
+
comfortably inside the hour `DELEGATION_TIMEOUT_MS` already declares.
|
|
536
|
+
|
|
537
|
+
The hold is now `min(remainingBeforeFinalize × 0.5, DELEGATION_TIMEOUT_MS)`,
|
|
538
|
+
where `remainingBeforeFinalize` is the time left before the run guard stops
|
|
539
|
+
asking for more work and asks for a closing summary (90% of `timeoutMs`), less
|
|
540
|
+
what the run has spent — carried across a resume, so a checkpointed run sizes
|
|
541
|
+
the hold from what is left of the RUN rather than of the process now hosting
|
|
542
|
+
it, and read when the wait starts rather than at the top of the iteration.
|
|
543
|
+
|
|
544
|
+
- **Half, not all.** The hold exists to put a worker's result where the model
|
|
545
|
+
can read it, and reading it costs a turn. Spending everything remaining would
|
|
546
|
+
deliver a notification into a run with no turn left to act on it — the same
|
|
547
|
+
failure the mechanism exists to prevent.
|
|
548
|
+
- **Bounded against the boundary that binds.** Measuring to the DEADLINE was
|
|
549
|
+
the first attempt and it looked safe: a hold cannot outlive the deadline
|
|
550
|
+
either way. But half of the time-to-deadline, started just under the warning
|
|
551
|
+
threshold, ends at 95% of the budget — so the slice the guard keeps for the
|
|
552
|
+
run to produce a closing answer is half spent waiting for the result that
|
|
553
|
+
answer was supposed to use. Against the finalize point the hold cannot reach
|
|
554
|
+
that reserve at all, which is what makes the guard's inability to interrupt
|
|
555
|
+
a hold a non-issue rather than a smaller issue.
|
|
556
|
+
- **A floor of zero, deliberately.** A run with no time left before it must
|
|
557
|
+
start finishing has no turn in which to read a notification. Nothing is
|
|
558
|
+
dropped by it: the wait returns before it looks at its timer when a
|
|
559
|
+
completion is already in hand.
|
|
560
|
+
|
|
561
|
+
**What changes for you.** A run with a short `timeoutMs` finishes when it said
|
|
562
|
+
it would instead of overrunning by minutes. A run with a long one keeps its
|
|
563
|
+
worker instead of abandoning it. If you were relying on a fixed two-minute
|
|
564
|
+
settle regardless of run configuration, set `timeoutMs` to about four and a half minutes to
|
|
565
|
+
get the same hold.
|
|
566
|
+
|
|
567
|
+
- 585a592: A caller can ask which effort levels a model accepts.
|
|
568
|
+
|
|
569
|
+
The answer existed, was modelled carefully, and was reachable only from inside
|
|
570
|
+
one driver. That matters because effort is **refused, not clamped**: a level a
|
|
571
|
+
model does not have makes the vendor reject the request, so a control offering
|
|
572
|
+
the wrong one produces a run that fails at the start rather than a quieter one.
|
|
573
|
+
|
|
574
|
+
Every option open to a caller without the answer was bad. Offering all five
|
|
575
|
+
breaks some models. Offering the intersection hides `xhigh` and `max` from every
|
|
576
|
+
model that has them, which is most of the reason to build such a control. And
|
|
577
|
+
copying the table looks fine and is worst: the ceiling has moved twice already,
|
|
578
|
+
so a copy goes stale on the next model and goes stale **silently**, surfacing as
|
|
579
|
+
a vendor rejection rather than a failing build.
|
|
580
|
+
|
|
581
|
+
**New optional `LLMProvider.effortLevelsFor(model, thinking?)`.** Three states,
|
|
582
|
+
each meaning something different: the method absent means the driver has no
|
|
583
|
+
effort concept at all and setting one will be refused; an empty array means the
|
|
584
|
+
driver implements effort and this model has none; a non-empty array is the set
|
|
585
|
+
to offer.
|
|
586
|
+
|
|
587
|
+
**`thinking` is a parameter, and that is the point.** At least one model family
|
|
588
|
+
accepts a narrower set while thinking is disabled than while it is on — so an
|
|
589
|
+
API returning two sibling arrays invites a caller to render a picker from one
|
|
590
|
+
and send the other, a combination the vendor rejects, on exactly one family.
|
|
591
|
+
Passing the configuration you will actually send makes that unspellable: there
|
|
592
|
+
is one answer and it is the one for your request.
|
|
593
|
+
|
|
594
|
+
The driver's implementation shares the same two resolution steps the request
|
|
595
|
+
path uses, so a caller's picker and the request it produces cannot disagree.
|
|
596
|
+
|
|
597
|
+
`@namzu/anthropic` also now exports `resolveThinkingCapability`,
|
|
598
|
+
`resolveThinkingBody`, `resolveEffort` and their types, for a caller that needs
|
|
599
|
+
the fuller picture — whether thinking can be switched off at all, not only which
|
|
600
|
+
effort levels apply. Prefer `effortLevelsFor` where it suffices: it is
|
|
601
|
+
provider-agnostic and cannot return the wrong one of the two sets.
|
|
602
|
+
|
|
603
|
+
Separately, the live wire-contract suite now retries a transient status rather
|
|
604
|
+
than reporting it as a contract failure. A 529 says the service is busy and
|
|
605
|
+
answers nothing about whether a schema is expressible — so a test named "every
|
|
606
|
+
shipped tool is expressible on this wire" was claiming something the run had not
|
|
607
|
+
established. That cost two manual re-runs in one day to discover the wire had no
|
|
608
|
+
opinion.
|
|
609
|
+
|
|
610
|
+
### Patch Changes
|
|
611
|
+
|
|
612
|
+
- 9ac8dd4: A background task whose completion arrived early no longer holds the run open forever
|
|
613
|
+
|
|
614
|
+
`CompletionInbox.drain()` handed the completion over and marked it claimed, but
|
|
615
|
+
left the task on the OUTSTANDING set. That set is meant to hold ids that are
|
|
616
|
+
still running, and only the gateway's completion listener takes an id off it —
|
|
617
|
+
so if the listener ran BEFORE the launching call said `expect()`, the id was
|
|
618
|
+
added to a set nothing would ever clear.
|
|
619
|
+
|
|
620
|
+
That order is reachable rather than theoretical: `expect()` runs one microtask
|
|
621
|
+
after `gateway.createTask()` resolves, and a worker that finishes fast is
|
|
622
|
+
announced in between. The result of it was `hasPendingWork === true` for the
|
|
623
|
+
rest of the run, with an empty inbox — so every attempt to settle waited out the
|
|
624
|
+
full background grace period for a result that was already in the transcript,
|
|
625
|
+
and did it again on the next turn, and the next.
|
|
626
|
+
|
|
627
|
+
Nothing to do on upgrade. If you were seeing runs pause for two minutes before
|
|
628
|
+
their final answer with no background work outstanding, this was why.
|
|
629
|
+
|
|
630
|
+
- 3d4315e: `PrepareStepResult.activeTools` documented the opposite of what it does.
|
|
631
|
+
|
|
632
|
+
Its comment promised that unregistered names are dropped so a phase list
|
|
633
|
+
outliving a tool rename would "narrow the surface, not kill the agent mid-run".
|
|
634
|
+
Since the list began bounding what may RUN rather than only what the model is
|
|
635
|
+
shown, dropping every name leaves the step able to call nothing — so the code
|
|
636
|
+
and its own documentation had said different things.
|
|
637
|
+
|
|
638
|
+
**The behaviour is right and the comment was wrong.** This list means "only
|
|
639
|
+
these": when a rename outlives it, the only set satisfying "only the tools that
|
|
640
|
+
no longer exist" is the empty one. Widening back to the run's list would grant
|
|
641
|
+
precisely the tools the caller asked to exclude, on the grounds that their own
|
|
642
|
+
list failed — a control that stops applying because it was aged out, which is
|
|
643
|
+
worse than a step that answers from what it already has. The run continues
|
|
644
|
+
either way; nothing crashes.
|
|
645
|
+
|
|
646
|
+
The warning now distinguishes the two cases, because they have different
|
|
647
|
+
consequences: some names dropped narrows the step, and all of them dropped
|
|
648
|
+
leaves it unable to call anything. "Ignoring them" was accurate for the first
|
|
649
|
+
and misleading for the second.
|
|
650
|
+
|
|
651
|
+
**Worth knowing if you rely on this:** the warning goes to the logger, so a host
|
|
652
|
+
that silences its logger sees a phase quietly stop doing anything. That is a real
|
|
653
|
+
gap and it is named here rather than papered over.
|
|
654
|
+
|
|
3
655
|
## 7.0.0
|
|
4
656
|
|
|
5
657
|
### 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;
|
|
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;CA8R1B"}
|