Agent configuration
agents-sandbox is agent-aware. A --agent <name> flag on run, shell, build, volume, stop, and kill
selects the coding-agent profile to run, build, provision, or manage. The agent can also be selected via the agent
config key or the AGENTS_SANDBOX_AGENT environment variable. Seven agents ship as built-in profiles:
opencode— daemon-based, with serve/attach, worktree sessions, and GitHub-release upgrade checks.opencode2(default) — opencode 2 (beta), installed from its upstream installer (https://opencode.ai/v2/install); the opencode update API’slatestchannel is used for upgrade checks and unpinned builds. Daemon-based with serve/attach, worktree sessions. It owns its own launcher config namespace (~/.config/agents-sandbox/opencode2/and.agents-sandbox/opencode2/), separate fromopencode, because v2’s config schema (e.g. structuredproviders.*.models.*.capabilities) differs from v1’s — so the two can now diverge. Its VM config path and host drop-in stay~/.config/opencode/(v2 reads the same files as v1).pi— the pi coding agent, run interactively; upgrade checks viapi.dev.claude-code— Anthropic’s Claude Code, run interactively; upgrade checks via the npm registry.codex— OpenAI’s Codex CLI, run interactively; upgrade checks via GitHub releases. Its startup update check is turned off because agents-sandbox offers upgrades itself. Codex’s own command sandbox is turned off too (sandbox_mode = "danger-full-access"), because the VM already isolates it. This overridessandbox_modeinconfig.toml; to choose another mode, pass it as an agent argument, e.g.agents-sandbox --agent codex -- --sandbox workspace-write. Codex still asks for approval according to itsapproval_policy.hermes— Hermes Agent, installed from its official installer at a pinned release tag and run interactively; upgrade checks via its GitHub releases.oh-my-pi— Oh My Pi (binaryomp), installed from its standalone GitHub release binary after its checksum is verified, and run interactively; upgrade checks via its GitHub releases. Its startup update check is turned off through a launcher-owned--configoverlay, because agents-sandbox offers upgrades itself. The binary is about 275 MB, so its runner image is larger than the others.
Experimental agents:
opencodeandopencode2are the tested default agents. Every other built-in profile (pi,claude-code,codex,hermes,oh-my-pi) is experimental — not well tested with agents-sandbox. The launcher prints a notice when an experimental agent is selected.
--worktree and --serve-only are rejected for pi, claude-code, codex, hermes, and oh-my-pi (they have no daemon); they run through the
interactive TUI instead. Passing an unsupported --agent name reports the valid names in its error message.
Note: opencode2 server auth — v2’s
serverequires HTTP basic auth on every/api/*route and would otherwise generate a random password the launcher cannot know. The runner image therefore bakes a fixedOPENCODE_SERVER_PASSWORD, and the health probe, worktree API calls, and in-VM attach client all use it. v2 hardcodes the basic-auth username toopencode(there is noOPENCODE_SERVER_USERNAME), so the fixed credential isopencode:opencode— what an external--serve-onlyclient must present on the/api/*routes (see the connect-desktop recipe). The root path and the static web UI are served without credentials; the/api/*routes and the/openapi.jsondocument require the basic-auth credential.
Each agent owns its config directories, one subdir per agent under the tool’s config base, in precedence order:
- Generic user:
~/.config/agents-sandbox/<agent>/(e.g.~/.config/agents-sandbox/opencode/,~/.config/agents-sandbox/opencode2/) - Per-slug user:
~/.config/agents-sandbox/<slug>/<agent>/(repo slug, shared by every clone; e.g.~/.config/agents-sandbox/myproject-<hash>/opencode2/) - Project:
.agents-sandbox/<agent>/(e.g..agents-sandbox/opencode/,.agents-sandbox/opencode2/)
While no dir exists in any scope (and no legacy fallback applies), every run and shell prints an info line naming the
paths, so a fresh install shows where launcher config goes. Creating one silences it.
Note: Legacy fallback, migrating from an older version — the
opencode2launcher config dir is new. While noopencode2dir exists in any scope,opencode2reads the old sharedopencodedir — which theopencodeagent also uses — and warns on every run while it falls back. To migrate, copy the files you wantopencode2to read from~/.config/agents-sandbox/opencode/into~/.config/agents-sandbox/opencode2/(and the per-slug user and project equivalents); copy, not move, if you also useopencode. The three scopes switch together: as soon as anopencode2dir exists in any of the generic user, per-slug user, or project scope, all three scopes read theiropencode2/dir and the warning stops — so copy all the files in one go, because a partialopencode2/dir disables the fallback for the other scopes too and silently ignores the leftovers. One consequence: a per-slugopencode2/dir suppresses a genericopencode/fallback, because the single namespace choice spans every scope. The guest~/.config/opencode/path is never affected.
VMs, home volumes, and state are also scoped per agent: the sandbox name, home volume, and state file all carry the agent, so switching agents no longer tears down the project sandbox and multiple agents can serve the same project concurrently (see Sandboxes).
Config snippet merge
agents-sandbox provisions a single agent config into the VM. No embedded provider or permission config is shipped. Instead, the agent config is assembled from snippet files that match the agent’s snippet pattern, collected from the agent config directories (generic user, per-slug user, project), and written to the agent’s VM config path:
- opencode — snippets match
opencode*.json*(e.g.opencode-model.json,opencode-permissions.jsonc); merged to/home/dev/.config/opencode/opencode.jsonc. A file named exactlyopencode.jsonno longer merges by default. - opencode2 — same snippet pattern and merged config path as
opencode(v2 reads the same files as v1), but read from its ownopencode2/config dirs instead of the sharedopencode/dirs. - pi — snippets match
settings*.json*in thepi/subdir; merged to/home/dev/.pi/agent/settings.json. - claude-code — snippets match
settings*.json*in theclaude/subdir; merged to/home/dev/.claude/settings.json. - codex — snippets match
config*.tomlin thecodex/subdir (e.g.config-model.toml); merged to/home/dev/.codex/config.toml, written as TOML. - hermes — snippets match
config*.yamlin thehermes/subdir; merged to/home/dev/.hermes/config.yaml, written as YAML. - oh-my-pi — snippets match
config*.{yml,yaml}in theoh-my-pi/subdir (e.g.config-model.yml); merged to/home/dev/.omp/agent/config.yml, written as YAML. omp falls back toconfig.yaml, so both names are reserved for the merged output.
Matching files are parsed and deep-merged into one config document. The generic user directory is merged first, then
the per-slug user directory, then the project directory; within each directory files are merged in alphabetical order,
so later files override earlier ones.
Snippet parsing supports JSON, JSONC, JSON5, YAML, and TOML, chosen by file extension (YAML only applies to agents whose
pattern includes YAML extensions, e.g. a pattern like pi-*.{json,yaml}). The merged config is written as TOML when the
agent’s VM config path ends in .toml (codex), as YAML when it ends in .yaml/.yml (hermes, oh-my-pi), and as JSON otherwise.
If no snippet files exist, no merged config is produced.
A snippet that matches the pattern but cannot be read or parsed is skipped, not fatal: at startup the
launcher names it in a warning and leaves it out of the merge, while agents-sandbox config agent reports
it as an error. When every matching snippet fails to read or parse, agents-sandbox config agent reports
that no snippet could be merged.
Run agents-sandbox config agent to print the merged config that would be provisioned into the VM.
Note: the merged config is written to the agent’s VM config path (for opencode,
opencode.jsonc, the last file opencode loads:config.json<opencode.json<opencode.jsonc), so it always wins the deep merge. When snippets exist, the config-file family (for opencode:config.json,opencode.json,opencode.jsonc, …) is removed from the VM so a host drop-in copy of any of those files cannot shadow the merged snippet config.
See the permissions example for a concrete snippet.
Verbatim config directory mirror
Beyond the snippet merge, the <agent> config directories (~/.config/agents-sandbox/<agent>/,
~/.config/agents-sandbox/<slug>/<agent>/, and .agents-sandbox/<agent>/) act as a 1:1 verbatim mirror of the
agent’s VM config directory. Every file and
subdirectory in them is copied verbatim into the VM, preserving its relative path (for opencode: tui.json, AGENTS.md,
agents/, commands/, themes/, plugins/, skills/, tools/, …). This makes your whole agent setup available in
the VM without extra configuration. For opencode2, read from its own opencode2/ dirs rather than the shared
opencode/ dirs.
Two families of files are not mirrored:
- Files matching the agent’s snippet pattern (
opencode*.json*,settings*.json*,config*.toml, …) are deep-merged (see Config snippet merge), never mirrored. - The agent’s config-file family (for opencode:
config.json,opencode.json,opencode.jsonc, …) is reserved for the merged output and never mirrored, so a mirror copy cannot shadow the merged snippet config.
Precedence when the same VM path is reachable from multiple sources:
home: > merged snippet config > verbatim mirror > drop-in provisioning
Note: File modes — home mappings, the verbatim mirror, and the default drop-in copy preserve ordinary Unix permission bits, including executable bits. Launcher scripts therefore remain directly executable in the VM.
The mirror is always active, independent of provision-host-config. Stale mirrored files (deleted from the host)
are left in place in the VM. Run agents-sandbox config agent to list the mirror files, each shown as its host source
path → VM path. Previously non-pattern files in <agent>/ were silently ignored; they are now mirrored verbatim.
Default drop-in provisioning
Beyond the snippet merge, when running the launcher now copies the active agent’s config + credential files from the host into the VM by default, driven by a per-agent gitignore-style include-list manifest (provision rules). This means your normal agent setup (e.g. an existing opencode config) works without extra configuration.
The drop-in copy is scoped to the agent’s settings and, for opencode, its credential file. Runtime state is not copied:
- opencode —
~/.config/opencode/**(excludingnode_modules/,package*.json, and.gitignore) plus~/.local/share/opencode/auth.json.node_modules/is not copied because packages built on your machine do not run reliably in the Linux VM. When~/.config/opencode/package.jsonlistsdependencies,agents-sandbox runwarns that plugins importing them will not load; install them inside the VM as described in Install Opencode Plugin Dependencies. - opencode2 — same drop-in copy as
opencode(~/.config/opencode/**and~/.local/share/opencode/auth.json).opencode2reads credentials from its own database and ignores the copiedauth.jsonin a new sandbox; see GitHub Copilot with opencode2. - pi —
~/.pi/agent/settings.json. - claude-code —
~/.claude/settings.json(runtime state and the machine-managed.credentials.jsonare not copied). - codex —
~/.codex/config.toml(sessions, logs, and theauth.jsonlogin cache are not copied). - hermes —
~/.hermes/config.yaml(the rest of~/.hermes—state.db,sessions/,memories/,skills/— is runtime state and is not copied; credentials in~/.hermes/.envare never copied). - oh-my-pi —
~/.omp/agent/config.ymlorconfig.yaml(the rest of~/.omp— sessions,agent.db, the extracted native addon — is runtime state and is not copied;models.ymland~/.omp/agent/.envcan hold literal provider keys and are never copied).
Precedence: the merged snippet config and any home: mappings override the drop-in copy for the same VM path.
When snippets exist, the drop-in copy of the config-file family is skipped entirely (see Config snippet merge) so host config cannot override the merged snippets. Non-config files — e.g. plugins, custom commands, themes — are still copied.
To switch from your agent’s own config to agents-sandbox/<agent> snippet provisioning, create snippet files in the
user or project snippet directories (see Config snippet merge). Once a snippet matching the
agent’s pattern exists, it wins over the host config for the merged config path. To stop the native-config drop-in
entirely so only the snippet merge and home: mappings apply, set provision-host-config: false.
To turn off the drop-in copy altogether (config and credentials), set provision-host-config: false in the launcher
config:
provision-host-config: false
This skips the whole host-config copy (for opencode: ~/.config/opencode/** and auth.json), while the snippet merge
and home: mappings keep working. On the next run, previously drop-in-copied config and credential files are removed
from existing home volumes so they cannot linger.
Authentication: file copy vs. env-secret
Warning: because of the drop-in provisioning, the opencode
auth.jsoncredential file is now copied into the VM by default. If you prefer to deliver credentials exclusively through the env-secret mechanism (which never writes them into the VM, see Secrets), you can opt out of the credential file copy. The env-secret channel remains fully supported and unchanged; this does not replace it.
To opt out, exclude auth.json from the drop-in copy by placing a home: entry that overrides the provisioned path
(see Home provisioning & startup hooks), or remove the credential file from the host before running. The launcher does not inject
host secrets in any other way; the env-secret mechanism is the supported channel for secrets you do not want on disk in
the VM.
For pi, claude-code, codex, hermes, and oh-my-pi, the drop-in copy does not include credential files. pi, claude-code, hermes, and oh-my-pi authenticate with env secrets instead; codex logs in inside the VM:
- pi — per-provider env vars, e.g.
ANTHROPIC_API_KEY,OPENAI_API_KEY,GEMINI_API_KEY(see pi’s docs for the full list). Put them in anenv.secret/env.secret.yamlfile. - claude-code —
ANTHROPIC_AUTH_TOKEN,ANTHROPIC_API_KEY, orCLAUDE_CODE_OAUTH_TOKEN. Claude’s.credentials.jsonis machine-managed and not hand-provisioned, so env vars are the supported channel here. - codex — the interactive Codex CLI reads its login from
~/.codex/auth.json, not from an env var, so you log in once inside the VM (see Codex login). The hostauth.jsonis not copied because a ChatGPT login rotates its refresh token, and a second copy using it would sign the host out. - hermes — the provider key Hermes is configured to use (
OPENAI_API_KEY,ANTHROPIC_API_KEY,OPENROUTER_API_KEY, …), supplied throughenv.secret. Hermes reads the process environment, so nothing needs to be written into its.env. - oh-my-pi — per-provider env vars, e.g.
ANTHROPIC_API_KEY,OPENAI_API_KEY,GEMINI_API_KEY, supplied throughenv.secret. omp reads the process environment, so nothing needs to be written into~/.omp/agent/.env. - opencode —
OPENCODE_API_KEY.
Codex login
The default public network profile already lets Codex reach OpenAI. With profile: none, add these hosts to
network.egress-allow (see Networking); the network policy applies when the
VM is created, so restart the sandbox afterwards:
network:
egress-allow:
- auth.openai.com # ChatGPT login
- chatgpt.com # ChatGPT backend: workspace routing and the model API for ChatGPT logins
- api.openai.com # model API for API-key logins
Without chatgpt.com, a ChatGPT login completes in the browser but Codex then reports
workspace routing discovery failed.
ChatGPT plan: choose Sign in with ChatGPT. Codex opens the sign-in page in your host browser,
and agents-sandbox passes the browser’s return to http://127.0.0.1:1455/auth/callback on to Codex in the sandbox (see
Opening links).
If that doesn’t work, for example because port 1455 is in use on your computer or open-links is off, choose Sign in
with Device Code (or run codex login --device-auth), open the link on your host, and enter the one-time code. Device
code login is off by default: enable it in your ChatGPT security settings (personal account) or ask a workspace admin.
While it is off, Codex fails with device code request failed with status 403 Forbidden.
API key: declare OPENAI_API_KEY as an env secret for api.openai.com, then
run printenv OPENAI_API_KEY | codex login --with-api-key in agents-sandbox shell --agent codex. The VM only sees the
secret’s placeholder, so auth.json stores the placeholder and the network proxy replaces it on requests to
api.openai.com.
The login is stored in ~/.codex/auth.json on the home volume, so you sign in once per project and agent.
Example: permissions
Opencode permissions are configured through opencode config snippets, which agents-sandbox merges (user-first, then
project). The snippet file name must match the agent’s snippet pattern opencode*.json*; a file such as
permission.json5 is not merged. Both examples in this section deny reading .env and .envrc files.
opencode (v1) uses the permission object, keyed by tool, in .agents-sandbox/opencode/. This example allows
everything else:
{
// .agents-sandbox/opencode/opencode-permissions.json5
permission: {
"*": "allow",
read: {
"*": "allow",
"*.env": "deny",
"*.envrc": "deny",
},
},
}
opencode2 (v2) uses the permissions rule list, in .agents-sandbox/opencode2/. Its default policy already allows
every action and only asks before reading .env files; your rules are appended after the defaults, and the last
matching rule wins. This example also allows directories outside the project and web search without asking, and lets
opencode pick a web search provider instead of prompting for one:
{
// .agents-sandbox/opencode2/opencode-permissions.json5
permissions: [
{ action: "external_directory", resource: "*", effect: "allow" },
{ action: "websearch", resource: "*", effect: "allow" },
{ action: "read", resource: "*.env", effect: "deny" },
{ action: "read", resource: "*.envrc", effect: "deny" },
],
websearch: { provider: "random" },
}
opencode2 still reads the v1 permission object, but marks it deprecated. See the
opencode v1 migration guide.
Warning: these rules are advisory for opencode’s own Q&A tools. The
bashtool executes arbitrary commands inside the VM and can read any file regardless of these deny rules, so they are not a security boundary — keep secrets out of the VM or rely on the secret mechanism instead.
Capability support matrix
Every agent attaches interactively in the VM and supports config snippet merge, host-config drop-in, and version/upgrade checks. Beyond that base, coverage differs by agent:
- ✅ Supported — agents-sandbox implements it for this agent.
- ◐ Supportable — the agent exposes a surface agents-sandbox could use, but does not implement yet.
- ✖ Unsupported — the agent has no such surface.
| Capability | opencode |
opencode2 |
pi |
claude-code |
codex |
hermes |
oh-my-pi |
|---|---|---|---|---|---|---|---|
| Interactive attach | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Config snippet merge | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Host-config drop-in | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Version + upgrade check | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| Serve/attach daemon | ✅ | ✅ | ✖ | ✖ | ◐ | ◐ | ◐ |
| Worktree sessions | ✅ | ✅ | ✖ | ✖ | ✖ | ✖ | ◐ |
| Notify event stream | ✅ | ✅ | ✖ | ✖ | ◐ | ◐ | ◐ |
| Session quiescence | ✅ | ✖ | ✖ | ✖ | ✖ | ✖ | ✖ |
The ◐ entries name an upstream surface agents-sandbox has not wired up: codex app-server (a JSON-RPC daemon with a
turn/item event stream) for Codex, hermes serve / hermes chat --format stream-json for Hermes, and omp --mode rpc /
omp acp (stdio servers) and the omp worktree CLI for Oh My Pi.