@ssheleg/agent-stack 0.19.1 → 0.21.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 +74 -0
- package/package.json +1 -1
- package/plugins/agent-stack/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-stack/skills/agent-harness/references/tools.md +44 -0
- package/plugins/agent-stack/skills/agent-interop/references/gateway.md +19 -0
- package/plugins/agent-stack/skills/agent-orchestrator/SKILL.md +16 -30
- package/plugins/agent-stack/skills/agent-orchestrator/references/llm-proxy-billing.md +90 -0
- package/plugins/agent-stack/skills/agent-orchestrator/references/patterns.md +45 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,77 @@
|
|
|
1
|
+
## v0.21.0 — the risk one tool cannot show you, and the money an iteration refund does not cover
|
|
2
|
+
|
|
3
|
+
Two findings, both of them about a rule that is right on one axis and silently assumed to
|
|
4
|
+
cover a second.
|
|
5
|
+
|
|
6
|
+
**Tool annotations are a risk vocabulary, and their defaults are asymmetric on purpose.**
|
|
7
|
+
`readOnlyHint` **false**, `destructiveHint` **true**, `idempotentHint` **false**,
|
|
8
|
+
`openWorldHint` **true** — so a server author who omits annotations entirely has declared
|
|
9
|
+
the *most dangerous* shape. That is the correct fail-closed choice and the opposite of what
|
|
10
|
+
most authors think they are doing: annotation **narrows** an assumption, silence widens it.
|
|
11
|
+
And every one is a hint rather than a contract — the specification says a client must treat
|
|
12
|
+
them as untrusted unless the server is, so an annotation may inform a UI or a policy default
|
|
13
|
+
and may never be the thing that decides whether a destructive call runs.
|
|
14
|
+
|
|
15
|
+
**The lethal trifecta now has a name here.** Private data, untrusted content, and the
|
|
16
|
+
ability to communicate externally: any two are safe, all three in one session are an
|
|
17
|
+
exfiltration path that no prompt-level instruction reliably closes. It lands in
|
|
18
|
+
`agent-harness/references/tools.md` rather than in a permission section for a structural
|
|
19
|
+
reason — **it is a property of the tool set assembled in a session, so per-tool analysis
|
|
20
|
+
cannot see it by construction.** Every tool can pass its own review and the combination
|
|
21
|
+
still be unsafe; adding a `fetch(url)` beside a private-data reader closes the triangle and
|
|
22
|
+
will not look like a security change. `agent-interop/references/gateway.md` gains the
|
|
23
|
+
composer's half: a gateway's whole value is assembling many servers into one surface, which
|
|
24
|
+
is exactly the operation that can create a risk none of its inputs had, so **granting a role
|
|
25
|
+
one more server is a trifecta question, not only a least-privilege one.**
|
|
26
|
+
|
|
27
|
+
**Refund the iteration, charge the money.** Our loop refunds an iteration on a recoverable
|
|
28
|
+
provider error — a 502 should not consume one of the ten attempts. That is right, and it
|
|
29
|
+
says nothing about money: the provider still billed the call. A response that arrived and
|
|
30
|
+
then failed to parse was generated, metered and charged. The observed failure is precise —
|
|
31
|
+
a harness charged its cost on the **success path** of `query()`, so a format error meant the
|
|
32
|
+
cost was never added, and the code compensated inside the exception handler. Where a cost
|
|
33
|
+
ceiling is the *primary* bound (that harness ships `cost_limit = 3.0` with the step limit
|
|
34
|
+
**off**), a leak there is a leak in the only guard. Accounting belongs in a `finally`, and
|
|
35
|
+
**a budget that under-counts is worse than one that over-counts**: over-counting stops a run
|
|
36
|
+
early and visibly, under-counting is invisible until the invoice and biases toward the
|
|
37
|
+
failing runs, which are the expensive ones.
|
|
38
|
+
|
|
39
|
+
## v0.20.0 — what a trajectory cannot carry across a vendor, and where the capability goes
|
|
40
|
+
|
|
41
|
+
Three findings from the harvest, all landing on the same question: **which model does what,
|
|
42
|
+
and what survives when that changes mid-run.** §6 shipped three traps about routing, and all
|
|
43
|
+
three quietly assume the *request* is what moves.
|
|
44
|
+
|
|
45
|
+
- **A trajectory carries a vendor credential, and it may not be attached to the reasoning.**
|
|
46
|
+
Tool calls and results are portable — different structure, same meaning, re-render and
|
|
47
|
+
send. Reasoning is portable *text* plus a **non-portable credential** the vendor attaches
|
|
48
|
+
to prove the reasoning is its own, and vendors disagree on what they demand: one end
|
|
49
|
+
validates nothing, the other rejects any credential it did not issue. The credential
|
|
50
|
+
sometimes sits on the **tool call** rather than the reasoning — which is why *"just strip
|
|
51
|
+
all reasoning before failing over"*, the policy that sounds safest, is the one that
|
|
52
|
+
produces a 400. Store trajectories in a neutral internal format, keep the text, discard
|
|
53
|
+
the credential, re-render per vendor at send time, and put the failover boundary
|
|
54
|
+
**between turns**. A fallback chain never exercised mid-trajectory has not been tested: a
|
|
55
|
+
green health probe answers a question about the endpoint, not about your history.
|
|
56
|
+
- **Capability is not spent evenly — the planner is the bottleneck.** *Plan-and-Act*
|
|
57
|
+
(arXiv:2503.09572) found that with good enough planning a relatively simple executor
|
|
58
|
+
suffices, and with a wrong decomposition every downstream executor is building on a false
|
|
59
|
+
premise; their 54% on WebArena-Lite came from improving the **planner**, not the executor.
|
|
60
|
+
So the strongest model and the most carefully written prompt go to the **manager**. It
|
|
61
|
+
also says where to look when a multi-agent system underperforms: **a weak plan is
|
|
62
|
+
invisible in every executor's transcript**, because each one did its own step correctly.
|
|
63
|
+
- **Steps the agent cannot see buy nothing.** Standard agents have no budget awareness, so
|
|
64
|
+
at **300 steps** they still plateau at roughly what they achieve at **30**. A
|
|
65
|
+
max-iteration guard is the floor of this rather than the mechanism — it stops the spend
|
|
66
|
+
and never changes the behaviour that led there.
|
|
67
|
+
|
|
68
|
+
**And the second displacement in two releases.** The body was at 4609/4750 after v0.19.0
|
|
69
|
+
bought that headroom back; these three lines would have left **5 tokens**. §1's context
|
|
70
|
+
dataclass and sub-agent base class moved to `references/patterns.md`, beside the loop
|
|
71
|
+
listing that went there in v0.19.0, landing the body at **4631/4750**. That is now twice in
|
|
72
|
+
a row that an addition has cost a displacement, which is the auditor's own signal — *the
|
|
73
|
+
answer then is a split, not a trim* — and it is filed rather than absorbed again.
|
|
74
|
+
|
|
1
75
|
## v0.19.1 — the class the umbrella had been catching for us, twice
|
|
2
76
|
|
|
3
77
|
`B-126`'s board row shipped in v0.19.0 with **nine cells against the eight its header
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"name": "agent-stack",
|
|
4
4
|
"displayName": "Agent Stack",
|
|
5
5
|
"description": "Four skills: agent-orchestrator — tool-calling loops, pipelines with checkpoints, provider routing with fallback, memory architecture, plus the wallet side of reselling LLM access; agent-evals — run/trace/thread evals, LLM judges, and fixtures grown from production; agent-interop — MCP servers and clients, A2A agent cards, the MCP Registry, and gateways; agent-harness — system prompts, tool shaping, workflow-vs-agent, and auditing an agent system.",
|
|
6
|
-
"version": "0.
|
|
6
|
+
"version": "0.21.0",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "ssheleg",
|
|
9
9
|
"url": "https://x.com/sshlg93"
|
|
@@ -126,6 +126,48 @@ the mistake cannot be made**, rather than documenting the mistake.
|
|
|
126
126
|
- A required `confirm: true` on a destructive action, so a partially-formed call fails
|
|
127
127
|
closed.
|
|
128
128
|
|
|
129
|
+
## Annotations, and the risk one tool cannot show you
|
|
130
|
+
|
|
131
|
+
MCP tools carry four hints, and their defaults are asymmetric on purpose:
|
|
132
|
+
|
|
133
|
+
| Hint | Default | So an unannotated tool is assumed to be |
|
|
134
|
+
|---|---|---|
|
|
135
|
+
| `readOnlyHint` | `false` | one that writes |
|
|
136
|
+
| `destructiveHint` | `true` | destructive |
|
|
137
|
+
| `idempotentHint` | `false` | unsafe to repeat |
|
|
138
|
+
| `openWorldHint` | `true` | reaching outside your system |
|
|
139
|
+
|
|
140
|
+
**A server author who omits annotations entirely has declared the most dangerous shape**,
|
|
141
|
+
which is the correct fail-closed choice and the opposite of what most authors assume they
|
|
142
|
+
are doing. Annotate to *narrow* the assumption; silence widens it.
|
|
143
|
+
|
|
144
|
+
**Every one is a hint, not a contract.** The specification is explicit that a client must
|
|
145
|
+
treat descriptions and annotations as untrusted unless the server itself is trusted — so an
|
|
146
|
+
annotation informs a UI and a policy default, and may never be the thing that decides
|
|
147
|
+
whether a destructive call runs.
|
|
148
|
+
|
|
149
|
+
### The lethal trifecta — a property of the session, not of a tool
|
|
150
|
+
|
|
151
|
+
Three capabilities that are individually ordinary and jointly an exfiltration path:
|
|
152
|
+
|
|
153
|
+
1. access to **private data**,
|
|
154
|
+
2. exposure to **untrusted content**,
|
|
155
|
+
3. the ability to **communicate externally**.
|
|
156
|
+
|
|
157
|
+
Any two are safe. All three in one session mean untrusted content can instruct the agent to
|
|
158
|
+
read private data and send it out, and no prompt-level instruction reliably prevents it.
|
|
159
|
+
|
|
160
|
+
**The reason it belongs here rather than in a permission check:** the trifecta is a property
|
|
161
|
+
of *the tool set assembled in a session*, so **per-tool analysis cannot see it**. Every tool
|
|
162
|
+
can pass its own review and the combination still be unsafe — which is why a review that
|
|
163
|
+
walks a server's tools one at a time answers a different question than "what can this
|
|
164
|
+
session do". Look at the set, and at what a gateway composes into it.
|
|
165
|
+
|
|
166
|
+
**The practical consequence for a tool author:** a tool that only reads private data is
|
|
167
|
+
fine; adding a `fetch(url)` beside it is what closes the triangle, and it will not look like
|
|
168
|
+
a security change in review.
|
|
169
|
+
|
|
170
|
+
|
|
129
171
|
## Evaluating tools
|
|
130
172
|
|
|
131
173
|
Tools deserve **thorough documentation and testing**, and testing means running the agent
|
|
@@ -150,5 +192,7 @@ Fix the interface, not the prompt, when the fault is in this table.
|
|
|
150
192
|
can do nothing with.
|
|
151
193
|
- **Treating tool output as trusted.** It is attacker-controlled input if the server is; the
|
|
152
194
|
specification says descriptions and annotations are untrusted unless the server is.
|
|
195
|
+
- **Reviewing tools one at a time.** The lethal trifecta is a property of the assembled set;
|
|
196
|
+
a per-tool review cannot see it by construction.
|
|
153
197
|
- **Adding a tool to fix a prompt problem.** The set grows, selection degrades, and the
|
|
154
198
|
original defect is still there.
|
|
@@ -147,6 +147,25 @@ authorization** — the last being the per-tool control that a generic gateway l
|
|
|
147
147
|
That is the strongest single argument for it in a cluster that already runs Gateway API: the
|
|
148
148
|
routing objects are ones your platform team already reviews.
|
|
149
149
|
|
|
150
|
+
## The gateway is where the lethal trifecta gets assembled
|
|
151
|
+
|
|
152
|
+
A gateway's whole value is composing many servers into one surface, and that is exactly the
|
|
153
|
+
operation that can create a risk none of its inputs had. The **lethal trifecta** — private
|
|
154
|
+
data, untrusted content, and the ability to communicate externally — is a property of the
|
|
155
|
+
**tool set in a session**, so every upstream can pass its own review and the composition
|
|
156
|
+
still be unsafe.
|
|
157
|
+
|
|
158
|
+
Two consequences for whoever runs the gateway:
|
|
159
|
+
|
|
160
|
+
- **A role is a trifecta decision, not only a least-privilege one.** Granting a role one
|
|
161
|
+
more server is the moment to ask which of the three corners it just completed — not
|
|
162
|
+
whether that server is individually trustworthy.
|
|
163
|
+
- **Per-tool annotations do not answer it.** They are hints about one tool (`readOnlyHint`,
|
|
164
|
+
`destructiveHint`, `idempotentHint`, `openWorldHint`, and their defaults assume the
|
|
165
|
+
dangerous shape — `agent-harness/references/tools.md`), and no combination of per-tool
|
|
166
|
+
hints computes a session-level property.
|
|
167
|
+
|
|
168
|
+
|
|
150
169
|
## Traps
|
|
151
170
|
|
|
152
171
|
- **Introducing a gateway and leaving the direct paths open.** The policy is then advisory.
|
|
@@ -40,23 +40,12 @@ OrchestratorAgent.run(AgentContext)
|
|
|
40
40
|
|
|
41
41
|
### Shared Context Object
|
|
42
42
|
|
|
43
|
-
Pass a single immutable-ish context object to
|
|
43
|
+
Pass a single immutable-ish context object to every sub-agent. It carries the request
|
|
44
|
+
(`project_id`, `user_question`, `chat_history`), the machinery (`llm_router`, `tracker`,
|
|
45
|
+
`workflow_id`), the resolved provider and model, and one `extra` dict for pipeline flags.
|
|
46
|
+
The full dataclass is in [`references/patterns.md`](references/patterns.md) → *The
|
|
47
|
+
orchestrator's shared context and sub-agent protocol*.
|
|
44
48
|
|
|
45
|
-
```python
|
|
46
|
-
@dataclass
|
|
47
|
-
class AgentContext:
|
|
48
|
-
project_id: str
|
|
49
|
-
user_question: str
|
|
50
|
-
chat_history: list[Message]
|
|
51
|
-
llm_router: LLMRouter # provider abstraction with retry/fallback
|
|
52
|
-
tracker: WorkflowTracker # SSE event emitter for real-time UI
|
|
53
|
-
workflow_id: str # unique ID for this request
|
|
54
|
-
connection_config: ... | None # external resource config
|
|
55
|
-
user_id: str | None
|
|
56
|
-
preferred_provider: str | None # e.g. "openrouter"
|
|
57
|
-
model: str | None # e.g. "<provider>/<model-id>"
|
|
58
|
-
extra: dict[str, Any] # pipeline_action, flags, overrides
|
|
59
|
-
```
|
|
60
49
|
|
|
61
50
|
**Key principles:**
|
|
62
51
|
- Sub-agents never modify context — they return typed results
|
|
@@ -65,20 +54,10 @@ class AgentContext:
|
|
|
65
54
|
|
|
66
55
|
### Sub-Agent Protocol
|
|
67
56
|
|
|
68
|
-
Every sub-agent extends
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
class BaseAgent(ABC):
|
|
72
|
-
@abstractmethod
|
|
73
|
-
async def run(self, context: AgentContext, **kwargs) -> AgentResult: ...
|
|
74
|
-
|
|
75
|
-
@property
|
|
76
|
-
@abstractmethod
|
|
77
|
-
def name(self) -> str: ...
|
|
57
|
+
Every sub-agent extends one base class with a single abstract `run(context) -> Result`,
|
|
58
|
+
so the orchestrator never learns what any of them does internally. The class is in the same
|
|
59
|
+
reference section.
|
|
78
60
|
|
|
79
|
-
@staticmethod
|
|
80
|
-
def accum_usage(total, usage): ... # merge token counters
|
|
81
|
-
```
|
|
82
61
|
|
|
83
62
|
Typed result subclasses per agent (e.g. `SQLAgentResult` with `query`, `results`, `attempts`).
|
|
84
63
|
|
|
@@ -107,7 +86,9 @@ was gathered rather than returning nothing. The full listing is in
|
|
|
107
86
|
- **In-loop trimming**: At ~80% capacity, collapse older assistant+tool pairs into one-liner summaries
|
|
108
87
|
- **Token limit recovery**: On `LLMTokenLimitError`, compress to 60% and retry once. If still fails, return partial answer
|
|
109
88
|
- **Max iterations guard**: Always have a hard limit. On exhaustion, compose best-effort answer from data gathered so far
|
|
110
|
-
- **Iteration refund**: a recoverable provider error is not charged to that guard
|
|
89
|
+
- **Iteration refund**: a recoverable provider error is not charged to that guard — the
|
|
90
|
+
money still is, and accounting on the success path alone under-counts the worst runs
|
|
91
|
+
- **Budget awareness**: tell the model what is left, or 300 steps performs like 30
|
|
111
92
|
|
|
112
93
|
---
|
|
113
94
|
|
|
@@ -197,6 +178,11 @@ fallback chain and per-provider retry with exponential backoff, and its
|
|
|
197
178
|
- **Model selection has three levels** — the request, the tenant, the system
|
|
198
179
|
default — and a tenant override that silently loses to a request parameter is
|
|
199
180
|
how a cheap model ends up billed at a premium one's rate.
|
|
181
|
+
- **Those three assume the REQUEST is portable; the trajectory is not.** Reasoning carries
|
|
182
|
+
a vendor credential — sometimes on the tool call — so mid-turn failover can 400, and
|
|
183
|
+
*strip all reasoning* is what causes it. Fail over between turns.
|
|
184
|
+
- **Capability is not spent evenly**: the planner is the bottleneck, so the strongest model
|
|
185
|
+
goes to the manager, not to whichever agent does the most work.
|
|
200
186
|
## 7. Multi-Layer Memory System
|
|
201
187
|
|
|
202
188
|
Four layers, each with a different lifetime and a different reason to exist:
|
|
@@ -24,6 +24,7 @@ the patterns hold for any upstream that issues per-tenant keys with limits.
|
|
|
24
24
|
- [Discovering spend you do not control](#discovering-spend-you-do-not-control)
|
|
25
25
|
- [Guardrails: budgets, loops, auto-pause](#guardrails-budgets-loops-auto-pause)
|
|
26
26
|
- [Key lifecycle and healing](#key-lifecycle-and-healing)
|
|
27
|
+
- [Refund the iteration, charge the money](#refund-the-iteration-charge-the-money)
|
|
27
28
|
- [The refund waterfall](#the-refund-waterfall)
|
|
28
29
|
- [Model routing and fallbacks](#model-routing-and-fallbacks)
|
|
29
30
|
|
|
@@ -226,6 +227,41 @@ handled three different ways.
|
|
|
226
227
|
|
|
227
228
|
---
|
|
228
229
|
|
|
230
|
+
## Refund the iteration, charge the money
|
|
231
|
+
|
|
232
|
+
These are two axes and it is easy to ship one rule for both.
|
|
233
|
+
|
|
234
|
+
`agent-orchestrator`'s loop **refunds the iteration** on a recoverable provider error: a
|
|
235
|
+
502 should not consume one of the ten attempts the agent has to finish its work. That is
|
|
236
|
+
right, and it says nothing about money.
|
|
237
|
+
|
|
238
|
+
**The provider still billed the call.** A response that arrived and then failed to parse
|
|
239
|
+
was generated, metered and charged upstream; so was the one that arrived truncated, and the
|
|
240
|
+
one whose tool call was malformed. The tempting symmetry — *the attempt did not count, so
|
|
241
|
+
it did not cost* — is how a spend guard under-counts on exactly the runs that go worst.
|
|
242
|
+
|
|
243
|
+
The failure mode is specific and worth naming, because it hides where nobody looks:
|
|
244
|
+
|
|
245
|
+
> A harness charged `self.cost` on the **success path** of its `query()`. When parsing the
|
|
246
|
+
> response raised a format error, `query()` never returned, so the cost was never added —
|
|
247
|
+
> and the code compensated for it explicitly inside the exception handler. Where a cost
|
|
248
|
+
> ceiling is the *primary* bound — that harness ships `cost_limit = 3.0` with the step
|
|
249
|
+
> limit **off** — a leak in that accounting is a leak in the only guard there is.
|
|
250
|
+
|
|
251
|
+
The rule, in one line each:
|
|
252
|
+
|
|
253
|
+
- **Iteration:** refunded on a recoverable provider error, never on a misconfiguration.
|
|
254
|
+
- **Money:** charged whenever the provider generated tokens, including on every path that
|
|
255
|
+
raises after the response arrived.
|
|
256
|
+
- **Therefore:** accounting belongs in a `finally`, or in the exception handler as well as
|
|
257
|
+
the success path. If the only place your cost is added is the line after a successful
|
|
258
|
+
parse, the guard is quietly optimistic.
|
|
259
|
+
|
|
260
|
+
A budget that under-counts is worse than one that over-counts: over-counting stops a run
|
|
261
|
+
early and is visible immediately; under-counting is invisible until the invoice, and it
|
|
262
|
+
biases toward the failing runs, which are the expensive ones.
|
|
263
|
+
|
|
264
|
+
|
|
229
265
|
## The refund waterfall
|
|
230
266
|
|
|
231
267
|
A payment refund has to come out of somewhere, and the money has usually moved.
|
|
@@ -260,3 +296,57 @@ model" is otherwise unanswerable.
|
|
|
260
296
|
|
|
261
297
|
See `patterns.md` for the retry, health-check and error-hierarchy patterns these
|
|
262
298
|
routing calls sit inside.
|
|
299
|
+
|
|
300
|
+
### What a trajectory cannot carry across a vendor
|
|
301
|
+
|
|
302
|
+
The routing above assumes the **request** is portable. Mid-run failover is a different
|
|
303
|
+
problem, because by then there is an accumulated history and not all of it can move.
|
|
304
|
+
|
|
305
|
+
- **Tool calls and results are portable.** They differ in structure between vendors and
|
|
306
|
+
mean the same thing, so re-rendering them is enough.
|
|
307
|
+
- **Reasoning is not.** It is portable *text* plus a **non-portable credential** the vendor
|
|
308
|
+
attaches to prove the reasoning is its own. Vendors disagree on what they demand: one end
|
|
309
|
+
validates nothing, the other rejects any credential it did not issue.
|
|
310
|
+
- **The credential is not always attached to the reasoning.** It may sit on the *tool call*
|
|
311
|
+
— which is why the apparently safe policy *"just strip all reasoning before failing
|
|
312
|
+
over"* is exactly what fails at some vendors, and fails as a 400 rather than as
|
|
313
|
+
degradation.
|
|
314
|
+
|
|
315
|
+
Design rules that follow:
|
|
316
|
+
|
|
317
|
+
- Store trajectories in a **neutral internal format**: keep the text, discard the
|
|
318
|
+
credential, re-render per vendor at send time.
|
|
319
|
+
- Decide the failover boundary deliberately. **Between turns** is cheap and safe; **inside
|
|
320
|
+
a turn**, after reasoning has been emitted, is where the credential problem lives.
|
|
321
|
+
- A fallback chain that has never been exercised **mid-trajectory** has not been tested.
|
|
322
|
+
A green health probe answers a question about the endpoint, not about your history.
|
|
323
|
+
|
|
324
|
+
### Where the capability goes — not evenly
|
|
325
|
+
|
|
326
|
+
The intuitive allocation is to spend evenly across agents, or to give the strongest model
|
|
327
|
+
to whichever agent does the most work. Both are wrong for a planner–executor pair.
|
|
328
|
+
|
|
329
|
+
*Plan-and-Act* (arXiv:2503.09572) found the **planner is the bottleneck of the whole
|
|
330
|
+
system**: with good enough planning a relatively simple executor suffices, and with a wrong
|
|
331
|
+
decomposition every downstream executor is building on a false premise. Their 54% on
|
|
332
|
+
WebArena-Lite came from improving the **planner's** planning, not the executor's execution.
|
|
333
|
+
|
|
334
|
+
So: **give the strongest model and the most carefully written prompt to the manager**, and
|
|
335
|
+
let the executors be cheaper. It also sets where to look when a multi-agent system
|
|
336
|
+
underperforms — a weak plan is invisible in every executor's transcript, because each one
|
|
337
|
+
did its own step correctly.
|
|
338
|
+
|
|
339
|
+
### Budget awareness — steps the agent cannot see buy nothing
|
|
340
|
+
|
|
341
|
+
Raising a step budget does not by itself buy more work. Google's *Budget-Aware Tool-Use
|
|
342
|
+
Enables Effective Agent Scaling* reports that standard agents have **no budget awareness**,
|
|
343
|
+
so at **300 steps** they still conduct shallow searches and plateau at roughly what they
|
|
344
|
+
achieve at **30**.
|
|
345
|
+
|
|
346
|
+
Spending a larger budget requires telling the model where it is in that budget, so it can
|
|
347
|
+
shift strategy — broad exploration early, narrowing later. The multi-agent form is the
|
|
348
|
+
manager allocating step budget per sub-task rather than handing every executor the same cap.
|
|
349
|
+
|
|
350
|
+
A max-iteration guard that only composes a partial answer at exhaustion is the *floor* of
|
|
351
|
+
this, not the mechanism: it stops the spend, and it never changes the behaviour that led
|
|
352
|
+
there.
|
|
@@ -200,6 +200,51 @@ for attempt in range(1, max_retries + 1):
|
|
|
200
200
|
|
|
201
201
|
---
|
|
202
202
|
|
|
203
|
+
## The orchestrator's shared context and sub-agent protocol
|
|
204
|
+
|
|
205
|
+
`SKILL.md` §1 states the two rules — one context object down, typed results back — and this
|
|
206
|
+
is the shape they describe. It moved here in v0.20.0 for the same reason §2's loop listing
|
|
207
|
+
did in v0.19.0: the body carries what is read every time, a reference carries what is read
|
|
208
|
+
once.
|
|
209
|
+
|
|
210
|
+
```python
|
|
211
|
+
@dataclass
|
|
212
|
+
class AgentContext:
|
|
213
|
+
project_id: str
|
|
214
|
+
user_question: str
|
|
215
|
+
chat_history: list[Message]
|
|
216
|
+
llm_router: LLMRouter # provider abstraction with retry/fallback
|
|
217
|
+
tracker: WorkflowTracker # SSE event emitter for real-time UI
|
|
218
|
+
workflow_id: str # unique ID for this request
|
|
219
|
+
connection_config: ... | None # external resource config
|
|
220
|
+
user_id: str | None
|
|
221
|
+
preferred_provider: str | None # e.g. "openrouter"
|
|
222
|
+
model: str | None # e.g. "<provider>/<model-id>"
|
|
223
|
+
extra: dict[str, Any] # pipeline_action, flags, overrides
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
**Key principles:**
|
|
227
|
+
- Sub-agents never modify context — they return typed results
|
|
228
|
+
- Provider/model preferences flow down from user → project defaults → app defaults
|
|
229
|
+
- `extra` carries pipeline state, flags like `_skip_complexity`, session ids
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
class BaseAgent(ABC):
|
|
233
|
+
@abstractmethod
|
|
234
|
+
async def run(self, context: AgentContext, **kwargs) -> AgentResult: ...
|
|
235
|
+
|
|
236
|
+
@property
|
|
237
|
+
@abstractmethod
|
|
238
|
+
def name(self) -> str: ...
|
|
239
|
+
|
|
240
|
+
@staticmethod
|
|
241
|
+
def accum_usage(total, usage): ... # merge token counters
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
The single abstract method is what keeps the orchestrator ignorant of any sub-agent's
|
|
245
|
+
internals; a second one is how that boundary starts leaking.
|
|
246
|
+
|
|
247
|
+
|
|
203
248
|
## The tool-calling loop, in full
|
|
204
249
|
|
|
205
250
|
`SKILL.md` §2 states the six steps and the guard; this is the listing they describe. It
|