Rounds that are not answers

Three defects with one shape: a round's record claimed something its own evidence did not support, and every reader downstream — the PR steward, the bug-fix pool, triage — acted on the claim.

1. A selected agent the catalog does not hold is an Error, never Completed (Plugins #2797)

When a round's explicitly selected agent matches no loaded agent (after the bounded catalog wait, AgentChatClient.MissingAgentGrace), AgentChatClient streams the sentence "Selected agent 'X' was not found among the available agents (…)" as the round's only output. No model runs and no tool runs. RoundOutcome.Classify read any non-empty text with no tool call as an answer, so the cell settled Completed — measured on the control instance as Hosting/Triage/_Thread/review-pull-request-…-6c03/c400d55d (2026-10-03). Every control-plane caller counts a Completed round as a spent attempt, so a catalog gap silently consumed their budgets.

The rule: an unresolved selection is its own verdict, RoundVerdict.SelectionUnresolved, decided ahead of every text rule and never Completed.

The "pr-reviewer not found" wave itself (fresh and resumed rounds dispatching without the space layer) was fixed by Plugins #2729 and the AI build that carried it; on the control instance the sentence has not appeared in any Hosting/Triage cell since 2026-10-04 06:30Z, over hundreds of pr-reviewer rounds. This section is about what such a round is CALLED when it does happen.

2. The supervisor's "last error" is the stuck round's, never an earlier round's (Plugins #3050)

ThreadSupervisor.Diagnosis embeds last error: in the Error cell it settles with and in the feedback it files. It took that text from Thread.Summary. A round writes Summary only in the write that ends it (the terminal Idle write), so on an executing thread — every Stale verdict — any Summary was written by an EARLIER round, possibly another episode, possibly days old.

Measured on the control instance (2026-10-07): round d0cb3e56 of Hosting/Triage/pull-request/systemorph-meshweaver-plugins-2969/_Thread/owner-…-2969 was cut three times by its host shutting down during successive rolls (Loki on the control instance: Host is shutting down, cannot route to …/d0cb3e56 from three different pods, at 02:02:32Z, 02:38:13Z and 03:10:02Z). The supervisor relaunched it twice and gave up at 03:15:36Z, quoting as last error an OpenRouter HTTP 403 … Key limit exceeded (daily limit) — written by round 233a0419 on 2026-10-05 23:48Z, 27 hours earlier. Triage filed it as "Agent thread wedges on an exhausted model key". The key was not involved: the next round that did hit the exhausted key (04:07:15Z, 271046c3) failed in 43 ms with a named Error cell and the dispatch pool closed the provider until midnight.

The rule: on an executing thread the last error is the round's own ExecutionStatus, or "none — the round in flight has written no error"; an earlier Summary is still reported, labelled "left by an EARLIER round, not this one". On an idle thread (Parked) the Summary IS the last round's terminal write and is read as before — including the #2236 cases after a wake or a relaunch (ThreadSupervisorSettleLoopTest).

3. An answer cut at the output cap is continued once, then named a truncation (Plugins #4459)

A provider that ends a call at the output cap SAYS so: the stream's finish reason is length. Nothing read it. The function-invoking loop saw a call with no tool call and stopped; RoundOutcome.Classify then judged the round by its TEXT alone. Reported twice in one build session on the public instance (2026-10-23): a delegated Worker generating code hit an output cap of exactly 16,384 tokens and its cell said "The model ran its tools but never wrote a closing answer" — which reads as a model that gave up, not one that was cut off. The same evidence on a call that HAD written text was worse: the round settled Completed over half an answer.

The rules:

A stall the guard catches (the connection stays open and the bytes stop) or a body cut mid-answer that surfaces as an exception is a different condition with its own handling (Provider stream faults): the stall guard names it and the retry regenerates a text-only call. A CLI harness round records no model calls, so it keeps the text rules.

4. A stream that ENDS with no finish reason was cut, not finished (Plugins #4459, the stall half)

The report's second occurrence was the main thread's last call: 15.6 s to first output, 24 output tokens, then the stream ended. No exception arrived, so neither the stall guard nor the retry saw anything; the ledger records such a call as Outcome: Completed with no finishReason. The round judged it by its text and said "never wrote a closing answer" — and on a call that had written text after the last tool result, it settled Completed over the fragment.

The rules:

Tests

Not established