Skip to content

fix(cli): preserve output budget for encrypted reasoning - #13349

Merged
marius-kilocode merged 1 commit into
mainfrom
investigate-luna-output-cap
Aug 24, 2026
Merged

fix(cli): preserve output budget for encrypted reasoning#13349
marius-kilocode merged 1 commit into
mainfrom
investigate-luna-output-cap

Conversation

@marius-kilocode

Copy link
Copy Markdown
Collaborator

Problem

Long-running Luna sessions sometimes sent a much smaller max_output_tokens value than the configured 32,000-token cap. Luna then consumed the entire reduced budget in reasoning and returned no actionable assistant output.

The reduced values were not random gateway behavior. They were calculated by the CLI before the request was sent.

Investigation Findings

The affected requests used openai/gpt-5.6-luna through the Kilo Gateway. The live model metadata reported:

Field Value
Context window 1,050,000 tokens
Maximum completion 128,000 tokens
Input limit Not reported by the catalog
Reasoning Enabled
Maximum effort max
Input modalities Text, image, PDF

The CLI's output budget calculation is:

estimated_context = normalized_message_estimate + tool_estimate
available_output = context_window - max(provider_reported_context, estimated_context) - 2,048

The old normalized estimate serialized the entire message history. It excluded encoded media, but treated provider continuation metadata as ordinary text. In particular, OpenAI's reasoningEncryptedContent was counted according to its encoded character length.

That value is opaque reasoning continuation state. Its serialized byte length is not equivalent to the provider's tokenized context length. In the affected sessions, the ciphertext was large enough to inflate the local estimate to approximately 1.04 million tokens.

The provider-reported context was lower, but the old implementation intentionally selected the larger value with Math.max. Therefore, the inflated local estimate overrode the accurate provider usage.

Incident Evidence

The five explicit output-limit cases identified in the local response and request records were:

Response record Sent output cap Provider input tokens Output tokens Terminal event
2026-08-21_08-26-29.071841+00_7361789 8,854 624,205 8,854 response.incomplete, max_output_tokens
2026-08-21_10-18-49.896391+00_7365221 1,822 334,486 1,822 response.incomplete, max_output_tokens
2026-08-21_10-26-34.680182+00_7365690 2,610 297,445 2,610 response.incomplete, max_output_tokens
2026-08-21_14-04-09.383612+00_7385561 4,295 490,972 4,295 response.incomplete, max_output_tokens
2026-08-21_14-19-32.427317+00_7386101 4,295 490,972 4,295 response.incomplete, max_output_tokens

The corresponding request records explicitly contained the reduced caps. The last two request records have identical hashes. The second request was a retry after a client-side stream timeout, not an independent cap decision.

The old client estimates can be inferred directly from the sent caps:

estimated_context = 1,050,000 - sent_cap - 2,048
Sent cap Implied old estimate Provider-reported input Difference
8,854 1,039,098 624,205 414,893
1,822 1,046,130 334,486 711,644
2,610 1,045,342 297,445 747,897
4,295 1,043,657 490,972 552,685

These values exactly satisfy the CLI formula. The gateway did not need to rewrite the cap to produce the observed behavior.

Ciphertext Contribution

The affected histories contained large encrypted reasoning values:

Sent cap Encrypted reasoning metadata in persisted history
8,854 Approximately 649 KB
1,822 Approximately 1.55 MB
2,610 Approximately 1.68 MB
4,295 Approximately 1.19 MB

The encrypted values were the decisive source of the estimate inflation. Replacing them with a fixed marker reduces their contribution to a small structural metadata cost while preserving the visible reasoning text, tool arguments, tool results, and ordinary message content.

Before And After Reproduction

Using the same Luna context limit, provider usage, and an incident-shaped reasoning continuation value:

Calculation Normalized estimate Raw diagnostic estimate Final configured cap
Before the fix 1,043,620 1,043,621 4,332
After the fix 59 1,043,621 32,000

The provider-reported context for this reproduction was 490,972 tokens. Before the fix, the inflated local estimate won the Math.max comparison. After the fix, the normalized estimate no longer includes the ciphertext size, so the provider-reported context remains the relevant baseline and the configured 32,000-token output budget is retained.

The raw estimate remains intentionally available for diagnostics. It represents serialized payload size, not a token estimate suitable for output budgeting.

Root Cause

The root cause was a mismatch between serialized request size and provider token usage:

  1. A long tool-heavy session accumulated encrypted reasoning continuation state.
  2. The CLI serialized that opaque state as ordinary message content for local token estimation.
  3. The character-based estimate inflated to approximately 1.04 million tokens.
  4. Math.max(reported, estimated) selected the inflated estimate over the provider-reported context.
  5. The CLI sent a reduced max_output_tokens value between 1,822 and 8,854.
  6. Luna at reasoning.effort: "max" consumed the reduced budget in reasoning before producing an actionable response.

Max reasoning effort made the failure visible because reasoning tokens are included in the output budget. It did not cause the reduced cap.

Implementation

The estimator now replaces reasoningEncryptedContent with a fixed opaque-state marker for normalized token accounting. This matches the existing treatment of encoded media:

  • Provider ciphertext is excluded from the normalized token estimate.
  • The raw serialized-size estimate still includes it for diagnostics.
  • Visible reasoning text remains counted.
  • Tool schemas, tool calls, tool results, and normal user or assistant text remain counted.
  • Both current providerMetadata and legacy providerOptions message shapes are covered.

The output-cap documentation now explicitly describes both excluded categories.

Scope And Limitations

This change fixes the identified Luna failure mode. It does not make character-based estimates equivalent to provider tokenization for all possible providers. Genuine large visible context, oversized tool schemas, media input, or incorrect model metadata can still require a lower output cap.

The live Luna catalog does not expose a separate input-token limit. The CLI therefore continues to use the model context window and provider-reported usage for this calculation. Adding an input limit would require upstream catalog metadata and separate handling; it is not part of this focused fix.

Release Note

The CLI changeset prevents encrypted reasoning state from incorrectly reducing the output token budget in long-running sessions.

@kilo-code-bot

kilo-code-bot Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: No Issues Found | Recommendation: Merge

Files Reviewed (4 files)
  • .changeset/keep-reasoning-output-budget.md
  • packages/opencode/src/kilocode/session/llm.ts
  • packages/opencode/src/kilocode/session/overflow.ts
  • packages/opencode/test/kilocode/session-overflow.test.ts

Reviewed by grok-4.6 · Input: 67.3K · Output: 7.2K · Cached: 489.3K

Review guidance: REVIEW.md from base branch main

@WebReflection WebReflection left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Curious to learn where "[opaque reasoning state]" strings comes from (I imagine cloud) but this looks good to me.

@marius-kilocode
marius-kilocode merged commit e8cd465 into main Aug 24, 2026
32 checks passed
@marius-kilocode
marius-kilocode deleted the investigate-luna-output-cap branch August 24, 2026 09:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants