Files
magpie/docs
TryWorld fde0c38c56 fix(gateway): a refusal that arrives once the reply began rests nobody (#1015)
A member whose reply had begun and whose vendor's safety filter then
refused the turn — Chat's code content_filter, Anthropic's stop_reason
refusal, Responses' bio_policy — was booked as a vendor failure
(Fail other) and rested with a backoff, so later requests skipped a
healthy account. The same refusal, said before any of the reply was, is
booked failRefused and rests nobody, and the request is handed to the
next member (#248). Only the timing differed: a refusal is about the
request's content, not about the account (5353ed54 rested every
WorkBuddy AI account over one system prompt).

The refusal was read only while the stream was still held for its first
content (`scan`): once that content has been said the reply goes through
as it comes, and an event after it is never classified. The holdWriter
now reads the events of a reply that has begun for a refusal in one
(`refusalAfter`), with a word filter before the JSON is read, so an
ordinary stream pays one substring test a write, and the mid-reply
branch books such a refusal as failRefused without resting it.

What the agent gets does not change: the reply and the vendor's own
error event go to it as they came, nobody else is asked (its 200 has
been sent), and the conversation is still booked as unanswered. An
error of another kind — an unreachable vendor, an overloaded one —
still rests as it did (#733), as does a reply too long.

TestRefusalMidReplyRestsNobody is new. On main, with a group of two
Chat members whose first answers half and is then refused:

    after content:  try0: status=200 fail=other rest=other
    before content: try0: status=400 fail=refused rest=<nil>

With this change the after-content case reads fail=refused,
rest=<nil>, as the before-content one does, and the test fails on main
and passes. TestRefusalAfterContentPassesThrough passes unchanged.

go test -race -count=5 -run TestRefusal, go test ./internal/middleware,
go vet (windows, darwin), the darwin, linux and windows builds, and
gofmt -l on the three files pass. One test in internal/gateway fails on
this machine either way — TestSweepBridgeProjectsTakesOnlyThe-
BridgesFolders needs SeCreateSymbolicLink — so it is skipped by name
rather than taken for a baseline. Not tried with a real vendor's
safety filter.
2026-10-06 19:54:53 +08:00
..
usage, gateway, gui: a reply that reasoned is timed by its answer, from its first text, not by all its output from its first token (tony on Discord #general: Usage › Requests read "gpt-6.1-sol 163 token/秒 · 8,036 个请求 · 首字 14 秒" and asked whether subagents were added up) — speed was every output token over the time after the first content (text, reasoning or a tool call). Output counts the reasoning, and a model whose reasoning is hidden (OpenAI's encrypted reasoning, a summary sent when it is done, Claude's omitted thinking) writes it before the stream shows anything, inside the TTFT; so a 2,000-token think and a 200-token answer over the 1.5 s after the first token read 2,200 in 1.5 s, 1,467 tok/s. Concurrency was not the cause: the aggregate is Σ tokens over Σ per-request windows, so subagents running at once never add up. In the owner's own usage.jsonl codex/gpt-6-luna turns read up to 600 tok/s (out 496, reasoning 224, 11,055 ms, ttft 10,228) and its aggregate 133; it is 80 now. usage.DecodeOf is the one rule: a reply with reasoning counts out − reasoning over ms − firstText, one that reasoned and wrote only tool calls tells no speed, one without reasoning is timed from its TTFT as before; DecodeWindow's burst cut (#731) still applies. Record.Decode feeds summarize, the ledger's Totals and its timeline Parts; a route keeps its reply's reasoning (Route.Reasoning, from gateway.go and codex_backend.go), and routing.js's decodeOf/speedOf times its rows, the story and a session's average by it (an older route takes its served usage's reasoning); app.js's ledDecode does the Usage page's rows, whose tooltip says the reasoning left out, and the Output speed help says the rule, in en, zh, ja and de. History is read the same way, as it keeps the reasoning and the first text. Routing never ranks by speed. TestReasoningReplyTimedByItsAnswer (the 2200/2000 turn, a summary 10 ms before the text, a concurrent subagent, tool calls only, reasoning over output, no reasoning, not streamed: 1,200 tokens in 11 s through summarize, addRow, LedgerSeries and Decode) fails without the change and passes -race -count=10; TestUsageKeepsTimeToFirstToken now has its paced replies report reasoning and checks the route keeps it. reasoning-speed.test.cjs (Routing rows 133, 100, none and 100, the story never 1,467, the session 105; the Usage rows, tooltip and KPI the same; no sideways scroll) passes on Chromium and WebKit in en, zh, ja and de, and fails against the old assets (1467 tok/s); routing-request-metrics, routing-burst-speed, usage-speed, usage-ledger-detail, i18n-keys, gui-ja, gui-de, ledger-hscroll and click-scroll pass on both. go test -race -tags nogui ./internal/... under a temp HOME passes but internal/proc's TestUserPath ("the login shell's PATH never came"), which failed under the full run's load, passes 3/3 alone and is not touched here; go vet, gofmt -l, linux and windows (nogui) builds pass. Not tried with tony's data. docs/reference.md says how output speed is counted.
2026-10-06 19:47:56 +08:00