Skip to content

pipeline: triage times out at its 10s ceiling on 79% of bench calls — every decision silently falls back to the keyword floor #2414

Description

@macanderson

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

  1. 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")'.
  2. 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;"
  3. 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).

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:pipelinestella-pipeline — triage→…→judge orchestrationin-progress

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions