From 990e987cfedad083581171bb886195f1333882e0 Mon Sep 17 00:00:00 2001 From: "Carlos D. Escobar-Valbuena" Date: Fri, 9 Oct 2026 16:56:09 -0500 Subject: [PATCH 1/3] =?UTF-8?q?feat(orchestrator-tick):=20fleet=20token=20?= =?UTF-8?q?economy=20=E2=80=94=20burn=20forecast,=20dynamic=20cadence,=20m?= =?UTF-8?q?odel=20tiers,=20context=20rollover=20=E2=80=94=20v0.46.0=20(BRO?= =?UTF-8?q?-3013)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 24 ++++++++++++++++++++++++ VERSION | 2 +- references/orchestrator-tick.md | 33 ++++++++++++++++++++++++++++++++- 3 files changed, 57 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d4615af..b3c965f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,29 @@ # Changelog +## 0.46.0 — 2026-10-09 + +### feat(orchestrator-tick): fleet token economy — burn forecast, dynamic cadence, model tiers, context rollover (BRO-3013) + +Measured on 2026-10-09: about 43M input+output tokens and about 17.9B cache-read tokens in 24 h. Most of +it came from long-lived sessions re-reading huge contexts on every turn. team@ went from 0% to 48% of its +7 d window in about 23 h while devteam@ was limited until 10-12, so the fleet was heading for a stretch +with no capacity, and the §7 gates (present utilization only) could not see it. + +- §2: the fire period is dynamic. The tick re-sets its own heartbeat from `capacity.cadence`: 2 h, 4 h, + 6 h, or read-only. +- §3: the snapshot adds `capacity` and `sessions` sections. +- §5: driven sessions above 600k context get a successor, not a resume. +- §7: the burn forecast. Per-account 7 d burn rate from 24 h of samples, with a window-average fallback. + Fleet runway against relief (the earliest 7 d reset). A cadence ladder with launch holds and caps on + driven sessions. Each tick appends an economy event to `loops/economy`. +- §8: launch briefs carry a model tier: Haiku 4.5 for mechanical work, Sonnet 5 for standard work, + Opus 5.5 or Fable 5.1 for design, governance and P20. +- §11: context accounting. A `handoff_candidate` above 400k, the orchestrator rolls over above 400k, and + successors are seeded by a handoff note. + +The broomva overlay's implementation is `scripts/fleet_economy.py` in broomva/workspace. It is +fixture-tested and mutation-proved. + ## 0.45.1 — 2026-10-09 ### fix(hooks): bg-task-stop-guard says when a background subagent is actually done (BRO-2815) diff --git a/VERSION b/VERSION index ff12df2..3010923 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.45.1 +0.46.0 diff --git a/references/orchestrator-tick.md b/references/orchestrator-tick.md index a45d50a..1db81a9 100644 --- a/references/orchestrator-tick.md +++ b/references/orchestrator-tick.md @@ -99,6 +99,8 @@ Each step has a tier. A tier is due when its `last_run` in `state.tiers` is olde The overlay can move a step to another tier, or add its own steps to a tier. It cannot drop the tick contract or the census. +**The fire period is dynamic.** The tick sets its own heartbeat's period from the snapshot's `capacity.cadence` (§7): 2 h with headroom, 4 h when the burn forecast runs out before relief, 6 h when it is critical, and read-only at 95% of the active account's 5 h window. When the recommended cron differs from the heartbeat's, the tick updates the heartbeat it is bound to, by name. It changes the cadence only, never the prompt, and records the change in state and in the tick's economy event. The loop row's `cadence` says `dynamic`, so a changed cron is not drift. Tiers are periods, not fire counts, so they hold at any fire period. + **Budget: fewer than 15 tool calls a tick**, counted and recorded in `data.calls`. The snapshot script is what makes this possible. It reads, in one call, everything the `every` tier needs, including each driven session's final message (§5). Tool calls are spent on acting, not on reading. When a tick would exceed the budget, carry the remainder into `open` for the next fire. Don't stretch the tick. @@ -113,6 +115,7 @@ git -C show :.broomva/loops/checks/.sh | bash The contract for the script: - **Read-only.** It never prompts, archives, merges or writes outside its cache. +- It includes a `capacity` section (§7) and a `sessions` section (§11), described there. - It includes a `census` section (§4), with the final assistant message of each `arc` agent and of each `owner` agent idle for more than 12 h. That message comes from the agent's transcript: `persistence.sessionId` in its registry file, and `~/.claude/projects/*/.jsonl`. - It prints one JSON object with one key per section. - A section that fails is `{"error": "..."}`, and the rest still print. @@ -150,6 +153,7 @@ On an idle or finished session, act on the final message: | Final state | Action | |---|---| | Died on a usage limit | Resume it once the window clears. Usage gates the resume (§7) | +| Context above 600k (`sessions.successor_due`) | Don't resume it. Launch a fresh successor seeded by a handoff note (§11) | | Died on an auth denial, or pinned to a disabled login | Start a fresh agent in the same worktree, seeded with the dead one's final message, ticket and branch. Clear the dead agent's children's parent labels (§6), then archive it | | Waiting on a background job (CI, a watcher) | Nudge it to wait in the **foreground**. A background wait ends the turn, and the result arrives nowhere | | Ended on a question | Answer it if the answer is a decision you can make with the evidence. Otherwise it goes to the owner as an ask (§8) | @@ -197,6 +201,25 @@ Your own successor is such a child, so clear it on the successor before archivin - **Resumes and owner close-outs:** only while every account's 5 h window is under 90%. - **When the active account's 5 h window is at 95% or above:** no launch and no resume. Ticks keep running read-only. +**The burn forecast gates launches before the windows do.** Present utilization says nothing about the run rate, so the snapshot's `capacity` section forecasts it: +- **Burn rate**, per account, in 7 d percentage points per hour. It is measured over the last 24 h of usage samples, which the snapshot appends to its cache on every run, counting only samples inside the current 7 d window. With fewer than an hour of samples, it falls back to the window's average: 7 d utilization over the hours since the window opened. +- **Runway**: the fleet's 7 d headroom, summed over the accounts that are not limited, divided by the summed rate of those accounts. +- **Relief**: the earliest upcoming 7 d reset of any account. +- **`crosses_relief`**: the runway ends before relief. That means a stretch with no capacity at all. + +It recommends a `cadence`, taking the most severe rung that applies: + +| Mode | When | Fire period | Launches | Driven sessions | +|---|---|---|---|---| +| `normal` | headroom | 2 h | per the gates above | no cap | +| `conserve` | `crosses_relief` | 4 h | held | at most 3 | +| `critical` | it crosses with under 12 h of runway, or every usable account is at 90% of 7 d or more | 6 h | held | at most 1 | +| `read_only` | the active account's 5 h window is at 95% or more | 6 h | none | none, and no resume | + +`hold_launches` holds new launches and Maestro queuing. `max_driven` caps how many sessions the tick drives, resumes included. The forecast's runway, relief and mode go in the tick report. The overlay names the forecast's implementation and its fixture suite. + +Each tick appends one **economy event** to the `loops/economy` stream. It holds the capacity numbers, the session count, the top context consumers, and what the tick did: launches, resumes, merges, archives, successors and any cadence change. The overlay names the recorder and the evaluator that turns the stream into tokens per merged PR and burn trends. + ## 8. Launching work, and owner answers **Launch through Maestro cards**, not a bare `create_agent`. The card gives the run a gate, a review state and a home in the owner's Decisions. Every brief carries: @@ -205,6 +228,10 @@ Your own successor is such a child, so clear it on the successor before archivin - the scope boundary: what it must not touch; - what done means, as a quoted check; - the merge rule: pinned to `--match-head-commit`, on green gates, never `--admin`; +- a **model tier**, recorded on the launch (in the card and the backlog item): + - **Haiku 4.5** for mechanical drives: rerun, update-branch, merge on green, cleanup, archiving; + - **Sonnet 5** for standard implementation and research; + - **Opus 5.5** (or Fable 5.1) only for design, governance-class changes and P20 reviewers; - a last line of exactly `ARC-STATUS: CLOSED` or `ARC-STATUS: HANDBACK `. **Owner asks** go into a `.control/asks/*.yaml` ledger. @@ -254,7 +281,11 @@ Once a day: ## 11. Rollover -When the session's context is past use (repeated compaction, or near its limit): +Every turn re-reads the whole context from cache, so a large context costs on every turn, not once. The snapshot's `sessions` section reports each live in-scope session's `context_k` (the last main-thread assistant usage: input + cache read + cache creation, in thousands) and its `cache_read_24h`, the orchestrator's own included (`sessions.self`). Sessions above 400k are `handoff_candidate`; driven sessions above 600k are `successor_due`. + +**Driven sessions above 600k get a successor, not another resume.** Launch a fresh session in the same worktree, seeded with a handoff note: the ticket, the branch, the PRs and their heads, the session's final message, and the one next step. Then close out the old session (§6). + +**The orchestrator rolls over** when its own context passes 400k, or when it is otherwise past use (repeated compaction, or near its limit): 1. Write `state.yaml`. 2. Close the tick with `outcome: partial` and `data.handoff: true`. 3. Ask for the rollover through the overlay's channel. From 546c1ae7ffa246e0d659d3cbdd95b9693c354a23 Mon Sep 17 00:00:00 2001 From: "Carlos D. Escobar-Valbuena" Date: Fri, 9 Oct 2026 17:12:15 -0500 Subject: [PATCH 2/3] docs(orchestrator-tick): stale usage fails closed to conserve (BRO-3013 P20 r3 minor) Co-Authored-By: Claude Opus 5.5 --- references/orchestrator-tick.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/references/orchestrator-tick.md b/references/orchestrator-tick.md index 1db81a9..580060b 100644 --- a/references/orchestrator-tick.md +++ b/references/orchestrator-tick.md @@ -212,7 +212,7 @@ It recommends a `cadence`, taking the most severe rung that applies: | Mode | When | Fire period | Launches | Driven sessions | |---|---|---|---|---| | `normal` | headroom | 2 h | per the gates above | no cap | -| `conserve` | `crosses_relief` | 4 h | held | at most 3 | +| `conserve` | `crosses_relief`, or usage is stale or unreadable (fails closed) | 4 h | held | at most 3 | | `critical` | it crosses with under 12 h of runway, or every usable account is at 90% of 7 d or more | 6 h | held | at most 1 | | `read_only` | the active account's 5 h window is at 95% or more | 6 h | none | none, and no resume | From 7a39db4c7e804430ef361b0970a19998e6c388a7 Mon Sep 17 00:00:00 2001 From: "Carlos D. Escobar-Valbuena" Date: Fri, 9 Oct 2026 17:20:09 -0500 Subject: [PATCH 3/3] docs(orchestrator-tick): successors vs launch holds, and rollover never held (BRO-3013, CodeRabbit) Co-Authored-By: Claude Opus 5.5 --- references/orchestrator-tick.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/references/orchestrator-tick.md b/references/orchestrator-tick.md index 580060b..c943835 100644 --- a/references/orchestrator-tick.md +++ b/references/orchestrator-tick.md @@ -285,6 +285,8 @@ Every turn re-reads the whole context from cache, so a large context costs on ev **Driven sessions above 600k get a successor, not another resume.** Launch a fresh session in the same worktree, seeded with a handoff note: the ticket, the branch, the PRs and their heads, the session's final message, and the one next step. Then close out the old session (§6). +A successor replaces a session and adds none, so `hold_launches` does not hold it, but it counts toward `max_driven`. When the cap is full, or the mode is `read_only`, the successor is deferred. Until it launches, the old session stays unresumed and unarchived, and it is named in `open`. The orchestrator's own rollover, below, is never held by the cadence mode: it lowers the cost of every later tick. + **The orchestrator rolls over** when its own context passes 400k, or when it is otherwise past use (repeated compaction, or near its limit): 1. Write `state.yaml`. 2. Close the tick with `outcome: partial` and `data.handoff: true`.