@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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ssheleg/agent-stack",
3
- "version": "0.19.1",
3
+ "version": "0.20.0",
4
4
  "scripts": {
5
5
  "test": "python3 test/validate.py && python3 test/plant_guard_test.py && node test/installer_test.js"
6
6
  },
@@ -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.19.1",
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 all sub-agents:
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 a base class:
69
-
70
- ```python
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