Personal modular AI agent system. FastAPI backend + vanilla JS client. The agent is named Navi — female, loyal, uncensored personal assistant. Runs locally via Ollama.
gemma4:e2b-it-q8_0 (2B active params, Q8 — small but capable)ollama_think: bool = True — model reasoning is enabled and streamed to client.venv/bin/uvicorn navi.main:app --reload --reload-dir navi --port 8000navi/core/agent.py)Tool-calling loop with llm.complete() for tool turns, llm.stream() for final response. Events yielded: ToolEvent, ThinkingDelta, ThinkingEnd, TextDelta, StreamEnd. Tool schemas built fresh on every run_stream() call from registry + tools/enabled.json.
Two tiers:
Built-in tools (navi/tools/):
web_search, filesystem, http_request, code_exec, terminal, ssh_exec, image_viewwrite_tool — Navi's primary self-extension mechanism (writes + reloads in one call)reload_tools — hot-reload all user tools without server restartlist_tools — returns actual live tool list from registry (Navi calls this when asked what she can do)tool_manual — returns manuals/<tool_name>.md if exists, else auto-generates from schemaUser tools (tools/*.py):
write_tool, or manuallyname, description, parameters, async def execute(params) -> strtools/enabled.json — list of user tool names to auto-include in all profilestools/_template.py — canonical format reference (starts with _, not auto-loaded)Currently created by Navi: get_current_datetime.py, user_notes.py (working, correct format).
navi/profiles/)secretary, server_admin, developer. Each has enabled_tools, system_prompt, model, temperature, max_iterations. All profiles have the same built-in tool set: [..., reload_tools, write_tool, list_tools, tool_manual]. User tools from enabled.json are merged in by Agent._tool_list().
config.json)Every autonomous-reasoning feature is gated by a flag on AgentProfile. New mechanics always add a flag first.
| Flag | Default | Purpose |
|---|---|---|
think_enabled |
true |
Extended LLM reasoning on every call |
planning_enabled |
false |
Planning phase before tool loop |
iteration_budget_enabled |
true |
Injects remaining iterations into context |
planning_reflect_enabled |
false |
Reflect 3-advisor pass in planning phase 1 |
goal_anchoring_enabled |
true |
Goal reminder every N iterations |
goal_anchoring_interval |
5 |
N for goal anchoring |
anti_stall_enabled |
true |
Stall detector (N iterations without todo progress) |
anti_stall_threshold |
3 |
Stall threshold in iterations |
step_validation_enabled |
false |
LLM check after each todo step (adds latency) |
adaptive_replan_enabled |
false |
Re-planning on step failure |
Low-latency profiles (e.g. future smart_home) can disable expensive flags; deep-reasoning profiles keep everything on.
navi/config.py → .env)NAVI_PERSONA env var — prepended to every profile's system prompt separated by ---. Contains: personality, self-extension instructions, write_tool usage rules, tool_manual usage.
navi/core/registry.py)ToolRegistry tracks _builtin_names to distinguish builtins from user tools on reload. reload_user_tools() drops all non-builtins and reloads from disk. Built-in tools with registry injection: ReloadToolsTool, WriteToolTool, ListToolsTool, ToolManualTool.
navi/core/sqlite_session_store.py)Persistent SQLite sessions. model_dump(mode='json') required for datetime serialization. Session ID in URL hash for bookmarking.
navi/api/websocket.py)client → server: {type: "message", content: "...", images: [...]}
server → client: stream_start
thinking_delta {delta} ← reasoning chunks (collapsible in UI)
thinking_end
tool_call {tool, args, result, success}
stream_delta {delta}
stream_end {content}
error {message}
client/)ES modules: app.js (state/routing), chat.js (DOM helpers), ws.js (WebSocket), api.js (REST), sidebar.js. Thinking blocks: open during reasoning, auto-collapse on thinking_end, re-openable (like tool cards). Tool cards: accordion, collapsed by default, click to expand. Images: paste/attach, base64 via FileReader, rendered in bubbles. No localStorage — session from URL hash or most recent server session.
navi/tools/loader.py)Tries module-level format first (preferred for user tools), falls back to class-based. Errors isolated per file — one broken file doesn't affect others. Detailed error messages: lists exactly which required definitions are missing.
.env)NAVI_PERSONA="..." # global personality + tool writing rules OLLAMA_HOST=... OLLAMA_DEFAULT_MODEL=gemma4:e2b-it-q8_0 OLLAMA_NUM_CTX=8192 OLLAMA_THINK=true
manuals/)Markdown files, one per tool. tool_manual serves them on demand. Currently: manuals/write_tool.md (full format reference + working example). Auto-generation fallback from tool schema if no .md exists.
write_tool validates code before writing (checks for 4 required definitions)write_tool adds tool to tools/enabled.json on success → available in all profilesrun_stream() entry)tool_manual("write_tool") before writing a toollist_tools when asked about her capabilities (not generate from memory)no-store cache middleware on /static/ — safe to hard-refresh during developmentwrite_tool (improving — model still sometimes struggles with format)tool_manual + explicit format feedback in write_tool errors is the current mitigationlist_tools fixes this if she uses it