| 2026-11-01 |

navi: a token count that matches the model, a type for every file, and a socket that admits it is dead
...
Compression decided everything in chars // 3, and on this workload that sits
~1.8x below reality — 101k real prompt tokens against a ~56k estimate — so the
trigger, the target, the truncate budgets and the pre-call size check all ran
in a unit half the size of the real one. TokenCalibration now fits
overhead + chars / chars_per_token per model from the prompt_eval_count that
every call already reports, and a profile declares its own context_window
(sent to Ollama as num_ctx) instead of every model being held to OLLAMA_NUM_CTX.
Superseded summaries were never demoted in the database: save() only updates
flags for rows in session.messages, a summary is is_display=False, so it lived
in session.context alone and every reload resurrected it — prod sessions
carried up to 15 live summaries. PgSessionStore.set_context_flag() writes the
flip for those rows directly.
Five extension tables disagreed about what a file is. navi/content_types.py is
the one detector — signature, then extension, then a text probe — it decides
the MIME the download route serves, and the session file list now carries the
type the viewers branch on.
The idle receive loop answers client pings and sends its own heartbeat. A
half-open socket keeps readyState at OPEN and never fires close, so traffic
coming back is the only proof the connection still works.
Eugene Sukhodolskiy
committed
1 day ago
|
| 2026-10-05 |

compressor: single-summary invariant — old summaries always fold into the fresh one
...
partition_messages now strips is_summary messages out of the turn structure
before grouping: a summary is already-compressed history, not a user request.
Previously its role=user made it a turn head, so midturn compression kept the
old summary verbatim beside the fresh one (head kept + new summary added),
and adaptive swap resurrected summary turns whose text pattern-matches the
importance heuristics. This piled stale summary copies into the context
(one session accumulated 6 copies = 120k of 239k chars) and made compression
no-op for whole stretches before a lucky pass finally shrank it.
Also folds summaries on the early-exit path so two stale summaries
consolidate via the meta-summary, and fixes the demotion loop in
compress_and_save_session to cover session.context too: after a reload,
context-only rows (is_display=False summaries) were absent from
session.messages and never demoted, so is_context=True persisted in the DB
and the copies survived into the loaded context.
Eugene Sukhodolskiy
committed
29 days ago
|
| 2026-09-09 |

compress: route every trigger path through compress_and_save_session
...
- CompressionWorker keeps the real-token gate (ctx.context_tokens) but now
delegates to compress_and_save_session(reason="postturn") — it used to
call compress_context directly with its own save logic and silently
no-op'ed on any summarizer failure, leaving the session over the
threshold until the next turn's gates
- pre-turn and /compact gates use real_baseline_estimate instead of the
chars//3 heuristic (mid-turn already did), so code-heavy contexts
compress in time instead of tripping ContextTooLargeError
- summarizer input keeps head + tail instead of head-only truncation,
which silently dropped the newest summarized messages closest to the
keep window
- critical tool results over the 4000-char budget keep head+tail halves
instead of collapsing to the 800-char non-critical preview (a 3999-char
read survived whole, a 4001-char one lost 98%)
- images count 500 tokens each, not 500 per message carrying them
- _plan_compression is the single partition decision shared by
compress_context and would_compress, so the dry-run prediction cannot
drift from the real path; the trigger reason is now logged
(preturn/midturn/postturn/forced)
Eugene Sukhodolskiy
committed
on 9 Sep
|
| 2026-07-12 |
config: raise context compression threshold 0.70 -> 0.90
...
Midturn compression now splits the in-flight turn correctly, so it's safe to
let the context grow closer to the limit before compressing. 0.90 gives the
model more raw context per turn and trips the compressor less often.
Worker test context_tokens bumped 50_000 -> 60_000 (76% -> 91.5%) to stay
above the new threshold; other compression tests pass the threshold as an
explicit argument and are unaffected.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 12 Jul
|

compression: fix auto-compress no-op on few-huge-messages + honest status
...
Root cause: the compression gate (should_compress) measures tokens, but the
partition measures message/turn count, and CompressionStarted was emitted
before the attempt. For navi_code's "few very large messages" shape (one big
file read = 1 user + assistant + 1 huge tool result, 66k tokens in 3 messages)
the gate fired, the UI showed "compression", but partition returned
to_summarize=[] -> compress_context None -> nothing shrank. The agent kept
going until the window overflowed. It wasn't running "during" compression —
there was no compression, just a no-op the user mistook for one.
A. Per-message head/tail truncation in context_builder.build(): oversized
tool/assistant messages (over context_message_token_budget, 0=num_ctx//6)
are capped head+marker+tail in the LLM view only (model_copy — stored
history and reloads are never affected). A single huge tool result can no
longer alone blow the window; user/system messages are never truncated.
B. Token-budget hard-truncate fallback in compress_session: when partition
no-ops but tokens exceed the threshold, drop oldest turns to num_ctx*0.5.
_hard_truncate is now token-aware (was a fixed message-count floor that
no-oped on <=6 messages even when huge). New would_compress() predicts
compress_session's real outcome with no LLM call.
C. Honest CompressionStarted: _compression_events_midturn/_preturn emit it
only after would_compress() confirms the partition (or token-budget
fallback) can actually shrink the stored context — no more "compression"
status with no ContextCompressed to follow.
Bonus: post-turn CompressionWorker now passes keep_recent_messages=
max(12, context_keep_recent*2), matching the midturn path, so a single long
autonomous turn compresses post-turn too (was always a no-op).
Tests (+14): would_compress agreement, token-budget fallback, token-aware
hard_truncate, build() truncation (preserves user, no mutation, head+tail),
agent no-CompressionStarted-when-nothing-to-compress, worker single-long-turn.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 12 Jul
|