[{"content":" Desktop App Share link Share this section Permalink Share via Twitter LinkedIn Facebook The Sonny desktop app is a Wails v2 shell around the same React UI the other clients use, bound to exactly seven narrow Go facades. It does not run sessions itself; it connects to the shared user-scoped supervisor. See Client Apps for that model and for what closing a window does and does not stop.\nThe pinned Wails version, build tags, and platform matrix are a recorded architecture decision in the source repository (docs/assurance/decisions/D21-2026-09-14-wails-baseline.md).\nInstall Share link sudo apt install sonny-desktop # Debian / Ubuntu sudo dnf install sonny-desktop # Fedora / RHEL / Rocky / Alma sonny-desktop depends on the exact same version of sonny, so installing or upgrading the desktop cannot silently pair it with a different client-protocol build. The native packages declare their WebKitGTK 4.1, GTK 3, and GLib runtime requirements, use distribution-native post-install and post-remove scriptlets to refresh the desktop and icon caches, and install a package-scoped CycloneDX inventory at /usr/share/doc/sonny-desktop/sbom.cdx.json.\nWhat is on the machine Share link apps/desktop/ wails.json Wails project config: frontend commands, wailsjsdir main.go entry point (//go:build desktop): embeds frontend/dist, dispatches bootstrap check or calls app.Run app/ Wails options and lifecycle (//go:build desktop) bootstrap/ shared desktop config + supervisor bootstrap, including the supported non-window verification path; no Wails bridge/ ProxyBridge: owns the sonny editor-rpc child process host/ Services: the HostServices facade lifecycle/ token-gated close policy, client quit, window restore desktopsettings/ token-gated, desktop-local settings and deep-link setup packsource/ detached git-source review, signature verification and exact-review installation configeditor/ token-gated native review and transition-bound proof windowcontrols/ token-gated, fixed native window state and actions assetpolicy/ the packaged-asset content policy middleware windowtoken/ the per-launch window token bridge and host share frontend/ the Vite React frontend bridge/ and host/ import no Wails package and carry no build tag, so they build and test in the ordinary suite. main.go and everything in app/ import github.com/wailsapp/wails/v2 and carry //go:build desktop, because importing that module always requires cgo, GTK, and WebKitGTK on Linux and there is no tag that turns it off. Gating it behind desktop is what keeps the default go build ./... and CI free of that toolchain.\nThe seven facades Share link app/app.go binds exactly seven structs and nothing else:\nFacade Package Contract ProxyBridge bridge exported methods are exactly Send and Close Services host the HostServices facade, including OpenPath and RevealPath lifecycleService lifecycle close policy, quit, window restore settingsService desktopsettings desktop-local settings packSourceService packsource git-source review and exact-review install configEditorService configeditor native review, no config filesystem reads or writes windowControlsService windowcontrols fixed native window state and actions The narrowness is the point. The surface a webview can reach is a fixed list, not whatever a struct happens to export.\nBootstrap verification without a display Share link The shipping binary has a supported automation mode that does not initialize Wails or require a display server:\nsonny-desktop --bootstrap-check It follows the same production path as a graphical launch: reads \u0026lt;config-dir\u0026gt;/desktop.yaml through desktopsettings.Store, resolves machine organization configuration and the signed personal runtime through internal/daemonbootstrap, selects the digest-pinned sonny binary, ensures or adopts the user-scoped supervisor, then performs its authenticated diagnostics read-back. The whole operation is bounded to two minutes, with the daemon launch itself bounded to 90 seconds.\nstdout is exactly one JSON document, schema sonny.desktop.bootstrap-check.v1: client version, effective non-secret desktop settings, the selected endpoint/binary/tenant, daemon and protocol versions, and whether the supervisor was started or adopted. Its bootstrap member is the shared sonny.daemon.bootstrap/v1 evidence — readiness, selected and effective profiles, configuration digest, and ordered source tiers. stderr carries progress. Exit status is zero only after config parsing and authenticated readiness both succeed with shared bootstrap state ready. A successful check whose signed configuration still lacks a provisioned secret emits the full evidence with needs-user-input state, then exits 1. An operational failure also exits 1 and writes status: \u0026quot;error\u0026quot; plus a diagnostic string. Typed failures carry the same bootstrap diagnostic that TUI and daemon callers can extract, including invalid and repairable state and the failing source tier. The mode deliberately does not start editor-rpc: that","permalink":"https://sonny.sh/docs/clients/desktop/","tags":["clients","desktop","wails"],"title":"Desktop App"},{"content":" Global flags Share link Share this section Permalink Share via Twitter LinkedIn Facebook These flags are persistent, so every command accepts them:\nFlag Type Description --analysis-json string versioned analysis JSON patch; protected settings may only narrow (SONNY_ANALYSIS_JSON supplies the environment layer) --config string config file (default: .sonny.yaml in working directory) --debug — enable debug mode -h, --help — help for sonny --log-format string log format: text|json (default \u0026ldquo;text\u0026rdquo;) --log-level string log level: debug|info|warn|error (default \u0026ldquo;warn\u0026rdquo;) -m, --model string model to use (overrides recipe default) -p, --provider string LLM provider (openai, anthropic, ollama, etc.) --quiet-deprecations — suppress schema and legacy recipe-name deprecation warnings -v, --version — version for sonny -C, --workdir string working directory (default \u0026ldquo;.\u0026rdquo;) Bare sonny is sonny run Share link Bare sonny with no subcommand is an alias for sonny run . The legacy shim in main.go rewrites sonny --dir foo into sonny run --dir foo before cobra ever sees it, and also promotes old single-dash long flags (-dir) to double-dash (--dir).\nTwo things follow from that shim:\nsonny -v does not work. Cobra\u0026rsquo;s root -v is a real flag (--version), but the shim only rewrites args that start with - into sonny run-style invocations, and run has no -v shorthand, so it fails as an unknown shorthand flag (exit 2). Use --version (two dashes) instead. --license, --sbom, --changelog, and --third-party-licenses only exist on sonny version — there is no root-level equivalent. Exit codes Share link Every classified refusal leaves the process with the status its code owns in this table. Exit 1 means only \u0026ldquo;an error escaped classification\u0026rdquo; — a defect worth reporting, never a reasoned refusal. A malformed invocation (unknown flag, unknown command, wrong argument count) is always 2.\nStatus Code Meaning 0 ok succeeded 1 — an unclassified error escaped; not a published outcome 2 usage malformed invocation: unknown flag, unknown command, bad argument count 3 config configuration missing, unreadable, or contradictory 4 incompatible_protocol no compatible wire version was negotiated 5 unauthenticated no usable identity 6 unauthorized an identity exists but lacks the authority 7 conflict expected revision, lease, or aggregate was won by someone else 8 denied_policy a policy rule refused the operation 9 denied_evidence required evidence is missing, stale, or failed verification 10 denied_approval a required human approval is absent or expired 11 frozen a freeze is in force (suspended, not forbidden) 12 unavailable a dependency is down or unreachable 13 timeout the operation did not finish in its budget 14 partial some of the requested work applied and some did not 15 indeterminate sonny cannot establish whether the effect happened 70 internal a defect in sonny 71 corruption persisted state failed an integrity check Under --json the same code rides inside the envelope\u0026rsquo;s error.code / error.exit_code, so a consumer never has to branch on prose. Some commands still predate the table and exit 1 or 2 on their own (sonny run --session \u0026lt;unknown-uuid\u0026gt; exits 2); those are being migrated command by command.\n","permalink":"https://sonny.sh/docs/cli/overview/","tags":["cli","flags","exit-codes"],"title":"CLI overview and global flags"},{"content":" Getting Started Share link Share this section Permalink Share via Twitter LinkedIn Facebook This page takes you from a machine with nothing installed to a working Sonny chat session. Sonny is commercial software; use the distribution and access provided for your organization. See Licensing and access .\nRequirements Share link Building from source needs Go 1.27.1 or newer; go.mod pins go 1.27.1. The Linux release binary and the deb/rpm/apk packages are built with cgo enabled (for the mesh identity resolvers), so they need a glibc-compatible Linux system. The macOS and Windows binaries are cgo-free. No recipes are preinstalled. The binary ships with one embedded recipe, sonny-base; everything else you add yourself. Install Share link Pre-built binaries Share link Every tagged release publishes an archive per OS and architecture:\nsonny_\u0026lt;version\u0026gt;_\u0026lt;os\u0026gt;_\u0026lt;arch\u0026gt;.tar.gz # linux, darwin sonny_\u0026lt;version\u0026gt;_windows_\u0026lt;arch\u0026gt;.zip # windows Download the archive for your platform, extract it, put sonny on your PATH, then confirm:\nsonny version No `-v` flag sonny -v does not work — there is no short flag for version. Use sonny --version or sonny version. Debian and Ubuntu Share link curl -fsSL https://apt.codepros.org/codepros-keyring.gpg \\ | sudo tee /usr/share/keyrings/codepros-archive-keyring.gpg \u0026gt; /dev/null echo \u0026#34;deb [signed-by=/usr/share/keyrings/codepros-archive-keyring.gpg] https://apt.codepros.org stable main\u0026#34; \\ | sudo tee /etc/apt/sources.list.d/codepros.list sudo apt update sudo apt install sonny Fedora, RHEL, Rocky, Alma Share link sudo rpm --import https://rpm.codepros.org/RPM-GPG-KEY-codepros sudo dnf config-manager --add-repo https://rpm.codepros.org/codepros.repo sudo dnf install sonny The repository serves x86_64 and aarch64 packages and its metadata is signed with the same key as the apt repository.\nThe desktop package Share link sonny-desktop is a separate native package for graphical workstations. It depends on the exact same version of sonny, so installing or upgrading the desktop can never pair it with a different client-protocol build.\nsudo apt install sonny-desktop # or: sudo dnf install sonny-desktop The desktop does not run sessions itself. It connects to one user-scoped local service, sonny supervisor serve, which it starts on first launch and which the editor integrations share. Closing the window stops only that window\u0026rsquo;s connection; sessions and scheduled work carry on. When you need a fresh service — after changing a credential the service reads at startup, or after an upgrade — stop it with Quit Sonny and stop daemon… in the tray menu or with sonny supervisor stop.\nAuthorized source access Share link Source builds require access granted by CodePros. Follow the build instructions in your authorized checkout; source access does not grant an open-source license.\nFirst run Share link sonny init sonny config path sonny init creates your config, data, and cache directories and seeds models.yaml with an example model-category catalog — but only if models.yaml does not already exist. It also creates models.d/ in the config directory for drop-in catalog fragments. Running it again is safe; it will not overwrite a models.yaml you have edited unless you pass --force.\nsonny config path prints every standard path Sonny uses: the config directory (models.yaml, pack-sources.yaml), the data directory (sessions.db, your recipes), the cache directory (cloned recipe repositories), and the state directory.\nPoint Sonny at an LLM endpoint Share link Sonny talks to LLM providers over plain HTTP. For the openai:// scheme — and anything OpenAI-compatible — two environment variables are enough:\nexport OPENAI_BASE_URL=\u0026#34;https://api.openai.com/v1\u0026#34; # or any OpenAI-compatible endpoint export OPENAI_API_KEY=\u0026#34;YOUR_OPENAI_API_KEY\u0026#34; Nothing else about your recipe changes when you switch providers. The URI scheme decides the transport; see Providers and models .\nYour first recipe Share link A recipe is a YAML file. An llm_profiles entry says which model to call, and a main prompt supplies the system instructions and the tools the model may use.\nSave this as hello.yaml:\nversion: 2 pack: id: hello kind: primary llm_profiles: - id: default uri: \u0026#34;openai://gpt-4o?temp=0.2\u0026#34; default_llm_profile_id: default prompts: - id: main type: main title: Hello system: | You are a concise assistant. Answer in one sentence. tools: allowed: [read_file, find] Validate it before running anything:\nsonny recipe validate hello.yaml Run one turn headless Share link --prompt runs exactly one turn and exits. It implies --simple (plain-text chat, no TUI) automatically, which makes it the right shape for smoke-testing a recipe, scripting, or invoking Sonny as a one-shot sub-agent.\nsonny run --file hello.yaml --prompt \u0026#34;say hello\u0026#34; --new --isolation-mode off --new starts a fresh session instead of resuming your last one, which keeps a one-shot call repeatable.\nSimple mode is a plain-text","permalink":"https://sonny.sh/docs/getting-started/","tags":["getting-started","install","quickstart"],"title":"Getting Started"},{"content":" VS Code Extension Share link Share this section Permalink Share via Twitter LinkedIn Facebook The sonny-vscode extension is a complete Sonny session surface for VS Code. It reuses the same React session UI and daemon client as the desktop application without starting a second agent runtime.\nRuntime shape Share link Opening the Sonny Activity Bar view activates dist/extension.cjs. The extension host then:\nresolves a trusted Sonny executable from sonny.executable, an absolute SONNY_BIN development override, or bin/sonny in the packaged extension; runs sonny supervisor ensure to start or attach to the local shared service and obtain its exact endpoint, executable, and tenant; starts only sonny editor-rpc --human-attach and bridges its NDJSON frames to the webview; and exposes the supported VS Code host services — open and reveal files, safe external links, notifications, pickers, and theme updates — over the same message boundary. Closing the view or deactivating the extension closes that editor connection. It deliberately does not stop the shared supervisor or any running session, so reopening the view reattaches to the existing service.\nThe browser-only Vite development entry still renders fixture data. That keeps component development deterministic, while the packaged extension always uses live daemon sources.\nCommands Share link Command Default keybinding Sonny: Open Sessions Ctrl+Alt+S (Cmd+Alt+S on macOS) Sonny: Reconnect to Local Service — Sonny: Show Output — Settings Share link Setting Behaviour sonny.executable Must be an absolute path; the extension never searches PATH sonny.supervisorEndpoint May override the platform socket or named pipe; normally empty sonny.tenant May override the resolved local-profile tenant; normally empty The PATH rule is deliberate. An editor extension loads in whatever environment your shell left behind, and silently picking up a different sonny than the daemon is running is the exact failure the desktop avoids with its digest-pinned sibling binary.\nBuilding and verifying Share link From ui/:\npnpm --filter sonny-vscode typecheck pnpm --filter sonny-vscode test pnpm --filter sonny-vscode build pnpm --filter sonny-vscode package build emits dist/extension.cjs for the Node extension host and dist/webview/ for the CSP-restricted webview. package creates dist/sonny-vscode.vsix; the VSIX contains built output and the Activity Bar icon — not source files, maps, or node_modules.\nDevelopment-host validation Share link Set an absolute SONNY_BIN. Press F5 with this package as the extension development path. Open the Sonny view, create or attach to a session, and complete a turn. Run Developer: Restart Extension Host and reopen the view. The session remains available after step 4, because only the editor connection was released — the supervisor and the session were never owned by the extension host.\nSource of truth Share link ui/apps/vscode/README.md ui/apps/vscode/package.json docs/config/supervisor-daemon-lifecycle.md ","permalink":"https://sonny.sh/docs/clients/vscode/","tags":["clients","vscode","editor"],"title":"VS Code Extension"},{"content":" MCP Configuration Share link Share this section Permalink Share via Twitter LinkedIn Facebook Where the files live Share link File Location User sidecar config mcp.yaml in the profile config directory — $XDG_CONFIG_HOME/sonny when set, otherwise the platform-native location Project chain .sonny/mcp.yaml in every directory from the cwd up to the filesystem root Allowlist mcp.allowlist.yaml, in the same profile config directory as the sidecar config --mcp-config \u0026lt;path\u0026gt; on sonny run, sonny mcp list, sonny tools list and sonny recipe resolve overrides the user sidecar path for that invocation only. It does not move the project chain or the allowlist.\nHow layers merge Share link Four sources merge, then two gates run, in this exact order:\n# Layer Conflict rule 1 Your user mcp.yaml the base 2 Pack-registry servers (the mcp_d/ directories of your recipe sources) only servers the active recipe\u0026rsquo;s mcp.allowed names explicitly join; \u0026quot;*\u0026quot; pulls none; each replacement is announced at startup, and a name two sources both define loads from neither 3 The .sonny/mcp.yaml project chain outer-first; a deeper directory replaces a shallower same-name server, and a project server wins over a user server 4 The active recipe\u0026rsquo;s own mcp.servers beats all of the above — the recipe is the concrete packaged contract After the merge, the recipe\u0026rsquo;s resolved mcp.allowed filters the set:\na list of names keeps only the servers it names, whichever layer declared them; an explicit allowed: [] keeps none; an omitted allowed, no mcp: block, or [\u0026quot;*\u0026quot;] with allow_wildcard: true keeps everything (\u0026quot;*\u0026quot; without allow_wildcard is ignored). A server dropped here never reaches the allowlist, and every surface says what it dropped: the TUI startup alert, a sub-agent stderr note, a sonny recipe resolve warning, filtered by mcp.allowed rows in sonny mcp list --pack-file (JSON: filtered_by_allowed), and a single mcp_not_allowed summary line in a headless --result-json.\nThen the two gates run — the integration gate and allowlist enforcement. They are covered in Trust model .\nWarning An unreadable or malformed user/project MCP file, project integration file, or allowlist is a startup error. Sonny does not skip the bad layer and does not restore a broader configuration from an earlier one. A single server that merely lacks approval is withheld and reported without blocking unrelated approved servers. Server schema Share link A mcp.yaml file is one key holding a list of servers:\nservers: - name: fs type: stdio command: /usr/bin/mcp-server-filesystem args: [\u0026#34;--root\u0026#34;, \u0026#34;/tmp\u0026#34;] work_dir: /srv enabled: true auto_connect: true - name: web type: sse url: \u0026#34;https://mcp.example.com/sse\u0026#34; egress: allow_hosts: [\u0026#34;mcp.example.com\u0026#34;] allow_ports: [443] auth: type: bearer token: ${MCP_TOKEN} Field Type Notes name string required; the merge key, the allowlist key, and the tool-name namespace type string stdio or sse. No http type exists; streamable_http is refused at load command string required for stdio; the executable to spawn args list argv for the stdio command url string required for sse; the SSE endpoint work_dir string working directory for the spawned stdio process env map extra environment for the stdio process; values expand ${VAR}/$VAR auth object none/api_key/bearer/basic, with api_key, api_key_header, api_key_env_var, token, username, password; only meaningful for sse egress object required for sse; see below. A stdio server cannot declare it enabled bool default true auto_connect bool default true; when false the server loads but is not connected at session start integration string Sonny-only; classifies the server for the integration gate (jira, gitlab, github, …). Omit it and Sonny guesses from name metadata map Sonny-only free-form labels (tenant, env, region, …) from_pack string Sonny-only; written by sonny mcp approve --write-config on an entry copied from a recipe that is not verified-signed The standalone sidecar is parsed strictly: it accepts the fields above plus env_allowlist, env_inherit_all and timeout. Any other key — including the upstream oauth block and schema_version — is rejected as a configuration error rather than silently ignored. A recipe\u0026rsquo;s inline mcp.servers: decodes into the full schema type.\nsse servers need an egress policy Share link Remote egress is deny-by-default. The MCP manager validates every remote server at registration and refuses one whose egress.allow_hosts does not name its URL host.\nservers: - name: local-dev type: sse url: \u0026#34;http://localhost:3001/sse\u0026#34; egress: allow_hosts: [\u0026#34;localhost\u0026#34;] allow_plain_http: true The full block is {allow_hosts, allow_ports, allow_plain_http, allow_private_networks, deny_private_networks, proxy_url} — the same block a pack\u0026rsquo;s mcp.servers: entry takes.\nEnvironment expansion Share link env values expand ${VAR}/$VAR against the current environment, or a recipe secrets: export, at connect","permalink":"https://sonny.sh/docs/mcp/configuration/","tags":["mcp","configuration","reference"],"title":"MCP Configuration"},{"content":" Authoring Recipes Share link Share this section Permalink Share via Twitter LinkedIn Facebook Learn how to create recipes for Sonny.\nRecipe Manifest Share link The manifest.yaml file defines the metadata and configuration for a recipe.\nRequired Fields Share link name: The name of the recipe. version: Semantic version of the recipe. description: A brief description. Optional Fields Share link authors: List of authors. license: License type. The examples below use a proprietary license identifier. Use the license that applies to your own recipe; this field does not change Sonny’s commercial license.\nMinimal Example Share link name: \u0026#34;example-recipe\u0026#34; version: \u0026#34;1.0.0\u0026#34; description: \u0026#34;A minimal example recipe.\u0026#34; Realistic Example Share link name: \u0026#34;advanced-recipe\u0026#34; version: \u0026#34;2.1.0\u0026#34; description: \u0026#34;An advanced recipe with multiple recipes.\u0026#34; authors: - \u0026#34;John Doe\u0026#34; license: \u0026#34;LicenseRef-Proprietary\u0026#34; recipes: - \u0026#34;recipe1.yaml\u0026#34; - \u0026#34;recipe2.yaml\u0026#34; models: - \u0026#34;model1.yaml\u0026#34; ","permalink":"https://sonny.sh/docs/packs/authoring/","tags":null,"title":"Authoring Recipes"},{"content":"Run an interactive chat session with a recipe. Bare sonny is this command (see Overview and global flags ).\nThis command starts an interactive session where you can chat with the LLM using the configured recipe. The session supports tool execution, LSP integration, and agent management.\nUsage: sonny run [flags] Examples Share link Share this section Permalink Share via Twitter LinkedIn Facebook # Run with packs discovered from default locations sonny run # Run with one or more pack directories (repeatable) sonny run --dir ./packs --dir \u0026lt;data-dir\u0026gt;/packs # Run with a specific pack file sonny run --file ./my-pack.yaml # Run with a specific provider and model sonny run --provider anthropic --model claude-sonnet-4-20250514 # Run in simple mode (no TUI) sonny run --simple Flags Share link Flag Type Description --continue — Resume the most recent session --control-db string Path to the control-plane SQLite database (default: $SONNY_CONTROL_DB, then a control.db sibling of the session DB). Only opened/migrated when the control plane is in use (this flag, $SONNY_CONTROL_DB, --mesh-self, or --mesh-addr) --critic string Comma-separated list of critic agents to run (code_quality,security,architecture,performance,testing,documentation) --critic-recipe string Critic recipe ID or path to a recipe YAML file (default \u0026ldquo;critic\u0026rdquo;) --dangerously-auto-approve-tools — DANGEROUS: approve every tool call automatically, with no prompt. Denied tools stay denied and isolation is unaffected. Refused when policy forbids it --dangerously-bypass-all — DANGEROUS: --dangerously-auto-approve-tools and --dangerously-bypass-isolation together --dangerously-bypass-isolation — DANGEROUS: run tools on the host with no isolation realm. Refused when a trusted isolation.yaml sets minimum_mode -d, --dir strings directory containing recipe YAML files (repeatable; env: SONNY_DIR) --eval — run critic and sentiment evaluations on conversation --eval-model string model for evaluations (fast/cheap recommended) (default \u0026ldquo;gpt-4o-mini\u0026rdquo;) --eval-provider string provider for evaluation model (default \u0026ldquo;openai\u0026rdquo;) -f, --file string single recipe YAML file to load --headless-mcp — connect the allowlisted MCP servers from --mcp-config in a headless --prompt run without an isolation realm; refused when trusted policy forbids it, same as the interactive session -h, --help — help for run --hyper-client-id string OAuth client ID for Hyperbolic (required for /login hyper) --init-models — Write the example model-category catalog to the user config dir and exit --isolation-assurance string minimum assurance override: guarded, native, docker, or vm --isolation-backend string isolation backend override: auto, native, docker, or vm --isolation-machine string trusted Docker broker configuration (default: user config isolation-docker.yaml) --isolation-mode string isolation mode override: required, preferred, or off --isolation-policy string trusted isolation policy file (default: user config isolation.yaml) --mcp-config string Path to the MCP server config (default: the profile config directory\u0026rsquo;s mcp.yaml) --mesh-addr string gRPC address of the mesh node to observe (default: SONNY_MESH_ADDR env) --mesh-peer strings gRPC endpoint of a mesh node to show in the UI Connect dialog (repeatable) --mesh-self — expose this chat session over an in-process mesh.Node so remote clients can attach --mesh-self-grpc string gRPC listen address for the in-process mesh node (--mesh-self) (default \u0026ldquo;:50052\u0026rdquo;) --mesh-self-synapse string synapse listen address for the in-process mesh node (--mesh-self) (default \u0026ldquo;:50053\u0026rdquo;) --models string Path to the user model-category catalog (default: $SONNY_MODELS_FILE, then \u0026lt;config-dir\u0026gt;/models.yaml; see \u0026lsquo;sonny config path\u0026rsquo;) --new — start a fresh session instead of restoring the last one --no-tools — disable all tools for a headless completion --no-trace — disable conversation tracing (default: on; env: SONNY_TRACE=0) --permissions — Prompt for approval before write/exec tool calls (default off; reads always auto-allow) --pick-recipe — force the recipe picker even when a default is set --policy-binding string path to the decision-policy binding a controller dispatched this worker with (a file path, never the binding itself) --prompt string run a single headless turn with this text and exit (implies --simple); used to spawn sonny as a one-shot sub-agent --recipe string select an already-discovered recipe by its id (skips the picker) --recipe-dir string DEPRECATED: alias for --dir (single dir) --recipe-file string DEPRECATED: alias for --file --result-json string write the headless turn\u0026rsquo;s result (final reply, usage, tool calls, trace ids) as JSON to this path; requires --prompt --session string Resume a specific session by UUID; fails fast if id is unknown --session-db string Path to the session SQLite database (default: $XDG_DATA_HOME/sonny/sessions.db) --show-prompt — show","permalink":"https://sonny.sh/docs/cli/run/","tags":["cli","run","headless"],"title":"sonny run"},{"content":"Sonny ships as a single static binary plus an optional desktop package. This page covers every install path and how to confirm the result. If you just want the fastest route to a first session, use Getting Started .\nVersion covered These instructions apply to Sonny 0.12.1. Run sonny version to check what is installed on your machine. Requirements Share link Share this section Permalink Share via Twitter LinkedIn Facebook Building from source needs Go 1.27.1 or newer (go.mod pins go 1.27.1). The Linux release binary and the deb/rpm/apk packages are built with cgo enabled (for the mesh identity resolvers), so they need a glibc-compatible Linux. The macOS and Windows binaries are cgo-free. No recipes are preinstalled. Sonny ships with one embedded recipe (sonny-base); everything else you add yourself through recipe sources . Pre-built binaries Share link Every tagged release publishes an archive per OS/architecture:\nsonny_\u0026lt;version\u0026gt;_\u0026lt;os\u0026gt;_\u0026lt;arch\u0026gt;.tar.gz # linux, darwin sonny_\u0026lt;version\u0026gt;_windows_\u0026lt;arch\u0026gt;.zip # windows Download the archive for your platform, extract it, put sonny on your PATH, then check the version:\nsonny version Obtain release assets through your authorized CodePros distribution channel. Verify each file against the signed checksums.txt supplied with that release. See Licensing and access .\napt (Debian / Ubuntu) Share link curl -fsSL https://apt.codepros.org/codepros-keyring.gpg \\ | sudo tee /usr/share/keyrings/codepros-archive-keyring.gpg \u0026gt; /dev/null echo \u0026#34;deb [signed-by=/usr/share/keyrings/codepros-archive-keyring.gpg] https://apt.codepros.org stable main\u0026#34; \\ | sudo tee /etc/apt/sources.list.d/codepros.list sudo apt update sudo apt install sonny sonny-desktop is a separate native package for graphical workstations. It depends on the exact same version of sonny, so installing or upgrading the desktop app cannot silently pair it with a different client-protocol build.\nsudo apt install sonny-desktop rpm (Fedora / RHEL / Rocky / Alma) Share link sudo rpm --import https://rpm.codepros.org/RPM-GPG-KEY-codepros sudo dnf config-manager --add-repo https://rpm.codepros.org/codepros.repo sudo dnf install sonny sudo dnf install sonny-desktop The repository serves x86_64 and aarch64 packages, and its metadata is signed (repo_gpgcheck=1) with the same key as the apt repository.\nWhat installing a package actually does Share link Installing a package (rather than dropping a tarball binary on PATH) does more than copy a file:\nCreates a system user and group named sonny (home /var/lib/sonny, no login shell) for server mode (sonny serve, the sonny-mesh unit). Runs sonny init --system once to seed system-wide configuration. Lays out /usr/local/share/sonny/packs, /etc/sonny (including a mesh/ subtree for mTLS material), /var/lib/sonny, /var/log/sonny, /run/sonny, and /var/cache/sonny. Installs the sonny-mesh systemd unit — not enabled and not started. You provision CA/cert material under /etc/sonny/mesh/ first, then run systemctl enable --now sonny-mesh yourself. This is deliberate: with no certificate material and no authz.yaml, there is nothing the unit could start safely with. See Running the mesh . The postinstall step is idempotent: reinstalling or upgrading re-runs it safely.\nPurge is destructive on Debian/Ubuntu apt purge sonny-desktop deletes each local account\u0026rsquo;s profile sessions/ and journal/ directories under ~/.local/share/sonny/profiles/{personal,org}/\u0026lt;profile\u0026gt;/. It keeps the sonny base package, desktop settings, keys, caches and control.db. A package script cannot discover a custom XDG_DATA_HOME, so delete profile data there by hand when a full purge is intended. RPM has no purge transaction: removal always preserves user data. From source Share link Source builds require CodePros authorization and access to the private repository. The following commands are for an existing authorized checkout.\n# In your authorized Sonny source checkout: make install # PREFIX defaults to /usr -\u0026gt; /usr/bin/sonny make install PREFIX=$HOME/.local # user install, no sudo make go-install # go install ./cmd/sonny ./cmd/sonny-api into $GOBIN make build-windows cross-compiles a Windows binary (WINARCH=amd64 by default; override with make build-windows WINARCH=arm64).\nWhat make install does Share link Beyond copying binaries to $(DESTDIR)$(BINDIR) (default /usr/bin), a plain make install with no DESTDIR does two extra things:\nShadow clean — if an older sonny earlier on PATH would shadow the one it just installed, the stale copy is removed. Set NO_SHADOW_CLEAN=1 to skip this. Daemon restart — if a sonny-mesh service is already running, it is restarted so the new binary takes effect. Set NO_DAEMON_RESTART=1 to skip. Pass DESTDIR=/tmp/stage to build a staged tree instead of installing on the current machine (what packagers do); both extra steps are skipped in that case.\nFirst run Share link sonny init sonny config path sonny init creates your config, data and cache directories and","permalink":"https://sonny.sh/docs/install/","tags":["install","packages","release"],"title":"Install"},{"content":" Neovim Plugin Share link Share this section Permalink Share via Twitter LinkedIn Facebook sonny-neovim is an AI code completion plugin for Neovim. Unlike the desktop and VS Code clients, it does not attach to the shared supervisor: it runs its own sonny-daemon and focuses on inline completion rather than agent sessions.\nWhat it does Share link Ghost text suggestions — completions appear inline as you type. Multiple LLM providers — OpenAI, Anthropic, Ollama, Gemini, Groq, Azure, Bedrock. Treesitter integration — uses Neovim\u0026rsquo;s built-in vim.treesitter to extract functions, types, and variables, plus the exact construct at the cursor (for example return_statement) and its ancestors, injected into the completion for structural awareness. Built-in Neovim feature support — running LSP clients (vim.lsp), diagnostics (vim.diagnostic), quickfix and loclist state, and buffer/version info, injected as an ## Editor Context section. Agent instruction files — reads your repo\u0026rsquo;s AGENTS.md / CLAUDE.md (provider-aware) and injects them into every completion. Codebase conventions — injects .editorconfig style settings so generated code matches the project. Language best practices — per-language guidance for Go, Python, JS/TS, Rust, Lua and others is baked into completions. LSP integration — gathers document symbols and diagnostics from language servers (gopls, pyright, tsserver, rust-analyzer, clangd, …). Documentation lookup — fetches docs from go doc, pydoc, and more. Linter integration — golangci-lint, ESLint, Pylint, Clippy, and more. Pre-commit support — parses and runs pre-commit hooks. Requirements Share link Neovim 0.8+ Go 1.27+ (for building the daemon) Network access to devnw.dev/llm (the backend module), using your Go credentials in ~/.netrc Installation Share link The Neovim integration is commercial software. Install the package provided through your authorized CodePros distribution channel, then ensure the daemon is on your PATH or set daemon_path in setup(). See Licensing and access .\nConfiguration Share link require(\u0026#39;sonny\u0026#39;).setup({ -- Daemon daemon_path = \u0026#39;sonny-daemon\u0026#39;, -- path to daemon binary pack_file = nil, -- optional: path to a recipe YAML request_timeout_s = 300, -- fail outstanding requests after this long -- LLM provider = \u0026#39;openai\u0026#39;, model = \u0026#39;gpt-5-mini\u0026#39;, -- Completion behaviour auto_trigger = true, trigger_delay_ms = 150, -- debounce trigger_min_chars = 3, max_tokens = 256, num_suggestions = 1, -- Context gathering include_related_files = true, include_open_buffers = true, use_treesitter = true, max_related_files = 5, max_lines_per_related = 50, max_context_lines = 100, use_lsp = true, -- Editor context include_lsp_context = true, -- running clients + capabilities + folders include_diagnostics = true, -- vim.diagnostic at cursor + buffer counts include_quickfix = true, include_nvim_context = true, disabled_filetypes = { \u0026#39;TelescopePrompt\u0026#39;, \u0026#39;neo-tree\u0026#39;, \u0026#39;NvimTree\u0026#39;, \u0026#39;lazy\u0026#39;, \u0026#39;help\u0026#39;, }, mcp_servers = {}, -- see lua/sonny/config.lua for examples debug = false, }) max_context_lines is the budget that matters most in practice: it caps the total context lines sent to the model, so raising max_related_files beyond what that budget allows just changes what gets evicted, not how much you send.\nKeybindings Share link Key Mode Action \u0026lt;Tab\u0026gt; Insert Accept full suggestion \u0026lt;C-Right\u0026gt; Insert Accept next word \u0026lt;M-Right\u0026gt; Insert Accept to end of line \u0026lt;C-]\u0026gt; Insert Next suggestion \u0026lt;C-[\u0026gt; Insert Previous suggestion \u0026lt;Esc\u0026gt; Insert Dismiss suggestions \u0026lt;C-Space\u0026gt; Insert Manual trigger \u0026lt;leader\u0026gt;cc Normal Toggle plugin All are configurable through keymaps, and :SonnyRebindKeys refreshes them from config without restarting Neovim.\nCommands Share link Command Description :SonnyEnable Enable the plugin :SonnyDisable Disable the plugin :SonnyToggle Toggle the plugin on/off :SonnyStatus Show plugin status :SonnyComplete Trigger completion manually :SonnyPack \u0026lt;path\u0026gt; Switch recipe :SonnyProvider \u0026lt;provider\u0026gt; [model] Switch LLM provider :SonnyModel \u0026lt;model\u0026gt; Switch LLM model :SonnyDoc [symbol] Look up documentation :SonnyDocSearch \u0026lt;query\u0026gt; Search documentation :SonnyLint [output] Run linter (diagnostics/quickfix/loclist) :SonnyLintFix Run linter with auto-fix :SonnyLintConfig Show linter configuration :SonnyPreCommit [action] Pre-commit hooks (info/run/all) :SonnyEditorConfig Show EditorConfig settings :SonnyRebindKeys Refresh keybindings from config :SonnyMCP [status|tools|connect|disconnect] [server] MCP server management :SonnyMCPCall \u0026lt;server\u0026gt; \u0026lt;tool\u0026gt; [json_args] Call an MCP tool Lua API Share link local sonny = require(\u0026#39;sonny\u0026#39;) -- Core sonny.complete() -- request completion sonny.accept() -- accept current suggestion sonny.accept_partial(\u0026#39;word\u0026#39;) -- accept next word sonny.accept_partial(\u0026#39;line\u0026#39;) -- accept to end of line sonny.next_suggestion()","permalink":"https://sonny.sh/docs/clients/neovim/","tags":["clients","neovim","completion"],"title":"Neovim Plugin"},{"content":" MCP Trust Model Share link Share this section Permalink Share via Twitter LinkedIn Facebook A configured MCP server is not an approved MCP server. Configuration says what exists; approval decides what runs. Two gates sit between them, and both apply to every surface that enumerates tools — a TUI tab, a headless run, a sub-agent, sonny mcp list --connect, sonny tools list --mcp-config and sonny recipe resolve --connect. A surface that can only list tools is not a way around either gate.\nGate 1 — the integration gate Share link Sonny classifies each server by its integration: key (or a name-based guess) and asks the integration gate whether that integration may run at all. The project integration file .sonny/integrations.yaml carries the decision; sonny init project --mcp and sonny mcp approve write it. A closed integration drops its server before the allowlist is consulted.\nGate 2 — the allowlist Share link mcp.allowlist.yaml in the profile config directory is the operator-owned decision record. A missing file is an empty allowlist — every server misses — not an error. A file that exists but cannot be read, or is not protected, is a hard error, and the response is to refuse every enabled server outright: \u0026ldquo;can\u0026rsquo;t verify the allowlist\u0026rdquo; never degrades to \u0026ldquo;connect anyway\u0026rdquo;.\nProtection is checked, not assumed. The file must be a regular file (not a symlink) owned by you or root, not group- or world-writable, at most 1 MiB, in a directory that is not a symlink, is owned by you or root and is not world-writable. A symlinked config directory is refused — for example one a dotfile manager such as GNU Stow made — while symlinks higher up the path, such as ~/.config itself, are still followed. An empty file counts as missing.\nentries: - name: fs command: /usr/local/bin/mcp-server-filesystem args: [\u0026#34;--root\u0026#34;, \u0026#34;/srv/data\u0026#34;] work_dir: \u0026#34;\u0026#34; env_sha256: \u0026#34;7e405390378983ffd9519beb762aa7d857c4d6645b7fd6669f8dd8e3db8f981b\u0026#34; sha256: \u0026#34;e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\u0026#34; added: \u0026#34;2026-09-24\u0026#34; - name: web url: \u0026#34;https://mcp.example.com/sse\u0026#34; added: \u0026#34;2026-05-18\u0026#34; What gets pinned Share link Server kind The entry must match stdio (name, command, sha256), plus args, work_dir and env_sha256 when the entry sets them stdio from the project chain all three of args, work_dir and env_sha256, set and matching stdio from an untrusted recipe all three of args, work_dir and env_sha256, set and matching isolated stdio (name, command, realm_sha256); command absolute, from the verified rootfs, never under /workspace sse (name, url) — there is no binary to hash sha256 is the hash of the binary at command, re-hashed on every connect attempt, so swapping the file at a pinned path breaks the pin even though the path string still matches. env_sha256 covers the environment as declared — env keys and values, env_allowlist, env_inherit_all and the auth block — before any reference is resolved or an untrusted-pack rule changes it. It is a digest, so the allowlist never holds a value.\nThe two strict cases exist for the same reason. A .sonny/mcp.yaml is repository content, and a recipe\u0026rsquo;s mcp.servers replaces a configured server of the same name. Without the stricter pin, a commit to a trusted repository — or an untrusted recipe — could reuse the name and command of a server you approved (npx, say) and launch it with arguments of its own such as -y some-package, or keep the arguments and add NODE_OPTIONS=--require … to its env, and run code as you.\nLegacy entries Share link An entry that leaves args, work_dir or env_sha256 unset is how stdio entries were written before those fields existed. It keeps working for a server from your own mcp.yaml or a trusted recipe, and each session that uses it prints a one-line hint to re-approve it. It never approves a server an untrusted recipe declares, nor one the project chain defines; the refusal names the fields, never their values, and prints the command to run.\nThe integration gate, project servers, and --project Share link For a server whose winning definition comes from the working directory or any ancestor, approve exactly that definition from inside the repository:\nsonny mcp approve server \u0026lt;name\u0026gt; --project It prints the same preview as approve pack, then pins args, work_dir and env_sha256 as the chain spells them. It cannot be combined with --command, --url, the launch flags or --write-config. The refusal prints the command with the name shell-quoted, and a name holding anything other than letters, digits, ., _ and - is never printed into a command at all. All refusal and preview text is stripped of terminal control sequences.\nApproving servers Share link Command Effect sonny mcp approve server \u0026lt;name\u0026gt; (--command cmd [--arg a …] [--work-dir dir] [--env KEY=VALUE …] | --url url | --project) Approve one server without a recipe. A stdio entry pins the --arg list, --work-dir and --env it is","permalink":"https://sonny.sh/docs/mcp/trust-model/","tags":["mcp","security","reference"],"title":"MCP Trust Model"},{"content":" Supported Providers Share link Share this section Permalink Share via Twitter LinkedIn Facebook Sonny supports multiple LLM providers. Each provider has specific configuration requirements.\nDeclaring a Provider Share link Use the provider field in your configuration to specify the LLM provider.\nSupported Providers Share link openai anthropic ollama Authentication Modes Share link Each provider supports different authentication methods:\nOpenAI: API key via OPENAI_API_KEY. Anthropic: API key via ANTHROPIC_API_KEY. Ollama: Token via OLLAMA_TOKEN. For more details, see Credentials .\n","permalink":"https://sonny.sh/docs/packs/providers/","tags":null,"title":"Supported Providers"},{"content":" chat Command Share link Share this section Permalink Share via Twitter LinkedIn Facebook Synopsis Share link The chat command allows users to interact with Sonny in a conversational manner.\nUsage Share link sonny chat [flags] [arguments] Flags Share link Flag Shorthand Type Default Description --input -i String None Provide input for the conversation. --verbose -v Boolean false Enable verbose output for debugging purposes. Examples Share link Start a Conversation Share link sonny chat --input \u0026#34;Hello, Sonny!\u0026#34; Start a Conversation in Verbose Mode Share link sonny chat --input \u0026#34;Tell me a joke.\u0026#34; --verbose Caveats Share link Ensure the input is properly formatted to avoid misinterpretation. The chat command is designed for interactive use; scripting may require additional flags. For more information, refer to the Sonny CLI Overview .\n","permalink":"https://sonny.sh/docs/cli/chat/","tags":["cli","chat","interaction"],"title":"Sonny CLI: chat Command"},{"content":" MCP Server Catalog Share link Share this section Permalink Share via Twitter LinkedIn Facebook Ten MCP servers ship under the sonny.sh/mcp/ namespace. Each is its own Go module in its own private repository. The sonny.sh/mcp/\u0026lt;name\u0026gt; pages provide machine-readable go-import metadata for authorized consumers. Browser visitors stay on the public documentation site. These modules are commercial software; see Licensing and access .\nServer What it covers Entrypoint analyst Reproducible statistical analysis cmd/analyst atlassian The complete Atlassian API capability surface cmd/atlassian code-manager Code, dependency and delivery intelligence for the CodePros organisation cmd/code-manager forensics Evidence-preserving forensic orchestration cmd/forensics msft Microsoft Graph and related Microsoft API surface, as permission-scoped tools cmd/msft-mcp netcap Capture, deep packet inspection, assets and controlled packet modification cmd/netcap project-manager Project management with evidence-preserving transcripts cmd/project-manager, cmd/pm-parse-worker threat-intel Provenance-aware threat intelligence cmd/threat-intel vuln Normalised vulnerability intelligence cmd/vuln web-crawler Controlled web acquisition and extraction cmd/web-crawler Each server also ships a generated configs/\u0026lt;name\u0026gt;.json server configuration, a docs/ tree, and (for most) a minimal examples/basic program, generated by apic from its API contract.\nModule paths matter here Share link The vanity prefix and the Go module path agree for eight of the ten, but not all:\nServer go.mod module path analyst, atlassian, forensics, netcap, project-manager, threat-intel, vuln, web-crawler sonny.sh/mcp/\u0026lt;name\u0026gt; code-manager gitlab.com/devnw/codepros/sonny/mcp/code-manager msft devnw.dev/mcp/msft For the eight, go install sonny.sh/mcp/\u0026lt;name\u0026gt;@latest works: the redirect and the declared module path agree. For code-manager and msft the vanity URL still redirects the browser and still resolves the repository, but Go requires the required path and the declared path to match, so install from the declared path:\ngo install sonny.sh/mcp/analyst@latest go install gitlab.com/devnw/codepros/sonny/mcp/code-manager/cmd/code-manager@latest go install devnw.dev/mcp/msft/cmd/msft-mcp@latest Warning These are private modules. go install needs access to the repositories and, for a private host, GOPRIVATE/GONOSUMDB and credentials configured. Check each repository\u0026rsquo;s own status before relying on it: several are at an early milestone — code-manager describes itself as the M0 foundation with real MCP wiring landing later, and msft as a bootstrap skeleton whose generated tool surface lands under its tracked epics. Read the repository\u0026rsquo;s README.md and docs/ for the current tool surface rather than assuming one. Registering one with Sonny Share link Write the binary you built into an mcp.yaml server entry, then approve it so the allowlist pins it (Trust model ):\n# ~/.config/sonny/mcp.yaml servers: - name: analyst type: stdio command: /usr/local/bin/analyst env_allowlist: [\u0026#34;ANALYST_TOKEN\u0026#34;] sonny mcp approve server analyst --command /usr/local/bin/analyst sonny mcp list --connect Or write a project-scoped entry in .sonny/mcp.yaml and approve it from inside the repository:\nsonny mcp approve server analyst --project Servers that expose an SSE endpoint are registered with type: sse and url, and must declare an egress.allow_hosts policy naming their own host — remote egress is deny-by-default and a remote server without a policy loads but is never connected.\nTransport notes Share link Most servers are built from the same generated skeleton, whose default server configuration listens on 127.0.0.1 with a /health endpoint, a /ws/mcp WebSocket route, and transports including http, websocket and stdio. Sonny speaks stdio and sse to MCP servers. Configure these servers as stdio subprocesses (or as sse with an egress policy); Sonny does not connect to a raw HTTP or streamable_http MCP endpoint. Consult each repository for how to select its stdio transport.\nRunning Sonny\u0026rsquo;s own tools as an MCP server Share link The catalog is not one-directional. sonny mcp-server exposes Sonny\u0026rsquo;s own catalog-classified, host-pure tools over stdio, so another MCP client can drive Sonny:\nsonny mcp-server --allowed-tools read_file,edit_file Flag Effect --allowed-tools \u0026lt;csv\u0026gt; comma-separated tool names to expose; empty exposes none --allow-host-effects explicitly permit model-driven filesystem, network and process effects on this host --realm-contained require and use Sonny\u0026rsquo;s verified realm process environment --proxy-config \u0026lt;file\u0026gt; / --proxy-server \u0026lt;name\u0026gt; expose an allowlisted upstream MCP definition through Sonny as a traced proxy --supervisor-endpoint / --supervisor-tenant / --supervisor-session serve admitted tools from an existing supervisor Warning Effectful tools are disabled by default and the surface refuses to run under a dangerous","permalink":"https://sonny.sh/docs/mcp/servers/","tags":["mcp","reference"],"title":"MCP Server Catalog"},{"content":" Model Configuration Share link Share this section Permalink Share via Twitter LinkedIn Facebook Sonny allows you to configure models for specific tasks.\nModel Profiles Share link Define model profiles in models.yaml. Each profile specifies:\nname: Model name. provider: Associated provider. parameters: Model-specific parameters. Example Share link profiles: - name: \u0026#34;gpt-4\u0026#34; provider: \u0026#34;openai\u0026#34; parameters: max_tokens: 1000 temperature: 0.7 - name: \u0026#34;claude-v1\u0026#34; provider: \u0026#34;anthropic\u0026#34; parameters: max_tokens: 800 temperature: 0.6 ","permalink":"https://sonny.sh/docs/packs/models/","tags":null,"title":"Model Configuration"},{"content":"Seed sonny\u0026rsquo;s config/data/cache directories on this host with a starter models.yaml. Idempotent — running it twice is harmless.\nUsage: sonny init [flags] sonny init [command] What it creates Share link Share this section Permalink Share via Twitter LinkedIn Facebook Directories:\nPath Contents config dir models.yaml, pack-sources.yaml config dir/models.d drop-in model catalog fragments data dir/packs user recipe YAMLs go here cache dir git-backed recipe clones Files (only if missing): models.yaml (embedded example, edit to taste).\nPass --system to use the system layout (/usr/local/share/sonny/...) in package post-install scripts. Pass --force to overwrite an existing models.yaml.\nFlags Share link Flag Type Description --default-model string optionally select a default model as source/model when none is saved --default-recipe string optionally select a default recipe when none is saved --force — overwrite existing files (models.yaml) -h, --help — help for init --system — use system data dir (for packagers / post-install scripts) All init subcommands accept the global flags .\ninit defaults Share link Select optional startup defaults. Existing choices are preserved. Skip either flag to keep automatic task routing. List choices with sonny recipe list --recipes and sonny models list.\nUsage: sonny init defaults [flags] Flag Type Description --default-model string optionally select a default model as source/model when none is saved --default-recipe string optionally select a default recipe when none is saved -h, --help — help for defaults init project Share link Harvest this directory\u0026rsquo;s agent-instruction files (AGENTS.md, CLAUDE.md, .claude/ commands/skills/agents, Cursor/Windsurf/Copilot files) into a generated recipe suite under .sonny/packs/.\nThe suite\u0026rsquo;s primary recipe inherits (parent:) from the nearest ancestor directory that has also run sonny init project, so instructions compose down the directory hierarchy. A drift manifest (.sonny/init-manifest.yaml) records source hashes; sonny run refreshes the harvested sections automatically when sources change.\nBy default the configured provider (--provider/--model, .sonny.yaml, or SONNY_PROVIDER/SONNY_MODEL) writes a synthesized main prompt; pass --no-llm (or configure no provider) for a deterministic template. Reruns never overwrite a hand-edited generated file: it is kept and reported as a conflict.\nUsage: sonny init project [flags] sonny init project [command] Flag Type Description --force — replace hand-edited generated files (explicit destructive choice) -h, --help — help for project --json — print the result as one JSON document --mcp — harvest MCP server configs into .sonny/mcp.yaml (remembered; --mcp=false disables) --no-llm — skip LLM synthesis; use the deterministic template Subcommands drive the same init service step by step and never prompt:\nSubcommand Purpose abandon Abandon an unfinished init operation apply Apply a reviewed init plan by id (never prompts) campaigns List evaluation, optimization, promotion and rollback receipts canary Explicit Canary actions: legacy migration and the evidence session dataset Build and list the project\u0026rsquo;s history datasets eval Evaluate the project\u0026rsquo;s recipe against its imported history dataset history Import this project\u0026rsquo;s attributable agent history (Sonny, Claude, Codex) index Publish the project\u0026rsquo;s local source index optimize Optimize the project\u0026rsquo;s recipe against its history dataset (staged, never activated) plan Show what an init apply would do, without writing anything promote Activate an improved optimization candidate by receipt rebind Bind the project\u0026rsquo;s private init state to this checkout repair Regenerate missing or stale init outputs, keeping hand-edited files resume Resume an unfinished init operation rollback Re-activate a recorded generation of the project\u0026rsquo;s recipes setup Provision local isolation and a protected analysis deployment status Show the project\u0026rsquo;s init state and per-subsystem readiness init project setup Share link Provision a per-user rootless Docker engine (or use the system Docker engine when it runs with userns-remap), verify a signed Sonny isolation image, and grant this project\u0026rsquo;s enabled analysis pipeline access to its workspace and configured model providers. Existing operator configuration is preserved.\nUsage: sonny init project setup [flags] Flag Type Description --dir string project directory (default: current directory) -h, --help — help for setup --host-local — grant analysis without an isolation realm (selected automatically when no usable isolation Docker engine exists) ","permalink":"https://sonny.sh/docs/cli/init/","tags":["cli","init","project"],"title":"sonny init"},{"content":" Credential Management Share link Share this section Permalink Share via Twitter LinkedIn Facebook Sonny securely manages credentials for LLM providers.\nCredential Sources Share link Credentials can be specified via:\nEnvironment Variables: OPENAI_API_KEY, ANTHROPIC_API_KEY, etc. Configuration Files: .sonny.yaml under $HOME. OS Credential Store: Managed by Sonny\u0026rsquo;s credential broker. Adding Credentials Share link Use the sonny credentials add command:\nsonny credentials add --provider openai --key \u0026#34;your-api-key\u0026#34; Rotating Credentials Share link sonny credentials rotate --provider openai --key \u0026#34;new-api-key\u0026#34; Security Note Share link Secret values are never displayed in logs or outputs. For more details, see Troubleshooting .\n","permalink":"https://sonny.sh/docs/packs/credentials/","tags":null,"title":"Credential Management"},{"content":"Manage sonny\u0026rsquo;s configuration file and inspect the effective, merged configuration.\nConfiguration can be specified via:\nCommand line flags Environment variables (SONNY_* prefix) Configuration file (.sonny.yaml in the working directory, then $HOME) Directory configuration (the directory: block of every .sonny.yaml from the filesystem root down to the working directory; created by sonny config init) Usage: sonny config [command] All config subcommands accept the global flags .\nconfig show Share link Share this section Permalink Share via Twitter LinkedIn Facebook Print the effective configuration merged from flags, environment, and config file.\nFlag Type Description -h, --help — help for show --supervisor-deployment string inspect this protected deployment without activation (default: SONNY_SUPERVISOR_DEPLOYMENT) Its ## From flags/environment: block also carries the two resolved dangerous-mode lines, auto_approve and isolation_bypassed, each with the origin that armed or forbade it. An ignored project .sonny/permissions.yaml is reported on stderr, so the posture block stays pipeable. This command reports; it never refuses.\nsonny config show config explain Share link Show, for every configuration key, the value that wins and every lower-precedence candidate it beat, with where each one lives. Secret-bearing keys show presence and location only.\nFlag Type Description -h, --help — help for explain --json — emit the versioned JSON envelope on stdout --supervisor-deployment string inspect this protected deployment without activation (default: SONNY_SUPERVISOR_DEPLOYMENT) config validate Share link Validate the analysis.version=1 configuration across its layers (protected operator base, user, global, project, environment, invocation) and report known startup blockers, without activating analysis or calling a model. Graph and policy validation does not attest provider capabilities or authorize model calls.\nFlag Type Description -h, --help — help for validate --json — emit the versioned JSON envelope on stdout --supervisor-deployment string inspect this protected deployment without activation (default: SONNY_SUPERVISOR_DEPLOYMENT) sonny config validate does not consult dangerous-mode policy at all.\nconfig init Share link Add a starter directory: block to .sonny.yaml.\nFlag Type Description --force — replace an existing directory: block, create ./.sonny.yaml even when it hides $HOME/.sonny.yaml, or write through a symlink that dangles or leaves the working directory -h, --help — help for init config path Share link Show every standard configuration / data / cache path.\nFlag Type Description -h, --help — help for path config resource-reuse Share link Show the effective resource_reuse configuration and its digest.\nFlag Type Description -h, --help — help for resource-reuse --json — emit the versioned JSON envelope on stdout Credential resolution: sonny credentials Share link Credential resolution is part of configuration, so it is documented here rather than on its own page. The command is sonny credentials — plural. There is no sonny credential command.\nsonny stores Copilot/Hyper OAuth tokens through the credentials.Broker (keychain, or a sealed file store) once a one-time migration has copied and verified every row from the legacy sessions.db table; until then the legacy store remains authoritative.\nUsage: sonny credentials [command] Available Commands: delete Remove the API key or sign-in stored in the keyring for a model source or provider migrate-status Migrate (or resume migrating) legacy OAuth tokens to the credential broker, and report status resolve Report which credential tier (keyring, bao, environment, .env) answers a catalog source, never the value retire-legacy Zero and delete the legacy OAuth rows and master key once migration has been verified Flag Type Description -h, --help — help for credentials credentials delete Share link sonny credentials delete \u0026lt;source or provider id\u0026gt; removes what Settings' Disconnect removes: every credential the broker holds under the provider id, and the key a model source stored under its own account (the keyring tier sonny credentials resolve reports). Environment variables, .env files and Bao are not touched. Exit 1 when nothing was stored.\nFlag Type Description -h, --help — help for delete credentials migrate-status Share link Run (or resume) the legacy OAuth token migration and report its state. Safe to run repeatedly.\nFlag Type Description -h, --help — help for migrate-status credentials retire-legacy Share link Zero and delete the legacy OAuth rows and master key once migration has been verified, after the confirmation window has passed.\nFlag Type Description --force — retire immediately, bypassing the confirmation window -h, --help — help for retire-legacy credentials resolve Share link Report which secret-resolver tier (keyring, bao, environment, .env) supplies a catalog source\u0026rsquo;s credential — never the value.\n","permalink":"https://sonny.sh/docs/cli/config/","tags":["cli","config","settings"],"title":"sonny config"},{"content":" Troubleshooting Share link Share this section Permalink Share via Twitter LinkedIn Facebook Resolve common issues in Sonny.\nMissing Credential Share link Error Share link Error: Missing credential for provider \u0026#39;openai\u0026#39;. Fix Share link Add the credential:\nsonny credentials add --provider openai --key \u0026#34;your-api-key\u0026#34; Unknown Provider Share link Error Share link Error: Unknown provider \u0026#39;xyz\u0026#39;. Fix Share link Check the provider name and use a supported one:\nopenai anthropic ollama Invalid Recipe Manifest Share link Error Share link Error: Invalid manifest.yaml: missing \u0026#39;name\u0026#39; field. Fix Share link Ensure the manifest includes all required fields:\nname: \u0026#34;example-pack\u0026#34; version: \u0026#34;1.0.0\u0026#34; description: \u0026#34;A minimal example pack.\u0026#34; ","permalink":"https://sonny.sh/docs/packs/troubleshooting/","tags":null,"title":"Troubleshooting"},{"content":"Manage git-backed recipe sources listed in pack-sources.yaml (add/list/update/remove tracked repos), separately from the prompt packs themselves (loaded by the standard discovery pipeline).\nsonny tracks recipe repositories listed in pack-sources.yaml under $XDG_CONFIG_HOME/sonny. Use these subcommands to add, list, update, or remove tracked repos. Cloned repos live under $XDG_CACHE_HOME/sonny/pack-repos and are loaded by the standard recipe discovery pipeline on startup.\nUsage: sonny recipe [command] Aliases: recipe, pack pack is an alias, so sonny pack list and sonny recipe list are the same command. The help output prints the primary name recipe.\nAll subcommands accept the global flags .\nSubcommands Share link Share this section Permalink Share via Twitter LinkedIn Facebook Subcommand Purpose add Register and clone a new recipe source approve-routing Trust a recipe source\u0026rsquo;s model routing (endpoints and credential env var names) artifact Inspect captured tool results without putting them in model context default Show, set, or clear the persisted default prompt recipe eval Run a recipe\u0026rsquo;s eval suite through the configured provider evidence Score two per-case ledgers into one paired evidence record and decide promotion inspect Show resolved scope, sub-scopes, validation, and provenance for a recipe list List tracked recipe sources (or --recipes to list prompt recipes) migrate Rewrite a v1 prompt recipe as v2 (in-place by default) optimize Evolve a recipe\u0026rsquo;s system prompt against its eval suite pin Pin a model-pack variant for a session, or as a pack-wide standing default qualify Assemble and freeze a model-variant campaign\u0026rsquo;s qualification document reload Force-refetch one or all tracked recipe sources remove Drop a recipe source and delete its cache directory replay Reproduce a pinned variant decision from a lock and a snapshot resolve Emit the fully-resolved, tool-aware compiled subject for a recipe search BM25-rank loaded prompt recipes against a query snapshot Write a replayable variant-resolution snapshot for one logical recipe update Fetch + reset tracked sources to their upstream branch tip validate Statically validate a recipe YAML file variant Operate on one model recipe variant envelope file variants List the model-pack variants discovery found for one logical recipe recipe add Share link sonny recipe add \u0026lt;git-url|dir\u0026gt;[@\u0026lt;ref\u0026gt;]\nRegister and clone a new recipe source from git-url. A local directory (., ./dir, ~/dir or an absolute path) works too: a git repository is cloned on its checked-out branch, and any other directory is snapshotted into the cache and re-read on every sync or reload (the directory itself is never modified). Append @\u0026lt;ref\u0026gt; to the URL (e.g. https://example.com/packs.git@v1.2.3) to pin the source to a specific semver tag or to @latest for the newest available tag. Pinned sources are frozen: sonny will not auto-update them on startup; use sonny recipe reload \u0026lt;name\u0026gt; to refresh.\nFlag Type Description --approve-mcp — after cloning, record your approval of the MCP servers every pack from this source declares (inherited ones included), as sonny mcp approve pack does for each: a pack-source server launches only on such an approval --approve-routing — after cloning, trust the source\u0026rsquo;s model routing (endpoints and credential env var names its catalogs declare) by copying it into \u0026lt;config-dir\u0026gt;/models.d/pack-\u0026lt;name\u0026gt;-routing.yaml; see sonny recipe approve-routing --branch string branch to track (default \u0026ldquo;main\u0026rdquo;) --default-recipe string override this source\u0026rsquo;s repository default primary pack; an explicitly empty value disables its default -h, --help — help for add --mcp-auto-approve — connect this source\u0026rsquo;s MCP servers without a prompt for the packs that name them, once sonny mcp approve registry \u0026lt;name\u0026gt; records your approval of their unchanged definitions; false writes mcp_auto_approve: false, so they get the stricter explicit-approval pinning (unset: on only for a source with --mcp-d) (default true) --mcp-d stringArray mcp.yaml-style directory relative to the clone root whose *.yaml servers load for the packs whose mcp.allowed names them, overriding same-name mcp.yaml servers (repeat for multiple dirs) --mcp-env-allow strings host environment variable NAME this source\u0026rsquo;s MCP servers may read, e.g. the credentials its packs\u0026rsquo; servers reference (repeat, or comma-separate; persists mcp_env_allow in pack-sources.yaml) --model-catalog stringArray model-catalog YAML path relative to the clone root (repeat for multiple files); replaces the default model-registry/models.yaml --models-d stringArray models.d-style directory relative to the clone root whose *.yaml drop-ins override same-id local sources (repeat for multiple dirs) --name string override the directory name (defaults to repo basename) --no-model-catalog — do not load the source\u0026rsquo;s default model-registry/models.yaml (persists models_auto:","permalink":"https://sonny.sh/docs/cli/pack/","tags":["cli","pack","packs"],"title":"sonny recipe"},{"content":"The mesh command group provides tools for managing distributed LLM sessions across multiple nodes. Sessions can be pushed to remote nodes, transferred between nodes, and continue running even when disconnected from the local terminal.\nUse mesh serve to start a mesh node, mesh push to send sessions to remote nodes, and mesh attach to connect to running sessions.\nUsage: sonny mesh [command] Persistent flags Share link Share this section Permalink Share via Twitter LinkedIn Facebook These apply to every mesh subcommand except serve, which has its own listener-side flags.\nFlag Type Description --client-ca string CA bundle file used to verify the server cert --client-cert string client TLS certificate file (PEM) for mTLS-protected mesh endpoints --client-key string client TLS key (PEM filesystem path OR pkcs11: URI per RFC 7512) --client-key-pin string PIN file for PKCS#11 client key (optional) --dev-insecure-transport — DEVELOPMENT ONLY: allow plaintext, unauthenticated mesh channels on loopback addresses (equivalent to SONNY_MESH_INSECURE_DEV_PROFILE=i-accept-plaintext-mesh-not-for-production) --endpoint string mesh gRPC endpoint (default: mesh.endpoint config, SONNY_MESH_ADDR, or localhost:50052) -h, --help — help for mesh --server-name string SNI / cert-SAN match target (required when --client-cert is set) Mesh subcommands also accept the global flags .\nSubcommands Share link Subcommand Purpose adopt Adopt a running session using a pinned, single-use offer attach Attach to a running session detach Detach from a session exec Run a shell command on a remote mesh node via its A2A endpoint host Bootstrap and manage SSH-reachable mesh hosts human-key Manage the keys that let a device attach to mesh sessions as their human identity Provision and inspect mesh node identity material list List nodes and sessions (alias ls) nodes List mesh nodes peers Print this daemon\u0026rsquo;s mesh participation snapshot as JSON push Push a session to a remote node relate Grant, revoke and list peer relationships between root sessions on two nodes serve Start a mesh node sessions List mesh sessions terminate Terminate one or many mesh sessions (aliases kill, stop) transfer Transfer a session to another node sonny mesh service install|uninstall|run exists only on Windows builds (manages the mesh node as a Windows service); it is absent from this command tree entirely on Linux/macOS builds, where systemd/launchd own that job instead.\nmesh serve Share link Start a mesh node that can host and transfer sessions. The node listens for gRPC client connections for session streaming and compatibility, advertises on the Synapse QUIC peer plane using protobuf framing by default, retains WebSocket and JSON negotiation for rolling-upgrade compatibility, accepts sessions from other nodes, and sends heartbeats to announce availability.\nUsage: sonny mesh serve [flags] Flag Type Description --advertise-addr string address peers should use to reach this node\u0026rsquo;s gRPC surface, e.g. a reachable tailscale DNS name like \u0026ldquo;lothiriel:9052\u0026rdquo; (default: --grpc); required on a multi-host mesh because the bind address is not routable from a peer --authz-policy string path to the RBAC policy YAML (default /etc/sonny/mesh/authz.yaml) --ca string TLS CA certificate file --cert string TLS certificate file --control-db string path to the SQLite mesh control-plane DB (default \u0026lt;data\u0026gt;/control.db) --coordinator string mesh coordinator address (if joining existing mesh) --grpc string gRPC listen address (default \u0026ldquo;:50052\u0026rdquo;) -h, --help — help for serve --key string TLS key file --label strings node labels (key=value) --managed-manifest string private managed-worker deployment manifest (required in production builds) --max-sessions int maximum concurrent sessions (default 10) --mesh-peer strings quic:// peer URL (preferred), or ws:// development / wss:// mTLS compatibility URL (repeatable) --models strings supported models --name string node name (default: $(whoami)@$(hostname)) --node-id string node ID (default: auto-generated UUID) --providers strings supported LLM providers --recipes strings available recipes --region string node region (for routing) --synapse-listen string address Synapse listens on for QUIC/UDP (default) and WebSocket/TCP compatibility (default \u0026ldquo;:50053\u0026rdquo;) --trust-domain string SPIFFE trust domain for peer SVIDs (default mesh.local) --zone string node zone (for routing) mesh push Share link Usage: sonny mesh push [flags] Flag Type Description -f, --file string local recipe file whose recipe ID is sent (or use --pack) --follow — follow session output after push -h, --help — help for push --node string target node ID (default: best available) --persist — persist session on detach (default true) --recipe string ID of the recipe the target node runs (or use --file) --session-dir string absolute working directory for the session on the target node (default: the current directory) mesh attach and mesh detach Share link","permalink":"https://sonny.sh/docs/cli/mesh/","tags":["cli","mesh","distributed"],"title":"sonny mesh"},{"content":" sonny with no subcommand, or sonny run, opens the interactive terminal UI. This page walks through each region of that screen and explains what it is telling you. It is deliberately honest about where its illustrations come from — read the note at the bottom before you treat any of them as a screenshot.\nThe startup banner Share link Share this section Permalink Share via Twitter LinkedIn Facebook The first thing on your terminal is the wordmark and the version line, followed on stderr by any posture warnings (an armed dangerous mode, an isolation result that did not satisfy its minimum). The banner is the source of the sentences the sidebar repeats later, so if you dismissed a warning at startup the sidebar is where you get it back.\nFigure:Rendered from ascii.txt at commit 9726c080d8 (v0.12.0-818-g9726c080d8), not from a live run The version string in the banner carries the same descriptor sonny --version prints, so it is the fastest way to confirm which build you are actually talking to before you file a bug against it.\nThe chat pane Share link The chat pane owns the transcript and the input box. The transcript is a turn-ordered log of what you said, what the model said, and what every tool call did — and it is the surface where Sonny is most insistent that what is drawn matches what was measured.\nEach case below is a frame from the UI\u0026rsquo;s characterization fixture — the golden file the layout tests compare every render against.\nFigure:Typeset from cmd/sonny/ui/testdata/chat_update_characterization.golden, cases 007/010/011/014/016 The transcript behaviour those frames pin down is worth knowing before you trust a long session:\nYour submission appears immediately. The you ▸ row is written as part of the submit, not after the first model token arrives, so a slow provider never looks like a dropped message. A streamed replacement replaces. When the provider sends a full-text update rather than a delta, the existing assistant row is rewritten in place instead of appended to. You never see the same sentence twice. A completed turn is sealed. Once the turn-done event lands, the assistant row stops accepting deltas. Late text starts a new row rather than quietly editing an answer you already read. Above the input box sits a recipe pill showing the active recipe and version, the resolved profile (or routing… while that resolution is still in flight), and provider://model. A failed operation is kept in its own panel immediately above the input, wrapped so the whole message stays readable, and it clears when you dismiss it, send another message, clear the chat, or the operation succeeds.\nThe sidebar Share link The sidebar only renders when the terminal is at least 100 columns wide, and it is fixed at 36 columns. Narrow the window below 100 columns and the chat pane takes the whole screen — nothing is hidden behind a scroll, the sidebar simply is not drawn.\nFrom top to bottom it stacks, and each section renders only if it has something to show:\nSection Header What it reports Logo + status — ○ idle, ● \u0026lt;turn phase\u0026gt;, ✓ done, or ! \u0026lt;failure\u0026gt; Goal — Only a user-set goal (/goal or --goal); recipe defaults stay hidden Pills — Provider, model, context; a locked: … row while pinned LSP ▼ LSP (N) One row per language server, with diagnostic tallies MCP ▼ MCP (N) State, name, ×N call count, ⇢ when proxied, transport Skills ▼ Skills (N) Active skills first Dangerous modes ▼ Dangerous modes (N) Only when something is armed or refused Isolation ▼ Isolation \u0026lt;status\u0026gt; Never hidden — an unknown posture says so Sub-agents ▼ Sub-agents (N) Worker tree with pack: and model: Sessions ▼ Sessions (N) Local ▢ / remote ⇄, active tab, cwd, posture Mesh ▼ Mesh (N nodes) ★ self / ● peer, state, last-seen Ns Changed files ▼ Changed files (N) +12 -3 path/to/file.go Tools ▼ Tools (N) exec ×17, most-used first Efficiency ▼ Efficiency (N) The tool-efficiency block, see below Capabilities ▼ Capabilities (N) id state detail ×N, actionable first Alt+C flips every section at once; /sidebar flips one. When the stack is taller than the window the sidebar collapses sections itself, least important first, and a section you opened by hand is left alone.\nDangerous modes Share link This is the section you most want to stay empty. It appears only when auto-approve is on, the isolation bypass is in force, or a request for one of them was refused by policy. A quiet session renders nothing here, because a permanent auto-approve: off row is exactly the line an eye learns to skip.\nFigure:Typeset from docs/user/tui-reference.md at commit 9726c080d8, not from a live run Three details make this section trustworthy rather than decorative:\nThe two switches are never merged. Auto-approve and the isolation bypass are independently granted, independently revocable, and independently audited, so they get separate rows with their own origin (flag, env, config, session_prompt, slash_command). A refusal is marked differently. A policy refusal renders with ✗,","permalink":"https://sonny.sh/docs/tui/","tags":["tui","reference"],"title":"The TUI"},{"content":"Every default binding the TUI ships with, in one table per surface. Each remappable action is listed under the exact tui.yaml key that rebinds it, so this page doubles as the reference for a hand-written keymap. Source of truth: docs/user/tui-reference.md in the sonny repository.\nRemappable actions Share link Share this section Permalink Share via Twitter LinkedIn Facebook Every action below can be rebound in tui.yaml; these are the defaults that ship unconfigured.\nAction (tui.yaml key) Default keys Effect palette.open ctrl+p Open the command palette help.open f1 Open help tab.new ctrl+shift+t New chat tab (opens the recipe picker); soft cap of 9 tabs; only on terminals with keyboard enhancements tab.chord_leader ctrl+t Tab-switch leader chord — press, then n/p/t/1-9 tab.chord_next n After the tab-chord leader: next tab tab.chord_prev p After the tab-chord leader: previous tab tab.chord_new t After the tab-chord leader: new chat tab (recipe picker) — works on every terminal session.manager ctrl+s Open the session manager history.search ctrl+r Filter command history and load a selection into the composer without sending it session.picker ctrl+b Session picker (local tabs + attached remotes) search.quick ctrl+; Quick-search sessions/history/mesh session.jump alt+s One list of every destination — open tabs, recent sessions, sessions on mesh nodes — Enter goes straight there session.resume alt+r Choose a session from the current directory to resume mesh.attach_recent alt+m Re-attach the last remote mesh session, no dialog search.files ctrl+/ Find file agent.picker ctrl+g, ctrl+shift+a Sub-agent picker (also /sub-agents) mesh.actions ctrl+n Mesh actions (node picker) tab.close ctrl+w Close the current tab (quits if last) — deletes the previous word instead while the draft is non-empty tab.next ctrl+tab, alt+n Cycle to the next chat tab tab.prev ctrl+shift+tab, alt+p Cycle to the previous chat tab tab.move_left alt+shift+left Move the current tab left tab.move_right alt+shift+right Move the current tab right tab.last alt+l Jump to the last-active tab remote.terminate ctrl+k Terminate the attached remote session — kills to end of line instead when the draft has text after the caret alert.dismiss ctrl+x Dismiss the failure panel above the input queue.clear ctrl+q Clear the prompt queue, after a y / n confirmation; a no-op with an empty queue queue.focus ctrl+e Toggle queue focus — moves to end of line instead when the draft has text after the caret clipboard.paste ctrl+v Paste (an image becomes an @path mention; otherwise plain text) clipboard.copy_reply ctrl+y Copy the last assistant reply sidebar.toggle_sections alt+c Collapse or expand every sidebar section (same as /sidebar toggle all) chat.expand alt+z Toggle full-width chat: hide the sidebar (same as /expand) transcript.verbose ctrl+o Toggle verbose transcript (same as /verbose) transcript.nav ctrl+up Leave the composer for transcript navigation mode; inside navigation it also steps to the previous message transcript.find ctrl+f Find in the session transcript — enters navigation mode if needed, or refocuses an open find bar session.plan_mode shift+tab Toggle the session\u0026rsquo;s requested mode between plan and execute (same as /plan); bound in the composer only. Rebinding it onto another action\u0026rsquo;s chord is refused tab.jump_1 … tab.jump_9 ctrl+1…ctrl+9, alt+1…alt+9 Jump directly to tab N Transcript navigation Share link Press ctrl+up to review a long reply without scrolling by hand: it leaves the composer and puts the transcript into navigation mode, selecting the most recent message and scrolling it fully into view. y/c then copy the whole message, or just its code blocks, in one keystroke. Navigation mode is per-tab and never saved; switching tabs or /clear exits it. Streaming output never moves your selection.\nThese actions act only while navigation mode has focus; outside it the same keys type into the composer.\nAction (tui.yaml key) Default keys Effect transcript.nav_prev up, k, shift+tab Select the previous message transcript.nav_next down, j, tab Select the next message transcript.nav_first g, home Select the first message transcript.nav_last shift+g (G), end Select the last message transcript.nav_page_up pgup Scroll up a page and select a visible message transcript.nav_page_down pgdn Scroll down a page and select a visible message transcript.nav_half_page_up ctrl+u Scroll up half a page and select a visible message transcript.nav_half_page_down ctrl+d Scroll down half a page and select a visible message transcript.copy y Copy the selected message transcript.copy_code c Copy the code blocks of the selected message transcript.nav_find / Find in the transcript (opens the find bar) transcript.find_next n Jump to the next find match transcript.find_prev shift+n (N) Jump to the previous find match transcript.nav_exit ctrl+down, esc, q, i Leave navigation and return to the chat composer Find bar Share link ctrl+f (or / once navigating) opens the find bar.","permalink":"https://sonny.sh/docs/tui/shortcuts/","tags":["tui","reference"],"title":"TUI shortcuts"},{"content":"Sonny is proprietary commercial software, not an open-source project. This page is for developers with CodePros-authorized source access. See Licensing and access .\nSonny is a Go project. Everything below is the contributor contract enforced by the repository\u0026rsquo;s own CI jobs and Git hooks, so a change that passes locally has a good chance of passing review.\nThe canonical source for this page is CONTRIBUTING.md at the root of the repository. If the two ever disagree, the repository file wins.\nSource access Share link Share this section Permalink Share via Twitter LinkedIn Facebook Repositories, issue tracking, and release assets are private. Use the access provided to your organization by CodePros.\nThe command-line engine lives in cmd/sonny/cli, the terminal UI in cmd/sonny/ui, and the distributed mesh in mesh/. The command surface is documented on the CLI reference and the terminal behaviour in the getting started guide .\nPrerequisites Share link The module declares go 1.27.3 in go.mod. Use that toolchain or newer — the lint and hook setup installs the same pinned version and toolchain as CI rather than falling back to whatever binary is on your PATH.\nLint runs through a pinned golangci-lint v2.14.0, installed into ./bin by the make targets. Keep bin/ between runs and share the normal Go and golangci-lint caches across worktrees.\nBuild, test, lint Share link # Build both binaries go build ./... # Quick unit tests go test -short -timeout=90m ./... # Full test suite (race + cover) go test -race -coverprofile=coverage.out ./... # Lint with the same pinned golangci-lint and .golangci.yml as CI\u0026#39;s go-lint job, # plus the nolint, complexity and skip-guard scripts make lint # Apply the formatters configured in .golangci.yml via golangci-lint fmt make fmt # Lint the build-tag and GOOS variants the default lint never loads # (integration/e2e/desktop/prod/pam tags, *_windows.go, *_darwin.go) make lint-variants # Check formatting without rewriting anything make fmt-check make lint adds --timeout 75m --concurrency 2 in CI and reports only issues new since the repository\u0026rsquo;s baseline revision, so a local run also surfaces older debt that CI will not fail you on. make fmt-check by default covers only the files the Linux lint never loads (the //go:build !linux files and the _windows.go / _darwin.go variants). Set FMT_CHECK_BASE=\u0026lt;ref\u0026gt; to add the files changed since that ref — that is what CI\u0026rsquo;s go-fmt-check job runs — or FMT_CHECK_ALL=1 to check the whole tree.\nRun make lint, make fmt and make fmt-check before opening a pull request. CI does not run those targets verbatim: its go-lint job applies the same linter and config as a new-issue ratchet, go-fmt-check runs make fmt-check FMT_CHECK_BASE=\u0026lt;diff base\u0026gt;, and the guard scripts run as their own jobs.\nHow much local lint covers Share link The local hook (including govet) checks the packages containing your changed Go files, and includes staged deletions. Deleting a whole package, changing module/workspace dependencies, or changing shared linter or hook settings runs the full root module. Nested modules stay outside the root-module check; CI still does the full-module lint. A package with no files in the default Linux build (Windows-only, desktop-tagged) is skipped locally — make lint-variants covers those.\nRepository build entry points have a hard ceiling of 12 workers. Memory-heavy checks stay lower: the default linter uses four package workers and one gosec worker per package, while CI lint and race suites use two. Explicit GOMEMLIMIT and GOMAXPROCS values below the 12-worker ceiling take precedence; larger concurrency values are clamped. A cold run still analyses the complete dependency graph; subsequent unchanged runs reuse that work.\nTo diagnose a slow lint run, use the pinned binary with profiling:\nbin/golangci-lint run -v --cpu-profile-path /tmp/sonny-lint.cpu ./... go tool pprof -top /tmp/sonny-lint.cpu The verbose log separates package loading from analyzer time. Set GL_DEBUG=goanalysis/issues/cache to see which packages miss the analysis cache.\nPull request rules Share link One concern per PR. If you find yourself touching unrelated files, split it. New behaviour requires a test that fails before the change and passes after. No mocks. A test may skip only when its environment genuinely cannot run it — a missing tool, testing.Short(), an unsupported OS/arch, or a missing credential/service — and the skip message must say which. Any other skip names a ticket (SONNY-, ENG-, SEC- or SRE-). A skipped premise hides a regression, so fail it with t.Fatalf instead. This is enforced by scripts/skip-reason-guard.py. Gap-driven changes should reference the relevant *GAP_ANALYSIS.md ID in the commit message, for example fix: close Q-007 - add .golangci.yml. Generated files (gen/**/*.gen.go, mesh/proto/**/*.pb.go) are regenerated, never hand-edited. If the regeneration pipeline breaks, surface that as a separate gap. Publish whole files with","permalink":"https://sonny.sh/docs/contributing/","tags":["contributing","development"],"title":"Authorized development"},{"content":"Sonny reads exactly four kinds of configuration: the hierarchical .sonny.yaml file, the project .sonny/ directory, environment variables (SONNY_*, SONNY_*), and command-line flags. This page tells you where each lives, which one wins, and how to check the answer.\nWhere files live Share link Share this section Permalink Share via Twitter LinkedIn Facebook Sonny layers the XDG basedir spec on top of Go\u0026rsquo;s standard library directory resolvers, so $XDG_*_HOME always wins when set — on every OS. Every path ends in /sonny so the base directories can be shared with other tools.\nPurpose Env override Linux/BSD default macOS default Windows default Config $XDG_CONFIG_HOME ~/.config/sonny ~/Library/Application Support/sonny %AppData%\\sonny Data $XDG_DATA_HOME ~/.local/share/sonny ~/Library/Application Support/sonny %AppData%\\sonny Cache $XDG_CACHE_HOME ~/.cache/sonny ~/Library/Caches/sonny %LocalAppData%\\sonny State $XDG_STATE_HOME ~/.local/state/sonny same as data same as data System data $XDG_DATA_DIRS /usr/local/share/sonny, /usr/share/sonny /Library/Application Support/sonny %ProgramData%\\sonny The four directories hold different things, and the split matters when you are backing up or cleaning up:\nConfig — models.yaml (the model catalog) and models.d/*.yaml, pack-sources.yaml, hooks.yaml, tui.yaml, mcp.yaml, mcp.allowlist.yaml, agents.yaml, plus the trusted-key directories pack-keys/*.pub and catalog-keys/. Data — packs/ (installed recipes), traces/ (conversation traces, on by default), sessions.db, control.db, memory/\u0026lt;id\u0026gt;.yaml. Cache — pack-repos/\u0026lt;name\u0026gt;/ (cloned git recipe sources), memindex/, worktrees/. Safe to delete, except that worktrees/ can hold a delegated child\u0026rsquo;s work until it is integrated. State — fitness.yaml, active-tabs.json, agent-availability.yaml, worker-spawn-audit.jsonl. To move everything to another disk, set XDG_CONFIG_HOME, XDG_DATA_HOME, XDG_CACHE_HOME and XDG_STATE_HOME before launching.\nThe precedence chain Share link For every key Sonny knows about, the effective value is the highest-priority source that set it:\ncode default / bound flag\u0026#39;s default (lowest) \u0026lt; .sonny.yaml \u0026lt; SONNY_* environment variable \u0026lt; an explicitly-set command-line flag (highest) The one thing to internalise: the environment beats the config file. That is the opposite of a typical .env-first CLI, and it is deliberate — Sonny loads the file into the config layer, then consults the environment, and only an explicit flag outranks that.\n# .sonny.yaml log_level: info export SONNY_LOG_LEVEL=debug # wins over the file sonny config show --log-level warn # wins over the environment Use sonny config show to print the effective provider, model, debug and log_level — the cheapest way to confirm which source actually won.\nNested keys are file- and flag-only Share link mesh.* and trace.* are dotted paths into a sub-map. The automatic environment lookup only substitutes - for _; it never turns . into _, so SONNY_MESH_ENDPOINT is not consulted for mesh.endpoint. Nested keys come from .sonny.yaml or from a flag bound to that key (--mesh-addr, --mesh-peer, --trace-server). The exception is a small set of keys bound by hand with an explicit name, which do sit in the normal environment tier.\n.sonny.yaml Share link The hierarchical file, found by walking from the current directory up to $HOME. Write a project\u0026rsquo;s version with sonny init.\nprovider: anthropic model: claude-sonnet-4-5-20250929 log_level: info permission_mode: acceptEdits workdir: /srv/work/my-service models: coding: provider: anthropic model: claude-sonnet-4-5-20250929 effort: high session: persist: true max_age: 30d mesh: endpoint: 127.0.0.1:50052 peer_addr: 0.0.0.0:50053 control_db: ~/.local/share/sonny/control.db workdir re-roots the search, so a project file in \u0026lt;workdir\u0026gt;/.sonny.yaml is found from anywhere. --config \u0026lt;file\u0026gt; replaces the whole search with one file.\nOperator-only keys Share link The trace.upload.*, trace.server.*, mesh.*, variants.* and agent_messaging.* blocks are operator-only: a project\u0026rsquo;s .sonny.yaml cannot set them unless that directory is a trusted workspace. A cloned repository therefore cannot route your telemetry or enrol you in a mesh just by being cloned. Put those keys in your $HOME file.\nThe project .sonny/ directory Share link Alongside .sonny.yaml, Sonny walks the same path collecting .sonny/ directories:\n.sonny/ ├── pack.yaml # project recipe (the entry point) ├── packs/ # other project packs ├── models.yaml # project model catalog ├── mcp.yaml # project MCP servers ├── config.yaml # project command surface └── hooks.yaml # lifecycle hooks (working directory only, never walked) The merge is outer-first: the directory nearest the filesystem root is lowest priority, the one nearest your working directory is highest. A .sonny/ directory is only read if it is owned by you or by root; an untrusted one — left behind by another user on a shared machine — is skipped silently.\nA project cannot","permalink":"https://sonny.sh/docs/configuration/","tags":["configuration","reference"],"title":"Configuration"},{"content":"Sonny reuses a few ordinary words for specific things. This page defines them the way the CLI, the configuration files, and the error messages use them, and links to the page that goes deeper.\nRecipe Share link Share this section Permalink Share via Twitter LinkedIn Facebook A recipe is the YAML file that tells Sonny who to be: which LLM profile or profiles to call, which tools it may use, and what system prompt to render. Recipes are versioned (version: 2 is the current schema), they compose through parent, includes, and sub-packs, and a recipe never contains a model name, endpoint, or credential — it names a category instead. See Recipes and authoring a recipe .\nBase recipe Share link The embedded doctrine every recipe composes with: the shared operating instructions that apply regardless of which recipe you select. You do not select it yourself; it is always part of the rendered system prompt.\nProfile (LLM profile) Share link An entry in a recipe\u0026rsquo;s llm_profiles list. A profile names a role — code, reasoning, fast, orchestrator — through a provider URI rather than pinning a concrete model. Which model actually serves a role comes from the catalog and the routing policy, so the same recipe runs against a different provider on a different machine with no edits. See Providers and models .\nProvider URI Share link The \u0026lt;scheme\u0026gt;://\u0026lt;model\u0026gt;?key=value dialect a recipe uses to name an LLM endpoint. It is a small addressing grammar, not a real URL: the scheme selects the transport, the path names the model, and the query parameters map onto request settings.\nCategory Share link A role name a recipe asks for (code, reasoning, fast, …) instead of a concrete model. Sonny resolves a category at pack-load time against your per-user catalog, ranking the candidates by a fitness score under a routing policy and picking the winner.\nCatalog Share link Your models.yaml file: the list of model sources (provider scheme, optional endpoint, credential environment-variable name) and how each source\u0026rsquo;s models map onto categories with fitness and optional pricing. It is the only place model names live. See models.yaml .\nCredential Share link The environment variable name a source reads its API key from — never the key itself. Because a catalog stores a name and not a secret, a catalog file and a recipe file are both safe to commit. See Credentials .\nSession Share link One conversation, stored as a row plus its turns in \u0026lt;data-dir\u0026gt;/sessions.db (SQLite, WAL mode, directories 0700). Sessions carry a stable, never-reused session number, the recipe id and version, the provider and model, and a working directory. --continue, --session, and --new choose which one a run picks up. See Sessions and the configuration reference .\nTrace Share link The append-only record of what happened during a run — turns, tool calls, results — written under the state directory and read back with sonny trace. Traces are what \u0026ldquo;the model said it ran the tests\u0026rdquo; is checked against.\nRecipe keys (pack-keys/) Share link The directory holding per-pack signing and trust material. It resolves from $SONNY_PACK_KEYS_DIR, falling back to ~/.config/sonny/pack-keys.\nAuthority mode Share link The control posture for tool approvals: off (authority is never consulted), observe (every decision is computed and recorded as a counterfactual, but the call is always admitted), and enforce (a missing, stale, or unavailable authority view fails the operation closed). It comes from the single SONNY_AUTHORITY_MODE environment variable, resolved once at process startup. sonny control status reports the effective mode.\nGrant Share link An approval recorded for one exact call, not for a tool in general. A grant that matches a different call does not apply. Older, broader \u0026ldquo;always allow\u0026rdquo; style grants are automatically quarantined rather than honoured.\nToolcap Share link The output-size cap every tool result passes through before it reaches the model, so one verbose command cannot crowd out the rest of the context.\nMCP server Share link A Model Context Protocol server Sonny connects to for extra tools. Servers are declared in configuration and are subject to approval: connecting one and using its tools are separate decisions. See MCP servers and configuration .\nMesh Share link Sonny\u0026rsquo;s distributed layer: a set of long-running sonny mesh serve nodes that host sessions, so a session survives your terminal closing and can be attached to from another machine or transferred between nodes. The mesh is the reason a single chat can scale out to sessions running on machines you control. See Running the mesh .\nNode Share link One running sonny mesh serve process. A node exposes a gRPC client plane (default :50052) that CLI tools, the TUI, and sonny-api dial, and a separate Synapse peer plane (default :50053) that nodes use to talk to each other over QUIC and protobuf, with WebSocket kept as an explicit compatibility transport. Production nodes fail","permalink":"https://sonny.sh/docs/glossary/","tags":["reference","concepts"],"title":"Glossary"},{"content":"sonny credentials is how you inspect and manage the credential sources Sonny resolves API keys from. The command name is plural — sonny credentials. There is no sonny credential command.\nThe concepts behind all of this — the resolution chain, the OS credential broker, and the legacy-credential migration — are on Credentials and Providers . This page is the command reference.\nUsage: sonny credentials [command] Available Commands: resolve Print the selected source kind and location for a source id migrate-status Report where every legacy credential lives and what has converged retire-legacy Remove a converged legacy credential copy delete Remove a credential source or provider from the credential store \u0026lt;div class=\u0026quot;callout-title\u0026quot;\u0026gt; \u0026lt;wa-icon name=\u0026quot;circle-info\u0026quot; aria-hidden=\u0026quot;true\u0026quot;\u0026gt;\u0026lt;/wa-icon\u0026gt; \u0026lt;span\u0026gt;Note\u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; Run sonny credentials --help to see the current subcommand list for your version. There is deliberately no --add, --list or --rotate flag: secrets are not meant to be typed onto a command line, where they land in shell history and in the process table. Add and rotate credentials through the TUI\u0026rsquo;s /setup flow or the Settings UI, or by writing a credential file with mode 0600. Subcommands Share link Share this section Permalink Share via Twitter LinkedIn Facebook Subcommand What it does sonny credentials resolve \u0026lt;source id\u0026gt; Prints the selected source kind and location for one credential source — the provider\u0026rsquo;s own entry point when it has one, otherwise the default. This is how you find out which of the five sources actually won, without guessing. sonny credentials migrate-status Reports where every legacy credential lives, per provider: provider config, auth store, credential broker, and whether each pair has converged. Optional --json. sonny credentials retire-legacy \u0026lt;provider\u0026gt; Removes a legacy copy only after it has converged. Takes --force to retire immediately, bypassing the confirmation window. sonny credentials delete \u0026lt;source or provider id\u0026gt; Removes what Settings\u0026rsquo; “Remove stored credential” writes — the stored credential for that source or provider. Environment variables, .env files, and files you point at with SONNY_CREDENTIAL_FILE live outside the store and are not touched. The resolution chain Share link Sonny resolves a credential source by checking each of these in order and using the first one found:\nEnvironment variables — e.g. ANTHROPIC_API_KEY. Credential files — ~/.config/sonny/credentials.json (user scope) or .sonny/credentials.json (project scope). External credential commands — credential_process, gcloud auth print-access-token, aws eks get-token. File-backed credentials — OAuth token files and Azure CLI token files. OAuth / SSO — interactive and headless login flows. Use resolve to see which one was selected:\nsonny credentials resolve openai # kind=env location=ANTHROPIC_API_KEY sonny credentials resolve anthropic # kind=file location=~/.config/sonny/credentials.json The output names the source and nothing secret: a key never reaches stdout.\nMigration and the credential broker Share link Long-lived API keys are migrating from plain-text credentials.json into the operating-system credential broker. Both stores are live during the migration, so a machine can hold two copies of the same credential and drift.\n# Where is each credential, and have the copies converged? sonny credentials migrate-status sonny credentials migrate-status --json # Once converged, drop the legacy copy. sonny credentials retire-legacy openai Retirement is staged on purpose. migrate-status tells you whether a provider is ready; retire-legacy refuses to delete a copy that has not converged, because that would delete the only working credential. Pass --force only when you have confirmed the broker copy is the one you want to keep.\nProvider configuration Share link Provider endpoints — not secrets — are configured with sonny config provider :\nsonny config provider add --provider openai --endpoint https://api.openai.com/v1 sonny config provider remove --provider openai Environment variables Share link Variable Purpose ANTHROPIC_API_KEY Anthropic credentials OPENAI_API_KEY OpenAI credentials GOOGLE_API_KEY Google/Gemini credentials GITHUB_TOKEN GitHub MCP server credentials GITLAB_TOKEN GitLab MCP server credentials SONNY_CREDENTIAL_FILE Point at a file-backed credential source Security notes Share link Credential files should be chmod 600. Project-scoped credentials can differ from user-scoped ones — check both when behavior is unexpected, and confirm with resolve. The TUI Settings → Credentials screen and sonny credentials resolve report the same information: source, kind, provider, masked key. Deleting a credential in Settings removes it from Sonny\u0026rsquo;s store but does not revoke it at the provider. Rotate or revoke the key with the provider too. See also Share link Credentials and Providers — the concepts, the","permalink":"https://sonny.sh/docs/cli/credential/","tags":null,"title":"sonny credentials"},{"content":"Every quoted error string below is reproduced from the binary or from the page that first verified it. If your output differs, trust the binary over this page and re-check the linked reference. When no row here matches, jump to Diagnostics .\nInstall Share link Share this section Permalink Share via Twitter LinkedIn Facebook Symptom Cause Fix Error: unknown shorthand flag: 'v' in -v -v isn\u0026rsquo;t wired to anything; only the long form is real. Use sonny --version or sonny version. sonny version prints an older version right after make install Shadow cleaning was skipped — you set NO_SHADOW_CLEAN=1, or staged the build with DESTDIR=, which skips it automatically. which -a sonny to find the earlier copy and remove it, or rerun make install without NO_SHADOW_CLEAN. The sonny-mesh systemd unit installed by the deb/rpm/apk package never starts on its own The postinstall installs the unit but deliberately does not enable or start it — there is no certificate material or authz.yaml yet for it to run safely with. Provision /etc/sonny/mesh/{node.crt,node.key,ca.crt} and /etc/sonny/mesh/authz.yaml, then systemctl enable --now sonny-mesh. models.yaml doesn\u0026rsquo;t reflect edits you expected sonny init to make sonny init only seeds models.yaml the first time; rerunning is a no-op if the file exists, so it never overwrites your edits. Expected. Edit models.yaml directly, or delete it and rerun sonny init to reseed. Recipes Share link Symptom Cause Fix error: [schema_shape] prompts: at least one prompt is required (exit 8, denied_policy) prompts: was nested under pack: instead of sitting beside it at the document root — the classic copy-paste mistake. Move prompts: back out to the document root, as a sibling of pack:, not a child. Error: pack \u0026quot;nope\u0026quot; not found among N discovered packs (exit 1) --pack \u0026lt;id\u0026gt; named an id that isn\u0026rsquo;t in the discovered set. This is an exact-match lookup, never fuzzy. Check ids with sonny recipe list or sonny recipe search, or drop --pack to use the single-primary short-circuit or the picker. no entry prompt found (expected id=\u0026quot;main\u0026quot; or a prompt with type=\u0026quot;main\u0026quot;) No prompt has id: main, and none has type: main with a non-empty id either. There is no \u0026ldquo;first prompt\u0026rdquo; fallback. Give exactly one prompt id: main (or type: main plus its own id). sonny recipe validate fails with wildcard_agent_without_optin / wildcard_mcp_without_optin You set pack.agents.allowed: [\u0026quot;*\u0026quot;] or pack.mcp.allowed: [\u0026quot;*\u0026quot;] without the matching allow_wildcard: true. Add pack.agents.allow_wildcard: true or pack.mcp.allow_wildcard: true, or list specific ids. (pack.tools.allowed: [\u0026quot;*\u0026quot;] needs no opt-in.) unreachable_tool_reference or retired_tool_reference warnings Advisory only — the recipe still validates. The prompt\u0026rsquo;s system text names a tool outside that prompt\u0026rsquo;s tools.allowed, or a retired alias (shell_exec, hosted glab_*/gh_* reachables only via tool_search). Add the tool to tools.allowed, or rename it in the prompt as the warning suggests (exec instead of shell_exec). A sub-pack\u0026rsquo;s tool list silently reverts to the parent\u0026rsquo;s You omitted tools: entirely (nil) rather than declaring an empty list. Under the default override inherit mode, nil falls back to the parent; only an explicit allowed: [] denies everything. Declare tools: {allowed: []} for deny-all, or inherit.tools: merge for the union. Providers and models Share link Symptom Cause Fix A profile with uri: \u0026quot;bedrock://…\u0026quot; validates clean, then at runtime drops into the interactive provider prompt (or errors headless) pack validate doesn\u0026rsquo;t parse uri: — it\u0026rsquo;s a Sonny-only field the recipe schema never checks. At load time uri.Parse fails on the unknown scheme and the profile\u0026rsquo;s provider/model are silently left unset. There is no bedrock://, vertex:// or openrouter:// scheme. Reach those providers with --provider \u0026lt;name\u0026gt;. Check a profile\u0026rsquo;s uri: scheme first when it unexpectedly prompts. An azure:// profile has nowhere to send requests azure:// is the one API scheme with no default base URL. Set AZURE_OPENAI_ENDPOINT, or a profile base_url:. Requests go to the wrong LiteLLM gateway Several deployments share LITELLM_API_KEY / LITELLM_BASE_URL, so the last environment value wins. Give each catalog source an env_prefix (e.g. ABS) and populate \u0026lt;PREFIX\u0026gt;_LITELLM_API_KEY / \u0026lt;PREFIX\u0026gt;_LITELLM_BASE_URL. A recipe profile\u0026rsquo;s base_url: / api_key_env: are ignored and the wrong account gets billed --provider (and --model) bypass profile lookup entirely — they\u0026rsquo;re consulted before the active profile\u0026rsquo;s own fields. Don\u0026rsquo;t pass --provider/-p when the recipe\u0026rsquo;s own profile should resolve the provider; let its uri:/category: do it. Anthropic returns HTTP 400 for opus-4-7 / opus-4-8 / opus-5 / sonnet-5 (or the floating opus/sonnet aliases) Those models reject temperature, top_p and top_k outright. Remove temp= / top_p= /","permalink":"https://sonny.sh/docs/troubleshooting/","tags":["troubleshooting","reference"],"title":"Troubleshooting"},{"content":" Mesh Architecture Share link Share this section Permalink Share via Twitter LinkedIn Facebook graph LR C[Client / CLI / Desktop] --\u0026gt;|gRPC :50052| N1[Node A] C --\u0026gt;|gRPC :50052| N2[Node B] N1 \u0026lt;--\u0026gt;|Synapse QUIC :50053| N2 N1 --\u0026gt; DB1[(control.db)] N2 --\u0026gt; DB2[(control.db)] The two planes Share link A node listens on two independent surfaces. Keeping them separate is what lets a mesh upgrade one plane without dropping sessions on the other.\nPlane Default listen Protocol Carries Client --grpc, default :50052 gRPC session streaming, attach/detach, session listing, transfer control Peer --synapse-listen, default :50053 Synapse QUIC/UDP, protobuf framing; WebSocket/TCP retained for rolling-upgrade compatibility node-to-node session movement and heartbeats Because :50052 is the client plane, it is also what sonny --mesh-self exposes when you embed a node inside a chat.\nAdvertise versus bind Share link The bind address is rarely the address a peer can reach. --grpc says where to listen; --advertise-addr says what to publish to peers — for example a Tailscale DNS name such as lothiriel:9052. On a single machine you can ignore it. On a multi-host mesh it is required, because publishing :50052 to a peer is publishing an address that peer cannot route to.\nIdentity, transport security and authorization Share link --cert, --key, --ca configure TLS on the gRPC plane and mTLS on the peer plane. --trust-domain sets the SPIFFE trust domain used for peer SVIDs (default mesh.local), so peers authenticate as workload identities rather than as hosts. --authz-policy points at the policy that decides which caller may do what. Treat the client plane as an authenticated surface: a node that will host sessions should not expose :50052 to a network it does not control. Control-plane database Share link Each node keeps a SQLite control-plane database, --control-db, defaulting to \u0026lt;data\u0026gt;/control.db. It is where session and node bookkeeping lives, which is why sonny mesh sessions can answer questions about sessions that finished a while ago and why --all can include completed ones.\nCapability advertisement Share link A node announces what it can actually run, so routing decisions are made against facts rather than guesswork:\nFlag Advertises --providers LLM providers this node can reach --models models this node can serve --packs recipes installed on this node --region, --zone placement hints for routing --label key=value arbitrary selector labels (repeatable) --max-sessions concurrency ceiling for the node Isolation reporting Share link Every session carries the isolation it actually achieved — backend, assurance level, and verification state. These are not configuration echoes; they are the achieved values, and sonny mesh sessions filters on them so you can prove which sessions ran where you required:\n# Sessions whose isolation was not verified sonny mesh sessions --isolation-state unverified # Sessions on a specific backend, sorted by assurance sonny mesh sessions --isolation-backend docker --sort assurance See Working with sessions for the full filter set.\nCoordinator Share link To attach a fresh node to a mesh that already exists, pass --coordinator with the address of a node already in it. Without a coordinator, the node bootstraps its own mesh and other nodes join it through --mesh-peer.\n","permalink":"https://sonny.sh/docs/mesh/architecture/","tags":["mesh","architecture","networking"],"title":"Mesh Architecture"},{"content":" Running the Mesh Share link Share this section Permalink Share via Twitter LinkedIn Facebook Start a node Share link sonny mesh serve \\ --grpc 127.0.0.1:50052 \\ --synapse-listen 127.0.0.1:50053 \\ --control-db /var/lib/sonny/control.db The node listens for gRPC client connections, advertises on the Synapse QUIC peer plane, sends heartbeats to announce availability, and accepts sessions pushed from other nodes.\nA two-node mesh over mTLS Share link Both planes take the same certificate material. --advertise-addr is what makes the pair reachable: each node publishes the address the other can route to.\n# host-a sonny mesh serve \\ --name worker-a \\ --grpc 0.0.0.0:50052 --advertise-addr host-a:50052 \\ --synapse-listen 0.0.0.0:50053 \\ --cert /etc/sonny/host-a.pem --key /etc/sonny/host-a-key.pem \\ --ca /etc/sonny/mesh-ca.pem \\ --trust-domain mesh.local \\ --region eu --zone eu-1 \\ --providers anthropic,openai --models gpt-5-mini \\ --max-sessions 8 # host-b sonny mesh serve \\ --name worker-b \\ --grpc 0.0.0.0:50052 --advertise-addr host-b:50052 \\ --synapse-listen 0.0.0.0:50053 \\ --mesh-peer quic://host-a:50053 \\ --cert /etc/sonny/host-b.pem --key /etc/sonny/host-b-key.pem \\ --ca /etc/sonny/mesh-ca.pem \\ --trust-domain mesh.local \\ --region eu --zone eu-2 --mesh-peer prefers quic://. ws:// is a development URL and wss:// is the mTLS compatibility URL; both exist for rolling upgrades, not for new deployments.\nManaged deployments Share link Production builds expect an explicit deployment contract rather than ambient trust:\nFlag Purpose --managed-manifest private managed-worker deployment manifest (required in production builds) --control-db path to the SQLite control-plane DB (default \u0026lt;data\u0026gt;/control.db) --authz-policy authorization policy file --node-id stable node ID (default: generated UUID) --name display name (default: $(whoami)@$(hostname)) --coordinator address of a node in an existing mesh to join Point a chat at the mesh Share link Three options, weakest to strongest coupling:\n# 1. Just show a peer in the TUI\u0026#39;s Connect dialog sonny run --mesh-peer host-a:50052 --mesh-peer host-b:50052 # 2. Observe a specific node from the mesh panel sonny run --mesh-addr host-a:50052 # 3. Embed a node in this chat so others can attach to it sonny run --mesh-self --mesh-self-grpc :50052 --mesh-self-synapse :50053 --mesh-peer is repeatable, and both --mesh-addr and --mesh-peer also read from the mesh: block of your config or from SONNY_MESH_ADDR, so you can set peers once in .sonny.yaml instead of retyping them.\nNote --mesh-self registers the current session with the embedded node. Without it, your session is local-only and nothing can attach to it. Checking the mesh Share link sonny mesh nodes # what is up sonny mesh sessions # what is running sonny mesh sessions -a # include completed sessions Flag-level detail lives in the mesh CLI reference .\n","permalink":"https://sonny.sh/docs/mesh/running-the-mesh/","tags":["mesh","operations","quickstart"],"title":"Running the Mesh"},{"content":" Working with Sessions Share link Share this section Permalink Share via Twitter LinkedIn Facebook Finding a session Share link sonny mesh sessions # running sessions sonny mesh sessions -a # include completed sonny mesh sessions --search refactor # substring over session, pack, model, provider, node sonny mesh sessions --sort assurance # updated | assurance | backend | isolation-state The filters are designed for answering \u0026ldquo;did this actually run where I said it should\u0026rdquo;:\nFlag Matches on --isolation-backend the backend the session achieved (for example docker) --isolation-assurance the assurance level achieved --isolation-state the isolation verification state --search free text across session, recipe, model, provider, or node --sort updated (default), assurance, backend, isolation-state -a, --all include completed sessions Attaching Share link sonny mesh attach \u0026lt;session-id\u0026gt; # drive it sonny mesh attach --read-only \u0026lt;session-id\u0026gt; # watch only --read-only is the safe default for supervising someone else\u0026rsquo;s worker: you see the stream, you cannot type into it.\nDetaching Share link sonny mesh detach # leave this session running in the background sonny mesh detach --stop # pause it instead of leaving it running sonny mesh detach \u0026lt;session-id\u0026gt; # detach a specific session Detach is deliberately the non-destructive option. The session keeps its context on the hosting node and you can re-attach later; --stop pauses it rather than backgrounding it.\nTransferring Share link sonny mesh transfer \u0026lt;session-id\u0026gt; \u0026lt;target-node\u0026gt; sonny mesh transfer --force \u0026lt;session-id\u0026gt; \u0026lt;target-node\u0026gt; Transfer moves a live session to another node. --force transfers even while the session is active — use it when you have already stopped writing to it, because two drivers on one session is exactly the race --force lets you create.\nTerminating Share link sonny mesh terminate \u0026lt;session-id\u0026gt; sonny mesh terminate \u0026lt;session-id\u0026gt; --force --reason \u0026#34;abandoned after rebase\u0026#34; sonny mesh terminate --all --node worker-a --yes sonny mesh terminate --all --all-nodes --yes Two guards worth knowing:\n--all refuses to run unless you also scope it with --node \u0026lt;id\u0026gt; or --all-nodes. --all asks for confirmation unless you pass --yes. --force skips graceful shutdown, so an in-flight tool call is cut rather than drained. Prefer the default and reserve --force for a session that is already wedged.\nPushing a local session out Share link sonny mesh push push sends the current session to a remote node so it survives your terminal closing. Combined with attach from another machine, this is the simplest way to hand a long job to a build host and walk away.\nTypical flow Share link # on the laptop: start work and hand it to the build host sonny run --pack go-refactor sonny mesh push # on the build host (or back at the laptop later): find and drive it sonny mesh sessions --search go-refactor sonny mesh attach 9f2c1a… # later, move it to a bigger box and leave it running sonny mesh transfer 9f2c1a… worker-b sonny mesh detach ","permalink":"https://sonny.sh/docs/mesh/sessions/","tags":["mesh","sessions","cli"],"title":"Working with Sessions"},{"content":"Sonny and the Sonny ecosystem, including Recipes, Lanning, client integrations, and Sonny MCP servers, are proprietary commercial software by CodePros. They are not currently open source.\nUse, distribution, and access are governed by your applicable CodePros license and agreement. Public documentation does not grant rights to the source code.\nGetting access Share link Share this section Permalink Share via Twitter LinkedIn Facebook Use the release packages, credentials, and support channel supplied through your CodePros agreement. If you need access, contact your organization\u0026rsquo;s CodePros account representative or administrator.\nSource repositories, release assets, and internal issue tracking require explicit authorization. Go module consumers also need authorized repository credentials; the sonny.sh import metadata does not make those repositories public.\nThird-party dependencies retain their own licenses. Those licenses do not change the licensing of Sonny or grant access to its private repositories.\n","permalink":"https://sonny.sh/docs/licensing/","tags":null,"title":"Licensing and access"}]