All posts

announcement / release

Sophon 2.1: Model Routing, Local-First Semantic Memory, a Coding Agent in the CLI, and Plan Recovery

Sophon Team

Sophon 2.1 is about putting work in the right place. You can now choose which model handles each job, so a frontier model can run the conversation while cheaper or local models handle sub-agents, research and background chores. Every install gets a vector store for semantic memory, and Sophon prefers an embedder on your own machine; no text goes to a new cloud embedder until an admin agrees. Run sophon inside a repository and the CLI becomes a coding agent bound to that folder, with diff approvals, permission modes and an undo journal. And a plan that stops, whether from a restart, a time limit, a budget or a failed step, now tells you why and can be resumed, retried or continued.

Model Routing: The Right Model for Each Job

Every model call in Sophon is now routed as one of five roles: the orchestrator (the chat turn itself), the executor (sub-agents and plan steps that write or run things), the researcher (read-only sub-agents and steps, and document Q&A), utility (titles, compaction, memory flushes and other chores) and vision (image captions, video frames and OCR). For each chat you choose one of three modes:

Default keeps the single-model behavior of 2.0, and upgraded chats stay on it. A few things changed underneath (see Upgrading below).

Auto picks a model for each role using a profile: Balanced (the default), Quality, Economy, Local-first or Local-only.

Pinned uses the provider and model you choose.

Auto only considers models you have listed under Models, plus each provider's default model. The orchestrator, executor and researcher roles need a model with tool calling, vision needs a vision-capable model, and each role has a minimum context window. Among the models that qualify, Auto ranks by tier first. Price only breaks ties, so Auto is not a cost optimizer. Cost comes in through the profile you pick (Economy keeps the orchestrator on your default model and puts every other role on the fast tier) and through your spending budgets. A local model needs its context window declared. Until it is, Sophon counts it as 8,192 tokens and keeps it off the tool roles. Each local provider serves one request at a time by default.

Routing can be set for a chat, for an agent, as your own default, or by an admin as the host default. A setting closer to the chat wins, and a role override beats a pin at the same level. To see which model each role will use, and which setting decided it, ask Sophon to explain:

sophon models discover ollama-7f3c2a9b1d4e5f6a7 --add qwen3:8b,qwen3:32b
sophon models context ollama-7f3c2a9b1d4e5f6a7 qwen3:8b 32768
sophon models roles --profile local-first      # admin: Auto · Local-first for this host
sophon models resolve --session <session-id>   # which model each role uses, and why

In a chat, /model shows the routing table and changes it for that conversation:

/model                                   # mode, Allow fallback, one row per role
/model auto economy                      # Auto with the Economy profile
/model role researcher ollama-7f3c2a9b1d4e5f6a7:qwen3:8b
/model anthropic-3f2a9c1b7d4e5f:claude-sonnet-4-6   # pin this chat
/model fallback on                       # let this chat fall back when the pin fails
/model save                              # make this chat's routing your default

Pins are honest now. A pinned model never moves to another provider unless you turn on Allow fallback, and it is off unless the chat, the agent, your default or the host turns it on. Without it, Sophon retries on the same provider and then fails the turn with a message that says it did not switch. With fallback allowed, or on an unpinned route, only temporary failures fail over: rate limits, overloads, timeouts, server errors, budget stops and context overflows. Authentication errors and bad requests fail the turn, because another provider would not fix them. Failover tries the same model on another provider of the same type, then an equivalent model at the same tier. It never lands on some other provider's default model, and a reply that fell back says so.

Local-only is a ceiling. With Auto · Local-only or a local pin without fallback, no model call made for that chat goes to a cloud provider, including its sub-agents, compaction and image captions. An admin's host-wide Local only applies to every chat, and nothing falls back to the cloud even with Allow fallback on. Three limits: neither setting changes the embedder, and memories are embedded later by the host embedder, so pick a local embedder too (next section); chores not tied to one chat, such as memory consolidation and Insights, follow your own and the host's routing; and queued document jobs such as captions and OCR run under your own routing, so a chat's local setting doesn't reach them. The Dashboard, the CLI, the REST API and SignalR all have routing controls; Sophon Mobile doesn't yet. See Model Routing.

Semantic Memory on Every Install

Installs without Qdrant, which includes Personal, native and Desktop installs, now come with a vector store. When no Qdrant endpoint is configured, Sophon keeps vectors in an embedded SQLite file in the data directory. There's nothing to install or run. Semantic recall still needs an embedder. Sophon looks for one on your own machine first: an Ollama or LM Studio server with an embedding model, or a local OpenAI-compatible server. A working local embedder is preferred over a cloud provider with a key. If Ollama is configured but has no embedding model, the Dashboard offers a one-click install of nomic-embed-text, about 274 MB, which stays on your machine:

sophon models embeddings install-local --wait   # pull nomic-embed-text through your Ollama provider
sophon models embeddings local                  # optional: never use a cloud embedder
sophon models embeddings status

A cloud embedder asks first. One that hasn't produced vectors yet embeds nothing until an admin consents. Before consenting, the admin sees how many memories, daily logs and documents would be sent. Its first full index also needs a confirmed start. One exception on upgrade: a 2.0 Qdrant install that was already embedding with Sophon Forge or OpenAI keeps that embedder, and it counts as consented. A local-only chat never calls a cloud embedder itself: its recall falls back to keyword search, and its memories are embedded later by the host embedder, which may be a cloud one.

The choice is sticky. While its provider is still there and active, Sophon doesn't switch embedders by itself; when a better local one appears, it offers it as a suggestion. Each embedder writes to its own collections, so switching never mixes or deletes vectors, and switching back reuses the old index. A background reindex fills in what's missing, such as a new embedder's index, documents processed while embeddings were down, or memory written during an outage.

Embeddings no longer take chat down with them. A failing or slow embedder or vector store doesn't fail chat turns, document search or memory search. After 8 seconds, search falls back to keyword results, and the status in the Dashboard and CLI says what actually works instead of showing a green light.

The limits: the SQLite store is single-node and scans vectors directly, up to the newest 200,000 points. Above about 100,000 points, or with more than one Gateway replica, use Qdrant. The vector file is a cache. Backups leave it out, and it is rebuilt after a restore, at the provider's price for a cloud embedder. Embedding calls count against your provider budgets and appear in Usage. See Embeddings and Vector Stores.

The CLI as a Coding Agent

Run sophon in a repository and the chat is bound to that folder: the nearest folder at or above where you are that holds .git, otherwise the current folder. The first time, the CLI asks whether to trust it. A trusted folder loads the project's AGENTS.md (or CLAUDE.md), sends the git branch and changed files with each turn, and allows accept-edits and remembered commands. Binding only works on the Gateway's own machine: the CLI must run as the same OS user and connect through localhost. Remote Gateways and Gateways in containers are refused. To opt out, set SOPHON_CLI_WORKSPACE=0 or "workspaceBinding": false in cli.json.

In a bound chat, every edit, file change and shell command asks first, whatever your approval threshold. Edit cards show the diff and shell cards show the command. Press A to approve, F to reject with a note the agent keeps working from, or R to reject and end the turn. Where Sophon offers it, 2 approves and always allows that kind of call for the session. File tools stay inside the folder, and .git and credential files are off limits. The agent has new editing tools: find-and-replace edits that must match one place in the file, multi-edits, patches for OpenAI-family models, and a regex search.

Each chat has a permission mode. Ask before edits is the default. Accept edits, available in a trusted folder, lets file edits inside it through without a card. It never covers shell commands, deletes, moves, or execution-sensitive files: package manifests and lockfiles, CI and build files, git hooks, IDE settings and agent instruction files such as AGENTS.md. Those still ask. Plan mode is read-only. ⇧⇥ cycles the modes you're offered (the pinned view has only /permissions), and /permissions sets them.

Every write by the edit tools is journalled first, so you can review and undo it:

/permissions accept-edits     # or ⇧⇥
/diff                         # everything this session changed
/diff turn src/Parser.cs      # one file, this turn only
/rewind                       # undo the latest checkpoint (shows the diff, asks first)
/rewind 3                     # three checkpoints back (up to 50)
/init                         # ask the agent to write AGENTS.md (arrives as an approval card)

/rewind restores files. It doesn't rewind the conversation. In the full-screen chat, Esc on an empty composer stops a turn and keeps the work that already finished.

In plan mode, only read-only calls run, for the agent and every sub-agent under it. A write it tries anyway is refused, with no card. The agent proposes a plan as a card with its goal, steps, files and risks. From the card you can run it here (optionally switching to accept edits), run it as a graph, edit it, or send it back with feedback. Only the chat's owner, from the CLI or the owner's Dashboard on Pro and Enterprise, can approve a plan that ends plan mode. The Personal Dashboard can only reject one. Plan mode isn't a network control: web search and fetch, GET requests and sub-agents still run. The agent can also keep a checklist of its own. In the full-screen chat it shows as a band you fold with ⌃T, and /todos prints it in the other views.

Sub-agents are now visible while they work. Each child reports its current tool, its last line of output, changed files, tokens and cost, and whether it is waiting on an approval. /subagents lists them, /subagents watch 2 opens one live in the full-screen chat, and /subagents stop 2 stops it while the parent turn goes on. The Dashboard has the same tree with Stop.

Be clear about the limits. The shell is a limit, not a sandbox: a command runs in the folder with a filtered environment, but it can still cd out. Every MCP call in a bound chat asks. The external coding agents (Claude Code, Codex and Copilot through Sophon) are confined only by their working directory. Once approved, they run their own tools, and their edits are not journalled, so /diff and /rewind don't see them. See Coding Agent.

Headless Runs for Scripts and CI

sophon -p (short for sophon exec) runs one prompt with no person present. It prints plain text, a single JSON result, or a stream of NDJSON events, and exits with a code your script can check:

sophon -p "run the tests and fix the failing one" \
  --trust-workspace --permission-mode acceptEdits \
  --allow "os.execute:dotnet test" \
  --output-format stream-json --timeout 900
echo "exit: $?"   # 0 success, 1 failed/timeout/connection error/usage, 2 denied, 130 interrupted

A headless run never approves anything on its own. Every approval card is rejected, and every question is skipped. Anything you want to allow has to be given up front. --trust-workspace is the only way to trust the folder for the run; the folders you trusted interactively don't count. --allow then creates rules before the first turn: a read-only network tool such as web.fetch, an edit tool, or a command prefix of at most three words such as os.execute:dotnet test. A covered command runs that exact program, without a shell. Shells, interpreters, curl, docker, git push, npm install and similar commands can never be remembered, and chained commands always ask. Rules last 8 hours by default. Once a session reads untrusted content, its network and command rules stop applying. Accept edits needs a folder Sophon can undo changes in (a git repository or the edit journal), or the run doesn't start. Be aware that with accept edits and a test command allowed, code the agent wrote runs without a card when the tests run.

With --permission-mode plan, the plan is printed instead of run, and the run exits 0. sophon -p "…" -c continues this repository's last session. In a chat, the same rules appear as "always allow" on approval cards, and /permissions rules lists them. See Headless runs.

Plan Recovery

In 2.0, a chat plan that stopped partway could only be resumed after a crash, and only from the Dashboard. In 2.1, Sophon records why every Graph-mode chat plan stopped and offers the action that fits.

A graceful Gateway stop now pauses running plans instead of cancelling them, and so does reaching the turn time limit (2 hours by default). Resume picks the plan up where it stopped. A step that was running starts again from the beginning, and it usually doesn't use up one of its retries. A plan that failed, was stopped, or finished with failed steps offers Retry failed steps. A plan stopped by its budget offers Continue with a new budget. In 2.0 that showed as failed with no way forward. Both start a new run that keeps every completed step and runs only the unfinished work. The Dashboard shows a preview first: the steps that run again, the steps that are kept, and a warning when a step may have side effects.

The CLI prints why a plan stopped and the command to run next:

Plan stopped at its budget: $2.10 spent (limit $2.00). /retry graphrun_1a2b3c4d continues it with a new budget.
> /retry graphrun_1a2b3c4d

/retry resumes, retries or continues, depending on the plan's state. /plan shows the session's latest plan after you reopen a chat, and r retries it from the full-screen overlay. Outside the chat, sophon plans gains list, show, resume and retry, and cancel now also stops a paused plan:

sophon plans list --status Interrupted
sophon plans retry graphrun_1a2b3c4d

You can also just ask, "retry the plan". The agent's new plan.recover tool always raises a Critical-risk approval card that auto-approve never covers. What it doesn't cover: Classic plans and sub-agent delegation graphs, and a Stop, a newer message, or a scheduled, heartbeat or webhook run still cancels rather than pauses. Sophon Mobile shows a paused plan but has no Resume or Retry buttons, and plan recovery supports a single Gateway instance. See Plan recovery.

Knowing What a Turn Costs

The full-screen CLI's footer now shows how full the context is, what the session has cost so far, the model that answered last, and the workspace and branch. Context turns amber at 80% and red at 95%. The new /context command prints the same details (context, tokens, cost, model, routing, mode and workspace) in every CLI view, including the classic and pinned prompts, which have no footer. Chat events also carry each turn's tokens, cost and context size for API clients.

The cost covers this session's own turns. Child agents and background calls such as compaction and titles aren't included. Token usage for a run now adds up every model call in the turn instead of keeping only the last one, so session stats and Insights totals will rise after the upgrade, and delegation graphs reach their token limit sooner. See Interactive Chat.

Smaller Changes and Fixes

Claude Code Stays Logged In

When the Gateway refreshes a Claude Code token, or you log in to Claude from the Dashboard, it writes Claude Code's own credential store, and those writes no longer log Claude Code out. A refresh changes only the token fields. The file is written atomically, keeps its permissions (a new file is owner-only), and the macOS Keychain write is fixed, though no automated test runs that path on a Mac. If an older Gateway already broke your macOS Keychain entry, log in once more from the Dashboard or with /login in Claude Code. In Container mode on Linux, a credential file the Gateway creates is owner-only, so the Claude Code runner's user needs read access granted once. See Subscriptions and Claude Code.

Security Hardening

Browser origin check. Browser requests to the hubs, the MCP endpoint and any API write must come from the Gateway's own origin or one listed in Sophon:Security:AllowedOrigins, otherwise they get 403. The CLI, Sophon Node and scripts send no Origin and are unaffected.

Personal no-login access is tighter. On the Gateway's own machine, opening it by hostname or LAN IP instead of localhost no longer grants the automatic Admin unless that name is in Sophon:Security:PersonalHosts. A request that a reverse proxy forwards no longer counts as local just because the proxy runs on the same machine; behind a known proxy, the forwarded client address decides. A proxy that adds no forwarding headers is still not detected.

Ownership checks. Approval decisions, task cancellation and every session route now check that the session belongs to you. Sub-agent sessions are read-only.

Shell commands. os.execute now kills the whole process tree on a timeout or cancel, and removes keys, tokens and Sophon configuration secrets from the environment it passes on. A secret under an unusual name can still get through.

Sandbox presets that work. The sandbox capability check looked up the wrong tool name, so every preset allowed os.execute_sandboxed. The standard, automation and hardened presets now deny it. os.execute now needs the process-execution capability, so the standard preset and the hardened preset used by workflows deny it.

Paths and build tools. UNC and device paths are caught after normalization. code.build and code.test validate their arguments and run without a shell. The Claude Code, Codex and Copilot tools the agent calls must work inside their configured workspace root.

Previews you can trust. Hidden characters in diffs and approval previews (bidi overrides, zero-width and control characters) are shown as visible markers in the CLI and Dashboard, with a count. Plan text never loads remote images.

Backups. Backups leave out the edit journal and the vector cache, and restores refuse entries that would land outside the data directory.

Dependencies. Scriban, MimeKit, SharpCompress, AngleSharp, the SignalR client and System.Security.Cryptography.Pkcs are updated to patched versions. ImageSharp stays on 3.1.12, because newer versions need a commercial license key. Its six known advisories are suppressed in the build, and that remains a known residual risk.

Fixes

sophon claude-code send now sends the message the Gateway expects, and gains --working-directory and --model. The working directory is passed as given and is not confined to the workspace root. Its help no longer calls Claude Code "sandboxed"; unless coding agents run in Container mode, the Gateway runs Claude Code on its host with permission prompts bypassed.

code.git diffs no longer repeat their file headers, and the diff tool's patch apply checks every line against the file and writes nothing on a mismatch.

Azure OpenAI sends each model to its own deployment. Anthropic, Claude and Claude Code providers run their configured Default model instead of a fixed one.

A provider is marked as failing only after two failed health checks in a row, and saving providers on Windows no longer fails when antivirus or the indexer briefly locks the file.

The planner's trigger words now match whole words, so "authentication" no longer counts as "then". An admin can set how eagerly Sophon plans with Sophon:AgentExecution:PlanningMode.

The Dashboard chat renders only the newest 200 entries until you scroll up, so long chats stay fast. The composer stays blocked while any approval is unanswered.

The full-screen CLI detects true color on Windows Terminal and falls back to 256 colors where needed. Resuming a session shows only approvals that are still live.

Upgrading

The 2.1 Gateway applies four database migrations at first start. They only add columns and one table, but there is no automatic downgrade path, so stop the Gateway and back up first. Upgrade the Gateway and its Dashboard image together, then the CLI, then Mobile. Do not downgrade to 2.0 while any plan is paused by a restart or the turn time limit. Turn pausing off, then resume or cancel those plans first; the upgrade guide has the steps.

Most installs need nothing else. Check these before you upgrade:

Reverse proxies and host names. Proxies must forward the Host header with its port (nginx $http_host, not $host), and the Dashboard image must be rebuilt or re-pulled. A Dashboard served from another origin goes in Sophon:Security:AllowedOrigins.

Qdrant. An endpoint with no port now means the gRPC port 6334, and collections are named per embedder. Existing 2.0 collections are aliased on first start, not deleted.

Vector store and embedders. Installs without Qdrant get the embedded SQLite store. Set Sophon:Vectors:Provider=none to stay keyword-only. A new cloud embedder waits for an admin's consent.

Model routing. Chats stay on Default, but there is now exactly one default provider, and it beats Priority. Anthropic and Claude providers run their configured Default model, so check it. A pinned model no longer falls back silently.

Automation. Workflow runs (the hardened sandbox preset by default) and anything set to the standard preset lose os.execute unless an admin allows the process-execution capability, and delegation graphs reach their token limit sooner.

Integrations. SessionModelOverrideChanged is replaced by SessionModelRoutingChanged. A budget stop arrives as plan_interrupted, and /api/graph-runs refusals now return { error, message } with a stable code.

Full steps are in Upgrading to v2.1.0.

Get Sophon 2.1

Installers are on the downloads page, new installs start at Installation, and the complete list of changes is in the changelog. Then try the change you'll notice first: open a terminal in one of your repositories, run sophon, and trust the folder.