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.
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 |
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.16Run 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— tells Claude to use drun tools instead of native ones and how to bootstrap a session (create_sessionthensession_mount).
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— tells Gemini to use drun tools instead of native ones and how to bootstrap a session (create_sessionthensession_mount).
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 (skipped if it already exists) — Hermes's own highest-priority context file, auto-discovered per project the same wayCLAUDE.mdis for Claude Code. Without it, Hermes has no way to know drun exists or how to bootstrap a session beyond whatever's in its raw tool list — this is what tells it to callcreate_sessionthensession_mountup front, same instructionsCLAUDE.mdgives Claude Code. -
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.
pip install 'drun-sandbox[chat]'For a local model, install Ollama and pull a tool-calling-capable model:
ollama pull qwen2.5:14bThen run:
drun chat "your prompt" --mount ./my-project--model defaults to ollama_chat/qwen2.5:14b. 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/session_commit may write back to. 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. |
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 two exceptions are web_port and session_idle_timeout_secs: both are only
applied at daemon startup, so changing either 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.

