| 2026-11-02 |
webclient: a dialog that answers which model is serving, and from where
...
The header only named a model when the route had fallen back, so "what is
running this session, on which host, with how much context" had no answer
in the UI. The context percentage doubles as the second way in: a session
bar is a fair place to ask what the bar measures.
GET /sessions/{id}/info reads in-process state only — the active route
entry, the health chain, the profile's effective window, run and client
counts — and the dialog polls it while open. /meta and the session head
now report the route's window instead of the global ollama_num_ctx, which
had been wrong for every cloud-backed profile.
Eugene Sukhodolskiy
committed
23 hours ago
|
| 2026-10-05 |

background tasks: review fixes B1-B11 batch
...
- stop mid-batch cancels in-flight tools (B1) and keeps real results
of already-finished ones, mixing them with synthetic stopped notes
in call order (B2)
- queued messages: headless drain publishes session_sync (B4), the
run's teardown broadcasts session_sync to other sockets but not the
owner socket (prevents double reload) (B5)
- task_update notifications are chained per task so late running
updates can't overtake the terminal one (B7; client drops late
re-flicker of a terminal task chip) (B8)
- client: ui_component results reference the owning message via
card.parentMsg instead of a stale msg reference (B6)
- tasks cancel reports the real outcome after a bounded wait instead
of an optimistic 'cancelled' (B9)
- session delete cancels its running background jobs and drops
pending result notes (B10)
- subagent tool loop routes background:true calls through
ToolExecutor._maybe_background like the main loop (B11)
- tests for all of the above; docs/tasks.md stop/cancel semantics
Eugene Sukhodolskiy
committed
28 days ago
|
| 2026-09-26 |
agent parallelism: background tools, bg spawn_agent, parallel tool batches, message queue
...
- backgroundable tools (terminal/ssh_exec/peer/spawn_agent/code_exec) detach
via TaskManager with per-session/global/spawn caps, rate limit and TTL
- completion delivery both ways: task_update event (out-of-band) + pending
notes injected into the next turn; tasks tool (list/check/wait/cancel)
- parallel tool-call batches (profile-level gate, off by default) with
ToolStarted up-front, tagged event mux, call-order results, one save,
plus dangling tool_call repair on session load
- user message queue instead of busy error: message_queued frame,
back-to-back drain on the same socket, headless fallback on disconnect
- pin mcp<2 (v2 renames FastMCP with breaking API changes)
- docs: tasks.md (new), websocket/api/agent/config updates, tasks manual,
spawn_agent background param, persona contract section
Eugene Sukhodolskiy
committed
on 26 Sep
|

PWA: installable webclient, offline shell, web push
...
Installability:
- public/manifest.webmanifest (standalone, theme #16161E) + PNG icons
generated from logo.svg (regular + maskable, served via /images mount)
- index.html: manifest link, theme-color, apple-touch-icon
Offline shell:
- hand-rolled sw.js (no workbox): navigation = network-first (3s race)
with cached-shell fallback + background refresh (a stale cached shell
would 404 on entry chunks after a deploy); /assets/* cache-first
(content-hashed); /images/* cache-first capped; /api,/ws,/auth,/push,
/content pass-through
- vite closeBundle plugin stamps __NAVI_BUILD_VERSION__ (digest of
index.html + asset names) into dist/sw.js; sw.js served no-store so
every deploy reactivates the SW and activation evicts old caches
- SW registration in main.js, PROD only (dev HMR untouched)
- OfflineBanner (useOnline composable) over the app shell
Web push (VAPID, pywebpush):
- navi/push/ package: push_subscriptions table (postgres, boot-time DDL),
PushSubscriptionStore, PushService (async fan-out, to_thread sends,
404/410 prunes dead endpoints, per-session cooldown)
- routes: GET /push/vapid-key, POST/DELETE /push/subscribe (auth-gated)
- trigger in orchestrator run_agent + run_recall: push on StreamEnd when
no WebSocket client watches the session; fire-and-forget, never
disturbs the run; anonymous fallback only when auth is off
- client: usePush composable + Notifications settings panel (enable/
disable via PushManager.subscribe with the server VAPID key)
- notification click focuses the app at /#<session_id> (hash routing
opens the right chat); payload body is a markdown-stripped <=140-char
preview
NAVIVAPID keys empty = push fully disabled (graceful, like other optional
integrations). dist/ artifacts committed per repo convention.
Tests: pytest push store/service/routes/trigger (+23), vitest usePush
(83 webclient tests green). Full suite 1143 passed.
Eugene Sukhodolskiy
committed
on 26 Sep
|
| 2026-09-09 |
Merge branch 'master' into feature/navi-code
...
# Conflicts:
# navi/api/websocket.py
# navi/config.py
# navi/core/agent.py
# navi/core/orchestrator.py
# navi/main.py
Eugene Sukhodolskiy
committed
on 9 Sep
|
| 2026-07-13 |

terminal: session-broadcast sink + lifecycle events (Etap 1)
...
Persistent terminals emitted their stream only into a per-tool-call sink, which
closed once the open action returned — so a background dev server's output
stopped reaching the client immediately after open. TerminalClosed was never
emitted at all (the handler existed, the emitter did not).
Replace the per-tool event_sink with a single callback the orchestrator wires to
the session's WebSocket(s):
- TerminalManager: set_event_callback(cb) + _emit(session_id, event). Reader
tasks and open/close deliver TerminalOutputDelta / TerminalOpened /
TerminalClosed through the callback, which lives for the terminal's (and
session's) lifetime — not the tool call's.
- New TerminalOpened event (terminal_opened: name/description/pid/background).
TerminalClosed now actually emitted from _close_one (explicit/idle/shutdown/
session_ended). Background output keeps streaming after open returns.
- terminal.py: drop the per-tool event_sink from _tm.open (no longer needed).
- orchestrator._on_terminal_event → _notify_session (per-session WS fan-out,
same path recall/mcp updates use). container wires the callback after building
the orchestrator.
- Events imported lazily inside terminal_manager methods to avoid a circular
import (navi.core.events → navi.core.__init__ → agent → registry → navi.tools).
Tests: open emits TerminalOpened; background output streams via the callback
after open returns (gap-3 fix); close emits TerminalClosed; no callback does not
break readers. Existing terminal tests unchanged. Full suite: 968 passed, 1 skipped.
Plan: docs/terminal_tool_plan.md (5 etaps, this is Etap 1).
Eugene Sukhodolskiy
committed
on 13 Jul
|
| 2026-07-12 |

recall: carry self-instruction (message) on the recall_update wire
...
The recall card could show call_type/trigger_at but not the self-instruction
(additional_context_message) — it was absent from RecallUpdate.to_wire, so
the user couldn't see what future-self was about to do at the scheduled or
fired moment. Extend the wire payload.
- events.RecallUpdate: add `message` field; to_wire emits "message".
- scheduler._publish_recall_update: accept and forward `message`.
- Publish sites carry message=recall.additional_context_message:
schedule_recall (scheduled) and orchestrator._finalize_recall
(rescheduled / fired / cancelled). manage_recall cancel/skip omit it
(no recall object handy; already visible in the prior scheduled card).
- TUI RecallRenderer: preview the message (first line + "(+N lines)",
capped at 80) on a `msg:` body line when present.
- Tests: RecallUpdate.to_wire carries message (defaults None); renderer
preview (single/multiline/truncate/empty) and scheduled-card renders it.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 12 Jul
|
| 2026-07-11 |

Navi Code: force context compression via typed /compact control message
...
Forced /compact previously sent a chat message, which ran a full agent turn
that only produced summary text instead of running the real context compressor.
Worse, even when wired correctly, forced compact always reported "Nothing to
compact yet — the context is still small" regardless of context size: the typical
navi_code shape is a single long autonomous turn (1 user message + many tool
iterations = one turn), and partition_messages finds nothing to summarize when
turns <= keep_recent. The midturn auto-compress path already bypassed this via
keep_recent_messages (intra-turn split), but compact_stream passed
keep_recent_messages=None, so the fallback was disabled.
Changes:
- WS protocol: {"type":"compact"} control message (distinct from {"type":
"message"}); rejected while an agent turn is active to avoid racing the agent.
- Agent.compact_stream: forced compression that bypasses the token threshold
but still runs the real compressor; passes keep_recent_messages=max(12,
context_keep_recent*2) so a single long turn compresses via intra-turn split
(mirrors midturn auto-compress). Raises NothingToCompactError when context is
genuinely too small.
- Orchestrator.run_compact + clear_run: broadcast agent events to subscribers,
end with done marker, surface NothingToCompactError as an error event.
- Terminal client: ws_client.send accepts str|dict; CompactCommand enqueues
{"type":"compact"}; TUI distinguishes forced compact (no stream_start) from
in-turn auto-compress via the _streaming flag.
- Tests: compact_stream (incl. single-long-turn regression), WS handler
dispatch/rejection, run_compact event/error broadcasting, ws_client send,
compact command.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 11 Jul
|
| 2026-07-09 |

feat: integrate navi_ui MCP server (card_grid + form) into master
...
Port the internal navi_ui MCP server from the vmkdemo branch (it never
landed on master). The server exposes render_component, which returns a
structured JSON envelope; navi/mcp/tools.py extracts metadata.ui_component
onto the role="tool" message, and the webclient renders the component
(card_grid, form) inline inside the assistant turn.
Backend
- navi/mcp/ui_server/: FastMCP server + component registry with card_grid
and form components (pydantic-validated payloads, LLM-friendly schema docs)
- mcp_servers.d/navi_ui.json: streamable_http config, group "ui"
- config.py: navi_ui_mcp_enabled/host/port flags
- main.py: start UI server in lifespan (task + wait-for-ready + cancel)
- mcp/tools.py: navi_ui envelope parsing; "Error:" results surface as
failed tool calls so the UI card is not green
- orchestrator.py + agent.py: run_stream(hidden=) for form submissions
(single is_display=False, is_context=True user message)
- api/websocket.py: extract _start_agent_run helper, add form_submit
branch that delivers submitted form values as a hidden user message
- profiles/secretary: enable navi_ui "ui" group (agent + subagent)
- .env.example: NAVI_UI_MCP_* flags
- tests/unit/mcp/test_ui_server.py
Webclient
- components/ui/{registry.js,CardGrid.vue,Form.vue}: auto-discovered
renderers (snake_case <-> PascalCase aliasing)
- components/messages/UiComponentCard.vue: wrapper rendered in
AssistantMessage when entry.kind === 'ui_component'
- stores/chat.js: extract ui_component from tool_call metadata in both
live stream and history-replay paths
- composables/useWebSocket.js: note that ui_component rides tool_call
- tests/unit/components/ui/* (17 tests)
- dist rebuilt
Excluded from the vmkdemo port: the stale single-file navi/mcp/ui_server.py
duplicate, the realtor profile, and the vmk_data server (unrelated real
estate work that was interleaved with navi_ui on vmkdemo).
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 9 Jul
|
| 2026-06-26 |
Navi Code: stop via Esc + project cwd propagation
...
- TUI: Esc stops active stream cooperatively via POST /sessions/{id}/stop
- TUI: render stream_stopped as status message
- CLI/WebSocket: send shell cwd in client->server message field
- Orchestrator stores cwd in session.session_metadata
- ContextBuilder injects [Working directory] into LLM context
- Agent sets current_working_directory ContextVar per turn
- tools/base: ToolContext gains cwd field
- filesystem/terminal/code_exec resolve relative paths against session cwd
- Add bin/navi-code wrapper for PATH symlink; document in README.md
- Update docs/websocket.md and tests
Full pytest: 544 passed, 1 skipped.
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 26 Jun
|
| 2026-05-26 |
Add persistent multi-session terminal tool with background support
...
- New TerminalManager module: named subprocess sessions per Navi session,
background readers, event-sink streaming, idle auto-cleanup
- Refactor terminal tool to multi-action: run, open, close, list,
status, send_input
- Add TerminalOutputDelta and TerminalClosed events for streaming
- Wire TerminalManager into AppContainer, orchestrator, and registry
- Persist session_metadata in Session model and pg_session_store
- Close all session terminals on session delete
- Webclient: handle terminal_output/terminal_closed WS events,
display live terminal output in tool cards
- Update unit tests for new terminal actions
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 26 May
|
| 2026-05-25 |

Fix 19 issues found in full codebase review
...
Backend:
- Stop session auth bypass: require auth for owned sessions, reject anonymous with 401
- upload_file: stream chunks directly to disk instead of buffering in RAM
- MCP config: validate name against path traversal regex
- auth deps: cleanup stale refresh locks periodically
- auth routes: expire mobile auth states after 10 min to prevent unbounded growth
- compressor: meta-summarize existing summaries before compression; preserve assistant content when tool_calls present; rewrite hard_truncate to keep whole turns
- orchestrator: configurable WS replay buffer size; async cleanup/remove_websocket/clear_busy; fix run_recall ContextVar order to avoid deadlock on _build_agent failure; await cleanup in finally
- agent: persist image_msg in session.messages; remove archived messages from session after archive; remove duplicate StreamStopped yield on tool stop
- websocket: try/except around create_task with cleanup on failure; await remove_websocket
Frontend:
- App.vue: hashchange listener lifecycle in onMounted/onUnmounted
- MessageList.vue: passive scroll, flash timeout cleanup, archive scroll snapshot
- InputBar.vue: 300 ms debounce on draft save to localStorage
- SessionList.vue: remove :key from DynamicScroller to avoid remount jitter
Tests: 422 passed, 1 skipped
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 25 May
|
Add archive message pagination, configurable WS replay buffer
...
Backend:
- Add archive_threshold to Session model and getSession response
- Add next_before_seq to archive endpoint for cursor pagination
- Make WS replay buffer size configurable via WS_REPLAY_BUFFER_SIZE
Webclient:
- Add getArchivedMessages API function
- Add archive pagination state and loadArchivedMessages to chat store
- MessageList: auto-load older messages on scroll-to-top with scroll
position preservation and loading spinner
Docs: update config.md with new env vars
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 25 May
|
| 2026-05-24 |
Add MCP health check loop, auto-reconnect, and system toast notifications
...
Backend:
- McpManager: keep all configured servers in the pool even if connect()
fails at startup; health-check loop (30s) tries to reconnect dead servers
and verifies live ones with list_tools()
- McpManager: set_on_server_connected callback re-registers tools when a
dead server comes back online
- McpClient: add mark_disconnected() for silent drop detection
- McpStatusTool: skip list_tools() for disconnected servers
- Orchestrator: broadcast mcp_status_update to all WebSocket sessions
- New event type McpStatusUpdate with server_name, status, tool_count
Webclient:
- useWebSocket: handle mcp_status_update → toast.success/toast.error
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 24 May
|
| 2026-05-23 |
Fix recall race, ContextVar leaks, dead code, and recall duplication
...
- run_recall: wrap busy check + create_run in session_lock to prevent
race between scheduler and websocket handler
- run_recall: save ContextVar tokens and reset in finally to avoid
leaking user context into subsequent background tasks
- websocket.py: reset user ContextVars in finally after run completes
- orchestrator.py: remove dead set_notify / _notify abstraction
- orchestrator.py: extract _finalize_recall to deduplicate success /
MaxIterationsReached / Exception finalization blocks
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 23 May
|
Unify in-memory session state in AgentSessionOrchestrator
...
Replace scattered _runs + _busy_sessions + _session_sockets with a
single _sessions: dict[str, SessionState] on the orchestrator.
- SessionState dataclass holds run, busy_event, and websockets
- _session_sockets module-level global removed from websocket.py;
socket tracking moved into orchestrator (add/remove_websocket)
- Event bus subscriber _on_recall_update moved into orchestrator
- Per-session asyncio.Lock added to protect concurrent-run guard
- _cleanup() auto-removes empty SessionState entries
Tests updated to reference _sessions instead of legacy _runs.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 23 May
|
| 2026-05-18 |
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
|
| 2026-04-08 |
Initial implementation of the agent system core
...
- FastAPI server with REST API and WebSocket streaming
- Modular LLM backend abstraction (Ollama implemented, OpenAI stub)
- Tool system: web_search (ddgs), filesystem, http_request, code_exec, terminal
- Agent profiles: smart_home, server_admin, secretary
- Tool-calling loop with concurrent tool execution
- In-memory session store with SessionStore ABC for future persistence
- Registry pattern for tools, profiles, and backends
- Orchestrator stub as foundation for multi-agent scenarios
Eugene Sukhodolskiy
committed
on 8 Apr
|