| 2026-09-10 |
swarm stage 2: peer-to-peer channel — /peer endpoints + peer tool
...
Server side (navi/api/routes/peer.py, port 8099):
- GET /peer/hello — open discovery ping (name, uuid prefix, version)
- GET /peer/status — PSK; identity, uptime, machine facts, hive view
- POST /peer/ask — PSK; one-shot agent run under PEER_ASK_PROFILE
(default server_admin) answers, one concurrent ask at a time
- loop guard: answering agent runs without the peer tool (deterministic
recursion cut) + own-uuid asks refused with 409
- verify_swarm_key in navi/swarm.py accepts .swarm-key.previous
(rotation window, constant-time)
Client side (navi/tools/peer.py):
- peer tool: list (hive book, stale cache fallback marked), status,
ask by swarm name; asks go direct peer-to-peer, hive never on the
message path
- audit events on both sides (peer.ask_sent/received/answered/failed)
peer tool added to server_admin, navi_code, developer profiles.
Eugene Sukhodolskiy
committed
11 hours ago
|
| 2026-09-09 |
swarm stage 1: instance identity, PSK, hive registry, announce loop
...
- navi/identity.py: adjective-animal names + uuid in instance.json
(generated at install, renameable by hand)
- navi/swarm.py: HiveAnnouncer - periodic POST /announce to the hive
with non-blocking reachability tracking (transitional logs, /health
export, hive_status context provider reports outages to the agent)
- hive/: standalone FastAPI address book (SQLite, port 8087, PSK via
X-Swarm-Key with .swarm-key.previous rotation window, TTL online
status, host from client IP, port from payload). Not installed or
started by default - run manually on the main server.
- ports moved to 8099 (API) / 8098 (UI MCP)
- install.sh: PYTHONIOENCODING=utf-8 in the systemd unit, generates
instance.json and .swarm-key on fresh installs
Eugene Sukhodolskiy
committed
12 hours ago
|
port: 8000/8001 -> 8099/8098 everywhere
...
navi runs on shared servers where 8000/8001 are usually taken. New
defaults: API 8099, navi_ui MCP 8098. Touched: config defaults
(navi_port, navi_ui_mcp_port, public_url, gnauth_redirect_uri),
navi-server launcher docs, env.template/.env.example, install.sh
health-check fallback, terminal client base_url, webclient dev configs
(useWebSocket, contentLinks, vite proxy), android url hint, docs.
Also made the navi_ui FastMCP constructor port settings-driven instead
of a hardcoded 8001 (it was overridden at start anyway).
Eugene Sukhodolskiy
committed
13 hours ago
|
deploy: one-command server bundle (dockerized postgres + systemd)
...
deploy/install.sh — everything in one command: prerequisite checks,
.env provisioning from env.template (generated DB password), dockerized
PostgreSQL (pgvector image, restart: always, bound to 127.0.0.1, vector
+ pg_trgm extensions), venv + pip install, a systemd unit with
Restart=always (the server lives from installation, per the roadmap
where these modules become each other's subagents), health check, and
navi-code/navi-server symlinks into PATH. Idempotent — safe to re-run.
Default deployed shape: web UI off, auth off, terminal client only.
Re-enabling the web panel later is editing .env + restart — nothing is
removed from the tree.
The deploy branch (next commit set) differs from master only by a
filled-in .env.
Eugene Sukhodolskiy
committed
20 hours ago
|
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
23 hours ago
|

security: critical batch 1 — RCE/XSS/CORS/webhook hardening
...
Backend:
- auth/deps: fix refresh-lock clock race (cleanup now monotonic like the cache)
- core/registry: add missing structlog logger (NameError on fallback path)
- config: GNAUTH_WEBHOOK_SECRET, NAVI_ALLOWED_ORIGINS (+list property)
- webhooks: HMAC-SHA256 signature verification (503 unconfigured in auth mode,
unsigned+warning in no-auth mode, 403 bad/stale signature, 400 bad JSON)
- main: CORS from NAVI_ALLOWED_ORIGINS in auth mode (fail-fast on empty),
eval router behind require_admin, /debug only registered in no-auth mode
- auth routes: mobile-done sid validation (32 hex) + CSP header + safe JS
escaping (reflected XSS); Secure cookie flag via helper (https base URL)
- websocket: anonymous WS rejected in auth mode (closes legacy-session RCE);
socket registered only after access checks; stop_session auth gate
- messages: REST agent start now takes session lock + busy flag (409 on
active run), contextvars reset in finally
Webclient:
- useMarkdown: DOMPurify.sanitize on all rendered markdown (stored XSS),
image-URL scheme whitelist, delegated error listener (no inline onerror)
- html.html artifact viewer: sandbox without allow-same-origin (opaque origin)
- tests: DOMPurify runs under jsdom (happy-dom Node.prototype.nodeName getter
breaks DOMPurify); useWebSocket tests get localStorage stub + ui-kit alias
Tests: pytest 1049 passed, 1 skipped; vitest 61 passed
Eugene Sukhodolskiy
committed
23 hours ago
|
| 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-22 |
Add NAVI_AUTH_ENABLED switch for optional auth
...
- Add navi_auth_enabled setting (default true) to navi/config.py and .env.example
- When disabled, treat every request as anonymous admin user (id='anonymous')
- Create/update fixed anonymous navi_users row on startup
- Bypass OAuth/cookie/API-token resolution in navi/auth/deps.py
- Update /auth/status to return {enabled, configured}
- Log security warning on startup when auth is disabled
- Update webclient: skip fetchMe/login screen, show Local mode footer,
expose /admin link, warn in API keys panel
- Rebuild webclient production bundle
- Add unit and integration tests for no-auth mode
- Update docs: auth.md, config.md, api.md, api_tokens.md, sessions.md,
websocket.md, mechanics.md, index.md
Co-Authored-By: Claude <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 22 Jun
|
| 2026-05-04 |
Fix default gnauth profile path to /account/profile
...
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 4 May
|
Revert "Fix avatar: use Gravatar instead of non-existent profile fields"
...
This reverts commit f485e54.
Eugene Sukhodolskiy
committed
on 4 May
|
Fix avatar: use Gravatar instead of non-existent profile fields
...
Investigated gnexus-auth UserinfoController and found that the profile
response only contains: username, display_name, first_name, last_name,
phone, birth_date, country, city, locale, timezone. There is no picture
or avatar_url field.
- Add make_gravatar_url() helper in navi/auth/__init__.py
- Update deps.py to generate Gravatar URL from user email
- Update config.py default gnauth_profile_path to /account/profile
- Update .env.example comment accordingly
- Frontend already handles avatar_url correctly
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 4 May
|
Add avatar display and gnexus-auth profile link
...
Backend:
- User model: add avatar_url field
- auth/deps.py: extract avatar_url from auth_user.profile (picture/avatar_url)
- auth.py /auth/me: return avatar_url + computed profile_url
- config.py: add gnauth_profile_path setting
- .env.example: document GNAUTH_PROFILE_PATH
Frontend:
- AppSidebar.vue: show user avatar (or initial fallback) next to name
- Clicking user info opens gnexus-auth profile in new tab
- Rebuild dist/
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 4 May
|
| 2026-05-03 |
Fix DDL race condition and PostgreSQL syntax error on startup
...
- _ensure_auth_tables now uses raw asyncpg.connect instead of get_session_store,
avoiding PgSessionStore._MIGRATE running before navi_users exists
- Move _ensure_auth_tables before ensure_tables in startup so navi_users is
created before any foreign-key references
- Replace invalid "ADD CONSTRAINT IF NOT EXISTS" with DO $$ block for
memory_facts_user_cat_key
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 3 May
|
| 2026-04-29 |
Update deployment docs — PostgreSQL-only, new .env vars, pg_trgm notes
...
- README.md: remove SQLite, add PostgreSQL setup (Ubuntu/Arch/Docker),
add pg_trgm notes, update .env example
- docs/config.md: remove DB_PATH, add OLLAMA_REQUEST_TIMEOUT / OPENAI_MODEL /
OPENAI_BASE_URL, update example .env
- .env.example: rewrite with all current variables and comments
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 29 Apr
|
| 2026-04-24 |

Add Ollama multi-server fallback with in-memory blacklisting
...
- New FallbackOllamaBackend (navi/llm/fallback.py): tries servers and
models in priority order; on LLMConnectionError blacklists the server
for the process lifetime, on LLMModelNotFoundError blacklists the
(server, model) pair — eliminates latency from repeated failed probes
- OllamaBackend now raises typed LLMConnectionError / LLMModelNotFoundError
instead of bare LLMBackendError; accepts list[str] | str | None for model
- AgentProfile.model changed from str to list[str] (str auto-normalised);
all profiles updated to ["gemma4:31b-cloud", "gemma4:26b-a4b-it-q4_K_M"]
- New config field OLLAMA_BACKENDS_FILE: path to [{host, api_key?}] JSON;
when set, registry creates FallbackOllamaBackend instead of OllamaBackend
- ollama_backends.json template added (gitignored — contains API key)
- current_model ContextVar type widened to list[str] | str | None
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 24 Apr
|
| 2026-04-22 |
Use gemma4 cloud model by default
Eugene Sukhodolskiy
committed
on 22 Apr
|
Support Ollama Cloud API key
Eugene Sukhodolskiy
committed
on 22 Apr
|
| 2026-04-09 |
SSH connection pooling: per-session, 20-minute TTL
...
- Pool keyed by session_id:host:port:username — parallel sessions share
no state even when targeting the same server
- Per-key asyncio.Lock prevents concurrent connection creation races
- TTL (20 min) and is_closing() checked on every access; expired/closed
connections are evicted and replaced transparently
- On disconnect error during command execution: evict + retry once with
fresh connection
- current_session_id ContextVar (set by Agent before tool calls) is read
in ssh_exec to build the pool key without changing tool signatures
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 9 Apr
|
| 2026-04-08 |
Switch all profiles to gemma4:e4b-it-q8_0
...
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 8 Apr
|
Filesystem tool: unrestricted mode by default
...
FS_ALLOWED_PATHS=* (default) allows any path, consistent with terminal.
Path check is now lazy (per-request) instead of module-level,
so config is always fresh after restart.
~ expansion added.
Eugene Sukhodolskiy
committed
on 8 Apr
|
Unrestricted terminal mode and SSH tool
...
- Terminal: TERMINAL_ALLOWED_COMMANDS=* (default) runs via shell,
supports pipes, redirects, subshells; allowlist mode still available
- FS_ALLOWED_PATHS expanded to cover /home /etc /var /opt
- New ssh_exec tool: execute commands on remote hosts via asyncssh,
supports named connections (ssh_hosts.json) and inline user@host
- ssh_hosts.json gitignored, ssh_hosts.json.example added as reference
- ssh_exec added to server_admin and smart_home profiles
Eugene Sukhodolskiy
committed
on 8 Apr
|
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
|