# Ephemeral runtime harness for agentic workflows open source

> Source: <https://github.com/dmosc/drun>
> Published: 2026-07-18 05:30:16+00:00

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:

: drun's MCP tools replace Claude's native file/shell/network tools inside a sandboxed workspace.[Via Claude Code](#claude-code): a CLI agentic loop that's integrated with[Standalone CLI](#standalone-cli)[Ollama](https://ollama.com/)and[LiteLLM](https://docs.litellm.ai/).: script sandboxed sessions directly, no LLM or daemon required.[Using the Python SDK](#python-sdk)

NOTE: There are plans in the future to support additional model providers like Codex and Gemini CLI. Consider this document as the official reference of production-ready offerings.

All journeys except for the [Python SDK](#using-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 | bash
```

This installs and configures a few things (skips if not applicable):

- The
`drun-mcp`

binary under`/usr/local/bin/drun-mcp`

. - A global config at
`~/.drun/config.toml`

with sensible defaults. `drun-mcp`

as a persistent background daemon (`launchd`

on macOS,`systemd`

on Linux) so a single process serves all simultaneous sessions running on the host.- The MCP registration in Claude Code pointing at the running daemon over SSE
(
`http://127.0.0.1:7273/sse`

).

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.8
```

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
`launchd`

agent (macOS) or`systemd`

user service (Linux). - Removes the drun MCP binary from
`/usr/local/bin/drun-mcp`

. - Unlinks the MCP reference from Claude Code.
- Removes
`.claude/settings.json`

from each project so native Claude tools are restored automatically. - Leaves
`~/.drun/config.toml`

and any`CLAUDE.md`

files untouched; delete these manually if not needed.

[Claude Code](https://code.claude.com/docs/en/quickstart#step-1-install-claude-code).- The
`drun-mcp`

daemon[installed](#global-install-once-per-machine)above.

From the root of any project you want drun to manage:

```
drun-mcp init
```

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_session`

then`session_mount`

).

This restriction is intentionally per-project; you wouldn't want native tools
blocked globally across every workspace. Run `drun-mcp init`

from any project
root to opt that project into the drun sandbox.

Validate that the MCP is live:

```
claude mcp list
```

`drun chat`

drives an LLM — local via [Ollama](https://ollama.com) or any cloud
model supported by [LiteLLM](https://docs.litellm.ai/docs/providers) — against a
sandboxed session.

- Python 3.9+.
- The
`drun-mcp`

daemon[installed](#global-install-once-per-machine)above. [Ollama](https://ollama.com)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:14b
```

Then 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-6
```

Each `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-sandbox
python
from 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](/dmosc/drun/blob/main/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. |
`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. Empty means all paths are permitted. Non-empty restricts mounts to the listed prefixes. |
`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. |
`export_root` |
`"drun-export"` |
Directory that `session_export` must write into. Relative paths are resolved from the current working directory. |
`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 list
```

Run `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 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.plist
```

**Linux**

```
systemctl --user restart drun-mcp.service
```

A dead or crash-looping daemon can look identical to an idle one from the
outside. See
[docs/troubleshooting.md's Health check section](/dmosc/drun/blob/main/docs/troubleshooting.md#health-check--is-drun-actually-running)
for commands to confirm it's running exactly once, actually listening, and not
stuck being killed and retried by launchd/systemd.
