Newer
Older
navi-1 / docs / profiles.md
@Eugene Sukhodolskiy Eugene Sukhodolskiy 12 hours ago 21 KB profiles: give the restricted profiles image_view

Profiles

Profiles define the agent's identity, tools, and behaviour for a specific domain.

Profile definition (navi/profiles/base.py)

Each profile is loaded from a directory under navi/profiles/<id>/:

  • config.json — all fields below
  • system_prompt.txt — domain-specific instructions
  • subagent_system_prompt.txt — injected into subagents spawned from this profile (optional)

All config.json fields

Identity

Key Type Default Description
id str required Unique profile identifier (matches directory name)
name str required Human-readable name shown in UI
description str required Longer description shown in profile picker
short_description str "" One-line summary injected into every profile's system prompt (cross-profile awareness)
full_description dict {} Structured dict: specialization, when_to_use, key_tools keys

LLM

Key Type Default Description
llm_backend str "ollama" Backend key: "ollama", "openai"
model str or list[str] ["gemma4:31b-cloud"] Model priority list — first available wins. String is accepted and auto-wrapped.
temperature float 0.7 Sampling temperature for main loop calls
max_iterations int 10 Hard cap on tool-calling iterations per turn
top_k int \ None None Ollama sampling top_k
top_p float \ None None Ollama sampling top_p
num_thread int \ None None CPU threads for local inference. None = Ollama default

Tools

Key Type Default Description
tools ToolConfig {} Required. Explicit tool configuration with two scopes: agent and subagent. Each scope has native: list[str] (built-in and user tool names) and mcp: dict[str, list[str]] (MCP server groups).

tools.agent controls what the main loop sees. tools.subagent controls what sub-agents spawned from this profile see. If tools.subagent is empty, it falls back to tools.agent.

spawn_agent may receive an optional profile_id. If omitted, the subagent uses the parent session's current profile. If provided, the subagent uses the selected profile's model, prompt, planning flags, and tools.subagent fallback.

MCP tool groups

Inside tools.{agent,subagent}.mcp, each key is an MCP server name and each value is a list of group names (or "*" for all groups). Named groups resolve to concrete tools via the server's config in mcp_servers.d/. Example:

{
  "mcp": {
    "navi-web": ["search", "browse", "request"]
  }
}

This exposes the tools mcp__navi-web__web_search, mcp__navi-web__web_view, and mcp__navi-web__http_request. * expands to every tool advertised by that server.

Deprecated tool fields

Older configs used enabled_tools, subagent_tools, and mcp_servers as flat top-level fields. The loader still auto-migrates them into tools.agent / tools.subagent for backward compatibility, but new profiles should use the explicit tools structure.

When tools.subagent is non-empty, only MCP groups listed there are exposed to the sub-agent. This prevents a profile's main MCP servers from leaking into restricted sub-agent contexts.

Thinking mechanics

Key Type Default Description
think_enabled bool true Pass think=True to LLM on every call (extended reasoning). Disable for latency-sensitive profiles.
iteration_budget_enabled bool true Inject remaining iteration count into context so the model knows when to wrap up.
goal_anchoring_enabled bool true Inject a goal-reminder system message every N iterations to prevent drift.
goal_anchoring_interval int 5 N for goal anchoring.
anti_stall_enabled bool true Detect looping without todo progress and inject a hard warning.
anti_stall_threshold int 8 Consecutive iterations without progress before stall warning fires.
step_validation_enabled bool false Reserved flag — todo validation is unconditional in the current implementation.

Bounded autonomy

One flag keeps an autonomous agent inside the user's literally requested scope. Defaults false (legacy "free flight" behavior — explore broadly, finish discovered work). Enabled on navi_code.

Key Type Default Description
scope_boundary_enabled bool false Inject a standing [Scope boundary] system message telling the agent to act strictly within the requested scope — do not expand to sibling/parent directories or projects, and do not execute discovered TODO/roadmap/milestone/backlog docs unless explicitly asked. Also gates the memory-facts scope filter (see below).

The memory-facts scope filter: when scope_boundary_enabled is on, _memory_facts_msg drops memory facts whose value is an absolute path outside the session cwd (e.g. a stale project_root pointing at another project). Facts inside the session cwd and non-path facts are kept. No facts are deleted — only injection is filtered. See docs/mechanics.md.

Planning

Top-level planning is agent-invoked: the agent calls the plan tool when a task warrants it (add it to tools.agent.native). There is no pre-turn gate — free conversation and trivial tasks execute immediately. The pipeline has two phases:

  • Phase 1 — Analysis: reformulates the task, identifies subtasks and unknowns, classifies COMPLEXITY: simple | medium | complex. Sub-agents can output DIRECT to skip planning for trivial subtasks (the shortcut is never offered at top level).
  • Phase 3 — Execution plan: assigns each subtask to TOOL / AGENT / SELF and uses adaptive plan depth.

When the plan is ready, the tool result tells the agent what to do next based on COMPLEXITY: complex — present the plan to the user and wait for confirmation; otherwise — proceed with execution. A re-plan is the same tool called with a reason (and optional updated_goal); it packs the current todo and scratchpad findings into the planning context and replaces the todo.

Key Type Default Description
planning_phase1_enabled bool true Enable Phase 1 (task analysis). When disabled, Phase 3 runs without analysis context.
planning_phase3_enabled bool true Enable Phase 3 (structured execution plan). When disabled, only Phase 1 (analysis) runs.

Sub-agent planning

Key Type Default Description
subagent_think_enabled bool \ None None Extended reasoning for sub-agents. None = inherit think_enabled from parent profile.
subagent_planning_enabled bool false Sub-agents spawned from this profile also run the planning pipeline before their tool loop.
context_providers list[str] [] Extra context providers to inject for this profile (by name). Global providers are always injected.
is_admin_only bool false If true, the profile may only be used by a user whose role is admin: it is absent from every profile list and from the injected "Available profiles" block, cannot be selected when a session is created, cannot be switched to, and cannot be spawned. Enforced by admin_only_blocked() in navi/profiles/base.py — the one place the flag/role rule lives. Set in config.json; a profile_overrides row written by PATCH /admin/profiles/{id}/availability is applied on top at startup and wins.
is_subagent_only bool false If true, profile can only be used via spawn_agent; switch_profile is blocked. Useful for narrow specialist agents that should never become the main session profile.

Compression

Per-profile overrides for context compression. When set, they take precedence over the global settings (CONTEXT_KEEP_RECENT, CONTEXT_SUMMARY_MAX_TOKENS) inside compress_context / compress_session and the summary system prompt. The active profile is propagated to the compressor via Agent.set_profile() and to the post-turn CompressionWorker via WorkerContext.profile.

Key Type Default Description
compression_keep_recent int \ None None Override CONTEXT_KEEP_RECENT — number of recent turns kept verbatim. navi_code uses 12.
compression_max_tokens int \ None None Override CONTEXT_SUMMARY_MAX_TOKENS — max output tokens for the summary LLM call.
compression_prompt_file str \ None None Extra instructions appended to the summary system prompt (loaded from the profile dir).

See sessions.md for the full compression algorithm and mechanics.md for the catalog.

Sub-agent system prompt

Key Type Default Description
subagent_system_prompt str "" Inline sub-agent system prompt. If empty, the loader reads subagent_system_prompt.txt from the profile directory. Prepended on top of the parent's system_prompt when inherit_system_prompt=True.

Unknown keys

AgentProfile uses model_config = {"extra": "allow"}, so unknown keys in config.json are accepted and preserved (round-tripped through the admin panel's GET/PUT) rather than rejected. This lets profiles carry custom metadata without code changes. |


Active profiles

Seven of the ten registered profiles are admin-only (is_admin_only: true): the owner's own working set. A user whose role is user neither sees nor can reach them.

ID Name Audience Models (priority order) Temp Planning
assistant Assistant user glm-5.3-flash:cloud → gemma4:31b-cloud → qwen3.5:397b-cloud → kimi-k2.6:cloud → gemma4:26b-a4b-it-q4_K_M → qwen3.6:27b 0.45 Yes
designer_3d 3D Designer user glm-5.3-flash:cloud → gemma4:12b-it-qat-128k → gemma4:31b-cloud → qwen3.5:397b-cloud → qwen3.6:35b → gemma4:26b-a4b-it-q4_K_M 0.35 Yes
coder Coder user glm-5.3-flash:cloud → gemma4:31b-cloud → qwen3.5:397b-cloud → kimi-k2.6:cloud → gemma4:26b-a4b-it-q4_K_M → qwen3.6:27b 0.30 Yes
secretary Personal Secretary admin glm-5.3-flash:cloud → gemma4:31b-cloud → gemma4:12b-it-qat-128k → qwen3.5:397b-cloud → kimi-k2.6:cloud → gemma4:26b-a4b-it-q4_K_M → qwen3.6:27b 0.45 Yes
server_admin Server Administrator admin glm-5.3-flash:cloud → gemma4:31b-cloud → gemma4:12b-it-qat-128k → qwen3.5:397b-cloud → gemma4:26b-a4b-it-q4_K_M → kimi-k2.6:cloud → qwen3.6:27b 0.25 Yes
developer Developer admin glm-5.3-flash:cloud → gemma4:31b-cloud → gemma4:12b-it-qat-128k → qwen3.5:397b-cloud → gemma4:26b-a4b-it-q4_K_M → kimi-k2.6:cloud → qwen3.6:27b 0.35 Yes
discuss Discussion admin glm-5.3-flash:cloud → gemma4:12b-it-qat-128k → gemma4:31b-cloud → qwen3.5:397b-cloud → kimi-k2.6:cloud → qwen3.6:27b 0.65 Phase 1 only
modeler_3d 3D Modeler admin glm-5.3-flash:cloud → gemma4:12b-it-qat-128k → gemma4:31b-cloud → qwen3.5:397b-cloud → kimi-k2.6:cloud → gemma4:26b-a4b-it-q4_K_M → qwen3.6:35b 0.35 Yes
navi_code Navi Code admin glm-5.3-flash:cloud → gemma4:31b-it-qat → gemma4:26b-a4b-it-qat → gemma4:12b-it-qat-128k → gemma4:31b-cloud 0.35 Yes
dispatcher Synapse Reaction Dispatcher hidden glm-5.3-flash:cloud → gemma4:31b-cloud → qwen3.6:35b → gemma4:26b-a4b-it-q4_K_M 0.20 No

Model chains are swapped as models come and go — navi/profiles/<id>/config.json is the source of truth. The dispatcher profile is additionally is_hidden and never appears in the picker.

All profiles share a base tool set. User tools from tools/enabled.json are merged in at runtime for every profile, admin or not — see "Restricted profiles" below.

Restricted profiles — assistant, designer_3d, coder

These three are the profiles an ordinary user gets. They exist to keep a household user inside the Navi server's own working directory and out of the rest of the owner's infrastructure, while still being useful for everyday work. All three carry the same native tool set — they differ in system prompt, model and MCP groups.

Deliberate omissions, none of which appears in any of the three (or in their tools.subagent, or in their MCP groups):

Tool Why it is withheld
ssh_exec Reaches any host, takes a password inline, host-key checking off.
peer Runs an agent on a neighbouring machine, where PEER_ASK_PROFILE=server_admin.
reload_tools, create_mcp_server Execute arbitrary code on the Navi host.
test_mcp_tool Calls any tool of any server, bypassing the profile's MCP groups — it would undo both the group limits and the BYOK rule.

One tool goes the other way: image_view is granted to all three, so the 3D designer can inspect the renders mcp__navi-3d__render_stl produces instead of shipping geometry it has never looked at. It reads any absolute path and fetches any http(s) URL, subject to a raster-only extension filter (.jpg, .jpeg, .png, .gif, .webp, .bmp) — it cannot read .env or any other text file, but it can read any image on the host. See the residual risk below; the boundary instruction in the three system prompts ("pass only session paths and the paths the 3D server returned") is a prompt rule, not a code check.

MCP groups are all read-only or local:

  • assistant — navi-web (search, browse), navi_ui (ui), and read-only use of the owner's knowledge and tracking servers: gnexus-book (read), gntodo (read), synapse (read), hard-panel (read).
  • designer_3d — navi-3d (modeling, analysis), navi-web (search, browse), navi_ui (ui).
  • coder — navi-web (search, browse), gnexus-book (read), gntodo (read).

The navi-web request group (http_request — arbitrary method, headers and body, issued from the Navi host) is given to none of them, and gnexus-creds and tgclient are not given at all, not even read-only.

Because MCP servers with a user_key slot require the user's own credential, those groups only take effect once the user has saved a key in Settings → MCP keys; without one the server is not offered to them at all. navi-3d, navi-web and navi_ui have no slot and are always available.

Two consequences worth knowing:

  • switch_profile and spawn_agent are in all three profiles, and none of the three is admin-only — so within a user session the agent can move between them, and the native tool surface a user actually has is the union of the three. The native sets are kept identical for exactly that reason.
  • The reaction dispatcher routes a Synapse event into one of the profiles its triggering user may use, so a regular user's reactions run in one of these three.

Accepted residual risk

Known and accepted by the owner (2026-10-09). These are recorded so that nobody reads the profile list above as a stronger guarantee than it is:

  • The working directory is not a sandbox. terminal and code_exec run as the Navi process; python3 script.py from a restricted profile can read /home/ubuntu/navi-1/.env. The only real defence is the model obeying the prompt.
  • Five dangerous tools stay unguarded in code. ssh_exec, peer, reload_tools, create_mcp_server and test_mcp_tool still check no role; they are withheld from the restricted profiles by composition alone. A model that talks itself into "remembering" one, or finds its description through tool_manual, still cannot call it — it is not in the list.
  • image_view is granted deliberately, and is the one file reader with no path check. It accepts any absolute path or URL whose extension is a raster image type, so it cannot read .env, source or logs, but it can read any image file on the host. It is in all three sets because the 3D designer needs to see its own renders, and because switch_profile would hand it to the other two regardless. The prompt asks for session paths only; nothing enforces that.
  • gmail is the exception, and is not covered by composition. The global file tools/enabled.json is merged into every profile's tool list by build_tool_list (navi/core/tool_utils.py), so gmail — Navi's own mailbox, not a personal one — is reachable from all three restricted profiles regardless of their tools.agent.native. The owner accepted this as no risk.
  • switch_profile gives the union of the three sets, as described above.
  • test_mcp_tool is why BYOK and the MCP groups hold only for profiles that lack it. Its absence from the restricted profiles is load-bearing.
  • user_data/ is tracked in git and holds files that were not meant to be distributed; the owner deferred cleanup.

navi_code

Terminal-first local coding assistant. Designed for the Navi Code CLI and single-user local deployments:

  • Native tools: todo, scratchpad, reflect, plan, switch_profile, list_profiles, filesystem, code_exec, terminal, image_view, ssh_exec, memory, list_tools, tool_manual, spawn_agent, schedule_recall, manage_recall.
  • MCP tools: navi-web (search, browse, request) — web lookup and page browsing.
  • Excluded: share_file, content_publish, gmail.
  • Planning: Phase 1 and Phase 3 enabled; the plan tool is in the native tool set, so the agent plans deliberately when a task warrants it. Phase 1 classifies COMPLEXITY: simple | medium | complex — complex tasks get "present the plan and wait for confirmation".
  • Bounded autonomy: scope_boundary_enabled is true — the agent stays within the requested scope (does not climb to sibling projects or execute discovered milestone/TODO docs). Flip it off to reproduce the legacy "free flight" behavior.
  • Safety: an authoritative backend permission gate for destructive tool calls is specified in permissions.md (designed, not yet implemented). The old prompt-level "confirm before destructive ops" nudge and the client-side permission dialog were removed.

Use it with NAVI_DEFAULT_PROFILE_ID=navi_code so POST /sessions without a profile_id creates a navi_code session automatically. See docs/navi_code.md for the full local-terminal setup.


System prompt construction

The LLM sees (injected fresh on every call, never stored in session):

{persona.txt content}

---

{profile system_prompt.txt content}

persona.txt — global layer: personality, CONTEXT FIRST principle, self-extension rules, scratchpad/todo/memory instructions, delegation rules.

system_prompt.txt — domain layer: tool priorities, workflow, safety rules for this profile.


Adding a profile

  1. Create directory navi/profiles/my_profile/
  2. Add config.json (minimal example):
    {
    "id": "my_profile",
    "name": "My Profile",
    "description": "...",
    "short_description": "...",
    "model": ["gemma4:31b-cloud", "gemma4:26b-a4b-it-q4_K_M"],
    "temperature": 0.5,
    "max_iterations": 20,
    "tools": {
     "agent": {
       "native": ["todo", "scratchpad", "plan", "filesystem", "terminal"],
       "mcp": {
         "navi-web": ["search"]
       }
     },
     "subagent": {
       "native": ["todo", "filesystem", "terminal"],
       "mcp": {}
     }
    },
    "planning_phase1_enabled": true,
    "planning_phase3_enabled": true,
    "think_enabled": true,
    "iteration_budget_enabled": true,
    "goal_anchoring_enabled": true,
    "goal_anchoring_interval": 5,
    "anti_stall_enabled": true,
    "anti_stall_threshold": 8,
    "step_validation_enabled": false,
    "scope_boundary_enabled": false,
    "subagent_planning_enabled": false
    }
  3. Add system_prompt.txt with domain-specific instructions.
  4. Optionally add subagent_system_prompt.txt.
  5. The profile is auto-discovered at startup — no registration needed.
  6. Decide who may use it. Anything that touches the host or the owner's infrastructure beyond the working directory gets "is_admin_only": true (see the field table above); a profile without it is offered to every account with role user.

A prompt that documents a tool with tool_manual("<name>") or a manuals/<name>.md path must name something that exists — tests/unit/tools/test_manual_drift.py walks navi/profiles/**/*.txt and fails on a citation that resolves to nothing.


Profile switching

switch_profile tool repoints the session with one narrow UPDATE sessions SET profile_id (PgSessionStore.set_profile() — never a full save(), which would hand out sequence numbers the running turn is still claiming). After each tool execution batch, run_stream() compares the stored profile against the profile the run is bound to — not against session.profile_id, since the store's write leaves those two equal again — and reloads profile + tools when they differ. Takes effect on the next LLM call. For the same reason save() leaves the profile_id column alone on conflict: a mid-run save carries the profile the run started with and must not write a switch back.

Rules (in persona): don't switch for a single off-topic question; switch when the domain clearly changes; never switch back and forth repeatedly.