I’ve also been experimenting with agent sandboxes lately. redoubtful is a work-in-progress sandbox that supports:

  • Linux-only sandboxes: I’m focusing on what Linux supports, specifically, rather than trying to support the lowest-common-denominator features that work cross platform.
  • Modular configuration profiles: See below.
  • Isolation using pasta and bwrap.
  • A shadow filesystem that looks like your home directory, so things like git worktree actually work correctly. You can also selectively mount existing parts of your filesystem in read-only or read-write mode.
  • Network port forwarding and filtering proxy server.
  • TODO: Proxy credential support.

But first, a warning: Nearly 100% of this code was written by coding agents, much of it by a local Qwen3.6 27B. I am, however, keeping a very close eye on the output—one of my goals here is to see just what a small agent like this can do. This is maybe only 80% as good as my handwritten code would be a similar point in a project.

And finally, this is an incomplete work-in-progress, and it has not been packaged nicely for anyone besides me yet.

Modular configuration “profiles”

One of the slightly novel parts of all this is the ability to define modular configuration. This allows us to invoke a sandbox with a specific set of credentials:

redoubtful run --uses pi --uses llama-server pi

Here, we’re running the pi.dev coding agent with a locally-served Qwen3.6 27B via llama-server. Qwen3.6 27B is a fantastic lightweight coding model, and it works very well with pi.dev’s minimalist prompt. And since we’re running in a sandbox, we don’t care that pi.dev provides no sandbox and no confirmation before acting.

To set up these two profiles, we first define a node profile:

# Standard Node setup. If you're using `nvm`, you'll need to fix the path_add
# entry to point to the correct nvm version.
#
# We might want some kind of plugin system to handle messy things like nvm.
[profile.node]
mounts = [
    { host = "~/.npm-global" },
    { host = "~/.local/share/nvm", access = "rw" },
]
path_add = ["~/.npm-global/bin", "~/.local/share/nvm/v24.15.0/bin"]

Then let’s make Rust work:

# A Rust setup, with optional rustup and advisory support.
[profile.rust]
mounts = [
    { host = "~/.rustup" },
    { host = "~/.cargo" },
    # Cargo audit/deny support, which needs to take a lock to update the
    # advisory database.
    { host = "~/.cargo/advisory-dbs/", access = "rw" },
]
path_add = ["~/.cargo/bin"]

And then basic git is easy—we just need enough config to read user.name and user.email:

# Things you will likely want for git.
[profile.git]
mounts = [{ host = "~/.gitconfig" }]

And then finally, we can set up pi itself:

# Profile for the pi coding agent. Run with:
#
#     redoubtful run -u pi pi
[profile.pi]
uses = ["node", "rust", "git"]
mounts = [{ host = "~/.pi", access = "rw" }]
# Pass through llama-server connections.
[profile.llama-server]
forwards = [{ host_port = 8080 }]
proxies = [{ host = "127.0.0.1" }]

What’s left?

The biggest missing piece is teaching the proxy server how to inject real credentials into network connections. This isn’t a new idea. The goal is to provide access to things like GitHub without giving an agent actual credentials.

After that, it’s just packaging everything up nicely and writing some docs, so that other people (or agents) can easily configure it for different purposes.