| 2026-11-01 |

llm: routes that name a host, a backup and a window, and a profile that reads them
...
`llm.json` replaces `OLLAMA_BACKENDS_FILE`. It has targets (host and key
environment variable) and routes, where a route is an ordered chain of
`model @ target` entries, each with its own `context_window` and options. The
`default` route now starts on `glm-5.3-flash:cloud` and backs it with
`qwen3.6:35b-128k` on the home host, both at 131072 tokens, so a failover no
longer changes the budget the compressor and `num_ctx` are accounted against.
Embeddings get their own `embedding` route instead of a pinned host.
The engine classifies a failure (network, quota, auth, server, missing model),
cools that entry and moves the call down the chain; a background probe watches
only degraded targets and lets the next turn return to the primary once it is
healthy — it never interrupts a running turn. A profile joins a route with
`llm_route`, and the loader now actually reads it: `llm_route` and
`context_window` were declared on `AgentProfile` and written into every
`config.json` while the loader skipped both, the same silent no-op that
`is_admin_only` used to be, so every profile ran the legacy model list at the
global window. The writer emits them too, or an admin edit would erase the
route from disk.
The turn's resolved entry travels to the UI: the first chunk is stamped with
model and target, `model_info` carries `fallback` when a backup answered, and
the chat header shows a chip only then. `serving_primary` compares the target
as well as the model name, because a route may run one model on two hosts.
`/health` gains an `llm` block with the active entry, target states and missing
models.
`deploy/ollama/` keeps the local model's definition honest: MTP off (slower at
every context on this host), `presence_penalty 0` (the base model's 1.5 breaks
agentic tool calls), and `num_gpu 40` — Ollama's automatic fit claims a 128k
f16 slot fits and then dies mid-prefill with CUDA OOM; 40 of 42 layers leaves
~400 MiB and decodes at ~77 tok/s, 38 layers collapses to 48, and 39 OOMs.
q8_0 KV measured 37 tok/s against 70 on f16 here, so the cache stays f16.
Eugene Sukhodolskiy
committed
1 day ago
|
| 2026-10-12 |

designer_3d: the sub-agent gets what the prompt promises it, and weather goes
...
The profile prompt tells a sub-agent it may use web search, page reading,
filesystem reads and image viewing, and that it must not write, publish,
share, run commands, update todos or call the modelling tools. The declared
sub-agent set said otherwise on both counts: it carried terminal, code_exec,
share_file, content_publish, todo and the whole navi-3d server, and it lacked
the image_view the prompt grants. It is now scratchpad, reflect, filesystem,
image_view, memory, list_tools, tool_manual plus navi-web (search, browse).
weather leaves designer_3d. It was declared in the profile and also merged
into every profile from tools/enabled.json, so both had to go; assistant and
secretary declare it themselves and keep it, and the admin-only profiles that
never asked for it lose it too. Since switch_profile unions the two restricted
profiles, this trims the 3D agent's schema rather than closing a door.
The agent's own set keeps terminal, code_exec, share_file and tasks — the
owner reports real use for them.
Eugene Sukhodolskiy
committed
21 days ago
|
| 2026-10-09 |

profiles: give the restricted profiles image_view
...
The 3D designer renders PNG previews with mcp__navi-3d__render_stl and then
had no way to look at them: image_view was withheld from all three
restricted profiles, and designer_3d's system prompt said so outright
("You cannot look at the render yourself"). It shipped geometry it had
never seen, with only lint/compile output behind it.
Add image_view to all three rather than to designer_3d alone. The three
sets are kept identical precisely because switch_profile lets a session
move between them, so a restricted session can reach image_view from any
of the three anyway; putting it in one profile would have made the
restriction look like a boundary it is not.
designer_3d now has to call it on every preview the renderer returned
(one angle is not enough), fix what it sees, and treat the visual check
as an addition to the mechanical ones rather than a replacement. The
prompt also asks for session paths only — image_view itself accepts any
image path or URL, so that part is a rule, not a check. Sub-agents keep
the tool out of their set: they gather facts, they never render.
docs/profiles.md loses the image_view omission row and gains the grant,
its raster-only limit and the accepted residual risk.
Eugene Sukhodolskiy
committed
24 days ago
|

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
25 days ago
|