Raise the default context window from 4096 to 8192 - #121
Merged
Conversation
A user reported chats losing continuity, refined after testing to "larger
system prompts seem to be overloading the model." That diagnosis was right,
but not for the reason it looks like -- this has nothing to do with model
size or attention quality.
Traced the full path first: a real two-turn conversation driven through the
actual /api/v1/generations -> jobs -> GenerationService orchestration showed
turn 2 genuinely receiving turn 1's full persisted history. Persistence and
loading are not the problem.
The problem is SynthesisAgent.fit_history_to_context, working exactly as
designed against a budget that was too small. num_ctx is one Cortex setting
applied the same way regardless of which model is selected -- it is not
tied to the model's own native context. Measuring the real trimmer (not the
test double, which skips trimming and would have hidden this) against a
realistic 30-exchange conversation, at the old default of 4096:
memory on, code-exec off: 10/30 exchanges survived
memory on, code-exec eligible: 6/30 exchanges survived
+ a handful of stored memories: 4/30 exchanges survived
The built-in system_prompt.txt + memory_prompt.txt cost ~1332 tokens before a
single word of the conversation is counted; add the code-execution JIT prompt
(triggered by ordinary phrases like "run this") and it climbs to ~1973 -- out
of a 3072-token prompt budget (num_ctx minus the 1024-token output reservation).
Every one of the models actually installed on this machine natively supports
far more (qwen3:8b alone is 40960), so history was being discarded well
within what the model could have used, and nothing in the UI said so -- it
just looked like the model forgot.
8192 was verified against the worst realistic combination (memory + stored
facts + code-execution eligibility + custom system instructions) and keeps
the full 30-exchange conversation. Left the existing 16384 ceiling alone --
raising it trades directly against local RAM/VRAM for the KV cache, which is
a real product decision, not a bug fix.
Changed everywhere a copy of the old default lived: the Pydantic field, two
defensive num_ctx fallbacks in services/generation.py and services/llm.py
(hit when model_options lacks the key, which a real snapshot never does, but
now stays in step with the real default instead of quietly reintroducing the
old bug), and three frontend fallback constants shown before Settings loads.
Added a regression test that reads the shipped default from CortexSettings
rather than hardcoding it, so it stays meaningful if the default changes
again, and fails loudly with the exact surviving-exchange count if it ever
regresses.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The report
Chats appeared to lose continuity after a few turns. Refined on testing to: "larger system prompts seem to be overloading the model and making it forget."
That diagnosis was right, but not for the reason it looks like — this has nothing to do with model size or attention quality.
What I checked first
Drove a real two-turn conversation through the actual
/api/v1/generations→ jobs →GenerationServiceorchestration. Turn 2 genuinely received turn 1's full persisted history. Persistence and loading are not the problem.What's actually happening
SynthesisAgent.fit_history_to_contextis working exactly as designed, against a budget that's too small.num_ctxis one Cortex setting applied identically regardless of which model is selected — it isn't tied to the model's own native context.Measured the real trimmer (not the test double, which skips trimming and would have hidden this) against a realistic 30-exchange conversation at the old default of 4096:
The built-in
system_prompt.txt+memory_prompt.txtcost ~1332 tokens before a single word of the conversation is counted. Add the code-execution JIT prompt (triggered by ordinary phrases like "run this") and it climbs to ~1973 — out of a 3072-token prompt budget (num_ctxminus the 1024-token output reservation). Every model actually installed here natively supports far more (qwen3:8balone is 40960), so history was being discarded well within what the model could use, and nothing in the UI said so.8192 was verified against the worst realistic combination — memory + stored facts + code-execution eligibility + custom system instructions — and keeps the full 30-exchange conversation.
Left the existing 16384 ceiling alone. Raising it trades directly against local RAM/VRAM for the KV cache — a real product decision, not a bug fix, and not what was asked for here.
Changes
Every place a copy of the old default lived:
GenerationSettings.num_ctx(the real default)services/generation.py/services/llm.py(only hit whenmodel_optionslacks the key, which a real snapshot never does — but now stay in step with the real default)contracts/openapi.jsonregeneratedNew regression test
test_default_context_window_survives_a_realistic_long_conversationreads the default fromCortexSettings()rather than hardcoding it, so it stays meaningful if the default ever changes again, and fails with the exact surviving-exchange count if it regresses.Verification
./scripts/check.ps1— 6/6 green. 507 backend tests (was 506), 163 frontend.🤖 Generated with Claude Code