Run a coding agent inside a CreateOS Sandbox microVM, and drive it from a Herdr pane. One pane maps to one sandbox.
Herdr stays the local control room. The pane is a local terminal attached to a
managed PTY inside the sandbox. Your keystrokes go into the microVM. The agent's
terminal output comes back. Herdr classifies the pane as working, waiting, or
idle, because the plugin sets HERDR_AGENT on the pane.
Herdr pane ──► createos sandbox process attach ──► PTY in the microVM
└── claude / codex /
opencode / pi /
cursor-agent
agent value |
Agent | Installed with |
|---|---|---|
claude-code |
Claude Code | curl -fsSL https://claude.ai/install.sh | bash |
codex |
Codex | curl -fsSL https://chatgpt.com/codex/install.sh | sh |
opencode |
OpenCode | curl -fsSL https://opencode.ai/install | bash |
pi |
Pi | curl -fsSL https://pi.dev/install.sh | sh |
cursor |
Cursor | curl https://cursor.com/install -fsS | bash |
shell |
none | opens a plain login shell |
Each installer is the official one. The plugin runs it inside the sandbox, then
resolves the binary with command -v and fails if the binary is missing.
- macOS or Linux
- Herdr 0.7.5 or newer
- Bun
- A CreateOS account.
build.shinstalls the CLI for you. Sign in once withcreateos login. - A Git worktree. The plugin uploads what Git tracks.
build.sh runs the official installer every time:
curl -sfL https://raw.githubusercontent.com/NodeOps-app/createos-cli/main/install.sh | sh -The installer verifies a checksum, and it upgrades an existing CLI in place. Set
CREATEOS_INSTALL_URL to point at a different installer. If curl is missing,
build.sh skips the install and uses any CLI already on the machine.
Warning: the installer replaces the createos binary. If you build the CLI from
source, back up your binary before you run build.sh.
One command does every step. It checks the prerequisites, installs the plugin, writes its configuration, and binds its keys.
createos sandbox setup herdrCheck first, change nothing:
createos sandbox setup herdr --doctorPick the agent and the sandbox size while you install:
createos sandbox setup herdr --agent codex --shape s-4vcpu-8gb| Flag | What it does |
|---|---|
--doctor |
Report the prerequisites and stop. |
--local <path> |
Link a local checkout instead of installing from GitHub. |
--agent <kind> |
claude-code, codex, opencode, pi, cursor, shell. |
--shape, --rootfs |
Sandbox size and image. |
--auto-pause |
Pause a sandbox after this long with no activity. |
--remote-root |
Where the worktree lands inside the sandbox. |
--no-keys |
Leave your config.toml alone. |
--force |
Replace an existing plugin config.json. |
The command backs up config.toml before it adds keys, adds only the bindings
you do not already have, and never touches an existing plugin config.json
unless you pass --force. Undo the keys with herdr config reset-keys.
herdr plugin install NodeOps-app/createos-plugin/packages/herdr-pluginFor local development, link the directory and run the build step yourself.
herdr plugin link does not run build commands.
herdr plugin link /path/to/packages/herdr-plugin
sh /path/to/packages/herdr-plugin/build.sh
herdr plugin action list --plugin createos.sandboxbuild.sh installs or upgrades the createos CLI, then writes run.sh with the
absolute paths to bun and createos. Herdr runs plugin commands as a bare
argv, with no shell expansion and an unreliable PATH.
Verified on 2026-08-25 in a clean sandbox. With no CLI present, build.sh
installed createos v0.0.24 to /usr/local/bin and wrote that path into
run.sh. A second run upgraded in place and exited 0.
createos sandbox setup herdr writes these for you. This section is for anyone
who used --no-keys, or who wants different keys.
Herdr ignores keys declared in a plugin manifest. Add them to
~/.config/herdr/config.toml, then run herdr config check and
herdr server reload-config.
[[keys.command]]
key = "prefix+shift+s"
type = "plugin_action"
command = "createos.sandbox.start"
description = "start an agent in a new CreateOS sandbox"
[[keys.command]]
key = "prefix+shift+a"
type = "plugin_action"
command = "createos.sandbox.apply"
description = "apply sandbox changes locally"
[[keys.command]]
key = "prefix+shift+c"
type = "plugin_action"
command = "createos.sandbox.attach"
description = "reattach the agent to this pane"| Action | What it does |
|---|---|
start |
Creates a sandbox, uploads the worktree, installs the agent, splits the pane, and attaches the agent PTY. |
attach |
Resumes the sandbox if paused, starts the agent again if its process died, and reattaches it to the pane. |
sync |
Opens a pane that runs createos sandbox sync --mode two-way until you stop it. |
apply |
Exports a Git patch from the sandbox and applies it locally, after git apply --check passes. |
pause |
Snapshots the sandbox. See the known problem below. |
resume |
Brings a paused sandbox back. |
info |
Prints the agent, sandbox, status, and paths mapped to the pane. |
delete |
Permanently deletes the sandbox. Invoke it twice within 60 seconds to confirm. |
boxes |
An overlay pane that refreshes createos sandbox list every 5 seconds. |
apply is incremental. It commits a baseline in the sandbox after each apply,
so the next apply carries only newer work. A second apply with no new work
reports no-changes.
Every action prints one machine-readable result line, on success and on failure:
CREATEOS_HERDR_RESULT: {"schemaVersion":1,"action":"apply","ok":true,"result":"applied","bytes":237}
The line is printed when the action finishes, so it is the last such line in the
log, not always the first line of output. start streams the agent installer
output before it. The boxes pane runs until you close it and prints no result.
Herdr runs actions asynchronously. Poll the outcome with
herdr plugin log list --plugin createos.sandbox.
herdr plugin config-dir createos.sandboxCreate config.json there. Every key is optional.
{
"agent": "claude-code",
"shape": "s-2vcpu-4gb",
"rootfs": "devbox:1",
"autoPause": "30m",
"remoteRoot": "/workspace",
"egress": ["registry.npmjs.org", "github.com"],
"excludes": ["fixtures/", "*.mp4"],
"syncExcludes": [".git", "node_modules"]
}An unknown key is an error, so a typo cannot silently disable a safety setting.
egress locks the sandbox to the listed hosts. An empty list lets the sandbox
reach anything. excludes accepts an exact repository-relative path, a
directory prefix that ends with /, or an extension such as *.mp4.
Do not put tokens in this file. Authenticate the agent inside its own sandbox, in the pane, the first time you start it.
The candidate list is git ls-files --cached --others --exclude-standard. Four
filters then run over it, in this order:
- Submodules. Every gitlink is dropped, with everything under it. Git reports a submodule as one bare directory name, so a name-based filter would never see the files inside it.
- Your
excludes. Checked first, so nothing below can override them. - The deny-list.
.envand.env.*(except.env.example,.env.sample,.env.template),.ssh/,.aws/,.gnupg/,.kube/,.config/gcloud/,.docker/config.json,.netrc,.npmrc,.pypirc,.envrc,.git-credentials,id_rsa,id_dsa,id_ecdsa,id_ed25519,*.pem,*.key,*.p12,*.pfx,*.keystore,*.jks,credentials,credentials.json,.terraform/,*.tfstate. git check-ignore --no-index. This judges tracked files by the ignore rules too. A credential committed once and ignored later is still dropped.
The archive is then built with tar --no-recursion from an explicit file list,
so no directory entry can pull in a file the filters never saw.
The upload is a filtered snapshot, not a mount. Host agent credentials are never copied into the sandbox.
This is a deny-list, not proof. It stops the credential shapes named above. It does not detect a secret inside an ordinary source file. Treat the sandbox as holding a copy of your working tree.
createos sandbox pauseandresumefail withContent-Length is requiredon CLI v0.0.24 and earlier. The cause is a missing request body in the CLI's own lifecycle helper, not a server fault and not this plugin. Both actions are correct and work on a fixed CLI. UseautoPauseuntil you have one, because it is applied at create time.- On an account holding thousands of sandboxes, the CLI resolves a sandbox by scanning only the first 200 rows it lists, so a sandbox outside that page is reported as missing even when addressed by full ID. Every action here can hit that. It is a CLI limit, not a plugin one.
- Herdr restores panes after a restart, but not the processes inside them. The
sandbox and the mapping survive. Use
attachto reconnect. syncneeds an SSH key in the sandbox and downloads Mutagen on first use.startdoes not need either.- The plugin does not follow a pane that moves to another workspace, because the mapping is keyed by pane ID.
Run the filter tests with bun test. They cover the deny-list, the
example-file exception, exclude precedence, and the submodule rule against a
real Git fixture.
Measured live on Herdr 0.8.2 and CreateOS CLI v0.0.24, with Claude Code 2.1.245:
startcreated the sandbox, uploaded the worktree, installed the agent, and opened the pane.herdr agent listreportedagent: claude,agent_status: idle. A.envtracked by Git was not uploaded.applymoved sandbox edits into the local worktree. The nextapplyreportedno-changes.- A
startwhose agent install failed under a restrictiveegressdeleted its own sandbox. The sandbox count was unchanged, and the sandbox showeddestroyed. - A
remoteRootcarrying'; touch /tmp/PWNED; #was refused before anything was created. No file was written. deleterefused the first invocation and deleted the sandbox on the second.- All five agent installers resolved their binary inside a sandbox.
build.shinstalledcreateos v0.0.24in a clean sandbox, then upgraded in place on a second run.createos sandbox setup herdr --local … --agent codexran against an isolatedHOME: it linked the plugin, ran the build step, wroteconfig.json, added 6 keybindings, and backed upconfig.toml.herdr config checkreportedok. A second run added nothing and kept the existing config.