Three of five API surfaces silently discard the model's reasoning #300
Closed
opened 2026-08-27 05:37:00 +00:00 by grenade
·
1 comment
No Branch/Tag Specified
main
feat/385-acestep-memory
fix/376-models-unhealthy-node
feat/music-surprise-genres
feat/web-decisions-route
fix/cold-load-serialise
fix/music-hide-stale-on-compose
feat/audio-disk-guard
feat/music-studio-ux
fix/cortex-glibc-floor
feat/music-history-sidebar
feat/audio-flac-mp3
feat/393-heartmula-serving
feat/391-heartmula-port
feat/387-acestep-rollout
feat/383-acestep-engine
feat/390-heartmula-fixtures
feat/381-acestep-fixtures
feat/402-music-ui
feat/386-audio-rates
feat/382-audio-job-api
feat/356-systemone-playground
fix/bench-skip-decision-models
feat/341-laya-residency
feat/341-laya-rollout
feat/337-laya-perf
feat/338-neuron-systemone
feat/335-laya-prompt-builder
feat/336-laya-engine
feat/339-laya-lang-detect
feat/338-systemone-surface
feat/334-laya-reference-fixtures
feat/96-mtp-probe
fix/324-qsa-visible-set
feat/310-ple-random-advice
feat/neuron-phase-timing
fix/qwen4-ple-conv-launch-bound
feat/318-per-layer-activation-hop
fix/318-release-host-memory
feat/318-per-model-max-in-flight
fix/318-logits-last-position
fix/318-cached-experts-residency
feat/318-host-source-tensors
feat/318-host-resident-experts
fix/322-cache-telemetry
feat/322-isq-cache
test/323-parity-long-prompt
fix/323-layer-type-alias
fix/323-qsa-scores-in-f32
feat/310-mmap-ngram-table
fix/quant-fallback-match-llamacpp
fix/qwen4-exp-expert-row-width
fix/qwen4-exp-load-reporting-and-pool-trim
fix/320-isq-reachable-and-log-levels
feat/qwen4-exp-quantised-experts
feat/qwen4-exp-expert-bank
feat/qwen4-exp-mtp-head
docs/mtp-wiring-settled
feat/qwen4-exp-snapshot
feat/rustingface-always
feat/qwen4-exp-harness
feat/qwen4-exp-qsa-oracle
feat/qwen4-exp-model
feat/qwen4-exp-decoder
feat/qwen4-exp-linear-attn
feat/qwen4-exp-moe
feat/qwen4-exp-full-attn
feat/qwen4-exp-qsa-indexer
feat/qwen4-exp-config
feat/qwen4-exp-qsa-selection
feat/qwen4-exp-ple-consumption
fix/seed-cutlass-anonymously
feat/qwen4-exp-ple-hashing
fix/seed-cutlass-in-cuda-ci
feat/hf-endpoint-env
feat/qwen4-exp-hyper-connections
feat/qwen4-exp-norm-primitives
fix/alias-test-temp-catalogue-collision
chore/retire-vestigial-reasoning-ladder
fix/context-limit-honours-kv-budget
feat/server-advertised-reasoning-ladder
feat/web-chat-collapsible-working
fix/router-sync-restarts-only-on-settings-change
feat/ci-owns-the-router-config
fix/infra-setup-ships-ratelimit-to-gateway
fix/nginx-preflight-checks-every-grant
fix/restore-max-in-flight-8
fix/retire-qwen36-27b
fix/nginx-sync-must-not-block-cortex-deploy
feat/nginx-search-alias-and-sync
fix/wire-surface-in-neuron-spans
fix/295-ignore-tiny-prefill-samples
fix/300-anthropic-streaming-thinking
fix/300-return-the-models-reasoning
feat/operator-repetition-knobs
fix/292-prefix-cache-budget
feat/stamp-preserve-thinking-state
fix/296-one-step-one-assistant-turn
feat/preserve-thinking-ab-gate
fix/client-capability-misattributions
fix/291-max-in-flight-and-config-only-ci
feat/290-reasoning-rungs-from-the-model
fix/284-gdn-state-f32
feat/bench-measurement-regimes
feat/285-nonstreaming-through-engine
fix/288-bench-authenticates
feat/283-operator-sampling-override
fix/286-clippy-all-targets
fix/replay-only-the-latest-reasoning
fix/developer-role-maps-to-system
fix/223-reasoning-effort-off
fix/fetch-the-models-own-prompt-format
fix/278-advertise-the-real-limits
fix/277-responses-reasoning-roundtrip
fix/272-sampling-gaps
fix/clippy-1.98-result-large-err
feat/sampling-defaults
fix/streaming-completion-log
fix/prometheus-histogram-buckets
fix/responses-reasoning-events
fix/stream-reasoning-content
fix/cortex-stream-read-timeout
fix/sse-keepalive-during-prefill
fix/anonymous-yields-to-authenticated
fix/upstream-test-fixture-collisions
fix/docs-directory-index
feat/angels-internal-vhost
v0.1.16
v0.1.15
v0.1.14
v0.1.12
v0.1.11
v0.1.10
v0.1.9
v0.1.8
v0.1.7
v0.1.6
v0.1.5
v0.1.4
v0.1.3
v0.1.2
v0.1.1
v0.1.0
Labels
Clear labels
out-of-scope
p1-now
p2-next
p3-later
Outside helexa's constraining principles: near-frontier models on consumer hardware, served transparently and predictably
Active pain or unblocking work — pick up first
Engine levers — start once P1 baselines exist
Dependent on earlier work, or demand-driven
No labels
Milestone
No items
No Milestone
Projects
Clear projects
No projects
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: helexa/helexa#300
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Reasoning is generated, counted in
usage, and then thrown away onthree of our five response paths. The client never sees it, so it can
never replay it, so on the next turn the model has no record of its own
thinking — only bare assertions it cannot account for.
The surface
/v1/chat/completionsstreamingchoice.delta.reasoning_content/v1/chat/completionsnon-streaming/v1/responsesstreamingresponse.reasoning_summary_*family/v1/responsesnon-streaming/v1/messages(Anthropic)/v1/chat/completions, non-streamingwire/openai_chat.rs, incollect_chat_completion:Explicitly dropped on the floor.
/v1/responses, non-streamingwire/openai_responses.rs,build_responseemits a single Message item:No
reasoningoutput item is ever constructed, though the streamingprojector builds the full item lifecycle.
Observed live: a request returning
{"output_tokens": 4000, "output_tokens_details": {"reasoning_tokens": 4000}}with an empty
output— 4,000 tokens generated, billed, and invisible./v1/messages(Anthropic) — the worst of the threecortex-core/src/translate.rs:97-113deliberately accepts theextended-thinking control:
and
openai_to_anthropicreturns:A bare text block. Anthropic's own shape is a
{"type":"thinking", "thinking": …}block alongside the text. So a client asks for extendedthinking, we take the parameter, spend the tokens, and hand back
nothing — every turn, silently.
No
thinking_deltaappears anywhere in the gateway, so this is notstreaming-only.
Why this matters beyond the wire
The visible symptom is a model that cannot account for its own prior
work: it asserts things it "decided" with no trace of the deciding, and
when asked to verify, has nothing to check against. On these surfaces
that is not a memory failure — the record was deleted before it ever
reached the client.
It also silently inflates cost:
reasoning_tokensare metered andunusable.
Not the cause of #296
pi streams
/v1/responses, so it does receive reasoning. This issue isa separate, wider defect found while investigating that one, and should
not be conflated with it.
Fix
collect_chat_completionaccumulates reasoning intomessage.reasoning_content, matching what the streaming path emits.build_responseconstructs areasoningoutput item when reasoningwas produced, mirroring the streaming projector's item lifecycle.
openai_to_anthropicemits athinkingcontent block. Needs care:Anthropic's shape carries a
signature, and a block we cannot signmay be rejected on replay — worth checking what a real client does
with an unsigned thinking block before choosing between emitting one
and documenting the gap.
silently reopen. The absence of exactly these tests is why three
paths diverged.
Related
#293 (a client-visible field we omitted, same shape of defect), #294
(schema conformance would catch the Responses case), #296, #223.
Fixed across all five surfaces —
039a353aandeb783c34./v1/chat/completionsstreaming/v1/chat/completionsnon-streamingmessage.reasoning_content/v1/responsesstreaming/v1/responsesnon-streamingreasoningoutput item/v1/messagesstreamingthinking_deltaevents/v1/messagesnon-streamingthinkingblockSix tests, one pair per surface: reasoning survives, and its absence
leaves the previous message shape untouched (absent, not empty).
The near-miss worth recording
The first fix (
039a353a) covered three paths and I called the issuedone. It had missed
AnthropicStreamTranslator— which readsdelta.contentanddelta.tool_callsand nothing else, and which isthe path Claude Code actually uses.
anthropic_sse.rscontainedzero occurrences of "thinking".
So the fix would have merged, passed CI, closed the issue, and changed
nothing for the client that needed it most. It surfaced only because
someone asked what a different client had been displaying.
That is the same pattern this issue documents — parallel paths diverging
with nothing comparing them — reproduced inside the fix for it. The
streaming and non-streaming halves of one surface are exactly the kind
of pair no test was checking, which is why three paths had drifted in
the first place.
Carried forward
The Anthropic
thinkingblock is emitted without asignature.Anthropic signs theirs; we cannot mint a valid one and inventing a value
would be worse than omitting it. If a real client rejects an unsigned
block the remedy is a flag, not a return to deleting the model's work —
recorded in both code paths so it does not need rediscovering. Worth a
deliberate short Claude Code session after this deploys rather than
finding out mid-task: the failure would appear on the second turn of a
session, when the block is replayed, which is an awkward thing to
diagnose cold.