@ssheleg/agent-stack 0.11.0 → 0.12.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,5 +1,68 @@
1
1
  # Changelog
2
2
 
3
+ ## v0.12.0 — 2026-08-19
4
+
5
+ Three places where this pack's own doctrine disagreed with the Proof of Done manifesto it
6
+ is built on. Each was confirmed from *inside* the pack before anything was changed.
7
+
8
+ ### The gate before every convergence asked how sure a branch was, never what it could show
9
+
10
+ The manifesto's checker contract is *arrived · matches its contract · **carries its
11
+ evidence** · does not contradict a sibling*. This pack named five things and substituted
12
+ **under-confident — a confidence signal below the bar** for the evidence item. In a family
13
+ whose first value is Evidence over confidence, and whose own text says "an uncalibrated
14
+ judge is an opinion with a number attached", the gate guarding every convergence never
15
+ asked what a branch could show.
16
+
17
+ Six mandatory items now, in run order, with `unevidenced` as item 3 — an assertion with no
18
+ receipt, explicitly distinguished from *wrong*. **Arrival is a count**, not an accident: a
19
+ never-returning branch is caught because it is missing, not because the host happened to
20
+ null it. The confidence signal is kept and demoted to a hint: **low confidence flags,
21
+ absent evidence blocks.**
22
+
23
+ ### A node was defined by three of its five fields, and read as whole
24
+
25
+ `One input, one output, one job` — against the manifesto's five, which add **one owner** and
26
+ **its own completion test**. Both texts then gave the same justification in nearly the same
27
+ words, which is what let the abridged version read as complete.
28
+
29
+ Worktree isolation answers **when** — two writers cannot corrupt one file. Ownership answers
30
+ **whose** — which node's version is authoritative once the branches return. Isolation without
31
+ ownership loses nothing during the run and moves the loss to the merge, where a silently
32
+ one-sided merge is indistinguishable from a convergence that only ever had one answer. The
33
+ `check` field matches `task-pipeline`'s exactly — same name, same one-string rule, same
34
+ `parked` exemption — rather than inventing a second vocabulary for one idea.
35
+
36
+ ### The suite that could never be authored up front had to gate the first release
37
+
38
+ `Never author the suite up front` read as an absolute because the pack had no word for the
39
+ other tier: `grep -ci observable` and `grep -ci requirement` both returned **0**. Taken as
40
+ written the rule made a first release ungateable — §3 names the offline suite "this is the
41
+ gate", and a suite that may never be authored up front cannot exist before there is
42
+ production to grow it from.
43
+
44
+ Two clocks now. The **observable** is a criterion, written before the implementation. The
45
+ **corpus** is a sample, grown from production. The original imperative survives word for
46
+ word and gains only its subject: *"Never author the suite up front — the corpus, that is:
47
+ the inputs."* Neither rule softens the other, because they govern different objects.
48
+
49
+ Each contract is declared machine-readably with a floor and named keys, so an item cannot
50
+ silently go missing again. Validator 10 → 13 checks; negative self-tests 15 → 19.
51
+
52
+ ## v0.11.1 — 2026-08-16
53
+
54
+ **This pack was the one place nothing was looking.** The family umbrella's shared checker
55
+ had an early exit for a member carrying no routed triggers, and `agent-stack` is that
56
+ member — so when a sibling's front matter turned out to be invalid YAML (B-56: a
57
+ colon-space inside an unquoted scalar, which every regex-based gate in the family reads
58
+ happily and a real parser refuses), nothing here would have caught the same mistake.
59
+
60
+ `test/validate.py` now asks that checker, which no longer exits early: it validates the
61
+ shipped front matter of every skill first, and only then the routed triggers a member may
62
+ or may not have. The table it reads is not copied here, so there is nothing to drift.
63
+ Watched refusing a planted `Broken: now a nested mapping.` in `agent-orchestrator`'s
64
+ description, and green after restore.
65
+
3
66
  ## [0.11.0] — 2026-08-16
4
67
 
5
68
  ### Changed
package/package.json CHANGED
@@ -1,7 +1,10 @@
1
1
  {
2
2
  "name": "@ssheleg/agent-stack",
3
- "version": "0.11.0",
4
- "description": "Production patterns for AI agent orchestrators \u2014 tool-calling loops, multi-stage pipelines with checkpoints, LLM provider routing with fallback, four-layer memory with confidence decay \u2014 plus the wallet side of reselling LLM access. This package is the installer CLI.",
3
+ "version": "0.12.0",
4
+ "scripts": {
5
+ "test": "python3 test/validate.py && python3 test/plant_guard_test.py"
6
+ },
7
+ "description": "Production patterns for AI agent orchestrators — tool-calling loops, multi-stage pipelines with checkpoints, LLM provider routing with fallback, four-layer memory with confidence decay — plus the wallet side of reselling LLM access. This package is the installer CLI.",
5
8
  "bin": {
6
9
  "agent-stack": "bin/agent-stack.js"
7
10
  },
@@ -2,7 +2,7 @@
2
2
  "name": "agent-stack",
3
3
  "displayName": "Agent Stack",
4
4
  "description": "Two skills: agent-orchestrator \u2014 tool-calling loops, multi-stage pipelines with checkpoints, provider routing with fallback, four-layer memory, context engineering, plus the wallet side of reselling LLM access; and agent-evals \u2014 run/trace/thread evals, judges, and fixtures grown from production.",
5
- "version": "0.11.0",
5
+ "version": "0.12.0",
6
6
  "author": {
7
7
  "name": "ssheleg",
8
8
  "url": "https://x.com/sshlg93"
@@ -20,7 +20,8 @@ license: MIT
20
20
 
21
21
  An agent's behaviour does not exist in its source. The code says what it is *allowed* to
22
22
  do; only a run says what it *did*. So the artifact under test is the execution record,
23
- and the suite is grown from production rather than authored up front.
23
+ and the suite runs on **two clocks**: the *observable* is authored up front, before the
24
+ implementation exists, and the *corpus* it runs against is grown from production (§6).
24
25
 
25
26
  Three claims follow, and they are what makes this different from testing ordinary code:
26
27
 
@@ -28,7 +29,8 @@ Three claims follow, and they are what makes this different from testing ordinar
28
29
  - **Every natural-language input is unique**, so the edge cases cannot be enumerated
29
30
  offline. Production is not only where you catch what you missed — it is where you
30
31
  discover what to test for.
31
- - **Traces become test cases.** The suite grows from what actually happened.
32
+ - **Traces become test cases.** The corpus grows from what actually happened; the
33
+ criterion it is measured against does not.
32
34
 
33
35
  ---
34
36
 
@@ -169,17 +171,27 @@ depends on **it** rather than on the branches — otherwise the gate has a bypas
169
171
 
170
172
  It matters here because it is the same machinery as §5, positioned differently:
171
173
 
174
+ <!-- checker-contract: missing, empty, unevidenced, malformed, contradictory, off-topic | optional: under-confident -->
175
+
172
176
  | It catches | Decided by |
173
177
  |---|---|
174
- | an empty or null output | a code check |
175
- | a confidence signal below the downstream bar | a code check |
176
- | a shape that will break the consumer's parsing | a code check |
177
- | two outputs that cannot both be true | a judge |
178
- | an output answering a different question than the one asked | a judge |
179
-
180
- Three of five are free. Run them first — §5's *cheap checks first*, applied to a position
178
+ | a **missing** branch the arrival count falls short of the fan-out promised | a code check |
179
+ | an **empty** or null result | a code check |
180
+ | an **unevidenced** claim — nothing attached that a reader could re-check | a code check |
181
+ | a **malformed** shape the consuming node cannot parse | a code check |
182
+ | **contradictory** siblings, including two that paraphrased one shared assumption | a judge |
183
+ | an **off-topic** answer to something nobody asked | a judge |
184
+
185
+ Four of six are free. Run them first — §5's *cheap checks first*, applied to a position
181
186
  in the graph rather than to a suite.
182
187
 
188
+ **An under-confident branch is a hint, not a row: its own confidence number is optional
189
+ and deliberately not one of the six.** §5 above is the reason: a score from an
190
+ uncalibrated source is an opinion, so it may order retries and it may not open a gate.
191
+ The gate asks for the third row instead — a receipt — which is the same reason this pack
192
+ ranks evidence over confidence everywhere else. The contract's home, with the argument in
193
+ full, is `agent-orchestrator/references/graph-engineering.md` §6.
194
+
183
195
  **A checker is a node, so it can be wrong, and its failure mode is silent approval.** A
184
196
  model checker that has never been shown a bad input passes everything, and a graph with a
185
197
  checker that always says yes is **worse** than one with none: the missing checkpoint has
@@ -198,10 +210,34 @@ and they are eval work rather than orchestration work:
198
210
  Where the checker sits in the shape, and why the convergence needs one at all:
199
211
  `agent-orchestrator/references/graph-engineering.md`.
200
212
 
201
- ## 6. The corpus grows from production
213
+ ## 6. Two clocks — the observable up front, the corpus from production
202
214
 
203
- Never author the suite up front. Every production failure and every thumbs-down becomes a
204
- fixture:
215
+ Two different objects get called *the eval*, and they are written at opposite ends of the
216
+ work. Naming them apart is what stops either rule from reading as the other's exception.
217
+
218
+ <!-- eval-tiers: observable, corpus -->
219
+
220
+ | Tier | Is | Written | Because |
221
+ |---|---|---|---|
222
+ | **Observable** | the criterion that would show one requirement was met — a pass/fail rubric (§4), a trajectory or state-change assertion (§2) | **before the implementation exists** | a requirement with no observable is unfinished: attach one afterwards and you are inventing the test having already seen the code, so the output has decided what counts as success |
223
+ | **Corpus** | the inputs those criteria run against — fixtures, datasets, minimised production failures | **from production, never up front** | every natural-language input is unique, so the edge cases cannot be enumerated offline; inputs invented in advance test your imagination |
224
+
225
+ **Neither rule softens the other, because they govern different objects.** An observable is
226
+ a *criterion* — what would count as success. A corpus is a *sample* — which inputs you
227
+ happen to have. The criterion costs nothing to write early and can only be written honestly
228
+ early; the sample written early is green on inputs no user sends. Both therefore hold at
229
+ full strength: **a requirement that ships without an observable is unfinished, and a corpus
230
+ with no production in it is imagination.** The requirement itself gets its id and its
231
+ definition of done from `task-pipeline`'s REQ spine — what this pack owns is the
232
+ observable's *form*, not the register it hangs on.
233
+
234
+ **The first release has no production, so its offline gate is observables only** (§3). That
235
+ is not the corpus rule suspended for a special case: the corpus is empty because nothing has
236
+ run yet, and it fills from the first real traces. Inventing *inputs* to fill it sooner would
237
+ still be imagination.
238
+
239
+ **Never author the suite up front** — the *corpus*, that is: the inputs. Every production
240
+ failure and every thumbs-down becomes a fixture:
205
241
 
206
242
  1. Capture the state at the failure point.
207
243
  2. Minimise it to the smallest input that still reproduces.
@@ -257,6 +293,8 @@ None of the above runs without these, and they are the part people skip:
257
293
  - [ ] Every checker node watched refusing a planted output, its verdicts stored as scores,
258
294
  and its rejection rate on the dashboard — a checker at zero rejections is a finding
259
295
  - [ ] Domain-expert review for output a general judge cannot grade
296
+ - [ ] Every requirement carries an **observable** written before the implementation — the
297
+ corpus waits for production, the criterion does not
260
298
  - [ ] Every production failure minimised into a permanent fixture
261
299
  - [ ] Annotation queue with filters, and the two reviewer roles kept separate
262
300
  - [ ] Simulated users trained on real transcripts, with adversarial personas
@@ -179,4 +179,5 @@ prompt.
179
179
  - [ ] One technique chosen per problem, with a reason — not ReAct because it was in a paper
180
180
  - [ ] Sub-agents return **distilled summaries**, not transcripts
181
181
  - [ ] The agent can be observed: which tool, which arguments, which observation, how many tokens
182
- - [ ] An eval exists before the prompt is tuned, or the tuning is folklore
182
+ - [ ] An observable before the implementation, an eval before the prompt is tuned, or the
183
+ tuning is folklore — only the corpus waits for production
@@ -365,7 +365,8 @@ heading — the ones that were learned by getting them wrong:
365
365
  to the input is a function call wearing a costume
366
366
  - [ ] Model, window and price are resolved at one boundary from configuration or the
367
367
  provider — never from a table of vendor ids in source
368
- - [ ] An eval exists before the prompt is tuned, or the tuning is folklore
368
+ - [ ] An observable before the implementation, an eval before the prompt is tuned, or the
369
+ tuning is folklore — only the corpus waits for production
369
370
 
370
371
  ## References
371
372
 
@@ -49,10 +49,55 @@ Four things are **this pack's**, not the source's, and each is marked where it a
49
49
 
50
50
  ## 1. Node and edge
51
51
 
52
- **A node is one unit of work.** One input, one output, one job. Not *"research the topic,
53
- summarise it, and check the sources"* that is three nodes wearing one name. The
54
- smaller and more defined the job, the more useful the node, because a node is also the
55
- unit you retry, cache, review and replace.
52
+ **A node is one unit of work, and it has five fields.** One input, one job, one output,
53
+ **one owner**, and **its own completion test**. Not *"research the topic, summarise it,
54
+ and check the sources"* that is three nodes wearing one name. The smaller and more
55
+ defined the job, the more useful the node, because a node is also the unit you retry,
56
+ cache, review and replace.
57
+
58
+ <!-- node-contract: input, job, output, owner, check -->
59
+
60
+ The first three get drawn every time. The last two are the ones a graph is usually drawn
61
+ without — this file shipped without them until 2026-08-19 — and each has a failure that
62
+ surfaces only at the convergence, where it is cheapest to mistake for success.
63
+
64
+ **One owner — exactly one node is answerable for a given artifact.** Not one *agent*: the
65
+ same subagent may own several nodes, and a node owned by a person is still a node. The
66
+ unit of ownership is the artifact, and the assignment is made when the layer is built, not
67
+ discovered when the writes come back.
68
+
69
+ **Ownership is not what `isolation: "worktree"` gives you, and the two are complementary
70
+ rather than alternatives.** §9's primitive table calls a private checkout the only safe way
71
+ to fan out writers, and against a *race* that is true. Isolation answers **when**: two
72
+ writers cannot corrupt one file, because neither can see the other's copy while it runs.
73
+ Ownership answers **whose**: which node's version is authoritative once the branches
74
+ return. Take isolation without ownership and nothing is lost during the run — the loss
75
+ moves to the merge, which is the quieter place for it, because a convergence that silently
76
+ takes one side is indistinguishable from a convergence that only ever had one answer. §4's
77
+ first rule is the *detection* half of this: two independent workers writing one file are
78
+ one node with a race in it. Ownership is the *assignment* half, and it is what makes
79
+ fanning writers out decidable rather than merely survivable.
80
+
81
+ **Its own completion test — the node names what will close it, before it runs.** One
82
+ string: a command a verifier can run, or a named judgement standing in where no command
83
+ can, with the judge named and the verdict recorded *as* judgement. Whatever it prints is
84
+ that node's evidence row.
85
+
86
+ The field's name across this family is **`check`** — `task-pipeline`'s node schema
87
+ (`graph.schema.json`) carries it as one required string per node and exempts only a
88
+ `parked` one, the single node nobody intends to close. Same name and same meaning here,
89
+ one string and never a list, because a node needing two unrelated checks is a node doing
90
+ two jobs and the answer is to split it. One gate built from two commands is `a && b`,
91
+ which is still one gate.
92
+
93
+ **A completion test is not a checker node, and carrying both is not redundancy.** §6's
94
+ checker sits between a parallel layer and its convergence and judges *arriving siblings*
95
+ from the outside — including the two things no node can establish about itself: whether it
96
+ contradicts a neighbour, and whether it answered the question the layer was actually
97
+ asked. The completion test is the node's own gate on its own output, run by whoever owns
98
+ it. Drop the per-node test and the checker becomes the only gate in the graph, which is how
99
+ a convergence ends up re-deriving what each branch should have proved about itself; drop
100
+ the checker and every branch can pass its own test and still disagree with its neighbour.
56
101
 
57
102
  **An edge is a dependency, and it carries data.** It exists when the second node
58
103
  genuinely consumes what the first produced. Not when the second merely *happens after*
@@ -151,19 +196,44 @@ A node between the parallel layer and the convergence whose only job is to decid
151
196
  each output may proceed. It synthesises nothing and writes nothing. It answers *is this
152
197
  usable* and then passes, flags, retries or drops.
153
198
 
154
- Five things it must catch — the list is the contract, and a checker that cannot say which
155
- of the five it is asserting is not a checker:
156
-
157
- 1. **Empty or null** the node returned nothing usable.
158
- 2. **Mutually contradictory** — two outputs that cannot both be true.
159
- 3. **Off-topic** — an output that answers a different question than the one asked.
160
- 4. **Under-confident** a confidence signal below the bar for the downstream decision.
161
- 5. **Malformed** a shape that will break the convergence node's parsing.
162
-
163
- Three of the five are code checks (1, 4, 5) and cost nothing; only 2 and 3 need a model.
164
- Run them in that order this is `agent-evals` §5's *cheap checks first*, applied to a
199
+ Six things it must catch — the list is the contract, and a checker that cannot say which
200
+ of the six it is asserting is not a checker:
201
+
202
+ <!-- checker-contract: missing, empty, unevidenced, malformed, contradictory, off-topic | optional: under-confident -->
203
+
204
+ 1. **Missing** — a branch the split promised never came back at all. This item needs an
205
+ **arrival count**: the fan-out is fixed when the layer is built, so the checker is
206
+ handed the number of results to expect and compares it with the number it is holding.
207
+ Without that number a vanished branch is caught only when its slot happens to hold
208
+ nothing item 2, by luck rather than by design, and only on a host that fills the slot
209
+ at all (§9's primitive table records what this one does with a thrower). A host that
210
+ simply drops it makes the gap invisible, and a short list looks like a complete one.
211
+ 2. **Empty or null** — the result arrived and carries nothing usable.
212
+ 3. **Unevidenced** — an assertion with no receipt: no citation, no tool result, no file
213
+ and line, nothing a later reader could re-check. Whether the claim is *wrong* is item 5
214
+ or 6's business; this item is about a claim nobody downstream can verify at all, and it
215
+ is the one most checkers are missing.
216
+ 4. **Malformed** — a shape that will break the convergence node's parsing.
217
+ 5. **Contradictory** — two results that cannot both be true, **including two that used
218
+ different words for one shared assumption**. The disagreement hides inside the
219
+ paraphrase, so comparing strings is not enough to find it.
220
+ 6. **Off-topic** — an output that answers a different question than the one asked.
221
+
222
+ Four of the six are code checks (1–4) and cost nothing; only 5 and 6 need a model. Run
223
+ them in that order — this is `agent-evals` §5's *cheap checks first*, applied to a
165
224
  position in the graph rather than to a test suite.
166
225
 
226
+ **A confidence signal is optional, and it is not the evidence item.** *Under-confident*
227
+ was item 4 of this list until 2026-08-19; it is kept, demoted to a hint. A branch's own
228
+ number is worth carrying as exactly that — which result to retry first, which to hand to a
229
+ person — and it is not a gate. It never stands in for item 3, and the argument is this
230
+ pack's own, made twice already: *an uncalibrated judge is an opinion with a number
231
+ attached* (`agent-evals` §5), and for anything consequential the control is a
232
+ deterministic limit or a human, **never a classifier's confidence**
233
+ (`references/governance.md`). A gate that asks a branch how sure it is and never asks
234
+ what it can show has graded the run instead of measuring it. So: low confidence *flags*,
235
+ absent evidence *blocks*.
236
+
167
237
  **What a checker costs, and how it turns into a rubber stamp — this pack's addition.** A
168
238
  checker is a node, so it has the failure mode of every node: it can be wrong. A model
169
239
  checker that has never been shown a bad input will pass everything, and a graph with a