Replies: 2 comments
|
Verified the mechanism end to end, and I think it moves the fix site: the one-token output is not the provider truncating anything — dsh asked for one token. Where the 1 comes from
const events = snapshot.models.streamSimple(model, context, {
...profileOptions(profile, reasoning, apiKey),
...
...options.maxTokens === undefined ? {} : { maxTokens: options.maxTokens },
const CONTEXT_SAFETY_TOKENS = 4096;
const MIN_MAX_TOKENS = 1;
export function clampMaxTokensToContext(model, context, maxTokens) {
if (model.contextWindow <= 0) return Math.max(MIN_MAX_TOKENS, maxTokens);
const available = model.contextWindow - estimateContextTokens(context).tokens - CONTEXT_SAFETY_TOKENS;
return Math.min(maxTokens, Math.max(MIN_MAX_TOKENS, available));
}Both constants are module-private, so nothing on the dsh side can observe the reserve it is subject to. The clamp is not specific to the completions protocol: every api implementation reaches it ( Arithmetic against your own numbers
Same shape, swept: prompt 250,000 → 8,046 output tokens; 258,000 → 46; ≥ 258,048 (= 262,144 − 4,096) → 1. The Scope of that check: I did not replay your session file; I ran your published usage numbers through pi-ai's own two exported functions from the pinned 0.85.1 build. The one assumption is the prefix-anchoring above, which your turn table satisfies. Why this makes the loop unbreakableEach 1-token answer is a valid prefix for the next estimate (it is neither aborted nor an error), so Your option 2 is unsafe on its own
Where I would put the fixThe sniff is downstream of the defect. Worth noting the asymmetry if the gate is merely relaxed: the clamp collapse fires at any resolved window — it is driven by the harness's own estimate, not by the provider — whereas "prompt fills ≥99% of So the single documented rule your "Relation to existing reports" asks for is roughly: a Related, but distinct from #5123 / #6671Those cover dsh's own ratio math against a per-request reserve. This is pi-ai's hidden Silence
|
|
Update: this is now a published plugin — Since the analysis above, I built the fix I described as mountable and verified it end to end against real shipped code. Posting it here so the gap has a plugin-shaped answer, and so anyone hitting the same dead session has something to install today. npm i @argszero/cordis-plugin-length-stop-overflow
Mount it with a bundle patch (no config needed): - insert:
- id: length-stop-overflow
name: '@argszero/cordis-plugin-length-stop-overflow'What it doesIt observes the public The synthesized failure carries the harness' own The verdict is window-independent, which matters because this thread's root cause is a window the harness believes and the provider does not (see #7213): a Verified end to end, with control arms48 tests. The interesting ones run against the real downstream rather than the plugin's idea of it:
Two properties I checked because they decide whether the rewrite is exact or merely plausible:
Configuration, and why you may want
|
Uh oh!
There was an error while loading. Please reload this page.
Summary
When a provider truncates an oversized prompt it can return
finish_reason: "length"with a tiny non-zero output. pi-ai's overflow sniff requires exactly zero output for that case, so dsh classifies the response as an ordinarymax-tokensstop:agent/request-errornever seesCONTEXT_WINDOW_EXCEEDED, no overflow-recovery compaction is spent, and the user'scontinueproduces one token and ends the turn — repeatedly, with no way out.Environment
0.1.6-alpha.2, commitddefc45fbc7f8e46dd73185e68295696d1297887openrouter/deepseek/deepseek-v4.1-flash, harness-resolved window 262,144@earendil-works/pi-ai@0.85.1session-2496881f-8d36-4ce6-93d9-1954a31930c7Reproduction
The session's last five turns, each answering
continue:turn/endmax-tokensmax-tokensmax-tokensmax-tokensmax-tokensEach assistant message's entire content is a single
reasoningblock containing"The". The prompt is 287k tokens against a 262,144 window, so the truncation signal is unambiguous — the only difference from the two turns that did raiseCONTEXT_WINDOW_EXCEEDEDis that their output was0.Current behavior
@earendil-works/pi-ai'sisContextOverflowhandles the length-stop case with:One output token and the check misses.
packages/llm/llm-pi-ai/src/stream.tsthen falls through to:so the turn ends normally from the harness's point of view. Nothing records that the model produced a single token on a full prompt, and the
agent/request-erroroverflow listener incompaction-basicnever runs. A user-visible dead end: everycontinuecosts a full request and returns nothing.Expected behavior
A
lengthstop whose prompt already fills the resolved window is context pressure, not an output cap. Suggested direction:stopReason === 'length'as overflow whenusage.input + usage.cacheRead >= contextWindow × ~0.99, regardless of whether the output was 0 or 1–2 tokens; orisRecoverableLength(message, desiredMaxOutput), which already exists for "a length stop below the caller's intended output limit" and is currently unused by dsh (grep -rn isRecoverableLength packagesfinds onlynode_modules).Either way the turn should reach the existing overflow-recovery path, which is exactly what the two
output = 0turns did.Relation to existing reports
length, the harness does not recognise it as overflow, and no recovery is even attempted.Evidence bundle
https://gist.github.com/gorban/4725c87390cb391047a2f14b56b98d28session-2496881f-degenerate-length-stops.json— the five stops with full usageEVIDENCE.mdsection 3session-2496881f-sanitized.jsonl— full session logAll reactions