diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 6a1fbd1..51d62ce 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -12,7 +12,7 @@ "displayName": "Agent Stack", "source": "./plugins/agent-stack", "description": "Four skills: agent-orchestrator — tool-calling loops, pipelines with checkpoints, provider routing with fallback, memory architecture, plus the wallet side of reselling LLM access; agent-evals — run/trace/thread evals, LLM judges, and fixtures grown from production; agent-interop — MCP servers and clients, A2A agent cards, the MCP Registry, and gateways; agent-harness — system prompts, tool shaping, workflow-vs-agent, and auditing an agent system.", - "version": "0.27.2", + "version": "0.28.1", "author": { "name": "ssheleg", "url": "https://x.com/sshlg93" diff --git a/CHANGELOG.md b/CHANGELOG.md index b47126d..24b88c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,32 @@ +## 0.28.1 — a limit stop is not a wait + +There is no 0.28.0 package. The tag `v0.28.0` was pushed by mistake onto `cb8f699` (the 0.27.1 +source) before this change merged. Its release run (38332687325) was cancelled before the +release and publish jobs ran, so nothing was published under that version. The tag ruleset +does not allow deleting the tag, so it stays. + +- **agent-orchestrator §4 and `references/runtime.md` → *A limit stop is not a wait*.** A worker + is a workflow lane, a sub-agent or a session. When it stops on a provider usage or session + limit ("resets 5am"), it is restarted at once from where it stopped and is never left until + the stated reset. The same holds when it fails on a rate-limit error after the host's own + retries, or goes silent for 15–20 minutes with its task unfinished and no command of its own + running. How each worker is restarted: + - a lane is resumed with the same run id and the same arguments, and finished agents replay + from the cache; + - a sub-agent gets `resume` at its id; + - a session is re-prompted. + + A second immediate stop backs off 2, 5, 10, then every 15 minutes while other work goes on. + Spend, credit and billing limits are a person's gate: they are reported once and never looped. + Every restart is logged. + + The reset time in a limit message describes one account at the moment of refusal. An account + rotator or a short rate window usually makes it stale within minutes. A team running parallel + workflow lanes observed this: six stopped on stated resets hours or days away, and each ran + when restarted minutes later. + + The references index row for `runtime.md` names the usage-limit stop. + ## 0.27.2 — owner board and release ancestry checks - The repository validator now rejects missing boards, shifted rows, duplicate IDs diff --git a/SKILL-CARD.md b/SKILL-CARD.md index 2d39b9a..bd4192f 100644 --- a/SKILL-CARD.md +++ b/SKILL-CARD.md @@ -5,7 +5,7 @@ | Field | Value | |---|---| | Pack | `agent-stack` | -| Version | `0.27.2` | +| Version | `0.28.1` | | Skills | `agent-orchestrator`, `agent-evals`, `agent-interop`, `agent-harness` | | License | MIT | | Source | https://github.com/ssheleg/agent-stack | diff --git a/docs/evidence/verification.md b/docs/evidence/verification.md index b64f571..2ce0332 100644 --- a/docs/evidence/verification.md +++ b/docs/evidence/verification.md @@ -10,6 +10,18 @@ This file exists because its absence read as zero exposure. `sshlg-skills` board --- +## Release candidate — v0.28.1 (2026-10-13) + +The change is doctrine only: no script, validator or installer change. It was measured on the +branch `feat/limit-stop-restart` (rebased on `e2120c2`) before any tag; nothing below is a +published-artifact claim. `v0.28.0` is a mis-pushed tag on `cb8f699`, and its run 38332687325 +was cancelled before release and publish (`npm view` → 0.27.2 afterwards); see CHANGELOG 0.28.1. + +| REQ | What ships | How it was confirmed | Confirmed | +|---|---|---|---| +| Limit stop | The agent-orchestrator `SKILL.md` §4 bullet *A limit stop is not a wait* routes to `references/runtime.md` → *A limit stop is not a wait*. That section holds the three triggers, the restart table, the backoff, the billing exception and the restart log | `grep -n "A limit stop is not a wait"` on both files; the runtime contents list carries the section | **observed**, local | +| Gate | `npm test` | EXIT=0 on 2026-10-13: `OK: agent-stack structurally valid (17 checks = 13 named + 4 per-skill, 4 skill(s), v0.28.1)`, `PASS: plant_guard — 9 cases`, `PASS: reap-own — 13 cases`, `PASS: installer — 11 case(s)`, all 25 `test/audit_regressions/*.py` exit 0; `claude plugin validate --strict` passed for `plugins/agent-stack` and the marketplace | **observed**, local | + ## Released — v0.27.2 (2026-10-13) Source integration: PR47 merged at `053c0f8725d28e464c98d063e87618c15174849a`. diff --git a/package.json b/package.json index 49bcad8..1562f9e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@ssheleg/agent-stack", - "version": "0.27.2", + "version": "0.28.1", "scripts": { "test": "python3 test/validate.py && python3 test/plant_guard_test.py && python3 test/member_owner_gates_test.py && python3 test/reap_own_test.py && node test/installer_test.js && npm run test:audit", "test:audit": "for t in test/audit_regressions/*.py; do python3 \"$t\" || exit 1; done" diff --git a/plugins/agent-stack/.claude-plugin/plugin.json b/plugins/agent-stack/.claude-plugin/plugin.json index ae54444..5a7bd79 100644 --- a/plugins/agent-stack/.claude-plugin/plugin.json +++ b/plugins/agent-stack/.claude-plugin/plugin.json @@ -3,7 +3,7 @@ "name": "agent-stack", "displayName": "Agent Stack", "description": "Four skills: agent-orchestrator — tool-calling loops, pipelines with checkpoints, provider routing with fallback, memory architecture, plus the wallet side of reselling LLM access; agent-evals — run/trace/thread evals, LLM judges, and fixtures grown from production; agent-interop — MCP servers and clients, A2A agent cards, the MCP Registry, and gateways; agent-harness — system prompts, tool shaping, workflow-vs-agent, and auditing an agent system.", - "version": "0.27.2", + "version": "0.28.1", "author": { "name": "ssheleg", "url": "https://x.com/sshlg93" diff --git a/plugins/agent-stack/skills/agent-orchestrator/SKILL.md b/plugins/agent-stack/skills/agent-orchestrator/SKILL.md index 4ee3a61..d1822a5 100644 --- a/plugins/agent-stack/skills/agent-orchestrator/SKILL.md +++ b/plugins/agent-stack/skills/agent-orchestrator/SKILL.md @@ -134,6 +134,11 @@ Three decisions the rest follows from: passes every check that only looks for an exception. - **Cap the attempts and return the best partial**, because the alternative to a partial answer is not a better answer, it is no answer and a spent budget. +- **A limit stop is not a wait.** A worker halted by a provider's usage limit + ("resets at 9pm"), a rate-limit error, or silence with its task unfinished is restarted + from where it stopped right away, not at the stated reset; back off only when the + restart fails again. Spend and billing limits are the exception — a person's gate. + Mechanics: [`references/runtime.md`](references/runtime.md) → *A limit stop is not a wait*. The hierarchy, the loop and the per-domain validators: [`references/patterns.md`](references/patterns.md). @@ -346,7 +351,7 @@ there; this table is only an index. | [`references/memory-landscape.md`](references/memory-landscape.md) | **build or adopt** a memory layer, and what is settled practice | | [`references/context-engineering.md`](references/context-engineering.md) | the loop is **running out of window** | | [`references/kv-cache.md`](references/kv-cache.md) | you are deciding **what goes where in a request** — the static prefix against the trajectory, what a miss costs, the 2^N cache populations a runtime condition in front of the boundary creates, the named invalidators, and the four common optimisations that pay cache for nothing | -| [`references/runtime.md`](references/runtime.md) | the agent must **survive a crash, a pause, a second message or a schedule** | +| [`references/runtime.md`](references/runtime.md) | the agent must **survive a crash, a pause, a second message, a schedule or a usage-limit stop** | | [`references/governance.md`](references/governance.md) | the question is **permission, not cost** — what it may do, and how you prove it | | [`references/llm-proxy-billing.md`](references/llm-proxy-billing.md) | the product **resells LLM access** | | [`references/provider-lifecycle.md`](references/provider-lifecycle.md) | the question is the **workforce, not the loop** — provider lifecycle, canary trust, fleet budgets | diff --git a/plugins/agent-stack/skills/agent-orchestrator/references/runtime.md b/plugins/agent-stack/skills/agent-orchestrator/references/runtime.md index d2b6454..5797cff 100644 --- a/plugins/agent-stack/skills/agent-orchestrator/references/runtime.md +++ b/plugins/agent-stack/skills/agent-orchestrator/references/runtime.md @@ -13,6 +13,7 @@ has only built the first one discovers the second in production. ## Contents - Durability, and where our own asymmetry was +- A limit stop is not a wait - The interrupt/resume contract - Double-texting: four policies - Streaming that survives a dropped connection @@ -42,6 +43,46 @@ Two properties earn their cost: - **A pause frees the worker.** An agent waiting for a human should hold no process. If waiting costs a worker, long approvals are quietly expensive and teams stop using them. +## A limit stop is not a wait + +A coding-agent host shows a usage stop as *You've hit your session limit · resets 5am*. +The time in that message describes **one account**, read when the request was refused. In +practice an orchestrator that restarts the worker minutes later usually finds it runs. An +account rotator may already have moved the session to an account with room, or the +limit was a short rate window. Waiting hours for the stated reset idles a run that would +have gone on. + +**Three triggers** send the orchestrator to restart a worker — a lane of a workflow, a +sub-agent, a whole session: + +- it stopped on a usage or session limit, whatever reset time it names; +- it failed on a rate-limit error after the host's own retries; +- it went silent: no transcript or event write for 15–20 minutes, its task unfinished, + **and no command of its own still running**. A build or a test suite that runs for half + an hour is not a stall, and stopping that lane loses the step in flight. + +**Restart at once, from where it stopped**, never from scratch: + +| Worker | Restart | +|---|---| +| A workflow lane | stop the workflow, then resume it with the same run id and **the same arguments**: finished agents replay from the cache, and only the failed or stalled ones run again | +| A sub-agent | send `resume` to its id, so it keeps its own context | +| A session | re-prompt it: *continue from where you stopped* | + +Changed arguments invalidate the cache, so the whole run is paid for again. + +**Back off only on a second stop.** If the restart halts again at once, retry at 2, 5, +10 and then every 15 minutes, and keep doing independent work in between. Do not sleep +until the reset time. + +**Not every limit is retried.** A spend cap, exhausted credits or a billing error does not +clear on a timer: someone has to pay or raise it. Report it to that person once and do +not loop. + +**Log every restart** in the run's journal: which worker stopped, on what, when, and the +attempt number. Ten restarts of one lane in an hour is a finding about that lane, not +noise. + ## The interrupt/resume contract The body has two mechanisms for one idea: `ask_user` raises a clarification error, and a