@ssheleg/agent-stack 0.3.0 → 0.5.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 +73 -0
- package/README.md +18 -1
- package/bin/agent-stack.js +26 -12
- package/package.json +1 -1
- package/plugins/agent-stack/.claude-plugin/plugin.json +6 -3
- package/plugins/agent-stack/skills/agent-evals/SKILL.md +230 -0
- package/plugins/agent-stack/skills/agent-orchestrator/SKILL.md +27 -0
- package/plugins/agent-stack/skills/agent-orchestrator/references/context-engineering.md +201 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,79 @@ All notable changes to this project are documented here.
|
|
|
4
4
|
Format: [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
|
|
5
5
|
Versioning: [SemVer](https://semver.org/spec/v2.0.0.html).
|
|
6
6
|
|
|
7
|
+
## [0.5.0] — 2026-08-12
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **A second skill: `agent-evals`** — how you know the thing built by
|
|
12
|
+
`agent-orchestrator` actually behaves. There was no evaluation doctrine anywhere in
|
|
13
|
+
this family; `grep -ril "llm-as-judge\|eval dataset\|regression fixture\|trace id"`
|
|
14
|
+
across four plugins returned nothing.
|
|
15
|
+
|
|
16
|
+
An agent's behaviour is not in its source — the code says what it is allowed to do,
|
|
17
|
+
only a run says what it did — so the artifact under test is the execution record.
|
|
18
|
+
Three primitives (run, trace, thread) crossed with three granularities (single-step,
|
|
19
|
+
full-turn, multi-turn), each with its own fixture shape and its own precondition:
|
|
20
|
+
step assertions need a stable architecture or they die at the next refactor; turn
|
|
21
|
+
assertions cover trajectory **and** response **and** state change, because an agent
|
|
22
|
+
that says it saved the preference and did not passes two axes out of three; thread
|
|
23
|
+
scripts checkpoint after every turn and fail fast, or turn 3 derails and turns 4–10
|
|
24
|
+
assert nothing while still reporting a result.
|
|
25
|
+
|
|
26
|
+
Then the parts that decide whether any of it is trustworthy: the offline/online/ad-hoc
|
|
27
|
+
timing axis and why offline is necessary and not sufficient; pass-fail rubrics with
|
|
28
|
+
enumerated failure conditions instead of scalar scores that name no fix; code checks
|
|
29
|
+
before model judges; judging the trajectory, not just the answer; **calibrating a judge
|
|
30
|
+
against human labels before trusting it**, because an uncalibrated judge is an opinion
|
|
31
|
+
with a number attached; the classes of output no general judge can grade; a corpus
|
|
32
|
+
grown from production failures where every fixed failure stays a fixture permanently;
|
|
33
|
+
annotation queues with the two reviewer roles kept apart; and simulated users made
|
|
34
|
+
deliberately worse so offline results predict production.
|
|
35
|
+
|
|
36
|
+
Body 214 lines / ~2500 tokens; description 911/1024 with paired RU triggers.
|
|
37
|
+
|
|
38
|
+
### Changed
|
|
39
|
+
|
|
40
|
+
- **Both installers iterate over `skills/` instead of naming one skill.** `install.sh`
|
|
41
|
+
and `bin/agent-stack.js` each hardcoded `agent-orchestrator`, so a second skill would
|
|
42
|
+
have shipped in the package and reached nobody. Verified by running both against a
|
|
43
|
+
clean `HOME` and listing what arrived. The CI smoke test now asserts both skills.
|
|
44
|
+
- README and both manifests describe two skills; the marketplace entry's description is
|
|
45
|
+
the manifest's, rather than a second one drifting beside it.
|
|
46
|
+
|
|
47
|
+
## [0.4.0] — 2026-08-12
|
|
48
|
+
|
|
49
|
+
### Added
|
|
50
|
+
|
|
51
|
+
- **`references/context-engineering.md`** — what the loop gives up when the window
|
|
52
|
+
runs out, which the skill named a threshold for and never answered. The five-rung
|
|
53
|
+
compaction ladder, cheapest first, with a re-measure between rungs so a small
|
|
54
|
+
overage never buys a model call; the rung that exists only for the case where the
|
|
55
|
+
compaction request itself does not fit; the **tool-pair invariant** — one boundary
|
|
56
|
+
finder used by every rung, because a truncation that orphans a `tool_use` fails the
|
|
57
|
+
*next* request with a 400 in the middle of a task; **typed carryover blocks** copied
|
|
58
|
+
across the boundary rather than summarized, since a summarizer keeps the discussion
|
|
59
|
+
and drops the state, including the flag that said not to write; **tool-output
|
|
60
|
+
offload** to a file with the path left in context, because trimming history cannot
|
|
61
|
+
save a window one result already filled; token estimation and the direction it errs;
|
|
62
|
+
the compaction circuit breaker; sub-agent context isolation; the filesystem as
|
|
63
|
+
context; and how to pick constants for your own window.
|
|
64
|
+
|
|
65
|
+
Ladder structure and the attachment taxonomy are adapted from
|
|
66
|
+
[HKUDS/OpenHarness](https://github.com/HKUDS/OpenHarness) (MIT),
|
|
67
|
+
`services/compact/__init__.py` and `services/tool_outputs.py`. The constants are
|
|
68
|
+
deliberately ours — and deliberately absent, with anchors for choosing them, because
|
|
69
|
+
a threshold copied without its window is a number nobody can defend.
|
|
70
|
+
|
|
71
|
+
### Changed
|
|
72
|
+
|
|
73
|
+
- **§12 Context Engineering** in the body: five traps an agent cannot know to look up,
|
|
74
|
+
because it does not know they exist. **§7 gains Layer 0 — carryover state**, the one
|
|
75
|
+
memory layer whose survival is deterministic rather than a model's choice. §2 gains
|
|
76
|
+
the iteration refund. Body lands at 486 lines / ~4985 tokens against the 500 / 5000
|
|
77
|
+
budget — every insertion was cut twice to fit, and the depth is in the reference
|
|
78
|
+
where it belongs.
|
|
79
|
+
|
|
7
80
|
## [0.3.0] — 2026-08-12
|
|
8
81
|
|
|
9
82
|
### Changed
|
package/README.md
CHANGED
|
@@ -9,7 +9,8 @@ Part of the [ssheleg skill family](https://github.com/ssheleg/sshlg-skills).
|
|
|
9
9
|
|
|
10
10
|
## What is in here
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Two skills — `agent-orchestrator` for building one, `agent-evals` for proving it
|
|
13
|
+
behaves — and three references the first loads on demand.
|
|
13
14
|
|
|
14
15
|
**The orchestrator** (`SKILL.md`) — what the agent reads first:
|
|
15
16
|
|
|
@@ -29,6 +30,22 @@ One skill, `agent-orchestrator`, and two references it loads on demand.
|
|
|
29
30
|
- context budget allocation by priority
|
|
30
31
|
- self-learning feedback loops
|
|
31
32
|
|
|
33
|
+
**The evals skill** (`agent-evals/SKILL.md`) — how you know any of it works. An
|
|
34
|
+
agent's behaviour is not in its source, so the artifact under test is the
|
|
35
|
+
execution record: three primitives (run, trace, thread) crossed with three
|
|
36
|
+
granularities (single-step, full-turn, multi-turn), the offline/online/ad-hoc
|
|
37
|
+
timing axis, pass-fail rubrics instead of scalar scores that name no fix, cheap
|
|
38
|
+
code checks before model judges, judges calibrated against human labels before
|
|
39
|
+
they are trusted, and a corpus grown from production failures rather than
|
|
40
|
+
authored up front — where every fixed failure stays a fixture permanently.
|
|
41
|
+
|
|
42
|
+
**`references/context-engineering.md`** — what the loop gives up when the window
|
|
43
|
+
runs out: the five-rung compaction ladder and why to re-measure between rungs,
|
|
44
|
+
the tool-pair boundary invariant, typed carryover blocks copied across the
|
|
45
|
+
boundary rather than summarized, tool-output offload to a file, token estimation
|
|
46
|
+
and the direction it errs, the compaction circuit breaker, sub-agent context
|
|
47
|
+
isolation, and how to choose constants for your own window.
|
|
48
|
+
|
|
32
49
|
**`references/patterns.md`** — the data models and algorithms underneath:
|
|
33
50
|
message and result protocols, pipeline models, the SQL validation loop,
|
|
34
51
|
context-window sizes and token estimation, learning-extraction heuristics,
|
package/bin/agent-stack.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
/*
|
|
3
3
|
* agent-stack installer CLI.
|
|
4
4
|
*
|
|
5
|
-
* Installs
|
|
5
|
+
* Installs every skill this plugin ships into ~/.claude/skills/<name>
|
|
6
6
|
* (same layout as install.sh). Idempotent: an existing install is skipped unless
|
|
7
7
|
* --force. Zero dependencies.
|
|
8
8
|
*
|
|
@@ -21,7 +21,7 @@ function usage() {
|
|
|
21
21
|
console.log(`agent-stack installer
|
|
22
22
|
|
|
23
23
|
Usage:
|
|
24
|
-
npx @ssheleg/agent-stack [--force] install
|
|
24
|
+
npx @ssheleg/agent-stack [--force] install every skill this plugin ships
|
|
25
25
|
into ~/.claude (skip existing unless --force)
|
|
26
26
|
npx @ssheleg/agent-stack --help
|
|
27
27
|
|
|
@@ -67,20 +67,34 @@ function main(argv) {
|
|
|
67
67
|
return 2;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
const
|
|
71
|
-
if (!fs.existsSync(
|
|
72
|
-
console.error(`error: skill sources missing at ${
|
|
70
|
+
const skillRoot = path.join(ROOT, 'plugins/agent-stack/skills');
|
|
71
|
+
if (!fs.existsSync(skillRoot)) {
|
|
72
|
+
console.error(`error: skill sources missing at ${skillRoot} — corrupted package?`);
|
|
73
|
+
return 1;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// Iterate rather than name one skill: a skill added to the plugin must not
|
|
77
|
+
// require an installer change to reach anybody.
|
|
78
|
+
const names = fs
|
|
79
|
+
.readdirSync(skillRoot, { withFileTypes: true })
|
|
80
|
+
.filter((e) => e.isDirectory())
|
|
81
|
+
.map((e) => e.name)
|
|
82
|
+
.sort();
|
|
83
|
+
if (!names.length) {
|
|
84
|
+
console.error(`error: no skills found under ${skillRoot} — corrupted package?`);
|
|
73
85
|
return 1;
|
|
74
86
|
}
|
|
75
87
|
|
|
76
88
|
const home = os.homedir();
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
89
|
+
for (const name of names) {
|
|
90
|
+
installOne(
|
|
91
|
+
`${name} skill`,
|
|
92
|
+
path.join(skillRoot, name),
|
|
93
|
+
path.join(home, '.claude', 'skills', name),
|
|
94
|
+
true,
|
|
95
|
+
force
|
|
96
|
+
);
|
|
97
|
+
}
|
|
84
98
|
return 0;
|
|
85
99
|
}
|
|
86
100
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ssheleg/agent-stack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"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
5
|
"bin": {
|
|
6
6
|
"agent-stack": "bin/agent-stack.js"
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-stack",
|
|
3
3
|
"displayName": "Agent Stack",
|
|
4
|
-
"description": "
|
|
5
|
-
"version": "0.
|
|
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.5.0",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "ssheleg",
|
|
8
8
|
"url": "https://x.com/sshlg93"
|
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
"openrouter",
|
|
20
20
|
"llm-billing",
|
|
21
21
|
"claude-code",
|
|
22
|
-
"cursor"
|
|
22
|
+
"cursor",
|
|
23
|
+
"evals",
|
|
24
|
+
"llm-judge",
|
|
25
|
+
"observability"
|
|
23
26
|
]
|
|
24
27
|
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: agent-evals
|
|
3
|
+
description: >-
|
|
4
|
+
Use when measuring whether an agent actually works — building an eval suite, judging a
|
|
5
|
+
trajectory rather than a final answer, turning production traces into regression
|
|
6
|
+
fixtures, calibrating an LLM judge against human labels, or gating a release on offline
|
|
7
|
+
evals. Covers the three observability primitives (run, trace, thread) crossed with three
|
|
8
|
+
eval granularities (single-step, full-turn, multi-turn), the offline/online/ad-hoc
|
|
9
|
+
timing axis, pass-fail rubrics over scalar scores, cheap code checks before model
|
|
10
|
+
judges, simulated users with adversarial personas, annotation queues, and what to
|
|
11
|
+
instrument so any of it is possible. Triggers - "agent eval", "eval suite", "LLM judge",
|
|
12
|
+
"regression fixture", "trajectory eval", "is the agent getting better", "эвалы агента",
|
|
13
|
+
"оценка агента", "LLM-судья", "регрессионный набор", "как проверить агента". Not for
|
|
14
|
+
unit tests of ordinary code, or for benchmarking a model.
|
|
15
|
+
license: MIT
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
# Agent evals — proving the thing behaves
|
|
19
|
+
|
|
20
|
+
An agent's behaviour does not exist in its source. The code says what it is *allowed* to
|
|
21
|
+
do; only a run says what it *did*. So the artifact under test is the execution record,
|
|
22
|
+
and the suite is grown from production rather than authored up front.
|
|
23
|
+
|
|
24
|
+
Three claims follow, and they are what makes this different from testing ordinary code:
|
|
25
|
+
|
|
26
|
+
- **You are testing reasoning, not code paths**, so one granularity is never enough.
|
|
27
|
+
- **Every natural-language input is unique**, so the edge cases cannot be enumerated
|
|
28
|
+
offline. Production is not only where you catch what you missed — it is where you
|
|
29
|
+
discover what to test for.
|
|
30
|
+
- **Traces become test cases.** The suite grows from what actually happened.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 1. Three primitives
|
|
35
|
+
|
|
36
|
+
| Primitive | Is | Carries |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| **Run** | one model call | the complete prompt — instructions, tools, context — and what came back |
|
|
39
|
+
| **Trace** | one full execution | every run, every tool call with arguments and results, nested to show how steps relate |
|
|
40
|
+
| **Thread** | many traces in one session | multi-turn context in order, **state evolution** (memory, files, artifacts), and elapsed time |
|
|
41
|
+
|
|
42
|
+
The thread level is the one most systems lack, and it is where a whole class of defect
|
|
43
|
+
lives: a bad memory write at turn 6 surfaces as a wrong answer at turn 11, and neither
|
|
44
|
+
the run nor the trace view can localise it.
|
|
45
|
+
|
|
46
|
+
**Precondition for all of this: traces are durable and queryable.** A live event stream
|
|
47
|
+
renders a progress bar and evaporates. If you cannot fetch last week's low-scoring runs
|
|
48
|
+
by id, nothing below is available to you — see §7.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 2. Three granularities
|
|
53
|
+
|
|
54
|
+
Each primitive gets its own kind of assertion.
|
|
55
|
+
|
|
56
|
+
### Single-step — validates a run
|
|
57
|
+
|
|
58
|
+
Fixture is a serialized run: prompt, tool schemas, context. Assert the decision at that
|
|
59
|
+
point — tool name, argument shape.
|
|
60
|
+
|
|
61
|
+
> "Schedule a meeting with Harrison tomorrow morning", with `find_meeting_times`,
|
|
62
|
+
> `schedule_meeting` and `send_email` available, must call `find_meeting_times` first.
|
|
63
|
+
|
|
64
|
+
Cheap, deterministic, CI-blocking. **Precondition: a stable agent architecture.** These
|
|
65
|
+
break on a graph refactor, and a suite that fails on every refactor gets deleted.
|
|
66
|
+
|
|
67
|
+
### Full-turn — validates a trace
|
|
68
|
+
|
|
69
|
+
Assert on three axes at once, with three different mechanisms:
|
|
70
|
+
|
|
71
|
+
| Axis | Assert | With |
|
|
72
|
+
|---|---|---|
|
|
73
|
+
| Trajectory | tool-call sequence — `read_file` → `edit_file` → `run_tests` | set/subset/order matchers |
|
|
74
|
+
| Final response | quality, tone, policy compliance | rubric or judge |
|
|
75
|
+
| **State change** | the memory row exists, the file was written, the artifact is there | direct inspection of the side effect |
|
|
76
|
+
|
|
77
|
+
The third axis is the one people forget. **Assert on side effects, not only on prose** —
|
|
78
|
+
an agent that says it saved the preference and did not is a pass on two axes out of three.
|
|
79
|
+
|
|
80
|
+
Easiest inputs to generate, hardest outputs to validate automatically.
|
|
81
|
+
|
|
82
|
+
### Multi-turn — validates a thread
|
|
83
|
+
|
|
84
|
+
A scripted turn sequence with a **checkpoint after every turn and fail-fast on
|
|
85
|
+
deviation**. Without that, turn 3 goes off the rails and turns 4–10 assert nothing while
|
|
86
|
+
still reporting a result.
|
|
87
|
+
|
|
88
|
+
> Turn 1: "I prefer Python over JavaScript." Turn 3's output must still be Python.
|
|
89
|
+
|
|
90
|
+
Hardest to implement well. Start here only for behaviour that is genuinely about memory
|
|
91
|
+
across turns.
|
|
92
|
+
|
|
93
|
+
**Production suites combine all three.** One vendor reports about half of theirs sitting
|
|
94
|
+
at single-step — recorded as their observation, not as a target to hit.
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## 3. Offline, online, ad-hoc
|
|
99
|
+
|
|
100
|
+
| When | Reference | Blocks a release? | Answers |
|
|
101
|
+
|---|---|---|---|
|
|
102
|
+
| **Offline** | a dataset, ground truth optional | **yes** — this is the gate | did my change break what used to work |
|
|
103
|
+
| **Online** | none — **definitionally reference-free** | no | is production drifting |
|
|
104
|
+
| **Ad-hoc** | none | no | what is actually happening out there |
|
|
105
|
+
|
|
106
|
+
**Offline is necessary and not sufficient.** A green suite proves you did not regress the
|
|
107
|
+
cases you already know about. It cannot prove the agent handles what nobody thought of,
|
|
108
|
+
because that input is not in the dataset — which is what the online tier is for.
|
|
109
|
+
|
|
110
|
+
Online evaluators fire on trace ingestion and check what needs no expected answer:
|
|
111
|
+
trajectory anomalies, step-count and latency trends, judge scores, error rates. Route
|
|
112
|
+
them over all traces, a sample, or a filtered subset; the sampling rate is a cost
|
|
113
|
+
decision, not a correctness one.
|
|
114
|
+
|
|
115
|
+
Ad-hoc is exploratory analysis over stored traces — clustering to surface failure modes
|
|
116
|
+
nobody predefined. A dashboard tracks metrics you chose in advance; this finds the ones
|
|
117
|
+
you did not.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 4. Rubrics beat scores
|
|
122
|
+
|
|
123
|
+
Generic metrics — helpfulness, naturalness, completeness — produce numbers and no
|
|
124
|
+
decision. A 3.4 out of 5 on "helpfulness" tells you nothing about what to change.
|
|
125
|
+
|
|
126
|
+
**Write narrow, behaviour-specific pass/fail rubrics, and write them with the people who
|
|
127
|
+
own the behaviour.** Each failure must point at one thing: a prompt, a tool description,
|
|
128
|
+
a workflow step, a missing capability.
|
|
129
|
+
|
|
130
|
+
A rubric that works, in full:
|
|
131
|
+
|
|
132
|
+
> **Escalation.** On a request for a human: push back once, escalate on the repeat.
|
|
133
|
+
> **Fails if** it escalates immediately · refuses after the second request · escalates
|
|
134
|
+
> before providing information it already had · continues several turns past the point it
|
|
135
|
+
> is clearly not helping.
|
|
136
|
+
|
|
137
|
+
Note the shape: one behaviour, an explicit pass condition, and an enumerated failure list.
|
|
138
|
+
That is what makes a judge reproducible and a disagreement resolvable.
|
|
139
|
+
|
|
140
|
+
---
|
|
141
|
+
|
|
142
|
+
## 5. Judges
|
|
143
|
+
|
|
144
|
+
**Cheap checks first.** Schema validation, exact match, format conformity, business-rule
|
|
145
|
+
assertions, tool-call correctness — all deterministic, all faster and cheaper than a model
|
|
146
|
+
call. Send to a judge only what cannot be decided by code.
|
|
147
|
+
|
|
148
|
+
**Judge the trajectory, not just the answer.** Right tools, right order, right arguments.
|
|
149
|
+
An agent that reaches a correct answer through three wrong tool calls is a latent outage.
|
|
150
|
+
|
|
151
|
+
**Calibrate the judge before trusting it.** Collect human labels on the same traces,
|
|
152
|
+
measure agreement, iterate the judge prompt until agreement is high — *then* let it score
|
|
153
|
+
unattended. An uncalibrated judge is an opinion with a number attached, and shipping on it
|
|
154
|
+
is exactly the failure of grading instead of measuring.
|
|
155
|
+
|
|
156
|
+
**Some things a judge cannot do.** Plausible-but-wrong domain output — an invented legal
|
|
157
|
+
citation, a subtly wrong SQL join — reads as correct to a general judge. Route those to a
|
|
158
|
+
domain expert and accept that this tier stays human.
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## 6. The corpus grows from production
|
|
163
|
+
|
|
164
|
+
Never author the suite up front. Every production failure and every thumbs-down becomes a
|
|
165
|
+
fixture:
|
|
166
|
+
|
|
167
|
+
1. Capture the state at the failure point.
|
|
168
|
+
2. Minimise it to the smallest input that still reproduces.
|
|
169
|
+
3. Add it to the tier that isolates it — step, turn or thread.
|
|
170
|
+
4. **It stays in the suite permanently.** A fixed bug that silently returns is the defect
|
|
171
|
+
this rule exists to prevent.
|
|
172
|
+
|
|
173
|
+
Two dataset shapes come out of review:
|
|
174
|
+
|
|
175
|
+
- **Ground truth** — the reviewer writes the correct output; the suite asserts equality.
|
|
176
|
+
- **Criteria-based** — for open-ended work, the reviewer labels dimensions (relevance,
|
|
177
|
+
completeness, tone) instead of an exact answer.
|
|
178
|
+
|
|
179
|
+
**The annotation queue** is what feeds both: filters route a subset of traces to humans —
|
|
180
|
+
low automated score, thumbs-down, a feature area, a cluster. Two reviewer roles, and
|
|
181
|
+
mixing them wastes both: generalists judge surface quality, domain experts judge
|
|
182
|
+
correctness only they can see.
|
|
183
|
+
|
|
184
|
+
**Simulated users, if you generate inputs.** A model playing a customer is articulate,
|
|
185
|
+
patient and cooperative, and inflates every pass rate. Fine-tune it on real user
|
|
186
|
+
transcripts and add adversarial personas — the refund-seeker, the AI-sceptic, the one who
|
|
187
|
+
wants a human immediately. Making the simulated user worse makes the offline result
|
|
188
|
+
predictive.
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
## 7. What to instrument first
|
|
193
|
+
|
|
194
|
+
None of the above runs without these, and they are the part people skip:
|
|
195
|
+
|
|
196
|
+
- **A durable trace store, queryable by id, filterable by score and time.** The live
|
|
197
|
+
stream is a view over it, never the record itself.
|
|
198
|
+
- **Scores as first-class records bound to a run**: `run_id`, key, value, and a `source`
|
|
199
|
+
of `human` | `llm_judge` | `code_check`. A score with no source cannot be calibrated,
|
|
200
|
+
audited, or trusted differently from its neighbours.
|
|
201
|
+
- **Whole prompts, not just messages** — instructions, tool schemas and context as they
|
|
202
|
+
were sent. A fixture cannot be replayed from a summary.
|
|
203
|
+
- **State snapshots at turn boundaries**, so a thread test can assert what carried.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Checklist
|
|
208
|
+
|
|
209
|
+
- [ ] Traces durable and queryable by id, not only streamed
|
|
210
|
+
- [ ] Scores bound to runs, each with a source
|
|
211
|
+
- [ ] Single-step fixtures for the decisions that must not drift
|
|
212
|
+
- [ ] Full-turn assertions on trajectory **and** final response **and** state change
|
|
213
|
+
- [ ] Multi-turn scripts with a checkpoint after every turn, failing fast
|
|
214
|
+
- [ ] Offline suite as the release gate; online evaluators reference-free and non-blocking
|
|
215
|
+
- [ ] Pass/fail rubrics with enumerated failure conditions, written with behaviour owners
|
|
216
|
+
- [ ] Code checks before model judges
|
|
217
|
+
- [ ] Judge calibrated against human labels before it is trusted
|
|
218
|
+
- [ ] Domain-expert review for output a general judge cannot grade
|
|
219
|
+
- [ ] Every production failure minimised into a permanent fixture
|
|
220
|
+
- [ ] Annotation queue with filters, and the two reviewer roles kept separate
|
|
221
|
+
- [ ] Simulated users trained on real transcripts, with adversarial personas
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Related
|
|
226
|
+
|
|
227
|
+
Building the agent this measures is the **`agent-orchestrator`** skill, shipped in the
|
|
228
|
+
same plugin — named rather than linked, because a packager may ship either directory
|
|
229
|
+
alone. Its context-engineering reference covers the context-pressure behaviour that
|
|
230
|
+
trajectory assertions most often catch drifting.
|
|
@@ -137,6 +137,7 @@ else:
|
|
|
137
137
|
- **In-loop trimming**: At ~80% capacity, collapse older assistant+tool pairs into one-liner summaries
|
|
138
138
|
- **Token limit recovery**: On `LLMTokenLimitError`, compress to 60% and retry once. If still fails, return partial answer
|
|
139
139
|
- **Max iterations guard**: Always have a hard limit. On exhaustion, compose best-effort answer from data gathered so far
|
|
140
|
+
- **Iteration refund**: a recoverable provider error is not charged to that guard
|
|
140
141
|
|
|
141
142
|
---
|
|
142
143
|
|
|
@@ -323,6 +324,9 @@ context window, so allocation has to be decided per call rather than per layer:
|
|
|
323
324
|
a session that trims chat history to fit a large set of learnings has quietly
|
|
324
325
|
chosen old generalities over what the user said sixty seconds ago. Give layer 1
|
|
325
326
|
a floor.
|
|
327
|
+
|
|
328
|
+
**Layer 0 — carryover state.** Goal, artifacts, verified work and restrictive
|
|
329
|
+
mode cross a compaction boundary as copied typed blocks, not prose (§12).
|
|
326
330
|
## 8. Self-Learning Feedback Loops
|
|
327
331
|
|
|
328
332
|
### Cycle 1: Automatic (Validation Loop)
|
|
@@ -436,6 +440,27 @@ async def handle_ask_user(tc, context, wf_id):
|
|
|
436
440
|
|
|
437
441
|
---
|
|
438
442
|
|
|
443
|
+
## 12. Context Engineering
|
|
444
|
+
|
|
445
|
+
**Compaction is a ladder, not a call.** Clear old tool results, collapse
|
|
446
|
+
oversized blocks, condense messages, and only then pay a summarizer —
|
|
447
|
+
re-measuring between rungs. Most pressure resolves before the first call.
|
|
448
|
+
|
|
449
|
+
**Never orphan a `tool_use`.** Every truncation point lands *between* an
|
|
450
|
+
assistant+tool pair, or the next request is a 400 in the middle of a task.
|
|
451
|
+
|
|
452
|
+
**Carry state across the boundary as typed blocks, not prose** — goal,
|
|
453
|
+
artifacts, verified work, restrictive mode. A summarizer keeps the discussion
|
|
454
|
+
and drops the state, including the flag that said not to write anything.
|
|
455
|
+
|
|
456
|
+
**Offload a large tool result to a file, keep the path.** Trimming history
|
|
457
|
+
cannot save a window one tool output already filled.
|
|
458
|
+
|
|
459
|
+
**A sub-agent's value is its own window**: it returns a typed summary, not a
|
|
460
|
+
transcript.
|
|
461
|
+
|
|
462
|
+
---
|
|
463
|
+
|
|
439
464
|
## Checklist — Building a New Orchestrator
|
|
440
465
|
|
|
441
466
|
- [ ] Shared `AgentContext` dataclass with all sub-agents
|
|
@@ -459,6 +484,7 @@ async def handle_ask_user(tc, context, wf_id):
|
|
|
459
484
|
- [ ] Multi-stage pipeline with checkpoints and resume
|
|
460
485
|
- [ ] `ask_user` clarification mechanism
|
|
461
486
|
- [ ] Graceful degradation (partial answers on context overflow or max iterations)
|
|
487
|
+
- [ ] Compaction ladder, tool-pair-safe boundaries, typed carryover, output offload
|
|
462
488
|
|
|
463
489
|
---
|
|
464
490
|
|
|
@@ -470,4 +496,5 @@ are the territory.
|
|
|
470
496
|
| File | Read it when |
|
|
471
497
|
|---|---|
|
|
472
498
|
| [`references/patterns.md`](references/patterns.md) | you need the **data models and algorithms**: message and result protocols, pipeline models, the SQL validation loop, context-window sizes and token estimation, learning-extraction heuristics, confidence lifecycle, fuzzy dedup, conflict resolution, cross-resource transfer, the no-LLM suggestion engine |
|
|
499
|
+
| [`references/context-engineering.md`](references/context-engineering.md) | the loop is **running out of window**: the five-rung compaction ladder, the tool-pair boundary invariant, typed carryover attachments, tool-output offload, token estimation, the compaction circuit breaker, sub-agent isolation, and how to pick your own constants |
|
|
473
500
|
| [`references/llm-proxy-billing.md`](references/llm-proxy-billing.md) | the product **resells LLM access**: tiered wallets and where markup applies, two-phase commit against a provider API with compensating transactions, advisory locking, optimistic concurrency for reclaims, spend-delta polling and its three cases, budget/loop/auto-pause guardrails, per-tenant key lifecycle and healing, the refund waterfall, model routing |
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# Context engineering — what survives the window, and how
|
|
2
|
+
|
|
3
|
+
**Load this when** the loop is running out of window: choosing a compaction strategy,
|
|
4
|
+
deciding what crosses the boundary, bounding a tool result that will not fit, or
|
|
5
|
+
splitting message history without breaking the next request.
|
|
6
|
+
|
|
7
|
+
`SKILL.md` §2 gives the loop and its two thresholds — trim at ~80% of the window,
|
|
8
|
+
inject the wrap-up at ~70%. This file is what happens *at* those thresholds. The
|
|
9
|
+
difference matters: §2 decides **when** to act, this decides **what to give up**.
|
|
10
|
+
|
|
11
|
+
Ladder structure and the attachment taxonomy are adapted from
|
|
12
|
+
[HKUDS/OpenHarness](https://github.com/HKUDS/OpenHarness) (MIT),
|
|
13
|
+
`src/openharness/services/compact/__init__.py` and `services/tool_outputs.py`. The
|
|
14
|
+
constants below are ours; theirs are tuned for a different window and a different tool
|
|
15
|
+
mix, and a threshold copied without its context is a number nobody can defend.
|
|
16
|
+
|
|
17
|
+
## Contents
|
|
18
|
+
|
|
19
|
+
- The ladder — five rungs, cheapest first
|
|
20
|
+
- Re-measure between rungs
|
|
21
|
+
- The tool-pair invariant
|
|
22
|
+
- Carryover attachments — what crosses the boundary
|
|
23
|
+
- Tool-output offload
|
|
24
|
+
- Estimating what you have left
|
|
25
|
+
- The circuit breaker
|
|
26
|
+
- Sub-agent context isolation
|
|
27
|
+
- The filesystem as context
|
|
28
|
+
- Choosing your own constants
|
|
29
|
+
|
|
30
|
+
## The ladder — five rungs, cheapest first
|
|
31
|
+
|
|
32
|
+
Compaction is not one call. It is an ordered set of strategies, and the expensive one
|
|
33
|
+
is last. A loop that summarizes at the first sign of pressure pays a model call and a
|
|
34
|
+
round-trip for what a string operation would have solved.
|
|
35
|
+
|
|
36
|
+
| Rung | What it does | Cost | Loses |
|
|
37
|
+
|---|---|---|---|
|
|
38
|
+
| 1. Microcompact | Replace old tool results with a tombstone: `[tool result cleared]`. Keep the N most recent. | free | old observations, kept recent ones |
|
|
39
|
+
| 2. Head/tail collapse | For an oversized text block, keep a head and a tail, elide the middle with a marker | free | the middle of long outputs |
|
|
40
|
+
| 3. Session condensation | Collapse each older message to a one-line summary, capped in total | free | phrasing, keeps the thread of events |
|
|
41
|
+
| 4. LLM compaction | One model call summarizes the transcript into a structured brief | a call + latency | anything the summarizer does not think to keep |
|
|
42
|
+
| 5. Prompt-round truncation | Drop the oldest whole prompt rounds, boundary-aligned | free | the earliest history entirely |
|
|
43
|
+
|
|
44
|
+
Rung 5 exists for one case: **the compaction request itself does not fit.** When rung 4
|
|
45
|
+
fails because the transcript it must summarize is over the limit, summarizing harder is
|
|
46
|
+
not available — you drop oldest rounds and retry, bounded by a small retry count.
|
|
47
|
+
|
|
48
|
+
**Eligibility, not just age.** A tool result becomes a rung-1 candidate by size as well
|
|
49
|
+
as position: anything past a few thousand characters, and every result from an external
|
|
50
|
+
tool server, whose outputs are the usual window hog and the least likely to be re-read.
|
|
51
|
+
|
|
52
|
+
## Re-measure between rungs
|
|
53
|
+
|
|
54
|
+
After every rung, measure again and stop if you are under the threshold. Two failure
|
|
55
|
+
modes hide here:
|
|
56
|
+
|
|
57
|
+
- **Running the whole ladder every time** turns a 200-character overage into a model
|
|
58
|
+
call. Rung 1 usually settles it.
|
|
59
|
+
- **Trusting a rung that did nothing.** Head/tail collapse on a transcript with no
|
|
60
|
+
oversized blocks changes nothing. If the estimate did not drop, reject the result and
|
|
61
|
+
escalate rather than recording a compaction that never happened.
|
|
62
|
+
|
|
63
|
+
## The tool-pair invariant
|
|
64
|
+
|
|
65
|
+
**A tool call and its result are one unit. Never split them.**
|
|
66
|
+
|
|
67
|
+
Every provider rejects a request where an assistant message announces a tool call whose
|
|
68
|
+
result is missing, or a tool result with no matching call. It is not a soft error — the
|
|
69
|
+
next request fails, mid-task, with a 400 that names nothing useful.
|
|
70
|
+
|
|
71
|
+
This makes every boundary computation in this file conditional: a truncation point, a
|
|
72
|
+
collapse range, a dropped round must all land **between** pairs. Write the boundary
|
|
73
|
+
finder once, use it from every rung, and give it a test that plants an orphan and
|
|
74
|
+
requires rejection.
|
|
75
|
+
|
|
76
|
+
The same invariant governs parallel dispatch: if one tool in a batch raises, its result
|
|
77
|
+
block must still be emitted — as an error result. A sibling's crash is not a reason to
|
|
78
|
+
send a malformed request.
|
|
79
|
+
|
|
80
|
+
## Carryover attachments — what crosses the boundary
|
|
81
|
+
|
|
82
|
+
A summarizer keeps prose and drops state. Ask one to compress a transcript and it will
|
|
83
|
+
faithfully preserve the discussion while losing the fact that you are in a read-only
|
|
84
|
+
mode, the path of the file you verified, and what the user actually asked for.
|
|
85
|
+
|
|
86
|
+
So state does not go through the summarizer. It is accumulated during normal execution
|
|
87
|
+
and re-attached after compaction as **typed blocks**:
|
|
88
|
+
|
|
89
|
+
| Block | Carries | Why it is lost otherwise |
|
|
90
|
+
|---|---|---|
|
|
91
|
+
| `task_focus` | the goal, recent sub-goals, active artifacts, verified state, next step | the summarizer rewrites the goal into its own words and drifts |
|
|
92
|
+
| `recent_files` | paths read or written, most recent first | the agent re-reads what it already has |
|
|
93
|
+
| `verified_work` | what was checked and how it was checked | re-verification, or worse, a claim of verification that was never re-run |
|
|
94
|
+
| `plan` | the active plan and any mode that restricts what may be done | **a safety mode forgotten across a boundary is how a read-only run starts writing** |
|
|
95
|
+
| `invoked_skills` | which doctrine is already loaded | re-loading a large file that is already in effect |
|
|
96
|
+
| `work_log` | one line per significant action | the agent repeats a step it already completed |
|
|
97
|
+
|
|
98
|
+
Accumulate them in bounded buckets during the loop — a small cap per bucket, oldest
|
|
99
|
+
evicted — so the attachment cost is known in advance and does not itself grow into a
|
|
100
|
+
context problem. Cap the number of attached blocks too.
|
|
101
|
+
|
|
102
|
+
The property that matters: this is **deterministic**. Structured state survives because
|
|
103
|
+
it is copied, not because a model chose to keep it.
|
|
104
|
+
|
|
105
|
+
## Tool-output offload
|
|
106
|
+
|
|
107
|
+
One large tool result poisons a window before any history trimming is relevant. Trimming
|
|
108
|
+
the conversation does not help when the problem is a single 200 KB response sitting in
|
|
109
|
+
the current turn.
|
|
110
|
+
|
|
111
|
+
The rule: **above an inline limit, write the full output to a file and put a preview
|
|
112
|
+
plus the path into the context.** The model reads the preview, and when it needs the
|
|
113
|
+
rest it reads the file with the tools it already has.
|
|
114
|
+
|
|
115
|
+
- **Inline limit** — the size above which offload happens.
|
|
116
|
+
- **Preview size** — enough to decide whether the rest is needed; head, or head and tail
|
|
117
|
+
when the tail carries the verdict (test runs, build logs).
|
|
118
|
+
- **Path** — stable and unique per call, so two calls to the same tool do not collide.
|
|
119
|
+
- **Location** — a scratch directory scoped to the run, not the project tree; artifacts
|
|
120
|
+
of a loop are not deliverables and must never arrive in a diff.
|
|
121
|
+
|
|
122
|
+
Both limits belong in configuration. The right value depends on the window and the tool
|
|
123
|
+
mix, and the only way to find it is to measure a real run.
|
|
124
|
+
|
|
125
|
+
## Estimating what you have left
|
|
126
|
+
|
|
127
|
+
Every threshold in this file depends on an estimate, and estimates are optimistic in the
|
|
128
|
+
direction that hurts:
|
|
129
|
+
|
|
130
|
+
- **Character-based estimation runs low.** A divisor calibrated on prose under-counts
|
|
131
|
+
code, JSON and non-Latin text. Apply a padding factor, and prefer being early over
|
|
132
|
+
being right.
|
|
133
|
+
- **Non-text content costs more than its representation suggests.** An image is worth
|
|
134
|
+
hundreds to thousands of tokens depending on its dimensions; a reference to a file is
|
|
135
|
+
worth nothing until it is read.
|
|
136
|
+
- **Reserve the output.** The threshold is not "the window" — it is the window minus the
|
|
137
|
+
space the answer needs, minus a buffer for the next tool result. Express the trigger
|
|
138
|
+
as a floor in absolute tokens, not only as a percentage: at a small window a
|
|
139
|
+
percentage silently reserves too little.
|
|
140
|
+
|
|
141
|
+
## The circuit breaker
|
|
142
|
+
|
|
143
|
+
Compaction can fail — the summarizer errors, the provider is down, the result does not
|
|
144
|
+
shrink. **After a small number of consecutive failures, disable compaction for the run
|
|
145
|
+
and degrade deliberately**: stop adding to the context, compose the best answer
|
|
146
|
+
available, and say that compaction is unavailable.
|
|
147
|
+
|
|
148
|
+
Without this, a failing compaction is retried at the top of every iteration, and the
|
|
149
|
+
loop spends its remaining budget on the one operation that is not working.
|
|
150
|
+
|
|
151
|
+
**Related: refund the iteration.** When an iteration ends in a recoverable provider
|
|
152
|
+
error — a token limit that can be clamped and retried, a rate limit — do not charge it
|
|
153
|
+
to the max-iteration guard. A misconfiguration should not consume the budget that exists
|
|
154
|
+
to stop a runaway agent.
|
|
155
|
+
|
|
156
|
+
## Sub-agent context isolation
|
|
157
|
+
|
|
158
|
+
A sub-agent's value is not that it is a different prompt. **It is that it has its own
|
|
159
|
+
window.**
|
|
160
|
+
|
|
161
|
+
The contract: the sub-agent receives a task and the context it needs, works in a window
|
|
162
|
+
the parent never sees, and returns a typed result — a summary, not a transcript. The
|
|
163
|
+
parent's window grows by the size of the result, not by the size of the work.
|
|
164
|
+
|
|
165
|
+
This makes delegation the strongest context tool available: a search across forty files
|
|
166
|
+
costs the parent one paragraph. It also sets the design rule — if a sub-agent's return
|
|
167
|
+
value is proportional to its input rather than its conclusion, it is a function call
|
|
168
|
+
wearing a costume, and it will fill the parent's window anyway.
|
|
169
|
+
|
|
170
|
+
## The filesystem as context
|
|
171
|
+
|
|
172
|
+
Anything durable belongs in a file, not in the transcript:
|
|
173
|
+
|
|
174
|
+
- **It survives the boundary.** Compaction cannot delete what was never in the window.
|
|
175
|
+
- **It survives the process.** A crash loses the conversation and keeps the work.
|
|
176
|
+
- **It is shared.** Two agents on one task coordinate through files; a transcript is
|
|
177
|
+
private to one loop.
|
|
178
|
+
- **It is addressable.** A path is a few tokens; the content it names can be any size.
|
|
179
|
+
|
|
180
|
+
The pattern is the same in each case: do the work, write the artifact, keep the path.
|
|
181
|
+
Where the runtime offers no real filesystem, the same contract works over any keyed
|
|
182
|
+
store — what matters is that the address is cheap and the content is retrievable, not
|
|
183
|
+
that it is POSIX.
|
|
184
|
+
|
|
185
|
+
## Choosing your own constants
|
|
186
|
+
|
|
187
|
+
Every number in this file is deliberately absent, because the useful value depends on
|
|
188
|
+
the window, the tool mix and the cost of a summarizer call in your stack. Pick them
|
|
189
|
+
against these anchors:
|
|
190
|
+
|
|
191
|
+
1. **Reserve output before you reserve anything else.** Start from window minus expected
|
|
192
|
+
answer minus one worst-case tool result.
|
|
193
|
+
2. **Rung 1's keep-count** is the smallest number of recent tool results the loop
|
|
194
|
+
actually re-reads. Measure it; the intuition is always too high.
|
|
195
|
+
3. **The inline limit for offload** should be crossed only by outputs that are genuinely
|
|
196
|
+
large — if half of all tool calls offload, the limit is too low and the model is
|
|
197
|
+
reading files instead of working.
|
|
198
|
+
4. **The circuit-breaker count** is small. Three consecutive failures is a pattern, not
|
|
199
|
+
a coincidence.
|
|
200
|
+
5. **Write them down where the loop reads them**, not inline at the call site. A
|
|
201
|
+
threshold nobody can find is a threshold nobody will tune.
|