@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 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 three references the first loads on demand.
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 the skill into `~/.claude/skills/`:
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
- Building an agent system, an orchestrator, an LLM-powered tool, a chatbot with
99
- tool use, or an AI pipeline. Also when the work is the money side: metering
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
- It does **not** trigger for a single LLM call in a script, or for prompt
103
- wording — that is not an orchestrator, and pulling 1200 lines of doctrine for it
104
- is how a skill teaches you to route around it.
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.5.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.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
- Load these when the task reaches them the checklist above is the map, these
494
- are the territory.
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**: 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 |
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 |
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
- Extended patterns, data models, and implementation details.
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.