@ssheleg/agent-stack 0.5.0 → 0.6.1
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 +64 -0
- package/README.md +31 -8
- package/package.json +1 -1
- package/plugins/agent-stack/.claude-plugin/plugin.json +1 -1
- 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,70 @@ 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
|
+
## v0.6.1 — 2026-08-13
|
|
8
|
+
|
|
9
|
+
Two CI fixes that had been sitting on `main` unreleased ship here, and the half of
|
|
10
|
+
standing instruction #6 that was missing arrives with them.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **Two plants used `sed -i` and were no-ops on macOS.** BSD sed requires an argument to
|
|
15
|
+
`-i`, so they errored and changed nothing; they could only ever be exercised in CI.
|
|
16
|
+
Converted to Python — the rule `task-pipeline` has enforced on itself for months.
|
|
17
|
+
- **Every plant now asserts that it changed the file.** The 2026-08-13 commit
|
|
18
|
+
*anchor the description plant on the file's shape, not its wording* applied one half of
|
|
19
|
+
instruction #6; this applies the corollary to the rest, so a plant that stops landing
|
|
20
|
+
says `PLANT DID NOT LAND: <why>` rather than reporting a healthy guard as broken.
|
|
21
|
+
- Also shipping, previously merged and unreleased: *one CHANGELOG-extraction pattern for
|
|
22
|
+
the whole family* (B-11), which is why this release can be cut at all.
|
|
23
|
+
|
|
24
|
+
All six plants verified by running them locally: each lands, and each makes the
|
|
25
|
+
validator fail.
|
|
26
|
+
|
|
27
|
+
## [0.6.0] — 2026-08-12
|
|
28
|
+
|
|
29
|
+
### Added
|
|
30
|
+
|
|
31
|
+
- **`references/runtime.md`** — the layer most orchestrators assume rather than
|
|
32
|
+
specify, and the one this skill was quietly missing. **Checkpoint every iteration,
|
|
33
|
+
not just the stages a human reviews**: the multi-stage path persisted and could
|
|
34
|
+
resume, the simple tool-calling path persisted nothing, so a crash lost the run that
|
|
35
|
+
executes most often — an asymmetry, not a design. Then one interrupt/resume contract
|
|
36
|
+
instead of the two mechanisms the body had for one idea (`ask_user` and a stage
|
|
37
|
+
checkpoint); the four double-texting policies and why interrupt and rollback differ
|
|
38
|
+
in what the transcript looks like afterwards; streaming with event ids so a dropped
|
|
39
|
+
connection rejoins instead of watching nothing for ninety seconds; forking a past
|
|
40
|
+
checkpoint, which debugs through the real loop rather than a reconstruction that may
|
|
41
|
+
not share the bug; stateful versus stateless schedules; and the seven cross-cutting
|
|
42
|
+
concerns welded into the loop pulled out as ordered interceptors — where **order is
|
|
43
|
+
semantics**, because redaction after summarisation redacts a summary that already
|
|
44
|
+
leaked.
|
|
45
|
+
|
|
46
|
+
- **`references/governance.md`** — permission, where `llm-proxy-billing.md` is cost.
|
|
47
|
+
**The greatest risk is usually not what the model says but what the agent can do**,
|
|
48
|
+
so the four boundaries get four control sets: model call, tool call, external server,
|
|
49
|
+
and agent-to-agent — the last being the one designs miss, since a sub-agent
|
|
50
|
+
inheriting its caller's authority silently widens every permission. The guardrail
|
|
51
|
+
taxonomy in order of reliability, and its honest limit: every content check is
|
|
52
|
+
probabilistic, so anything consequential takes a deterministic limit or a human, never
|
|
53
|
+
a classifier's confidence. Why an audit row without a **policy version** cannot prove
|
|
54
|
+
a control was applied. Cost attribution as a hierarchy, because "which team's agent
|
|
55
|
+
did this" is unanswerable from a flat tenant id. Failover that must be
|
|
56
|
+
policy-equivalent rather than merely available — a chain that silently fails into
|
|
57
|
+
another jurisdiction does it precisely when nobody is reading logs. Fail-open versus
|
|
58
|
+
fail-closed per workload. And blast radius: a sandbox protects the host, not the
|
|
59
|
+
sandbox, and credentials never enter it.
|
|
60
|
+
|
|
61
|
+
### Changed
|
|
62
|
+
|
|
63
|
+
- **The References table is an index again.** Each reference now opens with its own
|
|
64
|
+
`Load this when` line, so the trigger has exactly one home and the table cannot drift
|
|
65
|
+
from the files it points at. Compressing it returned ~100 tokens of body budget, which
|
|
66
|
+
is what paid for two new rows: the body sits at 489 lines / ~4883 tokens against
|
|
67
|
+
500 / 5000.
|
|
68
|
+
- README describes two skills and five references, and its trigger section covers the
|
|
69
|
+
evals skill and the permission surface, not only the orchestrator and the wallet.
|
|
70
|
+
|
|
7
71
|
## [0.5.0] — 2026-08-12
|
|
8
72
|
|
|
9
73
|
### Added
|
package/README.md
CHANGED
|
@@ -10,7 +10,7 @@ Part of the [ssheleg skill family](https://github.com/ssheleg/sshlg-skills).
|
|
|
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
|
|
13
|
+
behaves — and five references the first loads on demand.
|
|
14
14
|
|
|
15
15
|
**The orchestrator** (`SKILL.md`) — what the agent reads first:
|
|
16
16
|
|
|
@@ -46,6 +46,23 @@ boundary rather than summarized, tool-output offload to a file, token estimation
|
|
|
46
46
|
and the direction it errs, the compaction circuit breaker, sub-agent context
|
|
47
47
|
isolation, and how to choose constants for your own window.
|
|
48
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
|
+
|
|
49
66
|
**`references/patterns.md`** — the data models and algorithms underneath:
|
|
50
67
|
message and result protocols, pipeline models, the SQL validation loop,
|
|
51
68
|
context-window sizes and token estimation, learning-extraction heuristics,
|
|
@@ -71,7 +88,7 @@ waterfall, and model-routing precedence.
|
|
|
71
88
|
/plugin install agent-stack@agent-stack
|
|
72
89
|
```
|
|
73
90
|
|
|
74
|
-
**npm installer** — copies
|
|
91
|
+
**npm installer** — copies both skills into `~/.claude/skills/`:
|
|
75
92
|
|
|
76
93
|
```bash
|
|
77
94
|
npx @ssheleg/agent-stack
|
|
@@ -95,13 +112,19 @@ Restart your agent afterwards — skills load at session start.
|
|
|
95
112
|
|
|
96
113
|
## When it triggers
|
|
97
114
|
|
|
98
|
-
|
|
99
|
-
tool use, or an AI pipeline. Also
|
|
100
|
-
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.
|
|
101
124
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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.
|
|
105
128
|
|
|
106
129
|
---
|
|
107
130
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ssheleg/agent-stack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.1",
|
|
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"
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "agent-stack",
|
|
3
3
|
"displayName": "Agent Stack",
|
|
4
4
|
"description": "Two skills: agent-orchestrator \u2014 tool-calling loops, multi-stage pipelines with checkpoints, provider routing with fallback, four-layer memory, context engineering, plus the wallet side of reselling LLM access; and agent-evals \u2014 run/trace/thread evals, judges, and fixtures grown from production.",
|
|
5
|
-
"version": "0.
|
|
5
|
+
"version": "0.6.1",
|
|
6
6
|
"author": {
|
|
7
7
|
"name": "ssheleg",
|
|
8
8
|
"url": "https://x.com/sshlg93"
|
|
@@ -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.
|