@ssheleg/agent-stack 0.19.1 → 0.20.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 +36 -0
- package/package.json +1 -1
- package/plugins/agent-stack/.claude-plugin/plugin.json +1 -1
- package/plugins/agent-stack/skills/agent-orchestrator/SKILL.md +14 -29
- package/plugins/agent-stack/skills/agent-orchestrator/references/llm-proxy-billing.md +54 -0
- package/plugins/agent-stack/skills/agent-orchestrator/references/patterns.md +45 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,39 @@
|
|
|
1
|
+
## v0.20.0 — what a trajectory cannot carry across a vendor, and where the capability goes
|
|
2
|
+
|
|
3
|
+
Three findings from the harvest, all landing on the same question: **which model does what,
|
|
4
|
+
and what survives when that changes mid-run.** §6 shipped three traps about routing, and all
|
|
5
|
+
three quietly assume the *request* is what moves.
|
|
6
|
+
|
|
7
|
+
- **A trajectory carries a vendor credential, and it may not be attached to the reasoning.**
|
|
8
|
+
Tool calls and results are portable — different structure, same meaning, re-render and
|
|
9
|
+
send. Reasoning is portable *text* plus a **non-portable credential** the vendor attaches
|
|
10
|
+
to prove the reasoning is its own, and vendors disagree on what they demand: one end
|
|
11
|
+
validates nothing, the other rejects any credential it did not issue. The credential
|
|
12
|
+
sometimes sits on the **tool call** rather than the reasoning — which is why *"just strip
|
|
13
|
+
all reasoning before failing over"*, the policy that sounds safest, is the one that
|
|
14
|
+
produces a 400. Store trajectories in a neutral internal format, keep the text, discard
|
|
15
|
+
the credential, re-render per vendor at send time, and put the failover boundary
|
|
16
|
+
**between turns**. A fallback chain never exercised mid-trajectory has not been tested: a
|
|
17
|
+
green health probe answers a question about the endpoint, not about your history.
|
|
18
|
+
- **Capability is not spent evenly — the planner is the bottleneck.** *Plan-and-Act*
|
|
19
|
+
(arXiv:2503.09572) found that with good enough planning a relatively simple executor
|
|
20
|
+
suffices, and with a wrong decomposition every downstream executor is building on a false
|
|
21
|
+
premise; their 54% on WebArena-Lite came from improving the **planner**, not the executor.
|
|
22
|
+
So the strongest model and the most carefully written prompt go to the **manager**. It
|
|
23
|
+
also says where to look when a multi-agent system underperforms: **a weak plan is
|
|
24
|
+
invisible in every executor's transcript**, because each one did its own step correctly.
|
|
25
|
+
- **Steps the agent cannot see buy nothing.** Standard agents have no budget awareness, so
|
|
26
|
+
at **300 steps** they still plateau at roughly what they achieve at **30**. A
|
|
27
|
+
max-iteration guard is the floor of this rather than the mechanism — it stops the spend
|
|
28
|
+
and never changes the behaviour that led there.
|
|
29
|
+
|
|
30
|
+
**And the second displacement in two releases.** The body was at 4609/4750 after v0.19.0
|
|
31
|
+
bought that headroom back; these three lines would have left **5 tokens**. §1's context
|
|
32
|
+
dataclass and sub-agent base class moved to `references/patterns.md`, beside the loop
|
|
33
|
+
listing that went there in v0.19.0, landing the body at **4631/4750**. That is now twice in
|
|
34
|
+
a row that an addition has cost a displacement, which is the auditor's own signal — *the
|
|
35
|
+
answer then is a split, not a trim* — and it is filed rather than absorbed again.
|
|
36
|
+
|
|
1
37
|
## v0.19.1 — the class the umbrella had been catching for us, twice
|
|
2
38
|
|
|
3
39
|
`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.20.0",
|
|
7
7
|
"author": {
|
|
8
8
|
"name": "ssheleg",
|
|
9
9
|
"url": "https://x.com/sshlg93"
|
|
@@ -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
|
|
|
@@ -108,6 +87,7 @@ was gathered rather than returning nothing. The full listing is in
|
|
|
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
89
|
- **Iteration refund**: a recoverable provider error is not charged to that guard
|
|
90
|
+
- **Budget awareness**: tell the model what is left, or 300 steps performs like 30
|
|
111
91
|
|
|
112
92
|
---
|
|
113
93
|
|
|
@@ -197,6 +177,11 @@ fallback chain and per-provider retry with exponential backoff, and its
|
|
|
197
177
|
- **Model selection has three levels** — the request, the tenant, the system
|
|
198
178
|
default — and a tenant override that silently loses to a request parameter is
|
|
199
179
|
how a cheap model ends up billed at a premium one's rate.
|
|
180
|
+
- **Those three assume the REQUEST is portable; the trajectory is not.** Reasoning carries
|
|
181
|
+
a vendor credential — sometimes on the tool call — so mid-turn failover can 400, and
|
|
182
|
+
*strip all reasoning* is what causes it. Fail over between turns.
|
|
183
|
+
- **Capability is not spent evenly**: the planner is the bottleneck, so the strongest model
|
|
184
|
+
goes to the manager, not to whichever agent does the most work.
|
|
200
185
|
## 7. Multi-Layer Memory System
|
|
201
186
|
|
|
202
187
|
Four layers, each with a different lifetime and a different reason to exist:
|
|
@@ -260,3 +260,57 @@ model" is otherwise unanswerable.
|
|
|
260
260
|
|
|
261
261
|
See `patterns.md` for the retry, health-check and error-hierarchy patterns these
|
|
262
262
|
routing calls sit inside.
|
|
263
|
+
|
|
264
|
+
### What a trajectory cannot carry across a vendor
|
|
265
|
+
|
|
266
|
+
The routing above assumes the **request** is portable. Mid-run failover is a different
|
|
267
|
+
problem, because by then there is an accumulated history and not all of it can move.
|
|
268
|
+
|
|
269
|
+
- **Tool calls and results are portable.** They differ in structure between vendors and
|
|
270
|
+
mean the same thing, so re-rendering them is enough.
|
|
271
|
+
- **Reasoning is not.** It is portable *text* plus a **non-portable credential** the vendor
|
|
272
|
+
attaches to prove the reasoning is its own. Vendors disagree on what they demand: one end
|
|
273
|
+
validates nothing, the other rejects any credential it did not issue.
|
|
274
|
+
- **The credential is not always attached to the reasoning.** It may sit on the *tool call*
|
|
275
|
+
— which is why the apparently safe policy *"just strip all reasoning before failing
|
|
276
|
+
over"* is exactly what fails at some vendors, and fails as a 400 rather than as
|
|
277
|
+
degradation.
|
|
278
|
+
|
|
279
|
+
Design rules that follow:
|
|
280
|
+
|
|
281
|
+
- Store trajectories in a **neutral internal format**: keep the text, discard the
|
|
282
|
+
credential, re-render per vendor at send time.
|
|
283
|
+
- Decide the failover boundary deliberately. **Between turns** is cheap and safe; **inside
|
|
284
|
+
a turn**, after reasoning has been emitted, is where the credential problem lives.
|
|
285
|
+
- A fallback chain that has never been exercised **mid-trajectory** has not been tested.
|
|
286
|
+
A green health probe answers a question about the endpoint, not about your history.
|
|
287
|
+
|
|
288
|
+
### Where the capability goes — not evenly
|
|
289
|
+
|
|
290
|
+
The intuitive allocation is to spend evenly across agents, or to give the strongest model
|
|
291
|
+
to whichever agent does the most work. Both are wrong for a planner–executor pair.
|
|
292
|
+
|
|
293
|
+
*Plan-and-Act* (arXiv:2503.09572) found the **planner is the bottleneck of the whole
|
|
294
|
+
system**: with good enough planning a relatively simple executor suffices, and with a wrong
|
|
295
|
+
decomposition every downstream executor is building on a false premise. Their 54% on
|
|
296
|
+
WebArena-Lite came from improving the **planner's** planning, not the executor's execution.
|
|
297
|
+
|
|
298
|
+
So: **give the strongest model and the most carefully written prompt to the manager**, and
|
|
299
|
+
let the executors be cheaper. It also sets where to look when a multi-agent system
|
|
300
|
+
underperforms — a weak plan is invisible in every executor's transcript, because each one
|
|
301
|
+
did its own step correctly.
|
|
302
|
+
|
|
303
|
+
### Budget awareness — steps the agent cannot see buy nothing
|
|
304
|
+
|
|
305
|
+
Raising a step budget does not by itself buy more work. Google's *Budget-Aware Tool-Use
|
|
306
|
+
Enables Effective Agent Scaling* reports that standard agents have **no budget awareness**,
|
|
307
|
+
so at **300 steps** they still conduct shallow searches and plateau at roughly what they
|
|
308
|
+
achieve at **30**.
|
|
309
|
+
|
|
310
|
+
Spending a larger budget requires telling the model where it is in that budget, so it can
|
|
311
|
+
shift strategy — broad exploration early, narrowing later. The multi-agent form is the
|
|
312
|
+
manager allocating step budget per sub-task rather than handing every executor the same cap.
|
|
313
|
+
|
|
314
|
+
A max-iteration guard that only composes a partial answer at exhaustion is the *floor* of
|
|
315
|
+
this, not the mechanism: it stops the spend, and it never changes the behaviour that led
|
|
316
|
+
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
|