Skip to Content
Sophon Docs
Command Line

Coding Agent

Run `sophon` in a repository and work with a coding agent bound to that folder. Covers trust, the tools it uses, diff approvals, permission modes, standing rules, /diff and /rewind, plan mode, the checklist, sub-agents and the limits.

Since v2.1.0, running sophon inside a repository gives you a coding agent bound to that folder. You see every edit as a diff before it is written and every shell command before it runs. You choose how much it may do without asking, review what it changed with /diff, and undo its edits with /rewind.

This page is about Sophon's own agent in the CLI chat. It is a different thing from delegating a task to an external coding CLI such as Claude Code, Codex or Copilot. Those tools run their own file and shell tools, and their edits are not recorded by Sophon (see Limits).

The coding agent needs a v2.1.0 Gateway on the same machine as the CLI, running as the same OS user, reached through a loopback URL such as http://localhost:8470 (the native installer's address). A remote Gateway, or a Gateway in a Docker container, cannot bind your folder. The chat still works there, but without a workspace. Upgrade the Gateway before the CLI.

Start a session in a repository

1. Run sophon in the repository

cd ~/src/my-service
sophon

The CLI binds the chat session to the workspace root: the nearest folder at or above where you are that holds .git. It never climbs to your home directory or a drive root. With no .git above you, the root is the current folder.

2. Decide whether to trust the folder

The first time you run in a folder, the session binds untrusted and the CLI asks once:

Trust files in /home/me/src/my-service? Trusting loads AGENTS.md/CLAUDE.md,
runs git for status, and allows accept-edits and remembered commands.
KeyDoes
yTrust it. The root is saved to trustedWorkspaces in ~/.sophon/config/cli.json and the session rebinds trusted.
nKeep it untrusted. The root is saved to untrustedWorkspaces, and every change asks.
qDon't bind anything for the rest of this run.
EscDecide later. The session stays bound and untrusted.

The keys only count after a short delay and a pause in typing, so text you typed ahead cannot answer the question.

3. Ask for something

Type a request as you would in any chat. The workspace shows in the header as workspace my-service · main, and on the full-screen context line above the input as ~/src/my-service [⎇ main] (the branch shows only when the folder is trusted). Since v2.2.0 the context line also marks uncommitted changes with * and shows the branch's open pull request, as in [⎇ main*] [#42], when the Gateway serves workspace git status. Edits and commands arrive as approval cards, described below.

Workspace binding and trust

Binding is on by default once the Gateway serves the coding tools. Two settings turn it off:

# For one run. Wins over cli.json.
SOPHON_CLI_WORKSPACE=0 sophon
// ~/.sophon/config/cli.json, permanently
{
  "workspaceBinding": false
}

SOPHON_CLI_WORKSPACE=1 (or true) turns it on for a run. When either setting turned binding off, /trust on cannot override it in that run. Change the setting and start the CLI again.

/trust manages the folder from inside the chat:

CommandDoes
/trust or /trust statusShow the workspace, its root, its trust and what cli.json says about it
/trust onBind this folder trusted and save it to trustedWorkspaces
/trust offBind it untrusted and save the decline to untrustedWorkspaces
/trust unbindUnbind, and bind nothing for the rest of this run

/trust on also works when binding is off only because the Gateway lacks the coding tools, or after q or /trust unbind.

What trust changes. Only a trusted binding:

  • gives the agent your project instructions: AGENTS.md, or else CLAUDE.md, from the workspace root only. Nested files and @imports are not read. The agent sees the first 32 KB, and a file over 1 MiB loads nothing. The CLI prints Loaded AGENTS.md (42 lines, sha256 …) the first time it loads, and a notice when the file changes. The injection guard can refuse the file or load it with a warning;
  • sends the agent the git branch, HEAD and up to 40 changed paths from git status every turn;
  • allows accept edits, always-allow rules and /rewind.

An untrusted session is still bound: its file tools are confined to the root, and every change asks.

Same host only. To bind, the CLI proves it runs on the Gateway's machine as the Gateway's OS user by reading a single-use challenge file the Gateway writes. A refusal prints once per run and the session stays unbound. The Dashboard shows the binding as a chip and can unbind it, but it can never create one.

What a bound session gives up. It runs no automatic planner (no planner-made plan cards or graph runs; the agent can still propose a plan in plan mode), and it refuses cron and webhook dispatch.

What the agent uses

A bound turn uses the coding tool set. Its core is:

AreaTools
Read and searchfs.read, fs.list, fs.find, fs.search
Editfs.edit, fs.multi_edit, fs.write, or fs.apply_patch (see below)
Runos.execute, code.git
Plan and trackplan.propose, plan.recover, todo.write
Delegateagent.delegate, sessions.yield
Otherinfo.request, memory.search, memory.write, web.search, datetime.now

The edit tools follow the model family. OpenAI models (including Azure OpenAI, and GPT and o-series models through GitHub Copilot) get fs.apply_patch, which takes a *** Begin Patch … *** End Patch patch across up to 100 files. Every other family gets fs.edit and fs.multi_edit:

  • fs.edit replaces old_string with new_string. The match must be unique. When there is no exact match, a unique match that ignores trailing whitespace, repeated spaces or indentation is used. replace_all: true replaces every exact match. The file keeps its encoding and line endings.
  • fs.multi_edit applies 1 to 100 such edits to one file, in order.
  • fs.search is a regex search (.NET syntax, without backreferences, lookarounds, atomic groups or conditionals) with files_with_matches, content and count modes, up to 5 context lines, and a head_limit from 1 to 1000 (default 100). It skips binary files, links and files over 2 MiB.

Confinement. In a bound session the file tools resolve every path inside the root. Paths outside it, .git, and credential files such as .env are refused. Files over 32 MiB are not read. fs.find hides git-ignored paths and node_modules, bin, obj, dist and .venv.

The shell. A bound os.execute starts inside the root with an allowlisted environment, using cmd /d or PowerShell -NoProfile -NonInteractive on Windows. When the model sets no timeout, a bound command gets 120 seconds, and it can ask for up to 1,800 (unbound: 30 and 300). Output streams live, redacted, up to 1 MiB per call. Secrets such as provider keys, GITHUB_TOKEN and *_TOKEN / *_SECRET / *_API_KEY variables are removed from the environment. Cancelling kills the whole process tree.

The working directory is a limit, not a sandbox. An approved command can still cd out of the root and reach anything the Gateway's OS user can. Read each command before you approve it.

Approving edits and commands

In a bound session every edit, file change and shell command asks, whatever your auto-approve threshold, unless accept edits or a standing rule covers it. An edit card shows the diff (src/Http/RetryPolicy.cs +3 −1, then numbered rows). A shell card shows the command ($ dotnet test) with its folder, shell and timeout.

KeyDoes
A, Y, 1Approve once
2Approve and always allow, when the Gateway offers a rule for this card
FReject with feedback. You type what to do instead and the agent continues with your note.
R, NReject. This ends the turn.
VOpen the full preview
EEdit the call in $EDITOR, where the tool allows it
SSkip, and let the server's timeout decide
EscOn an empty composer: stop the turn (full-screen)

These keys are for the full-screen and classic chat. The pinned surface keeps A, R, E and S only.

  • Cut previews. When a preview was too large to show on the card (a diff over 128 KiB in all, over 400 lines or 64 KiB in one file, or an input over 2,048 characters), press V and read it to the end, then press A twice within 3 seconds. This gate is in the CLI and the Dashboard chat. Mobile, voice, channel replies and REST approve from a summary, so review large changes in the CLI or the Dashboard chat.
  • Stale cards. If a file changes after its card was built, the write is refused with STALE_APPROVAL even when you approved it, and the agent has to re-read the file and make the edit again.
  • Stopping. Esc on an empty composer (full-screen) or Ctrl+C stops the turn. Edits that already finished are kept, and the next turn sees them.

Permission modes

Each chat session has a permission mode. A change applies from the next tool call.

ModeWhat runs without a card
ask before edits (default)Nothing that changes files or runs commands, unless a standing rule covers it
accept edits (acceptEdits)File edits inside the trusted, bound workspace. Commands still ask.
plan mode (plan)Read-only. The agent investigates and proposes a plan before it changes anything.
allow all tools (allowAll)Every tool call, critical ones included, until the time limit ends or you leave it. Since v2.2.0, and only where the host turned it on; see Allow all tools.

Changing mode.

  • Full-screen chat: ⇧⇥ (Shift+Tab) steps through ask before edits, accept edits and plan mode, over the modes the session offers. The footer's first chip shows the mode: Interactive, Accept Edits, Plan or Allow All until 14:30.
  • Classic chat: ⇧⇥ changes the mode and the prompt shows [accept edits] ❯, [plan] ❯ or [allow all] ❯.
  • Any surface: /permissions, which opens a dialog when typed bare.

⇧⇥ never turns allow all on, and from allow all it goes back to ask before edits. It only cycles through the modes that are available. Accept edits needs a bound, trusted workspace, so in an untrusted folder the cycle is just ask before edits and plan mode. The CLI says why a mode is out of reach, for example accept edits: needs a bound workspace.

CommandDoes
/permissionsOpen the permissions dialog: mode, approval policy, plan approval and standing rules. Where no dialog can be shown, print the report
/permissions showPrint the report: the mode, why each other mode is or isn't available, the auto-approve reach and what it never covers, and the standing rules
/permissions default, accept-edits or planSet the mode
/permissions allow-allTurn allow all tools on, with no confirmation (typing it is the consent)
/permissions rulesList the standing rules
/permissions revoke <id> or revoke allRevoke one rule, or all of them. revoke all also ends accept edits.
/permissions plan-approval always, elevated-risk or neverWhen a proposed plan needs your approval
/permissions auto-approve on [none|low|medium|high] or offAuto-approve up to a risk ceiling (high when omitted). Replaces /auto-approve, removed in v2.2.0

What accept edits covers. Only fs.write, fs.edit, fs.multi_edit, fs.apply_patch and fs.create_directory, inside the trusted root, at Medium risk or below. You turn it on from a CLI on the Gateway's machine, which proves its connection the same way binding does. It never covers:

  • shell commands, coding agents, deletes, fs.copy or fs.move;
  • execution-sensitive files, which always get a diff card: git hooks and config, CI files (.github/workflows/** and others), IDE settings, toolchain pins, build and project files (*.csproj, *.targets, global.json, Makefile, …), package manifests and lockfiles (package.json, yarn.lock, …), Dockerfiles and compose files, scripts (*.sh, *.ps1, …), and agent instruction files (AGENTS.md, CLAUDE.md, .cursorrules, .claude/**, .mcp.json, …);
  • a turn whose project instructions the injection guard flagged.

A sub-agent follows its parent's mode and cannot change it. On Personal, the Dashboard can tighten a session (ask before edits, plan mode, revoke rules), but its loosening controls say "requires the CLI".

Allow all tools

New in 2.2. Allow all tools runs every tool call the session makes without an approval card, critical ones included: shell commands, edits, outbound requests, node.* desktop actions and coding agents. It covers every turn of the session, whoever sends it (Dashboard, Mobile and voice turns included), until the time limit ends or you leave the mode.

It is off unless the Gateway's host opts in. The operator sets this in <dataDir>/config/appsettings.user.json (applied on the next call, no restart):

{ "Sophon": { "ApprovalPolicy": { "AllowAllMode": true } } }

No Dashboard switch or API request sets it. Sophon:ApprovalPolicy:AllowAllTtlMinutes sets how long the mode lasts (default 120, clamped to 15–1440). The environment variable SOPHON__ApprovalPolicy__AllowAllMode=true works too, but needs a restart, and the file overrides it.

Who can enter it. Only the session's owner, with the Admin role (on Personal the local user has it), from a CLI whose connection is proven local, in a session bound to a trusted workspace whose history holds no untrusted content. /permissions allow-all turns it on at once; the dialog's Allow All row asks first. When it is out of reach, the dialog and /permissions show give the reason.

What still asks or refuses. Existing standing rules answer first. Disabled tools, schema errors, plan mode and the sandbox policy still refuse. plan.recover, agent.delete, plugin installs, plan cards and workflow approval nodes still ask a person. A call that arrives through Sophon's own MCP server is never covered. Each covered call is recorded as an auto-approved approval.

How it ends. /permissions default, accept-edits or plan, ⇧⇥, /new, revoking all rules, unbinding or changing the workspace's trust, or the time limit. The first time the session reads untrusted content (a web fetch, a flagged tool result), the Gateway ends allow all for good in that session; a new session starts clean. A headless run (sophon -p) on a session in allow all ends it, and sophon exec --permission-mode allowAll is a usage error.

The Dashboard shows the mode as a red chip, "Allow all tools · until HH:mm", whose only action is Leave allow all; it never turns the mode on. Settings → Security → Approval gates shows a read-only "Allow all mode (host)" row. Sophon Mobile shows nothing.

Before turning the host setting on, update the CLI and the Dashboard together with the Gateway: an older client keeps showing the previous mode while calls run without a card. On Pro, anyone holding an Admin token can enter allow all once the host flag is on, and a demoted admin keeps that ability until their tokens expire (Sophon:Jwt:ExpirationMinutes, 1440 by default). Keep the Admin role narrow.

Standing rules

In a bound, trusted workspace, an approval card can offer always allow: press 2 in the CLI. What it offers depends on the card. A card whose preview was cut offers nothing, and a sub-agent's card offers no tool or command rule.

  • Edit cards offer "Accept edits in my-service for this session", which turns on accept edits for the session.
  • Tool rules cover the read-only network tools: http.request (GET and HEAD only), web.fetch and web.scrape. The card reads, for example, "Always allow web.fetch in my-service for this session".
  • Command-prefix rules cover os.execute commands that start with the same words. The prefix is the program plus up to two more plain words, three words in all. The card reads "Always allow dotnet test … in my-service".

A dotnet test rule covers dotnet test and dotnet test --no-build. It does not cover dotnet test -c Release (-c is refused; --configuration Release is fine), and it never covers anything chained with &&, |, ; or &. A covered command runs its program by absolute path, with no shell, and its output still streams.

Some commands never get a rule and always ask:

  • shells and interpreters: bash, pwsh, cmd, python, node, …;
  • network and remote tools: curl, wget, ssh, …;
  • containers and clouds: docker, kubectl, terraform, gh, …;
  • package runners: npx, uvx, pipx, …;
  • destructive or privileged programs: rm, sudo, …;
  • subcommands that download, publish or change config: git push, git pull, git config, npm install, pip install, dotnet tool, dotnet add, cargo install, …

MSBuild property and target switches (-p:, -t:) and absolute paths are refused too.

Rules last 8 hours by default (an operator can set 1 to 24 with Sophon:Workspace:StandingRuleTtlHours), with at most 32 per session. List them with /permissions rules and remove them with /permissions revoke <id|all>.

Once any turn in a session reads untrusted content, network and command rules stop covering for the rest of that session. Its cards say so and offer no new rule. Calls to MCP tools (mcp.*) in a bound session always ask, and no rule or mode covers them.

Review and undo: /diff and /rewind

Before an edit tool writes a file in a bound session, Sophon saves the previous version in an edit journal under <dataDir>/checkpoints. The journal records fs.write, fs.edit, fs.multi_edit, fs.apply_patch and the diff tool's patch_apply. If the journal cannot save, the edit is refused.

/diff                        # every change this session (same as /diff session)
/diff turn                   # what changed since the latest turn that edited a file
/diff uncommitted            # git's view: staged, unstaged and untracked vs HEAD
/diff src/Http               # narrow to a file or folder (relative to the workspace root)
/diff turn src/Http/RetryPolicy.cs

session and turn work in any bound workspace. uncommitted needs a trusted workspace in a git repository with at least one commit. In full-screen, /diff opens an overlay. Classic and pinned print and page it.

/rewind          # undo the latest checkpoint
/rewind 3        # undo everything since 3 checkpoints back (1 to 50)

Each turn that edited a file is a checkpoint, and so is each rewind. /rewind shows exactly what it will undo, then asks y / n. It restores only what the edit journal recorded, and only while the files still match the diff you saw. It needs a trusted workspace, no turn running, and a CLI on the Gateway's machine.

/rewind does not undo shell commands, code.* runs, deletes, moves, copies, new folders, or anything an external coding agent changed. It does not rewind the conversation either, and the model is not told. Ask it to re-read a file before editing it again. Commit often: git is still your real undo.

/init: write AGENTS.md

/init asks the agent to read the repository and write AGENTS.md, or improve the one that exists. The CLI writes nothing itself: the file arrives as an approval card, so you approve it before it is written. /init takes no arguments and needs a bound, trusted workspace. AGENTS.md is execution-sensitive, so accept edits never writes it without a card.

Plan mode

In plan mode only read-only tool calls run, for the agent, its sub-agents at any depth, and nested calls. A write the model tries anyway is refused without a card, and its tool row ends plan mode. The agent then proposes its plan with the plan.propose tool.

The plan card shows the goal, a summary, up to 20 numbered steps (each with its dependencies, risk, files and tool hints) and the risks.

KeyDoes
1, Y, AApprove and run it. Each edit still asks.
2Approve and switch to accept edits, when offered (bound, trusted workspace)
GSwitch between running here and running as a graph, when offered
FKeep planning with your feedback (3 revisions by default; the card shows how many are left)
EEdit the plan as markdown
N, RReject and stop the turn
V, SView in full, skip

Pinned keeps A, R, E and S only. Approving a plan ends plan mode, and only the session's owner on an elevated connection can do that. On Personal that means the CLI: the Personal Dashboard can only reject a plan. On Pro and Enterprise, the owner's Dashboard can approve it too.

Plan mode is not a network control. Read-only tools still run, including web.search, web.fetch, web.scrape, http.request GET, memory search and agent.delegate.

For plans that stop part-way, see Plan recovery.

The checklist

On longer tasks the agent keeps its own checklist with the todo.write tool: 1 to 50 items, each pending, in_progress or completed, with at most one in progress. Each call replaces the whole list.

◐ Todos 1/3 · ⌃T
  ✓ Add jitter to the retry policy
  ◐ Running the tests
  ○ Update docs/CLI.md

In the full-screen chat the list is a band above the status band. ⌃T or /todos folds and unfolds it while the session has a checklist (otherwise ⌃T attaches an image, as before). In classic and pinned, /todos prints the list.

Sub-agents

The agent can hand work to sub-agents with agent.delegate: one task, or 2 to 5 tasks at once. You don't define sub-agents yourself. What's new in v2.1.0 is that you can see and stop them.

● main — Delegating Reviewer… (14s)
  ○ subagent Reviewer #1 · Read src/Http/RetryPolicy.cs · 5,760 tokens · 12s
  ○ subagent Counter #2 · Search "TODO" · 1,880 tokens · 12s
CommandDoes
/subagentsList child agents, running and finished. Full-screen opens an overlay: ↩ opens a child's live transcript, read-only, and x x stops it.
/subagents watch 2Open child #2's live transcript (full-screen only)
/subagents stop 2Stop child #2. The parent turn goes on.

A child waiting for an approval shows ⚠ … waiting for approval, and its card comes to your main chat, marked with where it came from. Sub-agents follow the parent's permission mode.

Status line and /context

Since v2.2.0 the full-screen chat splits this over two rows. The context line above the input shows the workspace and the session's usage; the footer below it shows the mode, the approval policy, the effort and the model the last reply ran on:

 ~/src/my-service [⎇ main*] [#42]                       ctx 11% · Session: $0.13 used
 ▎ ❯ fix the retry policy
 Accept Edits · Manual Approval · / commands · ? help      ▲ auto·high ↪ Anthropic · claude-sonnet-4-6

ctx turns amber from 80% and red from 95%. The cost covers this session's own turns. Child agents and Gateway background calls are not counted. /context prints the details on every surface, starting with the session's id, title, agent and creation time:

context    11% of 200,000 tokens (21,300 in the last prompt)
tokens     in 18,230 · out 2,210 · cache read 38,000 · reasoning 40
cost       $0.13 (this session's own turns; child agents are not counted)
model      Anthropic · claude-sonnet-4-6
routing    orchestrator · ↪ fell back from GPU box · qwen3:8b (timeout)
mode       ⏵⏵ accept edits
workspace  my-service · main

How the model is chosen is covered in Model Routing.

Pick up where you left off

sophon -c     # continue the last session you used in this repository

sophon -c (--continue) enters the newest session recorded for this repository on this Gateway. With none, it prints no earlier session for <folder>; run sophon to start one and exits 1. Sessions are recorded in ~/.sophon/config/cli-sessions.json, headless runs included.

A walkthrough

An example session, fixing a flaky retry test in a .NET service.

1. Start and trust

cd ~/src/my-service
sophon

Press y at the trust question. The CLI prints Loaded AGENTS.md (…) if the repository has one. If not, run /init first and approve the file it proposes.

2. Plan first

Press ⇧⇥ until the footer's mode chip shows Plan, then ask:

The RetryPolicy tests fail about one run in ten. Find out why and fix it.

The agent reads and searches the code, keeps a checklist, and proposes a plan card. Press F and type "keep the public API unchanged" to send it back once. When the plan looks right, press 2 to approve it with accept edits.

3. Let it work

Edits to src/ and tests/ now apply without a card. The first time it runs dotnet test, the shell card offers "Always allow dotnet test … in my-service". Press 2, and later test runs go through without a card for the next 8 hours (unless the session reads untrusted content, which ends command rules). A change to my-service.csproj still shows a diff card, because project files are execution-sensitive.

4. Review and undo

/diff              # everything this session changed
/diff turn         # just the last turn
/rewind            # didn't like the last turn? undo it (shows the diff, asks y/n)

5. Finish

Check /context for the cost, commit with git as usual, and exit. Tomorrow, sophon -c in the same folder brings the session back.

Run it without a person

sophon -p "<prompt>" runs one prompt headlessly, for scripts and CI. It never approves a card, prints text, JSON or NDJSON, and exits with a code you can branch on. See Headless runs.

Operator settings

Gateway settings under Sophon:Workspace, applied live from appsettings.user.json:

{
  "Sophon": {
    "Workspace": {
      "Enabled": true,
      "StandingRuleTtlHours": 8,
      "AllowedRoots": ["~/src"],
      "DeniedPaths": ["~/src/secrets"],
      "ShellEnvAllowList": ["JAVA_HOME", "MY_SDK_*"],
      "ExecutionSensitivePaths": ["deploy/**", "/scripts/release/**"]
    }
  }
}
KeyDefaultMeaning
Enabledtruefalse turns workspace binding off for the whole Gateway. Every turn then runs unbound, with host-wide tools. Use it to roll back, not to tighten.
StandingRuleTtlHours8How long an always-allow rule lasts (1 to 24)
AllowedRoots[]When set, only these roots can be bound. Empty means any root the policy accepts.
DeniedPaths[]Folders that may not be bound, nor any folder inside or above them
ShellEnvAllowList[]Extra environment variables a bound shell keeps. Secret-shaped names, PATH, BASH_ENV and ENV are never re-admitted.
ExecutionSensitivePaths[]Extra patterns that always get a card. The built-ins cannot be removed.
PullRequestLookuptrueSince v2.2.0. When gh is installed and signed in as the Gateway's OS user, the Gateway runs gh pr view with that user's GitHub credentials for each trusted workspace with a GitHub remote, to show the branch's open pull request. On a multi-user Gateway every user who can bind a trusted workspace queries GitHub with that one identity. false stops it; the uncommitted-changes * is unaffected.

Allow all tools has its own host settings under Sophon:ApprovalPolicy (AllowAllMode, AllowAllTtlMinutes); see Allow all tools.

To tighten a single session instead, use /permissions default, /trust off or SOPHON_CLI_WORKSPACE=0.

Limits

  • Same machine only. Binding needs the CLI and Gateway on one machine, under one OS user, over a loopback URL. Remote and containerized Gateways chat without a workspace.
  • The shell is not a sandbox. Commands start in the root but are not confined to it. Code written under accept edits runs as soon as you approve a test or build command, so read what a command will run.
  • External coding agents. coding.claude_code, coding.codex and coding.copilot are confined only by their working directory. Once approved, they use their own tools with the Gateway user's reach, and their edits are not journalled, so /diff session and /rewind miss them.
  • The journal is bounded. 32 MiB per file, 64 MiB and 1,000 writes per turn, 256 MiB and 50 runs per session, and 1 GiB in total. The oldest entries are pruned, and a pruned version cannot be rewound. Backups leave checkpoints/ out.
  • /rewind covers only the edit tools. It leaves deletes, moves, copies, shell changes and the conversation as they are.
  • Cut-preview gate is client-side. Approvals from Mobile, voice, channels or REST don't enforce reading a large preview to its end.
  • Pinned surface. No ⇧⇥, no 2, F or V on cards. Use /permissions and the classic or full-screen chat for those.
  • Bound sessions don't automate. No automatic planner, no cron or webhook dispatch.