| 2026-10-09 |

profiles: restricted profiles for ordinary users, and a role gate that holds
...
`is_admin_only` was checked in one place out of nine and was not read from
config.json at all, so all seven profiles were reachable by every account with
role `user`. The flag now lives in config.json — the file is the baseline, a
`profile_overrides` row still wins on top of it — and one predicate,
`admin_only_blocked`, is the single place the rule is expressed. The nine
surfaces that list, switch to, spawn or resolve a profile all consult it:
`POST /sessions`, the WebSocket, switch_profile, list_profiles, the system
prompt's "Available profiles" block, spawn_agent and the Synapse reaction
runner. The prompt cache is now keyed by (profile, role), so a user's prompt
can never be served an admin's profile list.
The seven existing profiles (developer, discuss, dispatcher, modeler_3d,
navi_code, secretary, server_admin) are marked admin-only. Three new ones take
their place for ordinary users: assistant, designer_3d and coder. They share one
native tool set — ssh_exec, peer, reload_tools, create_mcp_server, test_mcp_tool,
image_view and gmail are withheld — and differ only in system prompt, model and
MCP groups. navi-web's raw `request` group, and the whole of gnexus-creds and
tgclient, are withheld too.
MCP per-user keys gain the missing half of the rule: a server that declares a
`user_key` slot is refused to anyone but an admin who has no personal key, and
is left out of their tool list entirely, instead of quietly falling back to the
owner's credential and appearing as a tool that cannot work. The refusal names
the server and points at Settings.
Also closes `GET /agents/prompts`, which served every profile's system prompt to
anyone, with no user dependency at all.
The accepted residual risk is written down in docs/profiles.md: the working
directory is a convention, not a sandbox.
Eugene Sukhodolskiy
committed
3 hours ago
|
| 2026-10-08 |

tools, mcp: a description is a hook, not a manual
...
With 24 tools in a profile the schemas are the largest fixed block of every
request, and they are paid for whether or not the tool is called. Eleven of
them carried a manual's worth of prose in their `description` — examples,
error tables, "common mistakes", the mechanics of the file areas. Reading
them against the manuals first, the manuals already held all of it, and
richer: this was duplication sitting in the one place the model cannot avoid
reading, not knowledge that had nowhere else to live. So the descriptions
became selection hooks — what the tool is for, when to pick it over its
neighbour, and the one rule that cannot wait — and the detail moved to
tool_manual("<tool>"), which costs nothing until the tool is actually in
play.
filesystem, spawn_agent, schedule_recall, manage_recall, scratchpad, plan,
share_file, content_publish, reflect, todo and peer. Descriptions drop from
18.4 KB to 11.5 KB, those tools' schemas from 25.5 KB to 16.8 KB, and a
profile's whole native toolset from 38.3 KB to 29.6 KB (server_admin,
developer; secretary 33.7 → 25.0, navi_code 32.9 → 25.9, tool_developer
37.9 → 29.2, modeler_3d 33.4 → 24.8). Stated as a budget rather than a byte
count: roughly 4k tokens of the model's window per request, back.
Two things were deliberately not shortened. The filesystem edit ladder
(`edit` → `edit_lines` → `smart_edit`, last resort, costs an LLM call) and
todo's mandatory `validation` on `done` both exist to prevent an extra
round trip; a shorter description there would trade tokens for turns. What
todo lost is the prose around the rule, not the rule. `enabled.json` was
left alone too: weather, gmail and get_current_datetime are opt-in and the
DB shows 12, 16 and 40 calls, so they are opt-in tools that are actually
used.
peer had no manual at all, which is how this surfaced: its description was
the only documentation the tool had. manuals/peer.md is now written from
the tool and its /peer route — the ask/status/list actions, the fact that
the hive is only a phone book and the question travels peer-to-peer, the
one-concurrent-answer semaphore, the two independent recursion guards, and
the six error codes.
The MCP server instructions leave the system prompt the same way. They were
8 KB of always-present prose per request, most of it workflow detail the
model only needs when it is about to use that server. Each server now
contributes one line — a new optional `summary` field in mcp_servers.d/*.json,
defaulting to the first sentence of its `instructions` — and
tool_manual("<server>") returns the full text plus the tool names the server
declares. server_admin's MCP block goes from 8.3 KB to 1.3 KB, secretary's
from 8.1 to 1.2. tool_manual learned to answer for a server (a tool name
still wins over a server of the same name, and dash/underscore spelling is
tolerated), and the index names the servers alongside the tools.
One judgement call worth recording: gnexus-book's and navi-web's
instructions end in an absolute "NEVER bypass these tools with filesystem,
terminal or code_exec" rule. Moving that on demand would have been a
behavioural regression dressed as a token saving, so those two `summary`
fields carry the prohibition verbatim alongside the hook, and it stays
always visible.
docs/tools.md gains a section on the split (a description is paid for every
request, a manual only when the tool is used), which is where the next
person will look before trimming a description back into a manual.
Full suite green (1577 passed, 1 skipped). No frontend change and no new
dependency, so the deploy is a pull and a restart.
Eugene Sukhodolskiy
committed
8 hours ago
|
| 2026-10-07 |
mcp keys W2: per-user client clones, KeyResolver wiring, user_id in call path
Eugene Sukhodolskiy
committed
1 day ago
|
| 2026-09-09 |
mcp: a dead MCP endpoint no longer kills server startup
...
A failed transport connect inside the MCP SDK's anyio cancel scopes
surfaces as CancelledError without any task.cancel(). The runner mistook
that for a real teardown, cancelled the in-flight caller's future, and
McpManager.load_all (which only catches Exception) let it blow up the
whole lifespan startup — one unreachable server in mcp_servers.d/ meant
a dead Navi.
The runner now distinguishes real task cancellation
(asyncio.current_task().cancelling()) from the anyio artifact: the
latter is surfaced to the caller as a normal connect failure, the
server pool marks it disconnected and the health-check loop retries.
Also, when the runner dies for any reason, pending queue commands are
failed instead of leaving _send callers hanging forever.
Eugene Sukhodolskiy
committed
29 days ago
|
| 2026-05-21 |

Migrate MCP tool naming from mcp:server:tool to mcp__server__tool
...
The colon separator (mcp:server:tool) confuses many LLMs during
tool-calling because colons appear in schemas and URLs. Switch to
double-underscore separator (mcp__server__tool) for robust parsing.
Key changes:
- navi/mcp/tools.py: add build_mcp_name(), parse_mcp_name(), is_mcp_tool()
- navi/core/tool_executor.py: update _resolve_tool() with new helpers
and legacy colon fallback for old sessions
- navi/core/tool_utils.py, subagent_runner.py: use build_mcp_name()
- navi/api/routes/{admin,agents}.py: prefix via build_mcp_name()
- navi/tools/{list_tools,reload_tools}.py: migrated
- All profile configs + system_prompt.txt: replace mcp: with mcp__
- manuals/{model_3d,lint_scad,render_3d,spawn_agent}.md: updated
- mcp_servers.d/gnexus-book.json: instructions updated
- docs/{api,profiles,tools,mechanics,visual.html}: updated
- tests: test_tool_executor.py and test_mcp.py aligned
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 21 May
|
McpTool: auto-inject session_id + normalize navi-3d paths
...
- McpTool.execute() now forces the real session_id from current_session_id
ContextVar, preventing LLM hallucinations of wrong UUIDs (ghost-session bug).
- For navi-3d MCP server, source_path/output_path are normalized to basename
to prevent double path nesting when the LLM passes full relative paths.
- Updated MCP tool descriptions to ask for filenames only.
- Added system prompt instructions in context_builder and subagent_runner
reminding the model to pass bare filenames to navi-3d tools.
396 passed, 1 skipped
Eugene Sukhodolskiy
committed
on 21 May
|
| 2026-05-13 |
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 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 |
Clarify knowledge persistence prompts
Eugene Sukhodolskiy
committed
on 12 May
|
| 2026-05-10 |
Add MCP server support and fix memory tools user isolation
...
MCP integration:
- New navi/mcp/ package: client, manager, config, tools
- ToolRegistry learns register_external() for MCP tools
- reload_tools reconnects MCP servers on hot reload
- New built-in mcp_status tool
- Startup/shutdown wiring for MCP connections
- 12 new tests (unit + integration with real stdio server)
Memory tools fix:
- memory, memory_save, memory_search, memory_forget now read
current_user_id from tool context and pass it to MemoryStore
- Fixes invisible facts for authenticated users
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Eugene Sukhodolskiy
committed
on 10 May
|