| 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
28 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-14 |

compress: raise budget 6000 + richer prompt detail + input headroom
...
Increase the compression summary budget and push the prompt toward more
detail and completeness, so the agent keeps more durable facts when old turns
are folded into a summary.
Budget (output):
- context_summary_max_tokens 4000 → 6000 (global default).
- navi_code compression_max_tokens 4000 → 6000 (profile override).
Prompt (base, all profiles):
- New 'Intermediate Findings' section: durable facts from read/grep/log/terminal
that informed decisions (was forbidden by 'do not preserve intermediate
reasoning'). Existing sections get richer instructions (Active Files: key
symbol affected; Decisions: rationale + trigger; Completed: step + file +
verification outcome; Errors: snippet + what was tried + outcome).
- Loosen 'tight bullet points' → 'specific and complete; prefer listing over
generalizing — losing a fact costs more than a longer summary'.
- 'Do not write implementation code' → 'preserve short critical code verbatim
(final signatures, one-line fixes, key config lines); drop long blocks'.
Input headroom (so the summarizer can SEE what it must preserve):
- Non-critical tool result preview 300 → 800 chars.
- _MAX_SUMMARY_INPUT_CHARS 24000 → 32000 (fits 65k–128k windows with 6k output).
Profile prompt (navi_code/compression_prompt.txt): preserve short diagnostic
snippets verbatim (exact error line, failing assertion, one-line fix) instead
of blanket 'do not preserve long output'; keep code signatures + short key
snippets, not just names.
Tests: Intermediate Findings section in base prompt; 'short critical code'
phrasing; non-critical preview keeps 800 chars; meta-summary threshold raised
(2×6000 chars). Fixed flaky test_attach_session_seeds_context_fill (relied on
_startup worker timing after Etap 4 lengthened attach; now explicit attach
like its sibling + mock get_todos to skip a 30s×2 real-network timeout).
Full suite: 1008 passed, 1 skipped.
Eugene Sukhodolskiy
committed
on 14 Jul
|
| 2026-07-13 |
compressor: target hysteresis — shrink to 65% after compression, not just below the trigger
...
Compression triggered at 90% but left the context just under the trigger, so a
fixed keep_recent (navi_code: 12 turns ≈ 104 messages) re-triggered a couple
of messages later — the context yo-yoed at the trigger line instead of gaining
headroom. Add context_compression_target (0.65): in turn-based (preturn) mode
compress_context shrinks keep_recent until the verbatim kept region fits 65% of
the window, folding the extra turns into the same single summary LLM call; a
safety net in compress_session token-budget-truncates if the kept region alone
still exceeds the target (midturn kept a huge in-flight turn, or the
keep_recent floor can't fit). Trigger 90% → target 65% leaves real headroom.
Eugene Sukhodolskiy
committed
on 13 Jul
|
| 2026-07-12 |

compressor: split in-flight turn in midturn mode + flush incrementally
...
Two related fixes for long autonomous turns in planning profiles (navi_code):
A1 — partition_messages turn-based branch held the in-flight (current) turn
verbatim when turns > keep_recent, so midturn compression was shallow
(e.g. 150 -> 141 messages). Now, when keep_recent_messages is set, the
in-flight turn is split like partition_current_turn_messages: head (user
request) + tail (recent tool steps) kept, middle summarized. The adaptive
swap is disabled in midturn mode (it could move the in-flight turn into
old_turns and summarize the current request whole). First branch
(len(turns) <= keep_recent) is untouched, so locked-in midturn tests hold.
B1 — agent.run_stream had no try/finally around the for-loop, so an
asyncio.CancelledError (server restart/shutdown) unwound the stack with no
flush: all in-memory turn messages (sequence_number < 0) were lost, only the
user message survived. Add incremental save() after planning, after the
assistant tool-call decision, and after each tool result, so a crash loses at
most the single in-flight tool call, not the whole turn. B2 (try/except
safety-net) was dropped: B1 leaves no window where an append is unsaved.
Tests: midturn split with many turns (partition + compress_session), and
crash/cancel persistence via a snapshot session store that mirrors the DB
boundary.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 12 Jul
|
compression: profile-aware worker + real-token baseline estimator
...
Item 2 — thread the active profile into CompressionWorker so
compress_context applies per-profile overrides (compression_keep_recent,
compression_max_tokens, compression_prompt_file). navi_code now compresses
with keep_recent=12 instead of the global 8.
Item 1 — estimate the next LLM call's context from the *real* prompt_tokens
of the previous call (bulk) plus a heuristic delta for messages appended
since, replacing the chars//3 estimate that undercounts code-heavy tool
output and fired midturn compression too late (Navi kept working until the
window was exhausted). Baseline is recorded after each stream and cleared
after compression; check_context_size and the midturn gate use it, with a
heuristic fallback when no baseline exists or the context shrank.
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
|
| 2026-06-26 |
compressor: structured summaries, profile-aware compression, adaptive keep_recent
...
- Replace free-form summary with strict Markdown template (Goal, Active Files,
Decisions, Completed Work, Pending Work/Todo, Errors, Key Values).
- Keep filesystem/code_exec/terminal tool results and messages with
is_compression_critical=True verbatim during compression instead of 300-char truncation.
- Make compression profile-aware: AgentProfile gains compression_keep_recent,
compression_max_tokens, compression_prompt_file. navi_code uses dedicated
compression prompt and larger keep_recent/max_tokens.
- Adaptive partition_messages(): important turns (user corrections, errors,
critical tools) survive longer; filler/social turns compress sooner.
- Increase default context_summary_max_tokens from 3000 to 4000.
- Propagate active profile changes to ContextCompressor and SubAgentRunner.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 26 Jun
|
| 2026-05-25 |
Add meta-summary for multi-level compression
...
When to_summarize contains multiple existing summary messages whose
combined length exceeds 8000 chars (~1/3 of max summarizer input),
run a quick meta-summary pass first to consolidate them into a single
compact summary before the main compression. This prevents information
loss when repeated compressions stack up long summary chains.
- _meta_summarize(): fast LLM pass (think=False, max_tokens=1500)
- compress_context(): detects >1 long summaries and triggers meta pass
- Graceful fallback: if meta-summary fails, continue with raw summaries
- 3 new unit tests: consolidation, skipped for short summaries, failure fallback
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 25 May
|
Review fixes: restore _build_sessions, fix flags, search filter, tests
...
- Restored _load_messages_map and _build_sessions helpers that were
accidentally dropped in Phase 5 (list_all/list_page/search_list
called them but they were missing, causing NameError at runtime)
- _build_sessions now filters messages by is_display so list methods
return consistent display-only history like get()
- count_all/search_list EXISTS subquery now filters to is_display=true
so search only matches visible chat messages
- Updated pg_session_store docstring to remove stale dual-write claim
- compressor summary_msg now defaults to is_display=False
- Added unit tests for message flags (agent, compressor, planning)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 25 May
|
| 2026-05-16 |
Extract ContextCompressor, fix STL viewer, expand test suite, add architecture audit docs
...
- Extract ContextCompressor from agent.py (Step 1 of god-object refactor)
- Add retry + hard-truncate fallback logic to ContextCompressor
- Add unit tests: agent loop (14), compressor (18), KV store (8),
auth encrypt (3), auth deps (13), todo/scratchpad/image_view/memory
- Fix WebGL STL viewer: allow-same-origin sandbox + graceful fallback
- Add CompressionStarted event and client-side compression notice
- Add docs/architecture_weak_spots.md and plan_01_god_object_agent.md
- Update test_events.py and test_agent_context_size.py for new logic
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 May
|
| 2026-05-02 |
Refine 3D modeler workflow
Eugene Sukhodolskiy
committed
on 2 May
|
| 2026-04-29 |

Bootstrap test suite — Phase 1 unit tests
...
- docs/testing.md: testing strategy, mock strategy, phase breakdown
- tests/conftest.py: autouse fixture to reset navi.config.settings per test
- tests/conftest_factory.py: FakeLLMBackend, FakeTool, make_profile, make_registry helpers
- tests/unit/core/test_events.py: wire serialization for all 15 event dataclasses
- tests/unit/core/test_compressor.py: should_compress, partition_messages, format_for_summary, compress_context
- tests/unit/core/test_registry.py: ToolRegistry, ProfileRegistry, BackendRegistry
- tests/unit/core/test_context_builder.py: system prompt caching, persona injection, goal anchor, iteration budget
- tests/unit/profiles/test_base.py: Pydantic model coercion, defaults, extra fields
- navi/core/context_builder.py: use module-level `import navi.config` instead of `from navi.config import settings` so tests can swap the singleton
59 tests passing.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 29 Apr
|