A CLI that spins up lightweight Linux VMs to sandbox agentic coding tools. The agent runs inside the VM with full autonomy — it cannot damage the host or exfiltrate secrets beyond what was explicitly injected.
Agentic coding tools (Claude Code, etc.) are most useful when running in "dangerous mode" (auto-accept all tool calls), but this is risky on bare metal. pen gives each agent a disposable Linux VM with:
- Full isolation — the guest cannot modify the host filesystem outside the shared directory
- Scoped credentials — only the env vars you explicitly pass are available in the guest
- Near-native performance — Apple Virtualization.framework with virtio devices
- Zero config networking — NAT provides outbound connectivity for API calls and git
git clone https://github.com/agarcher/pen.git
cd pen
make build
make install # Installs to /usr/local/binDownload Binary
Download the latest release from the releases page. The binary must be ad-hoc code signed on your machine:
codesign --force --entitlements entitlements/pen.entitlements -s - penRequirements: macOS 13+ (Ventura or later), Apple Virtualization.framework
# Boot a VM sharing the current directory
pen shell myproject --dir .
# You're now inside an Alpine Linux VM at /workspace
# The host directory is mounted read-write via virtio-fs
ls /workspace
# Exit the VM (Ctrl-D or exit)
exitOn first run, pen automatically downloads a minimal Alpine Linux image (~15MB kernel + initrd).
Profiles let you declare packages, build scripts, and first-boot setup in a TOML file. Stable tools are baked into an immutable custom image (built once per profile); per-VM state lives on a persistent overlay disk.
# Create a profile
cat > ~/.config/pen/profiles/claude.toml <<'EOF'
packages = ["nodejs", "npm", "git", "ripgrep"]
build = """
npm install -g @anthropic-ai/claude-code
rm -rf /var/cache/apk/*
"""
setup = """
mkdir -p /root/.claude
"""
EOF
# Build the custom image (or let pen shell do it automatically)
pen image build claude
# Boot a VM with the profile
pen shell agent --profile claude --dir .See the Usage Guide for details.
| Command | Description | Details |
|---|---|---|
pen shell <name> |
Create, start, and attach to a VM | docs |
pen list |
List all VMs with status and profile | docs |
pen stop <name> |
Stop a running VM | docs |
pen delete <name> |
Delete a VM and its state | docs |
pen profile list |
List available profiles | docs |
pen profile show <name> |
Show a profile's configuration | docs |
pen image build <profile> |
Build a custom image for a profile | docs |
pen image list |
List built images with sizes | docs |
pen version |
Print version number | docs |
See the Usage Guide for detailed command documentation.
Pass credentials and configuration into the guest without them ever touching host disk beyond a brief window during boot:
# Explicit value
pen shell myproject --dir . --env OPENAI_API_KEY=sk-...
# Forward from host environment
pen shell myproject --dir . --env-from-host ANTHROPIC_API_KEY --env-from-host GITHUB_TOKENInside the guest, the variables are available in the shell session:
echo $ANTHROPIC_API_KEY # Shows the injected valueSee Environment Injection for details.
Users of wt (git worktree manager) can add a hook to .wt.yaml to automatically boot a VM for each new worktree:
hooks:
post_create:
- script: pen shell ${WT_NAME} --dir ${WT_PATH}See Architecture for an overview of the codebase.
make build # Build pen (CGo + codesign)
make test # Run tests
make lint # Run linters
make image # Build VM image locallyMIT