| 2026-04-14 |
self edited
ubuntu
committed
on 14 Apr
|
Add interactive HTML architecture visualization
...
Single self-contained HTML file covering all backend subsystems:
component diagram, request lifecycle, agent loop, tool system,
WebSocket protocol with event sequences, API reference, profiles,
memory, and config. Dark theme, sticky sidebar nav, no external deps.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 14 Apr
|
Add backend documentation
...
10 markdown files covering all backend subsystems: architecture,
agent loop, tool system, sessions, WebSocket protocol, profiles,
memory, config, and API reference. Structured for AI readability
with a single entry point (docs/index.md) and cross-references.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 14 Apr
|
| 2026-04-11 |
Review and tighten all system prompts
...
persona.txt:
- PLANNING: threshold now 'plan has 2+ steps' instead of '2+ tool calls'
- MEMORY: remove mandatory session-start memory_search (was conflicting with
planning order); replace with contextual trigger rules
- SCRATCHPAD: add 'if you've written anything to it' qualifier before read
- DELEGATION: clarify sequential spawning is fine; tighten when-not-to-spawn
secretary: trim redundant execution discipline, add 'test in code_exec before
writing to disk' rule, profile-specific scratchpad sections
server_admin: add explicit diagnostic workflow (gather → diagnose → act),
profile-specific scratchpad sections, expanded safety and delegation guidance
smart_home: add 'read-before-act' rule (check entity state before modifying),
profile-specific scratchpad sections, tighten safety rules
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 11 Apr
|

Strengthen Navi planning/delegation, unify toolsets, isolate subagent scratchpad
...
persona.txt:
- DELEGATION: 'default to spawning, not to doing inline' — stronger default,
clearer triggers, explicit when-not-to-spawn rules
- PLANNING: ties automatic planning phase to mandatory todo(op='set') as first
tool call; reconciles pre-loop plan with in-loop execution discipline
- SCRATCHPAD: new section — when to write, section naming conventions,
mandatory read before final answer
Profiles (secretary, server_admin, smart_home):
- All three now share the same 18-tool set (each file independent)
- planning_enabled=True on all three
- scratchpad and web_search added to smart_home
- System prompts updated with scratchpad/todo execution discipline sections
agent.py run_ephemeral:
- Each subagent gets a unique session ID (subagent_<uuid>) for scratchpad
isolation — parallel or sequential subagents no longer share working notes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 11 Apr
|
Skip planning phase for simple/direct requests
...
The planning prompt now asks the model to respond with "DIRECT" if the
request doesn't need multiple steps. Added a regex fallback: if the
response has no numbered steps it's also discarded. This prevents plan
cards appearing for conversational replies that would just duplicate
the final message.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 11 Apr
|
Add planning phase and scratchpad tool for smarter task execution
...
- ScratchpadTool: session-scoped working notepad with named sections
(write/append/read/clear). Lets Navi capture intermediate findings
between tool calls instead of losing track of them.
- Planning phase: when profile.planning_enabled=True, a fast pre-loop
LLM call (think=False, no tools) outlines a numbered plan before
any actions are taken. The plan is injected into session context as
an assistant message so the model naturally continues from it.
- PlanReady event + plan_ready WebSocket message + plan card in UI
(green-tinted, collapsible, mirroring thinking card design).
- secretary and server_admin profiles: planning_enabled=True,
scratchpad added to enabled_tools, system prompts updated with
explicit execution discipline instructions.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 11 Apr
|
Fix WebSocket state corruption preventing messages after first reply
...
Replace concurrent WS reads (_stream_recv + recv_task.cancel()) with
HTTP stop endpoint (POST /sessions/{id}/stop). Cancelling a background
receive_text() task corrupted Starlette's WS state, breaking all
subsequent receives. Now the WS has a single reader at all times.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 11 Apr
|
Fix mobile viewport height jumps (100dvh)
...
- Replace 100vh with 100dvh on .app and mobile sidebar — dvh tracks the
actual visible viewport and adjusts when the browser address bar or
virtual keyboard appear/disappear, eliminating layout jumps in Chrome
- Add overflow: hidden to html/body to prevent page-level scroll leaking
through when the virtual keyboard resizes the viewport
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 11 Apr
|
| 2026-04-10 |
Add responsive layout for mobile
...
- Sidebar becomes a fixed drawer on ≤768px: slides in from left with
transform + transition, backdrop overlay behind it
- Hamburger ☰ button in chat header (hidden on desktop, visible on mobile)
- Backdrop click closes sidebar; session select also closes it
- Wider message bubbles (90%) and tighter padding on small screens
- No layout changes on desktop
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 10 Apr
|
Refactor client input state management
...
- Replace setStreamMode + syncSendButton + setInputEnabled (three functions
competing for the same button) with a single updateInputUI() that derives
button state from streaming, inputAllowed, uploadCount — eliminates the
bug where upload completion during streaming could reset the stop button
- Add inputAllowed module var as explicit "session is ready" flag
- Remove stale client/app.js (was shadowing client/js/app.js, never loaded)
- Clean up localStorage draft on session delete to prevent key accumulation
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 10 Apr
|

Add stop button and fix context compression hang
...
Stop generation:
- Client: send button toggles to red ■ during streaming; sends {type:stop} via WS
- Server: _stream_recv concurrently reads incoming messages during streaming using
asyncio.wait — stop signal is handled immediately without polling
- Cooperative stop via asyncio.Event (current_stop_event ContextVar): agent breaks
out of LLM async-for cleanly so aclose() fires → Ollama stream closes gracefully,
model stays in VRAM. No task.cancel() which would eject the model.
- StreamStopped event propagates through run_stream/run_ephemeral; sub-agents stop
via the same shared stop_event inherited through task context
Context compression fix:
- compress_context passes think=False to llm.complete() — no extended reasoning
during summarization which caused GPU hang
- Input truncated to 12k chars before sending to summarizer
- LLMBackend.complete() / OllamaBackend.complete() accept think: bool | None override
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 10 Apr
|
ssh_exec icon 🖧→🔌
...
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 10 Apr
|
Terminal icon ⚡, subagent max_iterations 20→100
...
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 10 Apr
|
Fix ssh_exec: is_closing() → is_closed() (asyncssh API)
...
SSHClientConnection has no is_closing() — that's asyncio Transport.
The correct public method is is_closed().
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 10 Apr
|
Fix save(): persist profile_id to DB
...
profile_id was never included in the UPDATE statement — only set on
initial INSERT. Profile switches appeared to work in-memory but reverted
on page reload or server restart.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 10 Apr
|
Fix profile switch: reload tools/schema after switch_profile tool call
...
switch_profile updates profile_id in DB, but run_stream() held a stale
local session object — the final save would overwrite the change, and
subsequent LLM calls in the same turn still used the old tool schemas.
After each tool-call iteration, compare DB profile_id with the local
session object. On mismatch: update session.profile_id, reload profile,
tools, tool_schemas, and llm backend so the next LLM call gets the
correct schema and the final save preserves the new profile.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 10 Apr
|
Add ssh_exec to secretary profile
...
Was missing, causing 'tool not found' when Navi tried to SSH from secretary mode.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 10 Apr
|
Dynamic system prompt — inject per-call instead of storing in context
...
System prompt is no longer stored in session.context. Instead,
_build_context() prepends the current profile's system prompt fresh on
every LLM call. This means profile switches take effect immediately on
the next message — no stale prompt lingering in stored context.
Also strips any existing system messages from context for migration
safety (old sessions that have one stored will still work).
_with_memory() removed, replaced by _build_context(context, profile, mem).
run_ephemeral() context no longer includes system message either.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 10 Apr
|
Fix ssh_exec error reporting + circular import in switch_profile
...
ssh_exec:
- Non-zero exit status: output (stdout/stderr) now included in error
field so model can actually see why the command failed
- All error cases: error field now contains human-readable message
instead of short code (no_target, permission_denied, timeout)
- Named connections from ssh_hosts.json: default known_hosts=none
same as ad-hoc connections (was missing, caused host key failures)
switch_profile:
- Move ProfileSwitched import inside execute() to break circular
import: tools.__init__ → switch_profile → core.events → core.agent
→ core.registry → tools
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 10 Apr
|
docs: add profile_switched WS event and switch_profile flow
...
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 10 Apr
|
Profile switch: emit WS event so client updates UI immediately
...
ProfileSwitched event emitted by switch_profile tool via current_event_sink.
Client handles profile_switched: updates chat header, profile selector,
and local sessions[] — no page refresh needed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 10 Apr
|
Add switch_profile tool for automatic profile switching
...
Navi can now switch her own profile mid-session when the task domain
changes. The new profile (tools + system prompt) takes effect from the
next user message. Injected with session_store + profile_registry like
SpawnAgentTool. Added to all profiles' enabled_tools and persona.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 10 Apr
|
Add API reference documentation
...
Covers: REST endpoints (health, profiles, tools, sessions, files,
messages), WebSocket protocol (all event types, client→server format,
typical event sequences), error codes.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 10 Apr
|
Add workspace dir + clean up junk from project root
...
- workspace/ — persistent dir for Navi's long-term files (scripts, notes,
data); excluded from git, Navi instructed to use it instead of project root
- .gitignore: session_files/ and workspace/* added
- persona.txt: WORKSPACE section pointing Navi to workspace/
- Deleted scanner.py, network_scan_results.txt, targets.txt (Navi artifacts)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 10 Apr
|

Major feature batch: visibility, planning, file uploads, streaming
...
- stream_complete(): streaming with tools for all LLM turns — thinking
now streams as ThinkingDelta/ThinkingEnd in real-time during tool-
selection turns, not just on the final response
- todo built-in tool: session-scoped plan manager (set/view/update/clear);
persona + all profiles updated with mandatory planning instructions
- TurnThinking event: sub-agent thinking forwarded to parent sink as a
collapsible block in the spawn_agent card
- File uploads: non-image files uploaded via XHR, shown as badges in
message bubble; SVG treated as regular file (not base64 image)
- session_files: POST /sessions/{id}/files, TTL cleanup, forbidden exts
- WebSocket reconnect: _AgentRun broadcast pattern, re-attach mid-stream
- UI: favicon, sidebar logo, turn-thinking cards, subagent thinking blocks,
token counter, draft persistence, file progress bar
- Removed AgentNote (content is always None alongside tool_calls)
- Ollama stream_complete: tool_calls captured from non-final chunk (done=False)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 10 Apr
|
| 2026-04-09 |

Live tool visibility: pending cards, sub-agent step log
...
Backend:
- ToolStarted event: emitted before tool execution begins so client
can render a pending card with spinner immediately
- ToolEvent gains is_subagent flag; ToolStarted same
- current_event_sink ContextVar in tools/base.py — run_stream() sets it
to an asyncio.Queue before create_task(); run_ephemeral() reads it and
puts ToolStarted/ToolEvent into the queue as each sub-agent step runs
- run_stream() tool loop: sequential execution via create_task() +
polling drain loop (20ms sleep); yields ToolStarted → sub-agent events
from sink → ToolEvent (completed) for each tool call
- run_ephemeral() rewritten to inline sequential tool execution with
sink emission (replaces _execute_tool_calls gather)
- _run_single_tool() helper extracted for run_stream()
- websocket.py handles tool_started and adds is_subagent to tool_call
Frontend:
- appendPendingToolCard(): creates card with spinner; spawn_agent opens
body immediately to show sub-agent log as it fills
- finalizeToolCard(): fills result, removes spinner, adds toggle; strips
"[Sub-agent result — ...]" reminder prefix from displayed text
- appendSubagentStep() / finalizeSubagentStep(): live step log inside
spawn_agent card — each sub-agent tool call gets a ↳ row
- app.js: tool_started → pending card; tool_call → finalize card;
is_subagent routing to sub-step vs main card; abandonStream() resets
pendingToolCard/pendingSubStep
- CSS: .spinner-inline for card headers; .subagent-log / .subagent-step
for nested step display; .tool-body-open for always-open spawn_agent
body; .tool-card.pending suppresses chevron
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 9 Apr
|
spawn_agent: fix model behavior — synthesis reminder + lower delegation threshold
...
- ToolResult.output now prefixed with explicit reminder that the user
cannot see sub-agent output and the orchestrator must present findings
in its own final response; reminder appears adjacent to the result so
the model reads it in context
- spawn_agent description updated: lowers threshold from "3+" to "2+"
tool calls, makes USER CANNOT SEE warning prominent, tightens wording
- persona.txt DELEGATION section: clearer rule (default to spawning for
multi-step sub-tasks), explicit "never end turn after spawn results",
removed abstract threshold in favour of concrete examples
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 9 Apr
|
Add spawn_agent: sub-agent delegation with isolated context
...
- Agent.run_ephemeral() — runs a sub-agent loop without a persistent
session; accepts exclude_tools to block recursion; logs start/complete
- session_store made Optional in Agent.__init__ (None for ephemeral runs)
- SpawnAgentTool (navi/tools/spawn_agent.py): spawns an isolated Agent
for a focused task; resolves profile from parent session via ContextVar;
blocks spawn_agent recursion via exclude_tools=["spawn_agent"]
- build_default_registries() accepts session_store param; registers
SpawnAgentTool after BackendRegistry is built (patches _backend_registry)
- deps.py passes _session_store to build_default_registries
- All profiles: spawn_agent added to enabled_tools, max_iterations 10→30
- persona.txt: DELEGATION section — when/how to use spawn_agent
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 9 Apr
|
Client: wider sidebar, 2-line preview, loading spinners
...
- Sidebar width: 260px → 300px
- Session preview: nowrap/ellipsis → 2-line clamp (-webkit-line-clamp: 2)
- Action buttons: align-items flex-start + 2px padding-top so they sit
at the top of multi-line items instead of vertically centered
- Spinner shown in sidebar session list during initial data fetch
- Spinner shown in chat area during session history load; cleared on both
success and error
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 9 Apr
|