Problem
The triage role runs under a hard 10s latency ceiling
(triage_latency_ceiling, crates/stella-pipeline/src/pipeline.rs:498). A
timeout is swallowed silently — Err(RawCallError::Provider | RawCallError::Timeout) => None
in crates/stella-pipeline/src/pipeline/triage_stage.rs:93 — and the turn
falls through to resolve_task_class(None, goal), i.e. the pure keyword
floor in crates/stella-pipeline/src/triage.rs::deterministic_floor.
That fallback is correct by design. The defect is that it is now the common
case, and it is invisible: the run pays the full 10s of dead air and gets
zero bits, with nothing in the summary layer saying so.
Evidence
Three Terminal-Bench arm runs (openrouter / z-ai/glm-5.2), censused from
stella-events.jsonl (usage_incomplete with role == "triage"):
| run |
tasks |
triage answered |
triage timed out |
dev2-armA-stella |
18 |
0 |
18 |
DISCARDED-dev1-armA-stella |
14 |
7 |
7 |
smoke1-armA-stella |
2 |
0 |
2 |
27 of 34 triage calls (79%) burned the full 10,000ms and returned nothing.
Every usage_incomplete row reads
{"role":"triage","reason":"timeout","duration_ms":10000}. That is ~4.5
minutes of wall clock across the run purchasing no information.
The 7 calls that did answer took 4,684–8,587ms and emitted 330–778 output
tokens for a 3–4 line response.
Suspected cause: the triage prompt grew ~48x in output on 2026-08-06
Two PRs landed the same day, both adding to the triage payload:
Local telemetry (.stella/private/store.db, telemetry where
call_role='triage') across the same window:
| date |
n |
avg output tokens |
avg duration |
| 2026-07-26 → 2026-08-06 |
40 |
15–23 |
0.7–2.5s |
| 2026-08-08 |
3 |
266 |
4.1s |
| 2026-08-09 |
1 |
720 |
10.2s |
Same model on both sides of the jump (anthropic/claude-haiku-4.5 was
already in use on 2026-08-06 at 17 output tokens), so this is not a pure
model confound. n is small on the right-hand rows — this is a strong
hypothesis, not a proven cause.
The failure is self-defeating in a specific way: the RESEARCH: line is only
requested for multi, so the prompt is longest and the answer most expensive
exactly on the task class where triage's output would matter most — and that
is the class that then hits the ceiling and returns nothing.
Note role_output_cap already pins (Some(512), Some(ReasoningEffort::Low))
for ModelCallRole::Triage
(crates/stella-pipeline/src/pipeline/raw_usage.rs:104), so effort pinning
alone is not sufficient — see #2277 for a related case where a pinned
reasoning=off still burned the budget on OpenRouter.
Files
crates/stella-pipeline/src/pipeline/triage_stage.rs (the call, the
timeout swallow at :93)
crates/stella-pipeline/src/pipeline.rs:498 (triage_latency_ceiling)
crates/stella-pipeline/src/triage.rs:994 (triage_prompt), :1032
(TRIAGE_INSTRUCTIONS, including the RESEARCH: block)
crates/stella-pipeline/src/pipeline/raw_usage.rs:104 (role output cap /
effort)
Repro / verify
- Census any recent arm run:
jq -rc 'select(.type=="usage_incomplete" and .role=="triage") | .reason' agent/stella-events.jsonl
over every task dir; compare against
jq -rc 'select(.type=="step_usage" and .role=="triage")'.
- Locally:
sqlite3 .stella/private/store.db "SELECT date(ts), COUNT(*), AVG(output_tokens), AVG(duration_ms) FROM telemetry WHERE call_role='triage' GROUP BY 1 ORDER BY 1;"
- To attribute to prompt growth, run the same goal against
triage_prompt(goal, "") (empty structure renders the legacy payload
byte-for-byte) and against the current prompt, and compare output tokens.
Constraints
Definition of done
- A timed-out triage is observable rather than silent: a run whose class
came from the deterministic floor because the model never answered says so
in the receipt / summary layer, so a bench conclusion cannot be drawn from
a triage that never ran.
- Triage's p95 latency sits comfortably inside its ceiling on the multi-step
path, or the ceiling is raised with a stated justification.
- A witness test pins whichever fix is chosen (e.g. a prompt-size assertion,
or a test that a timed-out triage emits the observability record).
Related: #2277 (triage reasoning burn on OpenRouter), #2381 (stages cannot be
ablated independently — the experiment that would size triage's real value),
#1963 (research findings paid for and thrown away).
Problem
The triage role runs under a hard 10s latency ceiling
(
triage_latency_ceiling,crates/stella-pipeline/src/pipeline.rs:498). Atimeout is swallowed silently —
Err(RawCallError::Provider | RawCallError::Timeout) => Nonein
crates/stella-pipeline/src/pipeline/triage_stage.rs:93— and the turnfalls through to
resolve_task_class(None, goal), i.e. the pure keywordfloor in
crates/stella-pipeline/src/triage.rs::deterministic_floor.That fallback is correct by design. The defect is that it is now the common
case, and it is invisible: the run pays the full 10s of dead air and gets
zero bits, with nothing in the summary layer saying so.
Evidence
Three Terminal-Bench arm runs (
openrouter/z-ai/glm-5.2), censused fromstella-events.jsonl(usage_incompletewithrole == "triage"):dev2-armA-stellaDISCARDED-dev1-armA-stellasmoke1-armA-stella27 of 34 triage calls (79%) burned the full 10,000ms and returned nothing.
Every
usage_incompleterow reads{"role":"triage","reason":"timeout","duration_ms":10000}. That is ~4.5minutes of wall clock across the run purchasing no information.
The 7 calls that did answer took 4,684–8,587ms and emitted 330–778 output
tokens for a 3–4 line response.
Suspected cause: the triage prompt grew ~48x in output on 2026-08-06
Two PRs landed the same day, both adding to the triage payload:
57df153f(feat(stella-pipeline): ground triage in the workspace listing, not the goal string alone #1768) — the bounded workspace listing (up toTRIAGE_STRUCTURE_CHARS = 4_000chars of input).1bdf2daf(feat(stella-pipeline): pre-plan research stage — triage names questions, parallel read-only sub-agents answer them (#1778) #1953, closing Pipeline: a pre-plan research stage — triage names the questions, parallel read-only sub-agents answer them before the planner runs #1778) — the optionalRESEARCH:line, whichasks the model for up to 4 self-contained questions.
Local telemetry (
.stella/private/store.db,telemetrywherecall_role='triage') across the same window:Same model on both sides of the jump (
anthropic/claude-haiku-4.5wasalready in use on 2026-08-06 at 17 output tokens), so this is not a pure
model confound.
nis small on the right-hand rows — this is a stronghypothesis, not a proven cause.
The failure is self-defeating in a specific way: the
RESEARCH:line is onlyrequested for
multi, so the prompt is longest and the answer most expensiveexactly on the task class where triage's output would matter most — and that
is the class that then hits the ceiling and returns nothing.
Note
role_output_capalready pins(Some(512), Some(ReasoningEffort::Low))for
ModelCallRole::Triage(
crates/stella-pipeline/src/pipeline/raw_usage.rs:104), so effort pinningalone is not sufficient — see #2277 for a related case where a pinned
reasoning=offstill burned the budget on OpenRouter.Files
crates/stella-pipeline/src/pipeline/triage_stage.rs(the call, thetimeout swallow at :93)
crates/stella-pipeline/src/pipeline.rs:498(triage_latency_ceiling)crates/stella-pipeline/src/triage.rs:994(triage_prompt),:1032(
TRIAGE_INSTRUCTIONS, including theRESEARCH:block)crates/stella-pipeline/src/pipeline/raw_usage.rs:104(role output cap /effort)
Repro / verify
jq -rc 'select(.type=="usage_incomplete" and .role=="triage") | .reason' agent/stella-events.jsonlover every task dir; compare against
jq -rc 'select(.type=="step_usage" and .role=="triage")'.sqlite3 .stella/private/store.db "SELECT date(ts), COUNT(*), AVG(output_tokens), AVG(duration_ms) FROM telemetry WHERE call_role='triage' GROUP BY 1 ORDER BY 1;"triage_prompt(goal, "")(empty structure renders the legacy payloadbyte-for-byte) and against the current prompt, and compare output tokens.
Constraints
(
triage_stage.rs:55-69documents this and it is load-bearing).&'static strfor prompt-cache stability (Management calls (triage/verdict/guidance) have no cacheable prefix — and cannot have one until the adapters carry cache-control #1434,invariant 7 in AGENTS.md). Anything conditional must ride the volatile
payload half, never the instructions.
Definition of done
came from the deterministic floor because the model never answered says so
in the receipt / summary layer, so a bench conclusion cannot be drawn from
a triage that never ran.
path, or the ceiling is raised with a stated justification.
or a test that a timed-out triage emits the observability record).
Related: #2277 (triage reasoning burn on OpenRouter), #2381 (stages cannot be
ablated independently — the experiment that would size triage's real value),
#1963 (research findings paid for and thrown away).