Isolated, pre-configured sandbox containers for AI coding agents — Claude Code, OpenAI Codex, Pi Agent, and more.
Spin up a fully-provisioned container where AI coding agents can operate with full permissions, persistent memory, and autonomous background tasks — without touching your host system.
Only host dependency: Docker.
git clone https://github.com/ryaneggz/open-harness.git && cd open-harness
cp .devcontainer/.example.env .devcontainer/.env # configure name, password, etc.docker compose --env-file .devcontainer/.env -f .devcontainer/docker-compose.yml up -d --buildOption A — Terminal (works everywhere):
docker exec -it -u sandbox openharness bash # use your SANDBOX_NAMEOption B — VS Code Attach to Container (local):
Install the Dev Containers extension → Cmd+Shift+P → "Attach to Running Container" → select your sandbox.
Option C — VS Code Remote SSH + Attach (remote server): If Docker is running on a remote host, SSH into the host first, then attach to the container.
-
Add an entry to your local
~/.ssh/configso credentials forward automatically:Host openharness HostName your-server-ip User openharness ForwardAgent yes -
In VS Code: Remote-SSH: Connect to Host →
openharness -
Once connected to the remote host: Attach to Running Container → select your sandbox.
Option D — SSH directly into sandboxes (multi-sandbox host):
Enable the sshd overlay and assign each sandbox a unique port. This lets you SSH straight into any sandbox — from your laptop, from another sandbox, or from CI. See Multi-sandbox SSH for full setup.
gh auth login # authenticate GitHub CLI
gh auth setup-git # configure git auth (no SSH keys needed)
pi # authenticate Pi Agent (OAuth) — powers Slack, heartbeats, and extensionsclaude # terminal coding agent
pi # automations — Slack, heartbeats, extensionsdocker compose -f .devcontainer/docker-compose.yml down -vThe sandbox image (Debian bookworm-slim) comes pre-installed with:
| Category | Tools |
|---|---|
| AI agents | Claude Code, OpenAI Codex, Pi Agent, Mom (Slack bot) |
| Runtimes | Node.js 22, pnpm, Bun, uv (Python) |
| DevOps | Docker CLI + Compose, GitHub CLI, cloudflared, tmux, cron |
| Browser | agent-browser + Chromium (headless, for web-capable agents) |
| Utilities | git, jq, ripgrep, nano, curl, wget, SSH server |
The sandbox user has passwordless sudo and full Docker socket access (with the docker overlay). Agents run with --dangerously-skip-permissions / --full-auto so they can operate autonomously.
Copy the example env file and edit to taste:
cp .devcontainer/.example.env .envDocker Compose reads .env automatically from the project root.
| Variable | Default | Description |
|---|---|---|
SANDBOX_NAME |
openharness |
Container name, compose project name, and CLI identifier |
SANDBOX_PASSWORD |
changeme |
Linux user password — only used when the sshd overlay is active |
TZ |
America/Denver |
Container timezone — affects cron schedules and log timestamps |
Heartbeats are cron-scheduled tasks that run an AI agent CLI on a recurring schedule (e.g., hourly issue triage). Task definitions live in workspace/heartbeats.conf.
| Variable | Default | Description |
|---|---|---|
HEARTBEAT_AGENT |
claude |
Which agent CLI runs heartbeat tasks (claude, codex, pi) |
HEARTBEAT_ACTIVE_START |
(empty) | Hour (0-23) when heartbeats start firing (e.g., 8 for 8 AM) |
HEARTBEAT_ACTIVE_END |
(empty) | Hour (0-23) when heartbeats stop firing (e.g., 18 for 6 PM) |
When both ACTIVE_START and ACTIVE_END are set, heartbeats only fire during that window. When empty, heartbeats run at any hour.
| Variable | Default | Description |
|---|---|---|
HOST_SSH_DIR |
(empty) | Host SSH directory mounted read-only for git auth. Setting this auto-enables the ssh.yml overlay — no need to add it to config.json manually. |
The Slack bot (Mom) connects to a workspace via Socket Mode and delegates messages to a Pi agent. Only used with the slack overlay.
| Variable | Default | Description |
|---|---|---|
SLACK_APP_TOKEN |
(empty) | Slack app-level token for Socket Mode (xapp-...) |
SLACK_BOT_TOKEN |
(empty) | Slack bot OAuth token for posting messages (xoxb-...) |
The base docker-compose.yml provides the sandbox container with bind-mounted workspace and persistent auth volumes. Overlays add optional services and capabilities.
Toggle overlays in .openharness/config.json:
{
"composeOverrides": [
".devcontainer/docker-compose.docker.yml",
".devcontainer/docker-compose.slack.yml"
]
}| Overlay | File | What it adds |
|---|---|---|
| postgres | docker-compose.postgres.yml |
Postgres 16 on a bridge network. Sets DATABASE_URL, PGHOST, PGUSER, PGPASSWORD, PGDATABASE automatically. Data persisted in a pgdata volume. |
| docker | docker-compose.docker.yml |
Mounts the host Docker socket so agents can run containers from inside the sandbox (Docker-in-Docker). |
| ssh | docker-compose.ssh.yml |
Mounts HOST_SSH_DIR read-only so git can authenticate with your existing SSH keys. Auto-enabled when HOST_SSH_DIR is set in .env. |
| ssh-generate | docker-compose.ssh-generate.yml |
Persists ~/.ssh in a named volume so generated keys survive container rebuilds. Use this instead of ssh if the sandbox should have its own keys. |
| sshd | docker-compose.sshd.yml |
Runs an SSH server inside the sandbox on port 2222:22. Enables direct SSH access for remote workflows and multi-sandbox setups. Uses SANDBOX_PASSWORD for auth. |
| slack | docker-compose.slack.yml |
Passes Slack tokens into the container and persists agent auth in a named volume. The bot auto-starts in a tmux session on container boot if both tokens are set. |
| cloudflared | docker-compose.cloudflared.yml |
Sets INSTALL_CLOUDFLARED=true and INSTALL_BROWSER=true so the entrypoint installs Cloudflare Tunnel and agent-browser on first boot. |
| git | docker-compose.git.yml |
Mounts GIT_COMMON_DIR for git worktree support across host and container. |
Multiple overlays can be combined. Order doesn't matter.
Run multiple sandboxes on a single host, each reachable via SSH on a unique port. This enables:
- Direct SSH from your laptop into any sandbox
- Sandbox-to-sandbox communication (agents orchestrating other agents)
- CI/CD integration — run commands inside sandboxes from pipelines
-
Enable the
sshdoverlay for each sandbox and assign unique host ports.Each sandbox gets its own project directory (or override file) with a distinct port mapping:
# sandbox-alpha — port 2222 services: sandbox: ports: - "2222:22" # sandbox-bravo — port 2223 services: sandbox: ports: - "2223:22"
-
Configure your local
~/.ssh/configso each sandbox is a named host:Host sandbox-alpha HostName your-server-ip # or 127.0.0.1 for local Docker Port 2222 User sandbox ForwardAgent yes Host sandbox-bravo HostName your-server-ip Port 2223 User sandbox ForwardAgent yesForwardAgent yespasses your local SSH keys through to the sandbox, so git operations inside the container use your host credentials without copying keys. -
Connect:
# Terminal ssh sandbox-alpha # VS Code # Remote-SSH: Connect to Host → sandbox-alpha
Sandboxes on the same host can reach each other via the host ports or the Docker network. An agent in sandbox-alpha can SSH into sandbox-bravo:
ssh -p 2223 sandbox@host.docker.internalThis is useful for orchestration patterns where one agent delegates work to others.
The base compose file creates three named Docker volumes that persist across container rebuilds:
| Volume | Mount | Contents |
|---|---|---|
claude-auth |
~/.claude |
Claude Code OAuth tokens and config |
cloudflared-auth |
~/.cloudflared |
Cloudflare Tunnel credentials |
gh-config |
~/.config/gh |
GitHub CLI auth tokens |
The workspace directory (workspace/) is bind-mounted from the host, so changes are immediately visible in both directions. Project files, agent memory, heartbeat configs, and skills all live here and survive container rebuilds without needing a volume.
Overlays may add additional volumes:
| Overlay | Volume | Contents |
|---|---|---|
slack |
agent-auth |
Pi agent auth (~/.pi) |
ssh-generate |
ssh-keys |
Generated SSH keys (~/.ssh) |
postgres |
pgdata |
Postgres data directory |
docker compose down -v removes all volumes. Omit -v to keep them.
The workspace/ directory is the agent's home. It's bind-mounted into the container at /home/sandbox/harness/workspace/.
workspace/
AGENTS.md # Operating procedures — decision rules, skills, sub-agents
CLAUDE.md # Symlink → AGENTS.md (Claude Code reads this automatically)
SOUL.md # Agent personality, tone, values, guardrails
IDENTITY.md # Name, role, mission, stack, URLs
USER.md # Owner preferences, constraints, goals
TOOLS.md # Environment, available tools, service endpoints
HEARTBEAT.md # Meta-maintenance routines
MEMORY.md # Long-term memory (learned decisions, lessons)
heartbeats.conf # Cron schedule for autonomous tasks
heartbeats/ # Task definitions (markdown files)
startup.sh # Runs on container boot after onboarding
memory/ # Daily activity logs (YYYY-MM-DD.md)
projects/ # Application code (e.g., Next.js app)
.claude/
rules/ # Coding standards (auto-loaded by Claude Code)
skills/ # Reusable skill definitions
agents/ # Sub-agent prompts
| File | Owns | Updated by |
|---|---|---|
IDENTITY.md |
Name, role, mission, stack, URLs | Orchestrator (initial), agent (evolves) |
SOUL.md |
Personality, tone, values, guardrails | Orchestrator (initial), agent (evolves) |
USER.md |
Owner preferences, constraints, goals | User or orchestrator |
TOOLS.md |
Environment details, service endpoints | Orchestrator |
AGENTS.md |
Decision rules, skills, procedures | Orchestrator (initial), agent (evolves) |
HEARTBEAT.md |
Meta-maintenance routines | Agent |
MEMORY.md |
Learned facts, decisions, lessons | Agent |
The orchestrator scaffolds these files during provisioning. Once the agent is running, it owns and evolves them.
Heartbeats are cron-scheduled autonomous tasks. The agent CLI runs a prompt from a markdown file on a recurring schedule.
workspace/heartbeats.conf uses a 5-field cron expression:
# cron expression | task file | agent | active_hours
*/30 * * * * | heartbeats/build-health.md | claude | 9-21
50 23 * * * | heartbeats/nightly-release.md | claude
0 * * * * | heartbeats/documentation-drift-delegate.md
| Field | Required | Description |
|---|---|---|
| Cron expression | Yes | Standard 5-field cron (min hour dom mon dow) |
| Task file | Yes | Markdown file with the prompt to run |
| Agent | No | Which CLI to use (defaults to HEARTBEAT_AGENT env var) |
| Active hours | No | START-END range (24h) — overrides env vars per-task |
# Inside the sandbox:
heartbeat.sh sync # Install cron entries from heartbeats.conf
heartbeat.sh status # Show active cron jobs and last run times
heartbeat.sh stop # Remove all heartbeat cron entriesLogs are written to ~/.heartbeat/heartbeat.log inside the container.
The openharness CLI runs on the host (outside the container).
| Command | Description |
|---|---|
openharness sandbox [name] |
Build and start a sandbox |
openharness run [name] |
Start an existing (stopped) container |
openharness shell <name> |
Open a bash shell inside the sandbox |
openharness stop [name] |
Stop the container |
openharness clean [name] |
Full cleanup — containers and volumes |
openharness onboard [name] |
Run the first-time setup wizard |
openharness list |
List running sandboxes |
openharness heartbeat <action> <name> |
Manage heartbeats (sync, stop, status) |
openharness worktree <name> |
Create a git worktree for parallel branches |
Run openharness with no arguments for interactive AI agent mode.
The CLI requires Node.js on the host. It's optional — all core operations can be done with docker compose and docker exec directly.
curl -fsSL https://raw.githubusercontent.com/ryaneggz/open-harness/refs/heads/main/install.sh | bash.devcontainer/ # Sandbox environment
Dockerfile # Debian bookworm-slim + all tooling
docker-compose.yml # Base sandbox service
docker-compose.*.yml # Compose overlays (postgres, sshd, slack, etc.)
entrypoint.sh # Container bootstrap script
.example.env # Environment variable template
install/ # Provisioning scripts
onboard.sh # First-time auth wizard (6 steps)
heartbeat.sh # Cron scheduler for autonomous tasks
entrypoint.sh # Late-stage container setup
cloudflared-tunnel.sh # Cloudflare Tunnel configuration
slack-manifest.json # Slack app manifest for Socket Mode
workspace/ # Agent workspace template (bind-mounted)
packages/
sandbox/ # @openharness/sandbox — CLI + container lifecycle
slack/ # Vendored fork of pi-mom — Slack bot
.openharness/ # Runtime config (config.json, settings)
.github/ # CI workflows + issue templates
docs/ # Documentation site (Nextra)
CalVer: YYYY.M.D (e.g., 2026.4.4). Push a tag to build and publish to ghcr.io/ryaneggz/open-harness.