Reset-day operator request (2026-08-08): the shared Moonshot Pro30 flatrate was drained in ~3 days (heavy 08-01/08-02, dead by 08-03) by the two kimi seats, and the operator asked whether the harness difference (Phoebe on opencode, Iris on kimi-code) was the culprit — with a harness switch on the table if yes. This ticket carries the forensics verdict and the fix. Operator observations quoted as leads, each verified against harness-side telemetry this session:
"sessions >500K tokens context window drain faster" — confirmed: 61.3% of all tokens Phoebe ever processed came from requests with 500K–1M context (2.2B of 3.6B); 250–500K adds 30.2%; sub-250K is 7.5%.
"first week, before Iris, you drained 13%/day" — consistent: solo-week processing 208–642M/day (avg ~410M).
"ONE more A2A message inside a long session moved 97% → 100% in a single turn" — the wake-delivered marginal turn at marathon context is the cliff event this ticket gates; precisely explained below (stale resume at ~0% cache hit).
"Iris and you only drained 2% today" — the natural experiment: young/short sessions post-reset are ~1%/seat/day. The fix is preserving that regime, not changing harnesses.
The Problem
Harness-side telemetry, 3 drain days (07-31 → 08-02), both seats:
Phoebe (opencode)
Iris (kimi-code)
API requests
1,714
2,673 (1.6× more)
fresh input
20.8M (12K/req)
13.2M (5K/req)
cache-read
715M (417K/req)
1,170M (438K/req)
Per-request context footprint is identical (~430K re-processed every turn); the MCP surface is byte-identical (same 4 servers in opencode.jsonc and .kimi-code/mcp.json). The harness is not the culprit; switching harnesses changes nothing. The weekly wall is ~1.9–2.05B raw tokens, hit two cycles running (Jul 24–26 ≈ 2.05B → dark Jul 27–30; Jul 31–Aug 2 ≈ 1.92B → dark Aug 3–7). The needle counts fresh input+output (~34–37M across the 3 drain days, both seats); cache-hit ratio decides how much of each turn bills fresh.
The TTL cliff (Iris's cross-harness measurement, 08-08 — identical both harnesses):
inter-call gap
iris hit%
phoebe hit%
<60s
99.3
98.2
60–300s
98.2
96.0
300–600s
98.7
90.9
600–1200s
92.7
82.7
1200–3600s
81.9
52.3
>1h
2.9
0.0
K3's prompt cache is excellent while warm; the TTL cliff sits at ~30–60 min idle. So marathon sessions hurt twice: big per-turn re-processing while warm (per-call input grows 68k → 737k across a day-long session), and a full re-bill on any >1h-stale resume — the 97→100% event was a >1h-stale resume of a ~700K session billing ~700K fresh at once. Median session 361 turns, max 1,286 (Phoebe history). No mechanism anywhere sees this cost: the wake path delivers A2A into a live session regardless of its context size or warm-window position, and no seat can see its own cumulative processed tokens.
The Architectural Reality
Live wake path (mapped in #16539): add_message → MC CoalescingEngineService (builds the wake/digest envelope) → launchd receiver on :3199 → local harness wake. Files: ai/daemons/wake/daemon.mjs, receiver.mjs, consumeWakeOutbox.mjs, armSeatWakeRoute.mjs, localWakeAdapters.mjs (the harness-adapter seam). No budget concept exists anywhere on this path.
The readiness decision that SHOULD gate sends — WakeDecisionService.decideWake (active AND idle AND ready) — is reachable only inside the disabled SwarmHeartbeatService.pulse() lane, so message-driven wakes never consult it (#16539 §3).
Per-provider cache warm windows (Mnemosyne, reference-verified 08-08): K3 TTL cliff ~30–60 min idle (measured, above); Anthropic default TTL 5 min / extended 1h (2× write cost), dropping to 5m in subscription overage — cache-hostility spikes exactly when budget is tightest; GPT seats unmeasured (open measurement gap, one Euclid/Emmy self-report).
Harness telemetry sources (both readable locally, no API dependency): opencode opencode.dbmessage.data.tokens JSON (input / output / cache.read / cache.write); kimi-code ~/.kimi-code/sessions/*/agents/main/wire.jsonlusage records (inputOther / inputCacheRead / inputCacheCreation / output; duplicated consecutive lines must be deduped). Iris's analysis scripts (burn-analysis, cache-ttl*) are the reporter core. Cross-check instrument: MC turn-presence records give idle-gap distributions fleet-wide, covering seats harness DBs can't see.
Tunables must live in AiConfig per ADR-0019 (no env re-derivation, no hidden defaults).
Adjacent wake-substrate work in flight: #16526 (fail-loud wake/A2A store binding, Grace) and #16582 (host-graph deletion, Vega) — the write layer; no overlap.
Positioning vs #16539 / #16540 (the wake-economy family)
Three complementary inputs to ONE send-path decision, three tickets:
#16539 — whether to wake at all (seat readiness): the inert bridgeDaemonEnabled kill-switch, two dead anti-flood layers, and AC-3's demand that readiness be consulted on the message-send path, not only in the disabled pulse lane.
#16540 — whether the message class justifies a wake: wakeSuppressed broadcast hygiene and the gate-language inversion.
#16682 (this ticket) — whether to wake into THIS session, and when: context size × warm-window position. The gate is a new input to the same send-path readiness decision #16539 AC-3 restores — not a parallel mechanism. If #16539 lands first, this gate plugs into its decision point; if this lands first, #16539 inherits the seam.
The Fix
Delivery-time context gate on the wake send path (as an input to the readiness decision restored by #16539 AC-3), decided on two axes — context size × warm-window position:
Above threshold T1 (~250K default, AiConfig): defer to a fresh session / post-compaction instead of appending. Past the warm window this is strictly optimal, not just preferable: a cold-append to a ~900K session re-processes ~900K fresh; a fresh boot with handover substrate costs ~30–150K.
A warm, active, sub-T1 session with queued messages has a delivery deadline: flushing before the window closes costs ~0.1× of the prefix, after ~1×. The decision weights pending-delivery urgency by time-remaining-in-warm-window — and must never be the marginal turn that re-warms a huge stale session for one message header (the 97→100% event, priced).
The message is never dropped; deferral and its reason are sender-visible.
Session cost telemetry: per-session cumulative processed tokens (fresh + cache-read) exposed where the seat can see it (wake envelope field and/or status surface), with warn threshold T2 < T1 so the agent self-sunsets before idle, not after (a >1h-idle large session is a fresh-boot candidate, never a resume candidate — the cache is gone anyway).
Per-seat cost reporter as an ai: buildScript/CLI (no new MCP tool per the operator's 07-26 ruling): per-seat daily export (calls, fresh input, cache-read, output, est. needle%) from both harness ledgers, plus a gaps > warmWindow incidence column cross-checked against MC turn-presence, plus the capped/uncapped ablation readout (Iris 650K cap live; Phoebe uncapped control). Validated against the hand-computed 07-31→08-02 table above. Target regime: ≤150M raw/seat/day. Leaf offered to Iris — her scripts are its core.
Contract Ledger
Target Surface
Source of Authority
Proposed Behavior
Fallback
Docs
Evidence
T1 / T2 thresholds (names indicative)
AiConfig reactive provider (ADR-0019)
Gate + warn tunables
No env reads; no hidden defaults
ADR-0019 §3
lint + spec
warmWindow per provider family
AiConfig leaves beside T1/T2
Delivery-deadline + defer-optimality terms
Unknown provider → conservative window (defer past T1, no deadline boost)
Decision Record impact: aligned-with ADR-0019 (thresholds via AiConfig SSOT). No ADR challenged or superseded.
Acceptance Criteria
AC-1: Wake delivery into a session above T1 is deferred — implemented as an input to the send-path readiness decision #16539 AC-3 restores; the message is preserved and the deferral reason is sender-visible.
AC-2: Unknown-context fail-open path delivers with a loud warn and increments a visible counter.
AC-3: T1/T2 live in AiConfig only; ADR-0019 §3 forbidden-pattern clean (no pass-along, no env re-derivation, no defensive ?., no hidden defaults).
AC-4: moved to #16707 (wake-carried session-context line), 2026-08-08 scope split for reviewability.
AC-5: moved to #16707 (seat-cost reporter incl. the gaps > warmWindow incidence column + ablation readout), same split.
AC-6: Unit specs: below-threshold deliver, above-threshold defer+preserve, unknown-context fail-open, warm-window deadline-flush vs cold-defer, reporter aggregation fixtures (dedupe of kimi-code's doubled usage lines covered).
AC-7: Sequencing with #16539 declared at PR time (plug into its decision point, or land the seam it inherits) — no parallel gate.
AC-8 (post-merge, operator): Moonshot dashboard settles the needle's exact composition (fresh vs cache-read metering); calibrate T1/T2/warmWindow accordingly.
AC-9: warmWindow per-provider-family leaves in AiConfig, consumed by the same decision point (Anthropic 5m/1h, K3 measured curve, GPT pending measurement); ADR-0019-clean.
AC-10: The decision treats >warmWindow-idle large sessions as fresh-boot candidates (never resume-and-append), with the fresh-boot-vs-cold-append cost asymmetry cited in code docs (~30–150K vs ~500–900K).
Out of Scope
Harness migrations or seat re-provisioning (falsified as a fix — see Problem table).
Moonshot plan/tier changes; model routing.
The wake/A2A store binding itself (#16526), host-graph deletion (#16582), seat-readiness gating (#16539), broadcast message-class discipline (#16540).
Skill/AGENTS.md discipline edits (session-length culture, "sunset before idle" as written norm) — follow-up once the mechanics exist to point at.
The 20-min in-session keep-warm poll as standing practice (~6M/day/seat by Iris's math — the session-growth engine re-born); the headless minimal-context poll is the destination and rides on this ticket's defer-to-fresh substrate. Cron cadence ruling is the operator's.
Avoided Traps
Switch both kimi seats to one harness — falsified by identical per-request context footprint, identical MCP surface, identical TTL cliff.
Cap requests instead of context — Iris fires 1.6× more requests at the same per-request cost; request caps would not bite the 61% of spend living in >500K turns.
Harness-side fixes — four harnesses in the swarm; the wake layer is the one seam every seat shares, and the only place a gate is portable.
Silent deferral on unknown context — the exact silent-correctness-loss mode #16526 exists to kill; the gate fails loud or fails open, never quiet.
A parallel context gate beside the readiness decision — two decision points for one send path is the #16539 inert-switch failure mode re-born; the gate is an input to the one decision.
One-axis (size-only) gating — the same wake into the same session costs ~0.1× warm vs ~1× cold; size-only gating misses both the defer-optimality and the delivery deadline (Mnemosyne's inverse case).
Sweeps
Live latest-open sweep: checked latest 25 open issues at 2026-08-08T12:05Z (list_issues, created-desc); no equivalent.
A2A in-flight claim sweep: latest 30 messages read pre-filing via manual MCP channel (this session's MC client detached at boot by the mc-server container restart race; server itself healthy); no overlapping claim. Post-filing correction: the 115-message backlog triage surfaced #16539 + #16540 (filed 08-05 while this seat was quota-offline, beyond the 30-message pre-filing window) — reconciled via the Positioning section; division of labor confirmed complementary, not duplicative.
KB semantic sweep (type=ticket): no equivalent.
Evidence base extended post-filing by Iris's measured data pack (TTL table, wall numbers, growth curves) and Mnemosyne's per-provider warm-window input — both absorbed above from the ticket comments.
Related
#16539 (wake kill-switch + readiness on the send path — the decision point this gate plugs into)
#16540 (broadcast wakeSuppressed discipline — the message-class sibling)
#16677 (MC MCP-surface wedge — both kimi seats lost their MC clients today; the wedge blocked this ticket's A2A path and forced the GitHub-comment route)
#16526 (fail-loud wake/A2A store binding — adjacent write layer, in flight)
#16582 (host-graph deletion — sequencing note from Grace applies)
Context
Reset-day operator request (2026-08-08): the shared Moonshot Pro30 flatrate was drained in ~3 days (heavy 08-01/08-02, dead by 08-03) by the two kimi seats, and the operator asked whether the harness difference (Phoebe on opencode, Iris on kimi-code) was the culprit — with a harness switch on the table if yes. This ticket carries the forensics verdict and the fix. Operator observations quoted as leads, each verified against harness-side telemetry this session:
The Problem
Harness-side telemetry, 3 drain days (07-31 → 08-02), both seats:
Per-request context footprint is identical (~430K re-processed every turn); the MCP surface is byte-identical (same 4 servers in
opencode.jsoncand.kimi-code/mcp.json). The harness is not the culprit; switching harnesses changes nothing. The weekly wall is ~1.9–2.05B raw tokens, hit two cycles running (Jul 24–26 ≈ 2.05B → dark Jul 27–30; Jul 31–Aug 2 ≈ 1.92B → dark Aug 3–7). The needle counts fresh input+output (~34–37M across the 3 drain days, both seats); cache-hit ratio decides how much of each turn bills fresh.The TTL cliff (Iris's cross-harness measurement, 08-08 — identical both harnesses):
K3's prompt cache is excellent while warm; the TTL cliff sits at ~30–60 min idle. So marathon sessions hurt twice: big per-turn re-processing while warm (per-call input grows 68k → 737k across a day-long session), and a full re-bill on any >1h-stale resume — the 97→100% event was a >1h-stale resume of a ~700K session billing ~700K fresh at once. Median session 361 turns, max 1,286 (Phoebe history). No mechanism anywhere sees this cost: the wake path delivers A2A into a live session regardless of its context size or warm-window position, and no seat can see its own cumulative processed tokens.
The Architectural Reality
add_message→ MCCoalescingEngineService(builds thewake/digestenvelope) → launchd receiver on :3199 → local harness wake. Files:ai/daemons/wake/daemon.mjs,receiver.mjs,consumeWakeOutbox.mjs,armSeatWakeRoute.mjs,localWakeAdapters.mjs(the harness-adapter seam). No budget concept exists anywhere on this path.WakeDecisionService.decideWake(active AND idle AND ready) — is reachable only inside the disabledSwarmHeartbeatService.pulse()lane, so message-driven wakes never consult it (#16539 §3).opencode.dbmessage.data.tokensJSON (input/output/cache.read/cache.write); kimi-code~/.kimi-code/sessions/*/agents/main/wire.jsonlusagerecords (inputOther/inputCacheRead/inputCacheCreation/output; duplicated consecutive lines must be deduped). Iris's analysis scripts (burn-analysis,cache-ttl*) are the reporter core. Cross-check instrument: MC turn-presence records give idle-gap distributions fleet-wide, covering seats harness DBs can't see.#16526(fail-loud wake/A2A store binding, Grace) and#16582(host-graph deletion, Vega) — the write layer; no overlap.Positioning vs #16539 / #16540 (the wake-economy family)
Three complementary inputs to ONE send-path decision, three tickets:
bridgeDaemonEnabledkill-switch, two dead anti-flood layers, and AC-3's demand that readiness be consulted on the message-send path, not only in the disabled pulse lane.wakeSuppressedbroadcast hygiene and the gate-language inversion.The Fix
T1(~250K default, AiConfig): defer to a fresh session / post-compaction instead of appending. Past the warm window this is strictly optimal, not just preferable: a cold-append to a ~900K session re-processes ~900K fresh; a fresh boot with handover substrate costs ~30–150K.T1session with queued messages has a delivery deadline: flushing before the window closes costs ~0.1× of the prefix, after ~1×. The decision weights pending-delivery urgency by time-remaining-in-warm-window — and must never be the marginal turn that re-warms a huge stale session for one message header (the 97→100% event, priced).T2 < T1so the agent self-sunsets before idle, not after (a >1h-idle large session is a fresh-boot candidate, never a resume candidate — the cache is gone anyway).ai:buildScript/CLI (no new MCP tool per the operator's 07-26 ruling): per-seat daily export (calls, fresh input, cache-read, output, est. needle%) from both harness ledgers, plus agaps > warmWindowincidence column cross-checked against MC turn-presence, plus the capped/uncapped ablation readout (Iris 650K cap live; Phoebe uncapped control). Validated against the hand-computed 07-31→08-02 table above. Target regime: ≤150M raw/seat/day. Leaf offered to Iris — her scripts are its core.Contract Ledger
T1/T2thresholds (names indicative)warmWindowper provider familyT1/T2T1, no deadline boost)WakeDecisionService.mjs(via #16539 AC-3) +localWakeAdapters.mjsT1, deadline-flush inside window, preserve message, sender-visible reason#16526failure modeai:cost reporter CLI--helpDecision Record impact: aligned-with ADR-0019 (thresholds via AiConfig SSOT). No ADR challenged or superseded.
Acceptance Criteria
T1is deferred — implemented as an input to the send-path readiness decision #16539 AC-3 restores; the message is preserved and the deferral reason is sender-visible.T1/T2live in AiConfig only; ADR-0019 §3 forbidden-pattern clean (no pass-along, no env re-derivation, no defensive?., no hidden defaults).gaps > warmWindowincidence column + ablation readout), same split.T1/T2/warmWindowaccordingly.warmWindowper-provider-family leaves in AiConfig, consumed by the same decision point (Anthropic 5m/1h, K3 measured curve, GPT pending measurement); ADR-0019-clean.warmWindow-idle large sessions as fresh-boot candidates (never resume-and-append), with the fresh-boot-vs-cold-append cost asymmetry cited in code docs (~30–150K vs ~500–900K).Out of Scope
#16526), host-graph deletion (#16582), seat-readiness gating (#16539), broadcast message-class discipline (#16540).Avoided Traps
#16526exists to kill; the gate fails loud or fails open, never quiet.#16539inert-switch failure mode re-born; the gate is an input to the one decision.Sweeps
list_issues, created-desc); no equivalent.#16539+#16540(filed 08-05 while this seat was quota-offline, beyond the 30-message pre-filing window) — reconciled via the Positioning section; division of labor confirmed complementary, not duplicative.type=ticket): no equivalent.Related
wakeSuppresseddiscipline — the message-class sibling)wakeSuppressed— deferral visibility must not repeat that silence)Origin Session ID:
ses_01ed929e9ffe1kG4Ne612BUHhT(opencode, 2026-08-08)Retrieval Hint: "flatrate drain forensics marathon session cache-read opencode wire.jsonl" · harness telemetry anchors:
~/.local/share/opencode/opencode.dbmessage.data.tokens;~/.kimi-code/sessions/*/agents/main/wire.jsonlusage· Iris's scripts:burn-analysis.mjs,cache-ttl*.mjs