Drun is a platform that allows you to virtualize components of your host into an ephemeral runtime to serve as the agent's workspace with git-like primitives which allow the agent to explore trajectories in parallel and discard dead-ends without disrupting the host state.
Drun surfaces a runtime abstraction layer with reliability harnesses to guardrail the agent's behavior across a range of OS-level aspects:
- Network domains (e.g. allowlisted domains)
- Command execution (e.g. forbidden commands)
- Access to filesystem paths (e.g. restrict filesystem access)
- Resource limits (e.g. memory and duration caps)
Rather than granting your agent raw access to your host, Drun exposes and enforces a highly-customizable policy layer with deterministic knobs for you to place absolute limits that can't be breached by design.
The drun framework can be consumed in the following ways:
- Via Claude Code: drun's MCP tools replace Claude's native file/shell/network tools inside a sandboxed workspace.
- Via Gemini CLI: same idea, for Gemini CLI.
- Via Codex CLI: same idea, for OpenAI's Codex CLI.
- Via Hermes: same idea, for local models run through Hermes.
- Standalone CLI: a CLI agentic loop that's integrated with Ollama and LiteLLM.
- Using the Python SDK: script sandboxed sessions directly, no LLM or daemon required.
All journeys except for the Python SDK require the
drun-mcp daemon installed and running in the host machine to operate. This is
done once with:
curl -fsSL https://raw.githubusercontent.com/dmosc/drun/main/install.sh | bashThis installs and configures a few things (skips if not applicable):
- The
drun-mcpbinary under/usr/local/bin/drun-mcp. - A global config at
~/.drun/config.tomlwith sensible defaults. drun-mcpas a persistent background daemon (launchdon macOS,systemdon Linux) so a single process serves all simultaneous sessions running on the host.- The setup wizard, to install everything else — the chat CLI, Ollama, Tailscale — one step at a time.
install.sh only handles the binary and the daemon — it does not wire up any
agent. Once it's done, point the binary at whichever bridge you use:
# Run from a project root — `claude`/`gemini` init are per-project scoped;
# `codex`/`hermes` init also register machine-wide, but still need a per-project
# run to drop their context file (AGENTS.md/HERMES.md) into this project.
drun-mcp claude init
drun-mcp gemini init
drun-mcp codex init
drun-mcp hermes initdrun-mcp bridges list shows every bridge drun currently supports (name, scope,
and what it does). drun-mcp bridges deregister-all undoes every bridge that's
currently registered in one call.
See Claude Code, Gemini CLI, Codex CLI, and Hermes for what each of these does.
Once installed, the following endpoints are available:
| Endpoint | Purpose |
|---|---|
http://127.0.0.1:7273/sse |
MCP transport (SSE); used by Claude Code |
http://127.0.0.1:7273/mcp |
MCP transport (streamable HTTP); used by the CLI |
http://127.0.0.1:7274 |
Web interface to manage sessions |
http://127.0.0.1:7275 |
Setup wizard |
install.sh finishes by launching drun-mcp setup in the background and
opening http://127.0.0.1:7275 in your browser. It never runs anything on your
machine itself — it's a checklist: every dependency lists the exact command to
install it and, right below, the command that undoes it. Copy a command into
your own terminal (the wizard's Open Terminal button opens one), run it,
fill in any password or prompt it asks for, then hit Validate dependencies
to re-check and flip that dependency's pill from "Not ready" to "Ready":
- drun daemon — confirms the MCP and web UI ports are actually up; no
commands here,
install.shalready handled it. - Homebrew (macOS only) — the package manager the Ollama/Tailscale commands below install through.
- Ollama — install, start the local server, and pull a default tool-calling model, for the standalone CLI.
- drun chat CLI — installs
drun-sandbox[chat]viapip; powersdrun chatand the web UI's per-session Chat button. - Tailscale — install, start its background daemon, sign in, and expose the
web UI over your tailnet — see Manage drun remotely.
Starting the daemon needs
sudo, which is exactly why this only ever shows you the command instead of running it.
Run the following commands to upgrade drun's MCP to the latest release:
The upgrade operation hard-reloads the daemon process, effectively dropping all in-memory objects, including ongoing sessions. Be sure to snapshot and close your sessions before updating.
# MCP binary
curl -fsSL https://raw.githubusercontent.com/dmosc/drun/main/update.sh | bash
# Update to a specific version
curl -fsSL https://raw.githubusercontent.com/dmosc/drun/main/update.sh | bash -s -- v0.3.16update.sh also upgrades drun-sandbox[chat] to its latest version,
best-effort, if it's already installed — skipped quietly otherwise.
Run the following command to uninstall drun from your host:
curl -fsSL https://raw.githubusercontent.com/dmosc/drun/main/uninstall.sh | bash
- Stops the background daemon and removes the
launchdagent (macOS) orsystemduser service (Linux). - Unlinks the MCP from any bridge it was wired to (e.g. Claude Code, Gemini
CLI, Codex CLI, Hermes) — via
drun-mcp bridges deregister-all, which knows every bridge drun supports withoutuninstall.shhaving to name them. - Removes the drun MCP binary from
/usr/local/bin/drun-mcp. - Removes
.claude/settings.jsonand.gemini/settings.jsonfrom every project drun was initialized in, so native tools are restored automatically. - Leaves
~/.drun/config.tomland any per-project context files (CLAUDE.md,GEMINI.md,AGENTS.md,HERMES.md) untouched; delete these manually if not needed.
- Claude Code.
- The
drun-mcpdaemon installed above.
From the root of any project you want drun to manage:
drun-mcp claude initThis does two things:
- Registers drun with Claude Code
(
claude mcp add --scope user --transport sse drun http://127.0.0.1:7273/sse) — a one-time, user-scope step; skipped if already registered. If theclaudeCLI isn't onPATH, it prints this command instead so you can run it yourself once Claude Code is installed. - Creates two files in the current directory (appends if they already
exist):
.claude/settings.json— restricts Claude to drun tools only for this workspace. Native file (Read,Edit,Write,NotebookEdit,Glob,Grep), shell (Bash,BashOutput,KillBash), network (WebFetch,WebSearch), and subagent delegation (Task) tools are all blocked, and drun's MCP tools are pre-allowed so Claude isn't prompted on every call.CLAUDE.md— instructs Claude how to use drun.
The tool restriction is intentionally per-project; you wouldn't want native
tools blocked globally across every workspace. Run drun-mcp claude init from
any project root to opt that project into the drun sandbox — the registration
step is idempotent, so re-running it across projects doesn't re-register with
Claude Code each time.
Validate that the MCP is live:
claude mcp listTo undo the global registration (leaving any per-project .claude/settings.json
and CLAUDE.md files in place — remove those by hand if you no longer want
them):
drun-mcp claude deregister- Gemini CLI.
- The
drun-mcpdaemon installed above.
From the root of any project you want drun to manage:
drun-mcp gemini initThis does two things:
- Registers drun with Gemini CLI
(
gemini mcp add --scope user --transport sse drun http://127.0.0.1:7273/sse) — a one-time, user-scope step; skipped if already registered. If thegeminiCLI isn't onPATH, it prints this command instead so you can run it yourself once Gemini CLI is installed. - Creates two files in the current directory (appends/skips if they already
exist):
.gemini/settings.json— excludes drun-overlapping native tools (ShellTool,EditTool,WriteFileTool,ReadFileTool,GlobTool,GrepTool,ReadManyFilesTool,LSTool,WebFetchTool,WebSearchTool) viaexcludeTools, for this workspace only.GEMINI.md- instructs Gemini how to use drun tools.
Same as Claude Code, the tool restriction is per-project and the registration
step is idempotent — re-running drun-mcp gemini init across projects doesn't
re-register with Gemini CLI each time.
To undo the global registration (leaving any per-project .gemini/settings.json
and GEMINI.md files in place):
drun-mcp gemini deregisterRun this from the root of any project you want drun to manage:
drun-mcp codex initThis does three things, all directly editing ~/.codex/config.toml (a
structural TOML merge via toml_edit, so any
comments or formatting you already have there survive untouched):
-
Creates
AGENTS.mdin the current directory (skipped if it already exists) — Codex's own auto-discovered context file, same roleCLAUDE.mdplays for Claude Code. -
Registers drun under
[mcp_servers.drun], pointing at the daemon's streamable-HTTP endpoint:[mcp_servers.drun] url = "http://127.0.0.1:7273/mcp"
-
Disables Codex's native shell tool (
features.shell_tool = false) so Codex relies on drun's sandboxedsession_bashinstead of running commands directly on the host. Becauseconfig.tomlisn't project-scoped, this applies to every Codex session on the machine, not just projects using drun.
No CLI availability check is needed here (unlike Claude Code, Gemini CLI, or
Hermes) — this is a plain file edit, so it works whether or not the codex
binary is on PATH yet.
Steps 2 and 3 are machine-wide and idempotent — re-running drun-mcp codex init
in a second project skips them and only step 1 (AGENTS.md) does anything new.
To undo everything codex init did (deregisters drun and re-enables the shell
tool; leaves any project's AGENTS.md in place):
drun-mcp codex deregisterRun this from the root of any project you want drun to manage — same as
drun-mcp claude init for Claude Code:
drun-mcp hermes initThis does three things:
-
Creates
HERMES.mdin the current directory which instructs Hermes how to rely no drun. -
Registers drun by writing a
drunentry directly into~/.hermes/config.yamlundermcp_servers, pointing at the daemon's streamable-HTTP endpoint:mcp_servers: drun: url: "http://127.0.0.1:7273/mcp" headers: Accept: "application/json, text/event-stream"
If the
hermesCLI isn't onPATHyet, it prints this block instead so you can add it manually once Hermes is set up. -
Disables Hermes's native
terminal,file,web,search, anddelegationtoolsets (viaagent.disabled_toolsetsin the same file) so Hermes relies on drun's sandboxed tools instead of touching the host directly. Because this key isn't project-scoped, it applies to every Hermes session on the machine, not just projects using drun — if you want Hermes to keep native tool access for other work, skip this step and edit~/.hermes/config.yamlby hand to register just themcp_serversentry.
Steps 2 and 3 are machine-wide and idempotent — re-running
drun-mcp hermes init in a second project skips them (already
registered/already disabled) and only step 1 (HERMES.md) actually does
anything new.
Start Hermes and it will discover drun's tools at connect time:
hermes chatTo undo everything hermes init did (deregisters drun and re-enables the
disabled toolsets machine-wide; leaves any project's HERMES.md in place — same
as Claude Code leaves CLAUDE.md, delete it by hand if you no longer want it):
drun-mcp hermes deregisterdrun chat drives an LLM — local via Ollama or any cloud
model supported by LiteLLM — against a
sandboxed session.
- Python 3.9+.
- The
drun-mcpdaemon installed above. - Ollama for local models, or an API key for a cloud model.
The setup wizard installs this with one click; check with
command -v drun before running this by hand:
pip install 'drun-sandbox[chat]'For a local model, install Ollama and pull a tool-calling-capable model. The setup wizard does both with one click each; by hand:
ollama pull qwen3.6:latestThen run:
drun chat "your prompt" --mount ./my-project--model defaults to ollama_chat/qwen3.6:latest. To use a cloud model
instead, pass --model and set the provider's API key:
ANTHROPIC_API_KEY=... drun chat "your prompt" --model claude-sonnet-4-6Each drun chat call creates a new session by default. Pass --session-id to
attach to one that's already running instead:
drun chat "keep going on the report in results.md" --session-id <id>Run drun chat --help for all flags.
Useful to spin up drun sessions programatically.
- Python 3.9+.
pip install drun-sandboxfrom drun import Session
session = Session()
session.write_file("hello.py", b"print('hi')")
checkpoint = session.execute_bash("python3 hello.py")
print(checkpoint.stdout)See examples/quickstart.py for a fuller walkthrough
(bash execution, write, diff, rollback, export).
The behavior of the drun MCP is orchestrated via ~/.drun/config.toml, a single
global file shared by the background daemon. It's re-read on every tool call;
without it, built-in defaults apply.
The following is a reference of all the controls available for tuning. All fields are optional.
| Field | Default | Description |
|---|---|---|
domain_allowlist |
["pypi.org", "files.pythonhosted.org", "cdn.jsdelivr.net"] |
Domains reachable via session_fetch. Defaults to the three built-ins if the key is absent; setting it explicitly (including []) replaces the defaults outright, so an operator can restrict below them. Use ["*"] to allow all, or "*.example.com" for subdomains. |
fetch_timeout_ms |
60000 |
Timeout for the full session_fetch response in milliseconds. |
connect_timeout_ms |
30000 |
TCP connection timeout for session_fetch in milliseconds. |
bash_timeout_ms |
30000 |
Maximum wall time for a single session_bash call. |
package_install_enabled |
false |
Enables session_package_install. Disabled by default because, unlike session_bash, its sandbox has network access (confined to a disposable staging directory, never the session workspace). |
package_install_timeout_ms |
180000 |
Maximum wall time for a single session_package_install call. |
max_workspace_mb |
512 |
Maximum workspace size per session in megabytes. Checked before each new checkpoint is appended. |
max_sessions |
50 |
Maximum number of concurrent sessions. |
max_checkpoints |
200 |
Maximum checkpoints stored per session. When the limit is reached, squash or drop old checkpoints. |
session_idle_timeout_secs |
3600 |
Seconds of inactivity before a session is considered abandoned and rejected. |
mount_allowlist |
[] |
Host path prefixes that session_mount may read from, and session_export/delete_from_host may write to or delete. Empty means all paths are permitted. |
mount_overlay_paths |
["node_modules", ".venv", "venv", "target", "__pycache__", ".git"] |
Directory names that session_mount registers as read-only host overlays instead of loading into the workspace. Overlay dirs are symlinked at execution time and never checkpointed. Set to [] to disable. |
snapshots_dir |
"~/.drun/snapshots" |
Directory where session_snapshot writes .drun files. |
snapshot_on_close |
false |
When true, automatically write a snapshot when session_close is called. |
env_allowlist |
[] |
Host environment variable names exposed to agents via session_get_env. Empty means no variables are exposed. |
bash_command_denylist |
[] |
Command substrings always rejected by session_bash before execution. |
bash_command_allowlist |
[] |
Command substrings permitted by session_bash. Empty means all commands are allowed (subject to the denylist). |
web_port |
7274 |
TCP port for the trajectory viewer web UI. Set to 0, or remove the field from the config file, to disable it. |
default_model |
"ollama_chat/qwen3.6:latest" |
See LiteLLM's provider list for full list (e.g. claude-sonnet-4-6, gpt-4o, openrouter/anthropic/claude-sonnet-4-6) |
anthropic_api_key |
unset | API key exported as ANTHROPIC_API_KEY for Anthropic's backend. |
openai_api_key |
unset | API key exported as OPENAI_API_KEY for OpenAI's backend. |
openrouter_api_key |
unset | API key exported as OPENROUTER_API_KEY for OpenRouter's backend. |
deepseek_api_key |
unset | API key exported as DEEPSEEK_API_KEY for DeepSeek's backend. |
gemini_api_key |
unset | API key exported as GEMINI_API_KEY for Gemini's backend. |
A couple of utility commands to update the configuration via the drun-mcp CLI
are available:
drun-mcp config add-domain example.com
drun-mcp config add-path /path/to/allow
drun-mcp config remove-domain example.com
drun-mcp config remove-path /path/to/allow
# To validate latest changes to config.
drun-mcp config listRun drun-mcp config --help to print a list of available commands.
~/.drun/config.toml is re-read on every tool call, so edits — via the CLI
above or by hand — take effect on the very next call, no restart, no dropped
sessions. drun-mcp claude init also allowlists the current project directory
for session_mount automatically.
The exceptions are web_port, session_idle_timeout_secs: these are only
applied at daemon startup, so changing any of them still requires a restart:
macOS
launchctl unload ~/Library/LaunchAgents/com.drun.mcp-server.plist
launchctl load -w ~/Library/LaunchAgents/com.drun.mcp-server.plistLinux
systemctl --user restart drun-mcp.serviceA dead or crash-looping daemon can look identical to an idle one from the outside. See docs/troubleshooting.md's Health check section for commands to confirm it's running exactly once, actually listening, and not stuck being killed and retried by launchd/systemd.
We recommend setting up Tailscale across your devices to remotely manage drun. The setup wizard installs Tailscale, walks you through signing in, and exposes the web UI in one click each — or by hand, roughly the steps are as follows:
-
Install drun and make sure that the web service is up by visiting
http://127.0.0.1:7274. -
Download Tailscale on the different devices you want to connect (e.g. computer and phone).
-
Boot up a Tailscale server pointing at the drun webserver with:
tailscale serve --bg --https=443 http://127.0.0.1:7274
-
If the command succeeds, your web server UI should be accessible at the domain printed in the console by Tailscale.
To drive sessions from your phone via the web UI's
Chat button — the main point of remote management,
since it needs no terminal — the host also needs drun-sandbox[chat] installed.
The setup wizard does this with one click; confirm with
command -v drun on the host, or see Standalone CLI to
install it by hand.
Each session card has a Chat button — send it a prompt and the daemon runs
drun chat against that exact session, so the agent gets the daemon's full
sandboxed tool suite (session_bash, session_fetch, ...) under the same
guardrails as any other bridge. Handy paired with
remote management: drive a session from your phone
without a terminal.
Requires the standalone CLI installed on the same machine as the daemon — one click in the setup wizard, or by hand:
pip install 'drun-sandbox[chat]'The card's status pill shows "Running" while the agent works; refresh the session's checkpoint list to see what it did.
By default the agent runs against a local Ollama model. To use a cloud model
instead (Claude, OpenAI, OpenRouter, DeepSeek, or Gemini), open Edit config
in the web UI and set default_model plus the matching provider's API key — see
Configuration for the field names. No restart needed; the next
Chat request picks it up.
