@ssheleg/agent-stack 0.4.0 → 0.6.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 +84 -0
- package/README.md +48 -8
- 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 +8 -5
- package/plugins/agent-stack/skills/agent-orchestrator/references/governance.md +138 -0
- package/plugins/agent-stack/skills/agent-orchestrator/references/llm-proxy-billing.md +7 -0
- package/plugins/agent-stack/skills/agent-orchestrator/references/patterns.md +5 -1
- package/plugins/agent-stack/skills/agent-orchestrator/references/runtime.md +151 -0
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,90 @@ 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.6.0] — 2026-08-12
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
|
|
11
|
+
- **`references/runtime.md`** — the layer most orchestrators assume rather than
|
|
12
|
+
specify, and the one this skill was quietly missing. **Checkpoint every iteration,
|
|
13
|
+
not just the stages a human reviews**: the multi-stage path persisted and could
|
|
14
|
+
resume, the simple tool-calling path persisted nothing, so a crash lost the run that
|
|
15
|
+
executes most often — an asymmetry, not a design. Then one interrupt/resume contract
|
|
16
|
+
instead of the two mechanisms the body had for one idea (`ask_user` and a stage
|
|
17
|
+
checkpoint); the four double-texting policies and why interrupt and rollback differ
|
|
18
|
+
in what the transcript looks like afterwards; streaming with event ids so a dropped
|
|
19
|
+
connection rejoins instead of watching nothing for ninety seconds; forking a past
|
|
20
|
+
checkpoint, which debugs through the real loop rather than a reconstruction that may
|
|
21
|
+
not share the bug; stateful versus stateless schedules; and the seven cross-cutting
|
|
22
|
+
concerns welded into the loop pulled out as ordered interceptors — where **order is
|
|
23
|
+
semantics**, because redaction after summarisation redacts a summary that already
|
|
24
|
+
leaked.
|
|
25
|
+
|
|
26
|
+
- **`references/governance.md`** — permission, where `llm-proxy-billing.md` is cost.
|
|
27
|
+
**The greatest risk is usually not what the model says but what the agent can do**,
|
|
28
|
+
so the four boundaries get four control sets: model call, tool call, external server,
|
|
29
|
+
and agent-to-agent — the last being the one designs miss, since a sub-agent
|
|
30
|
+
inheriting its caller's authority silently widens every permission. The guardrail
|
|
31
|
+
taxonomy in order of reliability, and its honest limit: every content check is
|
|
32
|
+
probabilistic, so anything consequential takes a deterministic limit or a human, never
|
|
33
|
+
a classifier's confidence. Why an audit row without a **policy version** cannot prove
|
|
34
|
+
a control was applied. Cost attribution as a hierarchy, because "which team's agent
|
|
35
|
+
did this" is unanswerable from a flat tenant id. Failover that must be
|
|
36
|
+
policy-equivalent rather than merely available — a chain that silently fails into
|
|
37
|
+
another jurisdiction does it precisely when nobody is reading logs. Fail-open versus
|
|
38
|
+
fail-closed per workload. And blast radius: a sandbox protects the host, not the
|
|
39
|
+
sandbox, and credentials never enter it.
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- **The References table is an index again.** Each reference now opens with its own
|
|
44
|
+
`Load this when` line, so the trigger has exactly one home and the table cannot drift
|
|
45
|
+
from the files it points at. Compressing it returned ~100 tokens of body budget, which
|
|
46
|
+
is what paid for two new rows: the body sits at 489 lines / ~4883 tokens against
|
|
47
|
+
500 / 5000.
|
|
48
|
+
- README describes two skills and five references, and its trigger section covers the
|
|
49
|
+
evals skill and the permission surface, not only the orchestrator and the wallet.
|
|
50
|
+
|
|
51
|
+
## [0.5.0] — 2026-08-12
|
|
52
|
+
|
|
53
|
+
### Added
|
|
54
|
+
|
|
55
|
+
- **A second skill: `agent-evals`** — how you know the thing built by
|
|
56
|
+
`agent-orchestrator` actually behaves. There was no evaluation doctrine anywhere in
|
|
57
|
+
this family; `grep -ril "llm-as-judge\|eval dataset\|regression fixture\|trace id"`
|
|
58
|
+
across four plugins returned nothing.
|
|
59
|
+
|
|
60
|
+
An agent's behaviour is not in its source — the code says what it is allowed to do,
|
|
61
|
+
only a run says what it did — so the artifact under test is the execution record.
|
|
62
|
+
Three primitives (run, trace, thread) crossed with three granularities (single-step,
|
|
63
|
+
full-turn, multi-turn), each with its own fixture shape and its own precondition:
|
|
64
|
+
step assertions need a stable architecture or they die at the next refactor; turn
|
|
65
|
+
assertions cover trajectory **and** response **and** state change, because an agent
|
|
66
|
+
that says it saved the preference and did not passes two axes out of three; thread
|
|
67
|
+
scripts checkpoint after every turn and fail fast, or turn 3 derails and turns 4–10
|
|
68
|
+
assert nothing while still reporting a result.
|
|
69
|
+
|
|
70
|
+
Then the parts that decide whether any of it is trustworthy: the offline/online/ad-hoc
|
|
71
|
+
timing axis and why offline is necessary and not sufficient; pass-fail rubrics with
|
|
72
|
+
enumerated failure conditions instead of scalar scores that name no fix; code checks
|
|
73
|
+
before model judges; judging the trajectory, not just the answer; **calibrating a judge
|
|
74
|
+
against human labels before trusting it**, because an uncalibrated judge is an opinion
|
|
75
|
+
with a number attached; the classes of output no general judge can grade; a corpus
|
|
76
|
+
grown from production failures where every fixed failure stays a fixture permanently;
|
|
77
|
+
annotation queues with the two reviewer roles kept apart; and simulated users made
|
|
78
|
+
deliberately worse so offline results predict production.
|
|
79
|
+
|
|
80
|
+
Body 214 lines / ~2500 tokens; description 911/1024 with paired RU triggers.
|
|
81
|
+
|
|
82
|
+
### Changed
|
|
83
|
+
|
|
84
|
+
- **Both installers iterate over `skills/` instead of naming one skill.** `install.sh`
|
|
85
|
+
and `bin/agent-stack.js` each hardcoded `agent-orchestrator`, so a second skill would
|
|
86
|
+
have shipped in the package and reached nobody. Verified by running both against a
|
|
87
|
+
clean `HOME` and listing what arrived. The CI smoke test now asserts both skills.
|
|
88
|
+
- README and both manifests describe two skills; the marketplace entry's description is
|
|
89
|
+
the manifest's, rather than a second one drifting beside it.
|
|
90
|
+
|
|
7
91
|
## [0.4.0] — 2026-08-12
|
|
8
92
|
|
|
9
93
|
### Added
|
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 five references the first loads on demand.
|
|
13
14
|
|
|
14
15
|
**The orchestrator** (`SKILL.md`) — what the agent reads first:
|
|
15
16
|
|
|
@@ -29,6 +30,39 @@ 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
|
+
|
|
49
|
+
**`references/runtime.md`** — what keeps an agent alive between requests, which
|
|
50
|
+
most orchestrators assume rather than specify: checkpointing every iteration and
|
|
51
|
+
not just the stages a human reviews, one interrupt/resume contract instead of two
|
|
52
|
+
mechanisms for one idea, the four double-texting policies, streaming a dropped
|
|
53
|
+
connection can rejoin, forking a past checkpoint to debug through the real loop,
|
|
54
|
+
stateful versus stateless schedules, and the seven cross-cutting concerns pulled
|
|
55
|
+
out of the loop into ordered interceptors — where order is semantics.
|
|
56
|
+
|
|
57
|
+
**`references/governance.md`** — permission rather than cost. The four boundaries
|
|
58
|
+
an agent crosses (model, tool, external server, agent-to-agent), each with its own
|
|
59
|
+
control set; the guardrail taxonomy and its honest limit — every content check is
|
|
60
|
+
probabilistic, so anything consequential gets a deterministic limit or a human;
|
|
61
|
+
why an audit row without a policy version cannot prove a control was applied;
|
|
62
|
+
cost attribution as a hierarchy; failover that must land somewhere approved rather
|
|
63
|
+
than merely available; fail-open versus fail-closed as a per-workload decision;
|
|
64
|
+
and blast radius — a sandbox protects the host, not the sandbox.
|
|
65
|
+
|
|
32
66
|
**`references/patterns.md`** — the data models and algorithms underneath:
|
|
33
67
|
message and result protocols, pipeline models, the SQL validation loop,
|
|
34
68
|
context-window sizes and token estimation, learning-extraction heuristics,
|
|
@@ -54,7 +88,7 @@ waterfall, and model-routing precedence.
|
|
|
54
88
|
/plugin install agent-stack@agent-stack
|
|
55
89
|
```
|
|
56
90
|
|
|
57
|
-
**npm installer** — copies
|
|
91
|
+
**npm installer** — copies both skills into `~/.claude/skills/`:
|
|
58
92
|
|
|
59
93
|
```bash
|
|
60
94
|
npx @ssheleg/agent-stack
|
|
@@ -78,13 +112,19 @@ Restart your agent afterwards — skills load at session start.
|
|
|
78
112
|
|
|
79
113
|
## When it triggers
|
|
80
114
|
|
|
81
|
-
|
|
82
|
-
tool use, or an AI pipeline. Also
|
|
83
|
-
usage, per-tenant keys, spend tracking, budget limits, loop detection
|
|
115
|
+
`agent-orchestrator`: building an agent system, an orchestrator, an LLM-powered
|
|
116
|
+
tool, a chatbot with tool use, or an AI pipeline. Also the money side — metering
|
|
117
|
+
usage, per-tenant keys, spend tracking, budget limits, loop detection — and the
|
|
118
|
+
permission side: what a tool may reach, what leaves the boundary, and what an
|
|
119
|
+
audit row has to carry to prove a control was on.
|
|
120
|
+
|
|
121
|
+
`agent-evals`: measuring whether the result behaves. Building a suite, judging a
|
|
122
|
+
trajectory rather than a final answer, turning a production failure into a
|
|
123
|
+
permanent fixture, calibrating a judge, gating a release on offline evals.
|
|
84
124
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
125
|
+
Neither triggers for a single LLM call in a script or for prompt wording — that
|
|
126
|
+
is not an orchestrator, and pulling this much doctrine for it is how a skill
|
|
127
|
+
teaches you to route around it.
|
|
88
128
|
|
|
89
129
|
---
|
|
90
130
|
|
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.6.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.6.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.
|
|
@@ -490,11 +490,14 @@ transcript.
|
|
|
490
490
|
|
|
491
491
|
## References
|
|
492
492
|
|
|
493
|
-
|
|
494
|
-
|
|
493
|
+
The checklist above is the map, these are the territory. Each file opens with its
|
|
494
|
+
own **Load this when** line — the authoritative trigger lives there, so this table
|
|
495
|
+
stays an index and the two cannot drift apart.
|
|
495
496
|
|
|
496
497
|
| File | Read it when |
|
|
497
498
|
|---|---|
|
|
498
|
-
| [`references/patterns.md`](references/patterns.md) | you need the **data models and algorithms
|
|
499
|
-
| [`references/context-engineering.md`](references/context-engineering.md) | the loop is **running out of window
|
|
500
|
-
| [`references/
|
|
499
|
+
| [`references/patterns.md`](references/patterns.md) | you need the **data models and algorithms** under the body |
|
|
500
|
+
| [`references/context-engineering.md`](references/context-engineering.md) | the loop is **running out of window** |
|
|
501
|
+
| [`references/runtime.md`](references/runtime.md) | the agent must **survive a crash, a pause, a second message or a schedule** |
|
|
502
|
+
| [`references/governance.md`](references/governance.md) | the question is **permission, not cost** — what it may do, and how you prove it |
|
|
503
|
+
| [`references/llm-proxy-billing.md`](references/llm-proxy-billing.md) | the product **resells LLM access** |
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Governance — what the agent is allowed to do
|
|
2
|
+
|
|
3
|
+
**Load this when** the question is permission rather than money: which model may see
|
|
4
|
+
which data, which tool may run against production, what leaves the infrastructure
|
|
5
|
+
boundary, and how you prove afterwards that the control was on. `llm-proxy-billing.md`
|
|
6
|
+
answers *what did this cost and who pays* — this answers *should it have happened at
|
|
7
|
+
all*, and the two are separate systems that share an audit row.
|
|
8
|
+
|
|
9
|
+
The claim worth keeping: **for an agent, the greatest risk is usually not what the model
|
|
10
|
+
says, but what the agent can do.** Content filters are aimed at the first. Most real
|
|
11
|
+
damage comes through the second.
|
|
12
|
+
|
|
13
|
+
## Contents
|
|
14
|
+
|
|
15
|
+
- Four boundaries, four control sets
|
|
16
|
+
- Guardrails, and their honest limit
|
|
17
|
+
- Where a control runs: before or after
|
|
18
|
+
- The audit row
|
|
19
|
+
- Cost attribution as a hierarchy
|
|
20
|
+
- Failover must land somewhere approved
|
|
21
|
+
- Fail-open or fail-closed, decided by risk
|
|
22
|
+
- Blast radius: sandboxes and credentials
|
|
23
|
+
|
|
24
|
+
## Four boundaries, four control sets
|
|
25
|
+
|
|
26
|
+
An agent crosses four kinds of boundary, and treating them as one is why "we have
|
|
27
|
+
guardrails" so often means only the first.
|
|
28
|
+
|
|
29
|
+
| Boundary | The risk | Controls that fit |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| **Model call** | cost; prompt content reaching a provider's logs | spend limits, redaction, provider routing, data-residency choice |
|
|
32
|
+
| **Tool call** | an unintended action on a real system | per-tool authorisation, argument validation, an audit row per invocation |
|
|
33
|
+
| **External server call** (MCP and similar) | data leaving your infrastructure boundary | allowlist of servers, logging, explicit scope per server |
|
|
34
|
+
| **Agent-to-agent** | errors compounding down a chain; context passed on without authority | tracing across hops, and **policy enforced at each hop**, not only at the entrance |
|
|
35
|
+
|
|
36
|
+
The last one is the one most designs miss: a sub-agent that inherits its caller's
|
|
37
|
+
authority silently widens every permission the caller had.
|
|
38
|
+
|
|
39
|
+
## Guardrails, and their honest limit
|
|
40
|
+
|
|
41
|
+
The content-layer checks worth having, roughly in order of reliability:
|
|
42
|
+
|
|
43
|
+
- **Structured secrets and identifiers** — API keys, tokens, card numbers, national ids.
|
|
44
|
+
Pattern-matched, high precision, cheap. Run these first.
|
|
45
|
+
- **Unstructured personal data** — names, locations, affiliations. Needs entity
|
|
46
|
+
recognition; precision drops.
|
|
47
|
+
- **Injection and jailbreak attempts** — classifier-based, adversarial by nature, and
|
|
48
|
+
the arms race is not winnable by pattern alone.
|
|
49
|
+
- **Groundedness** — does the answer follow from the retrieved material. Runs on output,
|
|
50
|
+
costs a model call, and is the least reliable of the four.
|
|
51
|
+
|
|
52
|
+
**Then the rule that makes the list honest: guardrails reduce risk, they do not
|
|
53
|
+
eliminate it.** Every item above is probabilistic. So for anything consequential —
|
|
54
|
+
money moving, data deleted, a message sent to a customer, a deploy — the control is a
|
|
55
|
+
**deterministic limit or a human**, never a classifier's confidence. A guardrail is a
|
|
56
|
+
filter on the way to a decision, not the decision.
|
|
57
|
+
|
|
58
|
+
A useful signal on top: **a sudden spike in guardrail violations is usually the first
|
|
59
|
+
sign that something upstream is wrong** — a prompt change, a new data source, an agent
|
|
60
|
+
in a loop. Alert on the rate, not just on the individual hit.
|
|
61
|
+
|
|
62
|
+
## Where a control runs: before or after
|
|
63
|
+
|
|
64
|
+
Almost everything belongs **before** the call: redaction, secret detection, provider
|
|
65
|
+
routing, rate and spend limits, tool authorisation. A control that runs after the
|
|
66
|
+
request has left has already failed at the thing it was for.
|
|
67
|
+
|
|
68
|
+
**After** the call, only what needs the output: groundedness, moderation of generated
|
|
69
|
+
text, structured-output validation.
|
|
70
|
+
|
|
71
|
+
Two consequences: pre-call controls sit on the latency path, so they must be cheap
|
|
72
|
+
enough to run every time; and a control that can only run post-call must be paired with
|
|
73
|
+
something that can undo or withhold the result.
|
|
74
|
+
|
|
75
|
+
## The audit row
|
|
76
|
+
|
|
77
|
+
An audit row exists to answer a question months later, when the person who ran the agent
|
|
78
|
+
is unavailable. It needs:
|
|
79
|
+
|
|
80
|
+
- **who** — the identity that ran the workload, and separately the identity that last
|
|
81
|
+
changed the policy
|
|
82
|
+
- **what** — the action, its arguments in redacted form, and the outcome
|
|
83
|
+
- **which policy version applied** — this is the field everyone omits and the one that
|
|
84
|
+
makes the record evidence. "The control was on" is unprovable without it; a policy
|
|
85
|
+
that changed twice since is unfalsifiable without it.
|
|
86
|
+
- **which model and which tools were reached**, including through sub-agents
|
|
87
|
+
- **when**, at a precision that survives clock skew between services
|
|
88
|
+
|
|
89
|
+
The rule to hold: **an audit trail written for reconciliation answers "where did the
|
|
90
|
+
money go"; an audit trail written for governance answers "prove the control was
|
|
91
|
+
applied".** They are different queries and the second needs the policy version.
|
|
92
|
+
|
|
93
|
+
## Cost attribution as a hierarchy
|
|
94
|
+
|
|
95
|
+
The billing reference tracks spend per tenant. Governance needs it resolvable up a
|
|
96
|
+
chain: **organisation → business unit → team → credential → individual**. Not because
|
|
97
|
+
finance asks, but because the question that actually gets asked in an incident is "which
|
|
98
|
+
team's agent did this", and a flat tenant id cannot answer it.
|
|
99
|
+
|
|
100
|
+
Limits belong at more than one level too — a per-credential cap does not stop twenty
|
|
101
|
+
credentials in one team from draining a budget together.
|
|
102
|
+
|
|
103
|
+
## Failover must land somewhere approved
|
|
104
|
+
|
|
105
|
+
The router in the body falls back to the next healthy provider. Governance adds one
|
|
106
|
+
constraint: **the fallback must be policy-equivalent, not merely available.**
|
|
107
|
+
|
|
108
|
+
A chain that silently fails over to a provider with different data handling, a different
|
|
109
|
+
jurisdiction, or a different retention policy has moved the data somewhere nobody
|
|
110
|
+
approved — and it does it precisely during an incident, when nobody is reading logs. Tag
|
|
111
|
+
each provider with the policy it satisfies, and let the fallback chain filter on the tag
|
|
112
|
+
before it filters on health.
|
|
113
|
+
|
|
114
|
+
## Fail-open or fail-closed, decided by risk
|
|
115
|
+
|
|
116
|
+
When the control plane itself is unavailable — the guardrail service times out, the
|
|
117
|
+
policy store is unreachable — the system either proceeds without the check or refuses.
|
|
118
|
+
**Both answers are correct for different workloads, and neither is a default.**
|
|
119
|
+
|
|
120
|
+
- Fail-**open** for a low-risk, high-volume path where refusing is the bigger harm.
|
|
121
|
+
- Fail-**closed** for anything consequential.
|
|
122
|
+
|
|
123
|
+
Write the choice down per workload, and make the control plane itself redundant enough
|
|
124
|
+
that the choice is rarely exercised: timeouts, load balancing, and a health check that
|
|
125
|
+
distinguishes "slow" from "gone".
|
|
126
|
+
|
|
127
|
+
## Blast radius: sandboxes and credentials
|
|
128
|
+
|
|
129
|
+
When an agent runs code, two rules carry most of the weight:
|
|
130
|
+
|
|
131
|
+
- **A sandbox protects the host, not the sandbox.** Anything the agent can reach *from
|
|
132
|
+
inside* is still reachable — network egress, mounted paths, environment. Restrict
|
|
133
|
+
egress explicitly and allowlist commands rather than denylisting.
|
|
134
|
+
- **Credentials never enter the sandbox.** Put a proxy in front that injects them per
|
|
135
|
+
request, so a prompt injection that dumps the environment gets nothing worth having.
|
|
136
|
+
|
|
137
|
+
Ephemeral is the default: create on demand, tear down after, never reuse across tenants.
|
|
138
|
+
A long-lived sandbox accumulates state that nobody audits.
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Reselling LLM access — metering, wallets and guardrails
|
|
2
2
|
|
|
3
|
+
**Load this when** the product resells LLM access: tiered wallets and the single
|
|
4
|
+
boundary where markup applies, two-phase commit across a database and a provider API
|
|
5
|
+
with compensating transactions, advisory locking, optimistic concurrency for reclaims,
|
|
6
|
+
spend-delta polling and its three cases, budget / loop / auto-pause guardrails,
|
|
7
|
+
per-tenant key lifecycle and healing, the refund waterfall, and model-routing
|
|
8
|
+
precedence.
|
|
9
|
+
|
|
3
10
|
When your product gives users LLM access and bills for it, you are running a
|
|
4
11
|
proxy with a wallet behind it. The failure modes are not model failures: they
|
|
5
12
|
are **double-credited transfers**, **spend you discovered after it happened**,
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
# Agent Orchestrator — Reference Guide
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Load this when** you need the data models and algorithms under the body: message
|
|
4
|
+
and result protocols, pipeline models, the SQL validation loop, context-window sizes
|
|
5
|
+
and token estimation, learning-extraction heuristics, the confidence lifecycle, fuzzy
|
|
6
|
+
deduplication, conflict resolution, cross-resource transfer, and the suggestion engine
|
|
7
|
+
that costs no LLM call.
|
|
4
8
|
|
|
5
9
|
## Contents
|
|
6
10
|
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# Runtime — what keeps an agent alive between requests
|
|
2
|
+
|
|
3
|
+
**Load this when** the agent must survive things a single request does not: a crash
|
|
4
|
+
mid-run, a human who has to approve before it continues, a user who sends a second
|
|
5
|
+
message while the first is still working, a dropped connection, a schedule. The body's
|
|
6
|
+
loop is the *harness* — what the model is given to work with. This is the layer beneath
|
|
7
|
+
it, and most orchestrators assume it exists rather than specify it.
|
|
8
|
+
|
|
9
|
+
The split is worth keeping in mind while reading: **a good harness makes an agent
|
|
10
|
+
capable, a good runtime makes it deployable.** They fail differently, and a team that
|
|
11
|
+
has only built the first one discovers the second in production.
|
|
12
|
+
|
|
13
|
+
## Contents
|
|
14
|
+
|
|
15
|
+
- Durability, and where our own asymmetry was
|
|
16
|
+
- The interrupt/resume contract
|
|
17
|
+
- Double-texting: four policies
|
|
18
|
+
- Streaming that survives a dropped connection
|
|
19
|
+
- Time travel and forking
|
|
20
|
+
- Scheduled and sleep-time work
|
|
21
|
+
- Middleware: the seven concerns, unwelded
|
|
22
|
+
|
|
23
|
+
## Durability, and where our own asymmetry was
|
|
24
|
+
|
|
25
|
+
**Checkpoint every iteration of the loop, not just the stages a human reviews.**
|
|
26
|
+
|
|
27
|
+
The body's multi-stage pipeline persists at each `stage.checkpoint` and can resume from
|
|
28
|
+
a `pipeline_run_id`. The simple tool-calling path persists nothing: a crash, a deploy or
|
|
29
|
+
a killed worker loses the entire run, including the tool calls that already cost money
|
|
30
|
+
and time. That asymmetry is a defect, not a design — the simple path is the one that
|
|
31
|
+
runs most often.
|
|
32
|
+
|
|
33
|
+
What a checkpoint holds: the message array, the iteration counter, accumulated token
|
|
34
|
+
usage, the carryover state (see `context-engineering.md`), and whatever the sub-agents
|
|
35
|
+
have returned so far. Keyed by a thread id that acts as a cursor into the run.
|
|
36
|
+
|
|
37
|
+
Two properties earn their cost:
|
|
38
|
+
|
|
39
|
+
- **Resume at the point of failure**, not at the last human review. The difference is
|
|
40
|
+
whole minutes of re-executed tool calls.
|
|
41
|
+
- **A pause frees the worker.** An agent waiting for a human should hold no process. If
|
|
42
|
+
waiting costs a worker, long approvals are quietly expensive and teams stop using them.
|
|
43
|
+
|
|
44
|
+
## The interrupt/resume contract
|
|
45
|
+
|
|
46
|
+
The body has two mechanisms for one idea: `ask_user` raises a clarification error, and a
|
|
47
|
+
pipeline checkpoint returns a paused result. **They should be one contract.**
|
|
48
|
+
|
|
49
|
+
- **Interrupt** — the run stops at a named point, persists its state, and surfaces a
|
|
50
|
+
payload describing what it needs: a question, a plan to approve, a destructive action
|
|
51
|
+
to confirm.
|
|
52
|
+
- **Resume** — the caller returns a decision, and execution continues *from that point*
|
|
53
|
+
with the decision in scope. Not a fresh run that re-derives its way back.
|
|
54
|
+
|
|
55
|
+
One contract means one persistence format, one place a UI has to understand, and one
|
|
56
|
+
answer to "what happens if nobody replies for a day".
|
|
57
|
+
|
|
58
|
+
**When an interrupt is mandatory** rather than optional: any action that is
|
|
59
|
+
hard to reverse or outward-facing. Content-level guardrails are probabilistic — see
|
|
60
|
+
`governance.md` — so consequential actions need a deterministic limit or a human, not a
|
|
61
|
+
classifier's opinion.
|
|
62
|
+
|
|
63
|
+
## Double-texting: four policies
|
|
64
|
+
|
|
65
|
+
A user sends a second message while the first is still running. This has four possible
|
|
66
|
+
answers, and a system that never chose one has chosen the worst by accident:
|
|
67
|
+
|
|
68
|
+
| Policy | Behaviour | Fits |
|
|
69
|
+
|---|---|---|
|
|
70
|
+
| **Enqueue** | finish the current run, then start the new one | a task where the first answer is still wanted |
|
|
71
|
+
| **Reject** | refuse the second message while busy | expensive or transactional runs |
|
|
72
|
+
| **Interrupt** | stop the current run, start the new one, keep what was produced | conversational agents — the usual default |
|
|
73
|
+
| **Rollback** | discard the current run *including its input*, start clean | the user is correcting themselves |
|
|
74
|
+
|
|
75
|
+
The difference between interrupt and rollback is what the transcript looks like
|
|
76
|
+
afterwards, and it is worth deciding deliberately: interrupt leaves a half-finished turn
|
|
77
|
+
in history that the next prompt will see.
|
|
78
|
+
|
|
79
|
+
## Streaming that survives a dropped connection
|
|
80
|
+
|
|
81
|
+
Four things are worth streaming, and they are not the same thing:
|
|
82
|
+
|
|
83
|
+
1. **State snapshots** after each step — for a UI that renders the whole picture.
|
|
84
|
+
2. **State deltas** — the same, cheaper.
|
|
85
|
+
3. **Tokens** — the typing effect.
|
|
86
|
+
4. **Custom events** — domain progress: "queried 3 of 7 sources".
|
|
87
|
+
|
|
88
|
+
The body's tracker emits an in-memory feed. Two properties turn it into something a
|
|
89
|
+
client can rely on:
|
|
90
|
+
|
|
91
|
+
- **Every event carries a monotonic id**, and a client reconnecting sends the last id it
|
|
92
|
+
saw. The server replays from there. Without this, a dropped connection during a
|
|
93
|
+
ninety-second run means the user watches nothing and then gets an answer from nowhere.
|
|
94
|
+
- **The feed is a view over the durable trace, not the record itself.** If the only copy
|
|
95
|
+
of what happened is a stream nobody stored, evaluation is impossible — see the
|
|
96
|
+
`agent-evals` skill, which cannot function without it.
|
|
97
|
+
|
|
98
|
+
## Time travel and forking
|
|
99
|
+
|
|
100
|
+
Once every iteration is checkpointed, one capability follows nearly free: **pick a past
|
|
101
|
+
checkpoint, modify the state, and resume from it.** The original history stays; the
|
|
102
|
+
modified run forks.
|
|
103
|
+
|
|
104
|
+
This is the debugging tool the loop otherwise lacks. "Why did it call that tool?" is
|
|
105
|
+
answerable by rewinding to the step before, changing one thing, and running forward
|
|
106
|
+
again — through the real loop, with real model calls and real tools, rather than a
|
|
107
|
+
reconstruction that may not share the bug.
|
|
108
|
+
|
|
109
|
+
It is also how a failed production run becomes a regression fixture: fork at the failure
|
|
110
|
+
point, minimise, save the state as the fixture's input.
|
|
111
|
+
|
|
112
|
+
## Scheduled and sleep-time work
|
|
113
|
+
|
|
114
|
+
Not all agent work starts with a user. Two shapes, and the distinction matters:
|
|
115
|
+
|
|
116
|
+
- **Stateful schedule** — each run appends to an existing thread, so the agent remembers
|
|
117
|
+
the previous ones. A daily briefing that should not repeat itself.
|
|
118
|
+
- **Stateless schedule** — each run starts a fresh thread. A monitor that must not drift
|
|
119
|
+
on yesterday's context.
|
|
120
|
+
|
|
121
|
+
Scheduled runs need the same retry and tracing as interactive ones, and one extra rule:
|
|
122
|
+
**a schedule that fails silently is worse than no schedule.** Failures must reach a human
|
|
123
|
+
through something other than the absence of a result.
|
|
124
|
+
|
|
125
|
+
**Sleep-time compute** is the useful pattern on top: work done between conversations —
|
|
126
|
+
consolidating memory, refreshing an index, pre-computing what tomorrow's first question
|
|
127
|
+
will need. It is also where memory consolidation belongs when the hot path is too busy
|
|
128
|
+
for it.
|
|
129
|
+
|
|
130
|
+
## Middleware: the seven concerns, unwelded
|
|
131
|
+
|
|
132
|
+
The body's loop hand-codes seven cross-cutting concerns inside itself: retry, provider
|
|
133
|
+
fallback, summarisation, human-in-the-loop, tool-call limits, redaction, and moderation.
|
|
134
|
+
Each is correct and none is separable — changing the retry policy means editing the loop.
|
|
135
|
+
|
|
136
|
+
The alternative is ordered interceptors at four points:
|
|
137
|
+
|
|
138
|
+
| Hook | Runs | Typical use |
|
|
139
|
+
|---|---|---|
|
|
140
|
+
| `before_model` | before the request is built | inject context, redact, enforce a budget |
|
|
141
|
+
| `wrap_model_call` | around the call | retry, fallback, timing, cost accounting |
|
|
142
|
+
| `wrap_tool_call` | around each tool | authorisation, rate limits, argument validation |
|
|
143
|
+
| `after_model` | on the response | moderation, structured-output repair, guardrails |
|
|
144
|
+
|
|
145
|
+
The hook names are borrowed vocabulary; the shape is generic. What it buys is
|
|
146
|
+
composition — a tool-call limit is one interceptor, not a counter threaded through three
|
|
147
|
+
functions — and testability: an interceptor is a unit, the loop is not.
|
|
148
|
+
|
|
149
|
+
The trap: **order is semantics.** Redaction after summarisation redacts a summary that
|
|
150
|
+
already leaked. Write the order down where the list is defined, not in the head of
|
|
151
|
+
whoever wrote it.
|