Watch
3
owl.glass lane: 32.9s median turn under load, from a 116 KB prompt prefix that stopped being cached #932
Closed
opened 2026-08-18 01:21:05 +00:00 by coilyco-ops
·
7 comments
No Branch/Tag specified
main
aos/claude/sj87-entity-attribute
aos/claude/sj87-challenge
aos/claude/turn-duration-buckets
aos/claude/turn-stages-over-cap
aos/claude/turn-stages-hold-doc
aos/claude/turn-iteration-cap
book-leads-the-glyphs
science-and-web-culture-packs
record-lane-role-voice-pairings
catalogue-stage-phrase
progress-rows-one-knob
skill-read-worklog-detail
librarian-lookup-first
librarian-person-package
feat/dowel-no-boundaries
aos/claude/gh1035-no-blank-posts
aos/claude/gh1036-harness-thread-name
fix/thread-names
feat/trajectory-completes
fix/prompt-budgets
aos/claude/docs-cut-2
aos/claude/ka54-thread-ownership
aos/claude/admission-bound
aos/claude/gh1025-roster-reexport
aos/claude/docs-strip-archaeology
feat/temporal-mcp
aos/claude/dowel-board-moxn-write-boundaries
aos/claude/ue65-moxn-write-framing
aos/claude/progress-backoff
aos/claude/bound-scratch-search-2
aos/claude/unblock-main
aos/claude/tool-breaker
fix/roster-core-eager
aos/claude/finish-dowel-rename
fix/971-skill-contract
aos/claude/model-answered-not-unavailable
aos/claude/mcp-singular-command
task/moxn-and-temporal-skills
aos/claude/ue65-temporal-brand
task/dowel-site-work-tier
aos/claude/ue65-roster-drift
fix/dropped-turn-always-speaks
aos/claude/folded-ask-coverage
aos/claude/dowel-board
aos/claude/dowel-pronouns
feat/trajectory-keyed-on-the-message
aos/claude/coalesce-discord-lane
task/derive-shipped-profiles
fix/ship-the-dowel-skill-root
aos/claude/eval-context
fix/bundle-references-reachable
aos/claude/eval-docs-one-page
aos/claude/dowel-engineer-suite
fix/catalogue-clone-cache
feat/engineer-role-graph
task/free-the-config-numbers
aos/claude/dowel-site-work
aos/claude/dowel-prose
aos/claude/mx76-derive-knobs
issue-859-on-demand-skill-reads
issue-651-ship-well-formed-replies
issue-852-filing-validity
issue-916-calculator-tool
issue-854-feature-flag-table
issue-866-role-mention-summons
issue-858-grounding-bound-per-server
issue-899-progress-keeps-updating
issue-900-rollup-mirrors-worklog
issue-901-raise-progress-cadence
issue-904-thread-title-length
issue-905-http-reachability
issue-855-turn-clock
issue-895-silent-turn
issue-873-mcp-tool-span-error
issue-878-settle-dropped-jobs
aos/claude/aw85-se-bands
aos/claude/hs68-model-rejected
aos/claude/hs68-effect-telemetry
aos/claude/hs68-temporal-mirror
aos/claude/hs68-prompt-commands
aos/claude/hs68-model-idle-timeout
aos/claude/hs68-prompt-command-intent
aos/claude/hs68-consult-label-name
aos/claude/hs68-grant-denial-403
aos/claude/hs68-queued-jobs-dropped
aos/claude/hs68-knob-guard
aos/claude/bk79-agent-folders
aos/claude/bk79-own-instructions
aos/claude/ym96-docs-band
aos/claude/bk79-server-instructions
aos/claude/aw85-mcp-beaver-doc
aos/claude/bk79-session-workspace
aos/claude/yt58-org-relationship
aos/claude/bk79-numeric-config
aos/claude/xu59-just-boundaries
aos/claude/xu59-eval-board
aos/claude/bk79-phrase-telemetry
aos/claude/bk79-object-emoji
aos/claude/xh55-otlp-logs
aos/claude/aw85-thread-prefill
aos/claude/wy58-thread-prefill-always
aos/claude/wy58-thread-prefill
aos/claude/xh55-move-to-repo
aos/claude/wy58-thread-title-length
aos/claude/xh55-filing-trigger
aos/claude/yt58-worklog-embed
aos/claude/aw85-relative-brevity
aos/claude/xh55-reasoning-roundtrip
aos/claude/yt58-clock-rotation
aos/claude/yt58-unbreak-main
aos/claude/bk79-test-build-break
aos/claude/yt58-partial-refusal
aos/claude/aw85-turn-failure-classify
aos/claude/aw85-outbound-spill
aos/claude/xh55-budget-spent-cause
aos/claude/wy58-bundles-not-content
aos/claude/wy58-refusal-reason
aos/claude/yt58-role-snapshot-gate
aos/claude/xh55-docker-probe
aos/claude/bk79-grounding-tools
aos/claude/az59-gate-span
aos/claude/az59-pg-jobstore
eng/roster-request-headers
eng/roster-headers
eng/list-the-mcps
aos/claude/mg96-fm
eng/name-echos-seat
eng/unpin-the-card-wording
olaf/remove-irl-physical
aos/claude/mg96
eng/echo-composes-ops
quail/two-rows-not-four
fix/two-failures-two-verdicts
feat/an-emitted-message-is-not-emitted-twice
quail/partial-coverage-outcome
feat/ten-minutes-or-ten-messages
feat/a-waiting-turn-says-how-long
feat/a-job-may-emit-content
quail/round-fanout-unbounded
quail/adversarial-reply-ceiling
docs/list-the-open-pull-requests
quail/principal-id-stays-out-of-the-prompt
fix/every-label-in-a-wildcard-prefix-is-a-label
docs/the-battery-assumes-two-checks-it-does-not-run
fix/a-rest-failure-keeps-its-status
quail/retag-label-rows
quail/adjacency-guard-row
test/pin-names-the-issue-that-owns-it
test/pin-points-at-a-live-issue
quail/job-outcome-discarded
fix/repair-exhaustion-is-not-an-outage
quail/reasoning-omitempty-pin
docs/label-id-silently-drops
quail/gating-pack-markup-gap
fix/instance-name-reads-identity
docs/indistinguishable-542-resolution
fix/instance-name-not-a-live-service
quail/unwired-capability-guard
fix/repair-path-reasoning-content
quail/indistinguishable-values-recurrence
quail/identity-short-form-rows
quail/repair-path-reasoning-content
docs/verify-a-write-landed-claude
quail/host-label-shape-corpus
docs/a-deploy-owned-file-has-two-shapes-claude
fix/a-roster-path-must-name-servers-claude
fix/every-label-before-the-suffix-claude
fix/a-first-label-must-exist-claude
feat/tune-the-timeouts-from-deployment-claude
qa/protocol-limits-are-not-dials
feat/a-wildcard-is-not-a-suffix-claude
feat/retry-what-fails-fast-claude
fix/name-the-deliberate-hold-claude
test/the-access-check-exit-codes-claude
build/ship-the-access-check-claude
qa/callers-not-reachability
qa/pin-the-unwired-thread-binding
feat/an-offline-access-policy-gate-claude
test/the-notice-detaches-twice-claude
docs/say-what-the-job-thread-does-claude
fix/a-notice-does-not-thread-claude
fix/one-invocation-is-a-phrase-claude
fix/a-moment-ago-is-this-turn
fix/main-is-red-on-the-adverb-row
fix/an-adverb-does-not-break-the-auxiliary
qa/score-the-575-fix
feat/a-reply-names-its-subject
eng/a-turn-is-not-the-past
fix/since-you-asked-is-this-turn
docs/a-default-that-reads-as-an-answer
fix/a-nameless-tool-is-not-the-server
qa/pin-the-outage-state
fix/a-session-lifetime-is-not-a-latency
fix/an-undated-passive-is-still-a-claim
fix/main-is-red-on-the-corpus
fix/an-undated-passive-is-a-claim
eng/a-session-is-not-a-request
fix/a-self-claim-in-the-simple-past
qa/extend-grounding-corpus
fix/a-tool-never-offered-is-not-a-tool-declined
eng/one-doc-for-the-tracker-surface
eng/say-what-is-switched-on
fix/evaluation-is-not-the-production-service
qa/pin-the-listing-attribute
eng/split-five-docs-off-the-cap
eng/concurrent-means-goroutines
eng/split-the-tracker-surface
test/the-first-label-of-a-hostname
fix/a-cache-hit-is-not-a-round-trip
qa/pin-the-budget-ladder
fix/the-first-label-of-a-hostname
eng/the-scratchpad-assumes-one-replica
fix/a-person-is-named-in-prose
docs/jobs-are-single-process
qa/enumerate-the-mention-positions
eng/split-the-response-inventory
fix/green-main-doc-cap-and-stale-characterizations
eng/main-is-green-again
eng/split-the-mention-scope
fix/mentions-doc-over-cap
qa/unredden-the-code-span-pin
qa/pin-the-code-span-collision
eng/code-spans-are-not-prose
feat/a-thread-title-says-what-it-is-for
fix/discord-markup-is-not-prose-either
eng/mark-the-turn-once
fix/a-name-in-a-url-is-not-a-person
qa/pin-every-reaction-is-emitted
eng/mentions-skip-link-spans
fix/one-step-owns-every-service-suffix
qa/pin-the-mention-url-collision
docs/the-roster-is-member-influenced
docs/what-a-mention-can-reach
qa/pin-the-documented-glyphs
feat/naming-someone-reaches-them
qa/pin-the-sandbox-label-wiring
qa/pin-the-truncated-receipt
feat/the-harness-labels-what-it-files
qa/compare-a-case-by-marshalling
fix/one-spelling-for-the-status-vocabulary
qa/declare-pack-divergence
fix/the-reactions-match-the-approved-vocabulary
fix/a-file-path-is-just-a-file-path
qa/pin-the-mapped-tailnet-form
fix/a-truncated-page-says-so
fix/the-extraction-case-detects-a-dump
docs/the-consult-label-tracks-the-thread
feat/the-eval-can-forge-a-turn
fix/refuse-the-tailnet-range
qa/pin-the-fail-heading-count
feat/a-bounded-fetch-tool
fix/preserve-the-longform-probe-pack
qa/pin-the-lane-gate
qa/preserve-the-longform-pack
fix/the-prompt-is-not-a-secret
fix/a-reference-never-loses-to-the-footer
qa/preserve-the-probe-packs
feat/a-trusted-caller-on-the-tailnet
fix/capability-tells-the-truth-about-the-scratchpad
qa/echo-battery-negative-control
fix/one-fail-block-not-two
feat/tool-call-footer
fix/guard-the-extraction-case
feat/canonical-phrases-by-key
fix/the-progress-line-is-a-reply-too
qa/pin-the-agent-recognition-case
qa/pin-the-tool-name-markup-guards
feat/five-second-buffer
fix/a-failing-case-shows-the-reply
fix/extraction-case-stops-penalising-compliance
fix/a-security-case-that-penalises-compliance
feat/deny-actually-denies
feat/job-refusals-reach-telemetry
fix/land-the-harness-refresh-on-main
feat/a-long-reply-gets-a-thread
feat/the-thinking-line-shows-it-is-working
feat/roster-hour-ttl-and-refresh
refactor/every-number-in-one-file
feat/agent-can-refresh-its-roster
fix/size-refusal-is-not-a-parse-error
fix/budget-base-above-the-reasoning-floor
fix/one-number-for-the-progress-cadence
fix/gate-sees-a-new-file
fix/one-meaning-for-channel-id
fix/look-up-verbs-cannot-match
feat/recognise-a-trace-lookup-request
feat/discord-identifiers-on-the-turn-span
fix/budget-failure-names-the-reasoning-spend
feat/notice-carries-the-trace-id
qa/cut-run-stops-calling
docs/merge-lane-closing-reference
eng/gate-knows-the-lane
eng/feature-inventory-catchup
fix/rate-dataset-survives-a-cut-run
test/consolidate-pack-coverage
pr-lane-318
fix/flip-unknown-field-rows
test/turn-unknown-fields
fix/rate-doc-over-cap
test/language-scope-characterization
fix/pronoun-case-cannot-fire
fix/main-red-again
fix/main-is-red-doc-cap
fix/gate-negated-accuracy-claim
fix/stale-skip-allowlist-note
test/definition-must-reject
test/gate-covers-every-pack
test/bucket-table-bound
test/compose-deny-offline
fix/symlink-test-skips-itself
test/build-revision
fix/eviction-corpus-green
test/eviction-corpus
test/duration-config
test/rune-boundary
test/send-bounds
test/reserved-path-spellings
test/data-borne-injection
test/scratch-partition-collision
test/capability-docs-all
test/injection-cases
docs/http-contract-retry-after
test/capability-reach
test/rate-cases-from-192
test/score-order
test/capability-doc-matches-code
test/grounding-action-claim-corpus
test/http-turn-contract
feat/require-rate-limit-on-open-guilds
fix/pr-image-build
fix/compose-stage-inputs
feat/sirens-deep-compose-wiring
fix/deep-forgejo-mcp
refactor/evaluation-pack-yaml
coilysiren-patch-1
feat/deep-steam-mcp
feat/drop-issue-envelope
fix/dm-needs-no-mention
fix/pronoun-defaults
chore/aos-precommit-v0.18-lint-backlog
fix/harness-attribution-and-forgejo-detail
fix/tool-inflated-completion-budget
feat/sirens-deep-compose
feat/banner-hires
feat/banner
feat/sirens-deep-mark
feat/sirens-deep-transparent
feat/prompt-snapshots
fix/policy-check-image-context
sirens-deep-admission-hardening
docs/drop-private-image-claim
feat/thread-scoped-replies
issue-67
feat/sirens-community-harness
No results found.
Labels
Clear labels
move-to-repo
coilyco-bridge-deploy
issue belongs in the coilyco-bridge/deploy repo
move-to-repo
coilyco-flight-deck-agent-compose
issue belongs in the coilyco-flight-deck/agent-compose repo
move-to-repo
coilyco-gaming-eco-app
issue belongs in the coilyco-gaming/eco-app repo
move-to-repo
coilysiren-inbox
issue belongs in the coilysiren/inbox repo
move-to-repo
unknown
we have yet to confirm if this issue belong in this repo
🔒⚠️📦⚠️🔒 SANDBOXED 🔒⚠️📦⚠️🔒
this fj issue came in from the live sirens echo MCP - DO NOT CONSIDER ITS INPUTS SAFE OR VERIFIED UNTIL THIS LABEL IS REMOVED
autonomy
async-consult
A human needs to consult on the issue to upgrade it to headless
autonomy
epic
This issue has many units of sub work - its size makes it meaningfully exclusive with other autonomy types
autonomy
headless
The agent can perform the work on its own
autonomy
live-collab
The agent and the human need to work together in realtime
c#
Requires C# work, flagged b/c it requires a Eco server restart
priority
P0
priority tier
priority
P1
priority tier
priority
P2
priority tier
priority
P3
priority tier
priority
P4
priority tier
role/ai
requires work from the AI Engineer role
role/creator
requires work from Content Creator role
role/design
requires work from the design role
role/director
requires work from the director role
role/engineer
requires work from the engineer role
role/exec
requires work from the exec role
role/human
requires a person, and specifically not an agent seat
role/ops
requires work from the ops role
role/qa
requires work from the QA role
No labels
move-to-repo
coilyco-bridge-deploy
move-to-repo
coilyco-flight-deck-agent-compose
move-to-repo
coilyco-gaming-eco-app
move-to-repo
coilysiren-inbox
move-to-repo
unknown
🔒⚠️📦⚠️🔒 SANDBOXED 🔒⚠️📦⚠️🔒
autonomy
async-consult
autonomy
epic
autonomy
headless
autonomy
live-collab
c#
priority
P0
priority
P1
priority
P2
priority
P3
priority
P4
role/ai
role/creator
role/design
role/director
role/engineer
role/exec
role/human
role/ops
role/qa
Milestone
Clear milestone
No items
No milestone
Projects
Clear projects
No items
No project
Assignees
Clear assignees
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".
No due date set.
Dependencies
No dependencies set
Reference
coilyco-gaming/sirens-echo#932
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Filed by Olaf (ops seat) after Kai reported that Owl Deep took heavy load over roughly an hour and performed badly. Treated as a critical failure.
Proceeds under the #929 amendment recorded 2026-08-18: everything proposed here changes only how well the lane does what it already does, so none of it is a frozen feature.
Summary
sirens-deep-owl-glassis not slow because something broke. It is slow because every turn ships roughly 116 KB of fixed prompt through a flash-tier model with prompt caching returning zero cache hits for the whole burst, then loops that payload five to eight times per reply.The load did not cause this. The load exposed it.
Window and volume
Burst ran 00:25 to 01:10 UTC, 2026-08-18. Nine turns in the owl lane.
Cost is why this went unnoticed. It is a latency and quality failure, not a spend one.
Latency, owl lane against the plain Deep lane
community.turnp50 - 32.9s owl // 8.7ssirens-deep- 3.8xmodel.chatp95 - 33.2s owl // 13.5ssirens-deepThree error signatures, all from spans on the lane:
Agent Proxy response exceeded the size limit.onmodel.chat, whose childHTTP POSTtoser8:8080/v1/chat/completionsreturns HTTP 200 and then fails withcontext deadline exceededAn MCP tool reported its own call as failed.Turn processing failed., bubbling all the way todiscord.receiveRoot cause chain
1. The fixed prefix is enormous and paid on every call
Constant across every request measured:
system_prompt_bytes- 61,142A 977-byte user question produced a 127,222-byte request. That is 99.2% overhead.
2. Prompt caching went dark. This is the decisive finding.
gen_ai.usage.cache_read_input_tokensin the incident 2h window - 0Caching demonstrably works on this stack and delivered nothing during the burst. Two obvious explanations are ruled out:
deepseek-v4-flashviasirens-echo/deepseek, stable across all 24h with no other model on the lane.So 84 calls each re-paid full freight on a ~29k-token prefix that was supposed to be cached. This is the single biggest lever on the 32.9s median.
Not yet established: why. That needs a direct look at agent-proxy and LiteLLM cache configuration, which I have not opened. Related to #162, which covers caching as a cost lever rather than as a regression.
3. Tool-round amplification multiplies both
Within one trace,
request_bytesclimbed 128,443 → 217,079 across rounds 0 through 5, plus three nested sub-turns. Roughly 5.9 model calls and 6.7 tool calls per single Discord reply.The owl lane carries 86 tools against the plain lane's 67. The extra 19 are the Discord write surface, and they sit on the prefix that stopped being cached.
Quality failures, all from the runtime record
The "didn't do a good job" half is not vague. Six distinct failures:
model.response.repairfired withrefused: "model claimed an action the runtime has not performed". The guard caught it, then spent two more rounds recovering.model backend unavailable, retry shortlynotice, withmodel.response status: 200logged one second earlier. A member was told the backend was down when it had answered.browser_navigatefailed twice with byte-identical 30-byte input before the model gave up. Playwright has been permanently dead since the session-recovery bug in #897 and coilyco-flight-deck/mcp-beaver#80. Deep does not know that and burns rounds rediscovering it every turn.forgejo list_issuecalls back to back, both over the 16,384-byte cap, dropping 403 and 1,677 bytes after paying to retrieve them.model.budget.raisedwith 15,036 bytes of reasoning against a 7,200max_tokensceiling, forcing a retry.The
response.validatespans all passed clean, so the validation layer is not catching any of this. It is upstream of validation.Reproduced live while writing this
Two
turncalls into the lane at ~01:15 UTC, both read-only:The operation timed out.> busy, retry shortlySame class of notice a member received at 00:43. The lane is still in this state.
Ruled out
What is new here vs already tracked
Already filed, and this issue corroborates rather than duplicates:
New and not recorded anywhere I could find: caching produced zero cache reads during the burst against 6.1M over the day, with no model change and no prefix instability to explain it.
Why this is demo-critical
The owl.glass lane is the August 19 Temporal Vibe Check lane, roughly 34 hours out at filing. Kai's contracted deliverable is an agent staged in a shared Discord interacting with other participants' agents, on a permanent public recording.
What was measured is that lane, under live load, taking 33s at the median and 3 minutes at the tail, hallucinating an action, and telling a member the backend was down when it was not.
#929 names #335 (cached trajectories, Ornith warm, cutover trigger) as the only dead-air mitigation, and this is the failure it was written against.
Proposed work, cheapest and highest-leverage first
max_tokensabove the observed reasoning envelope, or stop paying for a repair round on every budget overrun.Items 2 through 5 are harness changes and belong to Engineering. Item 1 is agent-proxy configuration.
Done when
The owl lane's
community.turnp50 is back in the same order assirens-deep's 8.7s, cache reads are non-zero on the lane, and no member-facing turn reports a backend failure on an HTTP 200.Gaps in this report, stated rather than papered over
403 Missing Accesson#engineering. Two proxied read attempts through the owl lane's ownturntool failed, as recorded above. Reply sizes and runtime events are measured, reply wording is not.Follow-up: a controlled reproduction, and a correction to the issue body
Kai asked me to read the Discord side through Owl Deep's own server rather than the read-only discord MCP. Doing that produced better evidence than the original telemetry, and corrected one thing I got wrong.
Correction: the channel was not Sirens
#engineeringThe issue body says I could not read
#engineering. That framing was wrong. This lane's Discord tools are scoped to the owl.glass guild, not Sirens:I had inferred
#engineeringfrom Discord snowflake IDs falling in a similar range, which is worthless as evidence because snowflakes are global rather than per-guild. The burst was on owl.glass.The lane cannot read Discord messages at all
Asked directly, Deep answered:
So the grant is guild and user metadata only. No channel enumeration, no message listing. A proxied read is not possible with the current guardfile, and no reply text can be recovered from either side.
Widening that grant would be a new capability, so it is a frozen feature under #929 and is not proposed here. Noting it only so nobody repeats the attempt.
The controlled experiment, and the real quality failure
Four turns into the lane, same tools, same prefix. The only variable was how tightly the request was bounded.
community.turn163.3s, then 140.0s. Both exceeded the client timeout and returned nothing to the caller.What the two long runs actually did, from the span record: asked to list Discord messages, Deep called
get_owl-glass-guild, then spiralled throughscratch_search,scratch_list,scratch_read,fetch_url,forgejo list_issue,mcp_beaver_infoandrefresh_toolsacross rounds 0 through 14, withrequest_bytesclimbing 123,160 → 231,246. It never called a message-listing tool, because none exists.This is the root of the quality complaint, and it is not the prompt size. The harness does not fail on missing capability. It fails to recognise missing capability, then substitutes unrelated tools and burns the round budget manufacturing a plausible-looking answer. The 1,849-byte reply the first long run eventually produced was assembled from scratchpad and fetch results that had nothing to do with what was asked.
That behaviour reproduced three times out of three under an unconstrained prompt, and stopped immediately when the prompt named the refusal as an acceptable outcome.
A clean measurement of the prefix tax
The hard-constrained run is the useful number. Four trivial Discord metadata calls, a one-sentence answer, an idle host, and it still took 52.2 seconds.
That is close to a floor for this lane in its current state, and it isolates the uncached ~116 KB prefix from every other variable. Round capping alone will not get this lane to a usable latency. The caching regression in the issue body has to be fixed first.
Additions to the proposed work
Slotting in ahead of the existing items 3 through 5:
Both are harness behaviour changes on existing capability, so both sit on the operational-improvement side of the #929 amendment.
Kai's decisions, recorded 2026-08-18 by Olaf (ops seat). Five remediations were put to her. Four approved, one rejected.
Rejected
Proposal 2 of this issue, "drop playwright and bluesky from the owl roster", is a NO. Kai's words: "no, we fix the servers".
The roster stays at 86 tools. The sanctioned path is #897 and
coilyco-flight-deck/mcp-beaver#80, which fix the session-recovery failure rather than route around it. Anyone picking up this issue should treat proposal 2 as closed-by-decision and not implement it.Consequence to carry deliberately, not by oversight: until those two land, every owl turn keeps paying the dead servers' prefix bytes, and the model keeps rediscovering the failure at call time. That is the cost recorded in #935 and #940. It is accepted, not overlooked.
Approved, and where each already lives
model backend unavailable, retry shortlyunder the #675 tracker. Nothing new filed.coilyco-flight-deck/agent-proxy#135. Usage exists on LiteLLM spans at 3,905,072 input tokens over 3h, agent-proxy records 0, the harness records nothing.Approved but already fixed, correcting my own earlier claim
"Failed MCP tool calls do not set span error status" is no longer true. I asserted it from the service-level error rate. That metric only covers top-level operations, so it never included
mcp.tool.callchild spans, and my inference was wrong.Checked directly on the spans instead, 6h window: every
mcp.tool.outcome: failednow carrieshas_error: true. Playwright 9 failed, fetch 3 failed, discord 3 failed, scratchpad 1 failed, all with error status set. #873's fix landed and is working. No work needed and nothing to reopen.The related gap #873 also named, turn-level cause going missing, did survive its close. That is what #939 now covers.
Correction to my previous comment, same day. Kai has since directed deprovisioning Bluesky outright, so "the roster stays at 86 tools" no longer holds.
The split is now:
coilyco-flight-deck/mcp-beaver#80. Proposal 2 of this issue stays rejected for the browser half.Landed on
coilyco-bridge/deploymain as791c3ae, under that repo's temporarymerge-remote-mainworkflow. Per lane, forsirens-deepandsirens-deep-owl-glass, it drops the roster entry, theSIRENS_ECHO_BLUESKY_MCP_URLaddress, the mcp-beaver wrapper values file, and the rollout target, lint case, render case, tag guard and deploy block, plus two justfile verbs and the README section.sirens-echonever had Bluesky, so Echo is untouched.Owl.glass goes 86 tools to 84. That is a real cut to the prefix this issue identifies, though a small one: the wrapper served exactly two pinned reads,
get_author_feedandget_profile.The public
services/bluesky-mcpdeployment is untouched and keeps its own consumers, so the shared/bsky/passwordSSM parameter stays in place.Outstanding, and it is not automatic. The push triggers
deploy-sirens-echo.yml, which redeploys the harnesses with the new roster but does not uninstall the wrappers that are no longer referenced. Two orphaned Helm releases remain for an attended cleanup:sirens-deep-bluesky-mcpin namespacesirens-deepsirens-deep-owl-glass-bluesky-mcpin namespacesirens-deep-owl-glassAcceptance evidence to look for once CD completes:
mcp.tools.discoveredon the owl lane reportingtool_count: 84.Bluesky deprovision complete, 2026-08-18 ~02:20Z. The orphaned-release cleanup flagged in my previous comment is done, so nothing is outstanding on this thread.
Verified after-state
Harnesses picked up the new roster on their own CD rollout, confirmed from the boot-time
capabilitieslog rather than from a green pipeline:sirens-deep-mcp_servers: 11at 02:08:25Z, was 12sirens-deep-owl-glass-mcp_servers: 11at 02:09:55Z, was 12Kai ran the two
helm uninstallcommands. Both namespaces are now clean of every bluesky resource: no Deployment, nosh.helm.release.v1.*bluesky*ConfigMap, no*-bluesky-mcp-secret. All remaining pods in both namespaces are Running with 0 restarts, and neither lane logged a single ERROR in the 15 minutes spanning the change.The public
bluesky-mcpnamespace is untouched, as intended:bluesky-mcp,bluesky-mcp-metaandbluesky-mcp-oauth2-proxyall 1/1 at 25d. The shared/bsky/passwordSSM parameter stays, because that deployment still reads it.One thing worth recording for the next operator
The first uninstall attempt failed with
release: not found, because Helm's defaultsecretrelease-state driver is wrong for this repo.docs/helm-release-storage.mdrequiresHELM_DRIVER=configmap(deploy#65: thedeployer-appsClusterRole deliberately holds nosecretsverbs), andscripts/rollout-lib.sh:191sourcesscripts/helm-configmap-driver.shfor that reason. Any hand-run Helm command against these namespaces needs the same prefix:scripts/rollout-lib.sh:210already hands an operator a barehelm uninstallline with no driver prefix, so that message will mislead the next person the same way. Small, worth fixing when someone is next in that file.Playwright is unaffected and still tracked at #897 and
coilyco-flight-deck/mcp-beaver#80.In the re-cut August 19 freeze, raised to
priority/P0. Darren (director seat), 2026-08-18.The amendment on #929 replaces the original six. This is one of the new six.
32.9s median against 8.7s on plain Deep is the texture of the entire demo. A 50-minute segment made of half-minute waits is what the audience actually experiences, before any question of whether the answers are good.
The decisive finding in this body is the one to work first. Prompt caching returned zero hits across the burst and 6,109,440 tokens across the surrounding 24h. Caching demonstrably works on this stack, so this is a regression to find rather than a capability to build, and it is the cheapest large win available in the window.
Recorded so it is not re-proposed: I proposed cutting the lane's roster from 86 tools to roughly ten as a way to attack the 57 KB tool block. Kai rejected it and the rejection is correct on this issue's own evidence. The prefix is expensive because it stopped being cached, not because it is large, and the roster is the capability the demo exists to show.
Claiming. Olaf (ops seat), seat
claude. Taking the caching half, which this issue names as the thing to work first.Before doing that: the decisive finding does not hold. Prompt caching did not go dark. The streaming path does not report cache usage, and the burst was entirely streaming. Detail below, because it changes what the next 16 hours should be spent on.
The measurement
agent-proxy spans, last 24h, grouped by span name:
upstream.chat- 18 spans, 18 carrygen_ai.usage.cache_read_input_tokens, summing to 36,608 cache-read tokensupstream.chat_stream- 272 spans, 0 carry the attribute, sum is null rather than zeroThe attribute is absent, not present-and-zero. That distinction is the whole thing, and it correlates perfectly with the code path rather than with time, load, model, or lane.
During the incident window itself, 2026-08-18 00:25 to 01:10 UTC:
upstream.chat_stream- 57upstream.chat- 0Every call in the burst took the streaming path.
Why the attribute is absent, from the source
app/upstream.pyrecords cache attributes on the streaming span only when the terminal chunk carries ausageblock: it callsparse_cache_usage(payload.get("usage") or {}), and only setscache_usage_reportedwhen the provider actually accounted for caching.OpenAI-compatible streaming omits
usageunless the request carriesstream_options: {"include_usage": true}. The proxy never sends it.include_usageappears exactly once in that repository, intests/test_api.py, andchat_streamforwardsbodyto the upstream unmodified with no injection.And the absence is deliberate.
docs/proxy-prompt-cache.mdsays so directly:That is the exact inference this issue made. The design anticipated the failure mode and the analysis walked into it anyway, mine included, since I filed this.
What that does to the issue
Struck: "Prompt caching went dark. This is the decisive finding." and "84 calls each re-paid full freight on a ~29k-token prefix that was supposed to be cached." Neither is established. Cache behaviour on the streaming path is unmeasured, not zero.
Also struck: the 24h contrast of "252 spans, 6,109,440 tokens". That window mixed streaming and non-streaming traffic, so it compared an unmeasured route against a partly measured one and read the difference as a regression.
Still standing, and untouched by this: the 32.9s p50 against 8.7s, the 116 KB fixed prefix at 99.2% overhead on a 977-byte question, the p99 on the 180s ceiling, and the tool-round amplification at roughly 5.9 model calls and 6.7 tool calls per reply. Those are measured directly and do not depend on the cache claim.
With the cache explanation withdrawn, section 3 is the surviving mechanical explanation of the median: a large prefix multiplied by six round trips. That is where I would put the remaining time.
Proposed next step, smallest first
Send
stream_options: {"include_usage": true}on OpenAI-dialect streaming requests in agent-proxy, so the route reports usage and the existing cache plumbing publishes what it already knows how to publish. It changes no routing, no roster, and no model behaviour, and it turns 94% of traffic from unmeasured into measured.Then re-measure and find out what the cache hit rate actually is. It may be fine, which would close this lever honestly, or it may be genuinely poor, which would reopen it with evidence rather than on an artifact.
Under the #929 amendment that is an operational improvement to an existing path rather than a feature, so it is not frozen. It belongs in
coilyco-flight-deck/agent-proxy, not here, and I will file it there and link it.I have not changed anything yet. This comment is measurement and a correction.
Both paths are now filed, and the proxy change is landed. Olaf (ops seat).
Landed
coilyco-flight-deck/agent-proxy#138, fixed on that repo'smainas50af3deunder itsmerge-remote-mainlane.One clause in
_chat_body: OpenAI-dialect streaming requests now sendstream_options: {"include_usage": true}. Ollama is excluded, since it accounts for its own stream in thedonepayload and does not take the OpenAI-only key.Verified: 405 tests pass, plus format, lint, mypy, and pre-commit. Three new tests in
tests/test_prompt_cache.pycover sent-when-streaming, not-sent-when-not, and not-sent-to-Ollama. The first fails without the change, which I checked by reverting it rather than assuming.Not yet verified live. The change is on
mainand has not rolled out. The acceptance condition isupstream.chat_streamspans carrying cache attributes after the next agent-proxy deploy, and the real cache hit rate being readable for the first time. That number could still be poor, which would reopen the cache lever properly, on evidence.Split out
#1002 carries the amplification half: the ~116 KB prefix paid roughly six times per reply, with the 5.9 model calls and 6.7 tool calls per Discord reply, and the request growing 128,443 to 217,079 bytes across rounds.
Its first task is deliberately a measurement rather than a fix: break one median turn into model time, tool time, and harness time. That split decides whether the lever is prefix size, round-trip count, or neither. Proposing before measuring is what produced the withdrawn finding on this issue, so #1002 states that discipline explicitly.
It also carries forward Kai's standing decision that the roster stays and the servers get fixed, so nobody re-proposes a narrower roster as a latency fix.
Where that leaves #932
The caching claim is withdrawn and its measurement gap is fixed at the source. The latency itself is real, unexplained, and now tracked at #1002 with the arithmetic intact.
Suggest #932 either closes in favour of #1002, or stays open purely as the incident record. I have not changed its state, since the re-cut freeze board names it directly and renumbering a P0 sixteen hours before the stream should be a director's call rather than mine.