| 2026-05-18 |
DRY: unify tool execution in ToolExecutor._execute_one()
...
Three methods (_run_single_tool, _execute_tool_calls,
_execute_tool_calls_streaming) duplicated identical logic:
resolve → middleware → execute → image extraction → build message.
- Extract canonical _execute_one(tc, tool_map) -> (ToolEvent, Message, image_msg)
- All three public methods now delegate to _execute_one
- Public signatures unchanged — no test or caller changes needed
392 passed, 1 skipped
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 18 May
|
Eliminate cross-registry patching in registry.py via proper creation order
...
- Reorder build_default_registries so profiles and cp_registry are created
BEFORE tools that need them
- All tools now receive full dependencies at construction time:
ListToolsTool(profile_registry=profiles, mcp_manager=mcp_manager)
SpawnAgentTool(backend_registry=backends, ...)
ReloadToolsTool(cp_registry=cp_registry, mcp_manager=mcp_manager)
- Remove all post-creation patch lines (_profile_registry, _backend_registry, _cp_registry)
- Add mcp_manager parameter to build_default_registries
- Update create_container to pass mcp_manager
392 passed, 1 skipped
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 18 May
|
Extract single shared Database pool, eliminate 4 duplicated pool creations
...
- Create navi/db.py::Database managing one asyncpg pool
- KvStore, PgSessionStore, MemoryStore, RecallScheduler now accept pool in constructor
- AppContainer holds Database, shutdown closes one pool instead of 4
- create_container creates one pool and passes it to all stores
- All tests updated to set _initialized=True on fakes to skip DDL
392 passed, 1 skipped
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 18 May
|
Extract WebSocket business logic into AgentSessionOrchestrator
...
- Create navi/core/orchestrator.py with AgentSessionOrchestrator and SessionRun
- Orchestrator owns _runs, _busy_sessions, Agent creation, run_agent(), run_recall()
- Transport-agnostic: accepts notify callback from WebSocket handler
- WebSocket handler (websocket.py) now only does serialization/deserialization
- _fire_recall delegates to orchestrator.run_recall() instead of inline logic
- recall_scheduler_loop now accepts orchestrator parameter
- AppContainer gains .orchestrator field, created in create_container()
- deps.py: add get_orchestrator()
- Update integration tests for scheduler_loop and websocket unit tests
All 392 tests pass.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 18 May
|
Fix PgSessionStore import in container.py
...
`PgSessionStore` lives in `navi.core.pg_session_store`, not `navi.core.session`.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 18 May
|

Replace global lazy singletons with explicit AppContainer + lifespan
...
- Create navi/core/container.py with AppContainer dataclass and create_container()
- Rewrite navi/api/deps.py: remove module-level singletons, add _container global
fallback + set_container(), use _resolve_container() for all getters
- Replace @app.on_event with @asynccontextmanager lifespan in main.py
- Update routes to use Depends(get_scheduler) instead of calling get_scheduler()
- Fix FastAPI body parsing bug: remove dataclass parameters from Depends getters
(FastAPI was treating AppContainer sub-dependencies as Body params, forcing
embed=True on all endpoint body params and causing 422 errors)
- Update websocket.py to use _resolve_container() instead of get_registries()
- Update integration test fixtures to build AppContainer and call set_container()
- Remove obsolete tests/unit/test_startup.py (tests removed _on_startup)
- Fix test_scheduler_loop.py fixture (get_registries no longer exists)
All 387 tests pass (excluding websocket hang tests).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 18 May
|
| 2026-05-16 |
Step 5-7: Extract async generators from run_stream, unify run() as wrapper
...
- _compression_events_preturn / _compression_events_midturn
- _consume_stream (uses StreamState)
- _execute_tools_with_sink
- run() is now a thin wrapper around run_stream() collecting StreamEnd
- Remove dead imports (json, LLMChunk)
- Mark god-object decomposition complete in architecture_weak_spots.md
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 May
|
Step 4: Extract SubAgentRunner from run_ephemeral()
...
- Create navi/core/subagent_runner.py with full sub-agent loop logic
- Move _iter_stream_guarded to navi/core/stream_guard.py
- Move _check_context_size to ContextCompressor.check_context_size()
- Extract build_tool_list() and load_user_enabled_tools() to tool_utils.py
- Agent.run_ephemeral() becomes a thin wrapper delegating to SubAgentRunner
- Remove ~310 lines from agent.py
- All existing run_ephemeral tests pass unchanged
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 May
|
Step 3: Extract AntiStallMonitor from run_stream()
...
- Create navi/core/anti_stall.py with AntiStallMonitor class
- Encapsulates stall detection (todo progress + repeated tool calls)
- Encapsulates adaptive re-plan (failed todo step detection)
- Provides init() / pre_turn() / post_turn() two-phase interface
- Remove ~50 lines of stall/replan logic from agent.py run_stream()
- Remove _todo_status_snapshot and _todo_failed_steps helpers from agent.py
- Update AgentTurnContext: remove stall fields (now live in AntiStallMonitor)
- Add 13 unit tests for pre_turn and post_turn behavior
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 May
|
Step 2: Extract AgentTurnContext dataclass from run_stream()
...
Move 10 turn-level local variables from run_stream() into AgentTurnContext:
- turn_start, tool_call_count, turn_tokens, subagent_tokens
- stall_no_todo, stall_repeat_tools, prev_tool_sigs
- known_failed, replan_msg, injected_fact_ids
This makes run_stream() readable and prepares the ground for
AntiStallMonitor (Step 3) which will consume this context.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 May
|
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
|
Add inherit_system_prompt and is_subagent_only mechanisms
...
- inherit_system_prompt: subagent parameter to prepend parent's system
prompt as a base layer before subagent specialisation
- is_subagent_only: profile flag blocking switch_profile, allowing
spawn_agent only; shown with [subagent only] tag in list_profiles
- Document both in docs/profiles.md and docs/tools.md
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 May
|
Enhance native toolset and add persistent KV store
...
- Add PostgreSQL-backed KvStore (navi/store/) for session-scoped data.
- Migrate todo and scratchpad from in-memory dicts to KvStore.
- Filesystem: add copy, grep, diff actions; compress description.
- CodeExec: remove language param, expose working_dir in schema.
- ImageView: resize to 1024px JPEG + Content-Type guard for URLs.
- Memory list: return distinct categories instead of all facts.
- SSH: add scp action with upload/download support.
- Update CLAUDE.md (Postgres-only), docs/tools.md, add docs/store.md.
- Fix agent/planning/context_builder async signatures for todo helpers.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 16 May
|
| 2026-05-15 |
cleanup: remove deprecated tools and orphaned memory tools
...
Removed (no profile used them, no cross-dependencies):
- write_tool.py, delete_tool.py, test_tool.py
- memory_save.py, memory_search.py, memory_forget.py
Updated:
- navi/core/registry.py — removed imports and registrations
- navi/tools/__init__.py — removed imports
- docs/tools.md — removed references, updated self-extension section to MCP
- navi/tools/tool_manual.py — updated example to create_mcp_server
Profile fixes:
- developer: +tool_manual, +ssh_exec
- discuss: +list_profiles
- tool_developer: +mcp_servers navi-web
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 15 May
|

fix(recall): stabilize scheduled callback system and improve UX
...
Backend fixes:
- stop_session now stops headless recall runs via _busy_sessions dict
- _fire_recall sets user ContextVars so tools work correctly
- MaxIterationsReached treated as success, not failure
- skip_next_recall uses GREATEST(trigger_at, now) for overdue recalls
- schedule_recall rejects past trigger times
- timezone offset double-adjustment fixed for aware datetimes
- _fire_recall registers _AgentRun for reconnect/replay support
- session_sync race with stream_start fixed
Frontend improvements:
- Recall banner moved to ChatHeader with live Cancel/Skip buttons
- Recall messages styled with is_recall flag and badge
- Real-time recall updates via WebSocket (recall_update events)
- Recall filter moved to sessions-header as toggle button
- Session list shows clock icon for sessions with pending recall
- Empty state messages for empty/filtered session lists
- Fixed missing api import in ChatHeader.vue
Tests:
- Updated scheduler_loop tests for _busy_sessions dict change
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 15 May
|
Refactor: move tool helper modules into _internal subpackage
...
Moves non-tool infrastructure out of navi/tools/ root so that only
actual tool classes live there:
base.py → _internal/base.py
loader.py → _internal/loader.py
middleware.py → _internal/middleware.py
logging_middleware.py → _internal/logging_middleware.py
_time_parser.py → _internal/time_parser.py
All imports updated across core/, api/, mcp/, tools/, and tests/.
No proxy files remain in navi/tools/ root.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 15 May
|
Add self-recall (scheduled callback) system
...
Core features:
- schedule_recall tool: once/recurring/immediate callbacks
- manage_recall tool: cancel/skip/list scheduled recalls
- Natural-language time parser (ISO, relative, "tomorrow at 09:00")
- PostgreSQL-backed RecallScheduler with lazy pool init
- Background recall_scheduler_loop with asyncio.Semaphore(3)
- _busy_sessions guard prevents user messages during headless runs
- Agent.run() preserves thinking field for session history visibility
- API endpoints: GET/DELETE/POST for session recall, admin list
- Frontend: recall badge, filter, cancel/skip in sidebar and chat header
- Tests: parser, scheduler CRUD, tools, API, scheduler loop (53 tests)
- Manuals: schedule_recall.md and manage_recall.md
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 15 May
|
fix: wire test_mcp_tool into MCP manager startup injection
...
The startup loop in main.py assigns _mcp_manager only to tools that
have the attribute, but test_mcp_tool never declared it and mcp_status
used _manager instead of _mcp_manager — so both received None forever.
Changes:
- test_mcp_tool: add __init__(mcp_manager) with _mcp_manager, fallback
to module-level import if startup wiring skipped
- mcp_status: rename _manager → _mcp_manager, same fallback
- registry.py: register create_mcp_server and test_mcp_tool builtins
- main.py: include test_mcp_tool in startup wiring loop
- client.py: add 5s timeout to disconnect cleanup
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 15 May
|
refactor: migrate MCP config to directory-based mcp_servers.d
...
Replaces monolithic mcp_servers.json with per-server files in
mcp_servers.d/<name>.json. Filename stem becomes the server name.
- load_mcp_servers() reads directory globs, auto-migrates legacy file
- save_mcp_servers() writes per-server files, cleans up stale ones
- Update docstrings in manager.py and context_builder.py
Auto-migration: existing servers (gnexus-book, navi-3d, navi-web) plus
new ones (project_health, time_toolkit) now live in mcp_servers.d/.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 15 May
|
| 2026-05-14 |
Add session search across messages with backend + frontend
...
Backend:
- GET /sessions now accepts `search` query param
- _session_summary computes match_indices and match_preview from messages
- pg_session_store: messages ILIKE added to search_list and count_all
- In-memory store: search_list also filters by message content
Frontend:
- AppSidebar: search toggle icon + debounced input, Ctrl+K shortcut
- SessionItem: highlight matching text, show match_preview from search
- SessionList: pass searchQuery to SessionItem
- SessionsStore: searchQuery/searchActive state, setSearch/clearSearch
- API layer: getSessions accepts search param
- MessageList: scroll to target message + brightness flash animation
- ChatStore: loadSession accepts targetMessageIndex, scrollToMessageIndex ref
- CSS: msg-flash keyframe animation in app.scss
- Tests updated for new getSessions signature
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 14 May
|
| 2026-05-13 |
Persist uploaded files in messages, live file tree updates, and UI polish
...
Backend:
- Add `files` field to `Message` model so uploaded file metadata survives page refresh
- Pass `files` through websocket handler → `agent.run_stream` / `agent.run`
- `list_tools`: make `profile_id` required; return error instead of all-tools fallback
Webclient:
- Call `fetchFiles()` after successful file upload for immediate Files tab update
- Live refresh file tree on filesystem (write/edit/append/mkdir/rm/cp/mv), terminal, and code_exec tool calls
- Add manual refresh button (desktop) and pull-to-refresh (mobile) to Files tab
- Fix live link updates: move regex creation inside per-message loop to avoid lastIndex state leak
- Restore full profile name text next to avatar in ChatHeader; hide avatar in header
- Fix mobile ArtifactsPanel: collapse tab text labels so close button fits with 3 tabs
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 13 May
|
Add profile_id support to list_tools
...
ListToolsTool now accepts an optional `profile_id` parameter. When
provided, it returns only the tools enabled for that specific profile
(enabled_tools + user tools + resolved MCP tools). This helps the agent
preview what a profile can do before switching.
- Updated ListToolsTool.execute() with profile-aware filtering
- Patched _profile_registry in build_default_registries
- Patched _mcp_manager in main.py startup loop
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 13 May
|
Fix gnexus-book MCP instructions and add old-format fallback in tool executor
...
- Updated mcp_servers.json gnexus-book instructions to use mcp:gnexus-book:
prefix instead of the old mcp_gnexus-book_ format.
- Added fallback in tool_executor.py for old underscore format
(mcp_server_tool → mcp:server:tool) so transitional models still work.
- Added unit test for the old-format fallback.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 13 May
|
Generate session titles in the user's language
...
Adds "Respond in the same language as the user's messages" to the
session title generator system prompt so titles match the conversation
language instead of defaulting to English.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 13 May
|
Fix agent.py _tool_list to use colon-delimited MCP names
...
The main registry and API routes were migrated, but Agent._tool_list
still built names with the old underscore format. This meant MCP tools
were silently dropped from the tool list passed to the LLM, causing
"tool not found" when the model correctly called mcp:server:tool names.
Also updates stale docstring in McpTools.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 13 May
|
Rename MCP tools to mcp:server:tool format and restore human-readable names
...
- Core naming: mcp_server_tool → mcp:server:tool (colon-delimited)
- navi-web tools: search/view/request → web_search/web_view/http_request
- navi-3d tools: compile_scad/render_stl/lint_scad (unchanged names)
- Updated all profile configs, system prompts, docs, manuals, tests
- Added new lint_scad.md manual
- Fixed modeler_3d prompt stale references (scad_lint, model_3d, render_3d)
- All 240 tests pass
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 13 May
|
Migrate web tools (web_search, web_view, http_request) to navi-web MCP server
...
New MCP server: mcp-servers/navi-web/
- app/search.py — SearXNG primary, DDG fallback, Brave tertiary
- app/browse.py — Playwright headless browser page extraction
- app/request.py — httpx raw HTTP requests
- app/mcp_server.py — FastMCP entry point with stdio transport
- pyproject.toml — deps: mcp, httpx, ddgs, playwright, pydantic
Changes:
- Added "navi-web" block to mcp_servers.json (stdio transport)
- Removed web_search, web_view, http_request from built-in registry
- Removed them from navi/tools/__init__.py
- Removed them from enabled_tools / subagent_tools in all profiles
- Added "navi-web": ["search", "browse", "request"] to mcp_servers
in all profiles (developer, secretary, server_admin, tool_developer,
modeler_3d, discuss)
Tests: 240 passed.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 13 May
|

Migrate 3D modeling tools to standalone MCP server navi-3d
...
Phase 0 — Fix MCP error propagation:
- McpClient.call_tool now returns (output, is_error) tuple
- McpTool.execute sets success=False when MCP reports isError=True
- Fixes silent failures where MCP errors looked like success
Phase 1 — Create mcp-server-navi-3d:
- New standalone MCP server in mcp-servers/navi-3d/
- Tools: compile_scad, render_stl, lint_scad
- Session-scoped path resolution via SESSION_FILES_DIR env
- Anti-escape validation for security
- Includes tests/unit/test_scad_analyze.py
Phase 2 — Remove from Navi core:
- Deleted navi/tools/{model_3d,render_3d,scad_lint}.py
- Removed from registry.py builtins and navi/tools/__init__.py
- Updated pyproject.toml testpaths to exclude mcp-servers/
Phase 3 — Wire into Navi config:
- Added navi-3d block to mcp_servers.json (SSE on :8002)
- Updated modeler_3d profile: mcp_servers + updated system_prompt
- All old tool names replaced with mcp_navi-3d_* equivalents
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 13 May
|
| 2026-05-12 |
Handle MCP tool aliases robustly
Eugene Sukhodolskiy
committed
on 12 May
|
Clarify knowledge persistence prompts
Eugene Sukhodolskiy
committed
on 12 May
|