{"id":50602361,"url":"https://github.com/travisbreaks/openclaw-ec2-sandbox","last_synced_at":"2026-06-05T19:33:13.642Z","repository":{"id":349064641,"uuid":"1170465276","full_name":"travisbreaks/openclaw-ec2-sandbox","owner":"travisbreaks","description":"Pattern for running a persistent OpenClaw agent safely on EC2 — Docker-isolated, kill-switch ready","archived":false,"fork":false,"pushed_at":"2026-03-02T06:36:44.000Z","size":13,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-04-04T03:35:48.957Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Shell","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/travisbreaks.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-03-02T06:36:34.000Z","updated_at":"2026-03-19T00:42:33.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/travisbreaks/openclaw-ec2-sandbox","commit_stats":null,"previous_names":["travisbreaks/openclaw-ec2-sandbox"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/travisbreaks/openclaw-ec2-sandbox","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/travisbreaks%2Fopenclaw-ec2-sandbox","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/travisbreaks%2Fopenclaw-ec2-sandbox/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/travisbreaks%2Fopenclaw-ec2-sandbox/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/travisbreaks%2Fopenclaw-ec2-sandbox/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/travisbreaks","download_url":"https://codeload.github.com/travisbreaks/openclaw-ec2-sandbox/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/travisbreaks%2Fopenclaw-ec2-sandbox/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33957498,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-05T02:00:06.157Z","response_time":120,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-05T19:33:12.989Z","updated_at":"2026-06-05T19:33:13.637Z","avatar_url":"https://github.com/travisbreaks.png","language":"Shell","funding_links":[],"categories":[],"sub_categories":[],"readme":"# OpenClaw on EC2 — Isolated Agent Setup\n\nA pattern for running a persistent OpenClaw agent safely on a cheap EC2 server. The agent lives in Docker, isolated from the host. If it gets loose, you nuke the container. If the container isn't enough, you nuke the instance. You're out fifteen dollars a month and back up in twenty minutes.\n\n---\n\n## Why isolation matters\n\nOpenClaw agents on uncontained machines have a reputation. File deletion. Prompt injection. If someone crafts the right input, or the agent interprets its instructions too liberally, the results range from expensive to catastrophic.\n\nThis setup puts the agent behind a Docker boundary. The host OS never sees its filesystem. Its RAM is capped. Its network is scoped. If something goes wrong, `docker stop` is a full stop.\n\nThe agent can still do real work: overnight research, monitoring jobs, GitHub crawling, writing reports you read in the morning. It just does that work from inside a box.\n\n---\n\n## Architecture\n\nTwo agents on one machine. A third on your dev box.\n\n```\n┌─────────────────────────────────────────────────────┐\n│                   EC2 Instance                       │\n│                                                     │\n│  ┌─────────────────────────────────────────────┐   │\n│  │  Host OS (ubuntu)                           │   │\n│  │                                             │   │\n│  │  HOST AGENT (Claude Code)                   │   │\n│  │  - Health monitoring (every 15 min)         │   │\n│  │  - Docker lifecycle                         │   │\n│  │  - Backups, security                        │   │\n│  │  - Reads/writes ~/agents/memory/            │   │\n│  │    (shared volume)                          │   │\n│  │                                             │   │\n│  │  ┌─────────────────────────────────────┐   │   │\n│  │  │  Docker: agent container            │   │   │\n│  │  │                                     │   │   │\n│  │  │  CONTAINER AGENT (OpenClaw)         │   │   │\n│  │  │  - Persistent workspace             │   │   │\n│  │  │  - Overnight tasks, research        │   │   │\n│  │  │  - Monitoring jobs                  │   │   │\n│  │  │  - Reads/writes /app/memory/        │   │   │\n│  │  │    (same shared volume, bind mount) │   │   │\n│  │  └─────────────────────────────────────┘   │   │\n│  └─────────────────────────────────────────────┘   │\n└─────────────────────────────────────────────────────┘\n              ↕ SSH + file reads\n┌─────────────────────────────────────────────────────┐\n│  Local Dev Machine                                  │\n│                                                     │\n│  LOCAL CLAUDE CODE                                  │\n│  - Reads container agent's memory files via SSH     │\n│  - Ships code based on agent's findings             │\n│  - Delegates tasks via shared mailbox               │\n└─────────────────────────────────────────────────────┘\n```\n\nThree agents. One server. Call it what you want.\n\nI call mine **Sentinel** (host) and **Egger** (container). The transmission linked at the bottom is the story of why.\n\n---\n\n## Component Roles\n\n### Host agent\n\nRuns as Claude Code directly on the EC2 instance. Owns the building.\n\n- Monitors system health (disk, RAM, swap, load) on a cron\n- Manages the Docker container lifecycle (start, stop, restart, gateway)\n- Runs daily and weekly backups of Docker volumes and memory\n- Does NOT own the container agent's world — it can restart the container but doesn't rewrite the agent's files\n\n### Container agent\n\nRuns inside Docker, isolated from the host. Has its own filesystem, its own workspace, its own memory.\n\n- Persistent: Docker volume survives container restarts\n- Gets tasks assigned via the shared mailbox\n- Writes reports to memory files that Local picks up in the morning\n- Has a defined identity: `SOUL.md`, `BOOT.md`, a voice, a purpose\n\n### Local\n\nClaude Code on your laptop or desktop. The third head.\n\n- SSHes into the server to read the container agent's output\n- Stages and ships code based on what the agent found\n- Sends new tasks via `sentinel-msg`\n\n---\n\n## Shared Memory Pattern\n\nThe key to multi-agent coordination: a bind-mounted directory that both agents can read and write.\n\n```\n~/agents/memory/          ← host path (host agent writes here)\n         ↕ bind mount\n/app/memory/              ← container path (container agent writes here)\n```\n\nNo API calls between agents. No message queues. Just files.\n\n### Key files in shared memory\n\n| File | Writer | Purpose |\n|------|--------|---------|\n| `sentinel-status.json` | Host agent (every 15min) | Disk, RAM, swap, load, container health |\n| `hydra-state.json` | Both | Event log for all activity, auto-pruned |\n| `sentinel-mailbox.json` | Host agent | Async messages to container agent |\n| `egger-mailbox.json` | Container agent | Async messages back |\n| `claude-usage.json` | Host agent | Rolling Claude API token/cost data |\n\nSee [memory/README.md](memory/README.md) for full schema documentation.\n\n---\n\n## Setup Guide\n\n### Prerequisites\n\n- EC2 instance (t3.small works, t3.medium is more comfortable)\n- Ubuntu 22.04 or 24.04\n- Docker + Docker Compose installed\n- Anthropic API key\n- Claude Code installed on host (`npm install -g @anthropic-ai/claude-code`)\n\n### 1. Provision the server\n\n```bash\n# SSH in\nssh ubuntu@\u003cyour-ec2-ip\u003e\n\n# Install Docker\ncurl -fsSL https://get.docker.com | sh\nsudo usermod -aG docker ubuntu\n\n# Install Claude Code\ncurl -fsSL https://deb.nodesource.com/setup_22.x | sudo bash -\nsudo apt-get install -y nodejs\nnpm install -g @anthropic-ai/claude-code\n\n# Set your API key\necho 'export ANTHROPIC_API_KEY=\"sk-ant-...\"' \u003e\u003e ~/.bashrc\nsource ~/.bashrc\n```\n\n### 2. Create directory structure\n\n```bash\nmkdir -p ~/agents/memory\nmkdir -p ~/scripts\nmkdir -p ~/backups/{daily,weekly}\n```\n\n### 3. Deploy the container\n\nCopy the `docker/` directory from this repo to your server, fill in `.env`, then:\n\n```bash\ncd ~/agents\ndocker compose up -d\n```\n\nThe container uses a named volume (`agent-data`) for persistence. The shared memory directory is bind-mounted.\n\n### 4. Install the scripts\n\nCopy the `scripts/` directory to `~/scripts/` on the server. Make them executable:\n\n```bash\nchmod +x ~/scripts/sentinel-*\nchmod +x ~/scripts/backup-volumes\n```\n\n### 5. Set up cron\n\n```bash\ncrontab -e\n```\n\nAdd:\n\n```\n# Health check (every 15 min)\n*/15 * * * * /home/ubuntu/scripts/sentinel-check \u003e\u003e /var/log/sentinel.log 2\u003e\u00261\n\n# Status update (every 15 min, offset by 2)\n2,17,32,47 * * * * /home/ubuntu/scripts/sentinel-status-update \u003e\u003e /var/log/sentinel-status.log 2\u003e\u00261\n\n# Daily backup (02:00 UTC)\n0 2 * * * /home/ubuntu/scripts/backup-volumes \u003e\u003e /var/log/backup.log 2\u003e\u00261\n\n# Weekly backup (Sunday 03:00 UTC)\n0 3 * * 0 /home/ubuntu/scripts/backup-volumes --weekly \u003e\u003e /var/log/backup.log 2\u003e\u00261\n```\n\n**Note**: Cron runs with a stripped PATH. Always use absolute paths in scripts.\n\n### 6. Configure your agent\n\nCopy `agent/SOUL.md.example` into your container's workspace as `SOUL.md`. Fill it in. Same for `BOOT.md.example`. These tell your agent who it is and what to do when it wakes up.\n\n### 7. Send your first message\n\n```bash\n./scripts/sentinel-msg \"Hey — you're live. Check in when you're ready.\"\n```\n\n---\n\n## Scripts Reference\n\n| Script | What it does |\n|--------|-------------|\n| `sentinel-check` | Checks disk, RAM, swap, and container health. Writes to hydra-state.json. |\n| `sentinel-status-update` | Writes a full status JSON (disk, RAM, swap, load, container health, timestamp). |\n| `sentinel-msg` | Sends a message to the container agent via the shared mailbox. |\n| `backup-volumes` | Backs up Docker volumes, shared memory, and scripts. Supports `--weekly` flag. |\n\n---\n\n## What Breaks\n\n**Memory exhaustion**: t3.small has 2GB RAM. With 2GB swap, you have 4GB total. Docker container at 1.5GB limit + host cron scripts + VS Code Remote SSH = tight. If swap hits 98%, SSH daemon can't accept new connections. The OS is fine; userspace is frozen. Fix: stop the instance from the AWS console, wait for \"stopped\", then start it. Elastic IP means same address. Docker auto-restarts the container.\n\n**Cron PATH**: bare script names fail silently in cron. Always use absolute paths.\n\n**Shared volume conflicts**: if both agents write to the same key in the same file at the same time, you'll get corruption. Use separate files for each agent's writes, or implement simple file locking.\n\n**Context evaporation**: Claude Code sessions don't persist across restarts by default. Write a session file (`sentinel-session.json`) that the host agent reads on boot. Include: current task, in-flight work, pending follow-ups.\n\n---\n\n## What it Enables\n\n- **Overnight research**: assign a task before bed, read the report in the morning\n- **Passive monitoring**: container agent watches things on a schedule, alerts via mailbox\n- **Async handoffs**: agent writes a bug report; Local picks it up at next session\n- **Running context**: both agents accumulate memory over weeks, not just one session\n\n---\n\n## The story behind this\n\n[Transmission #053: SENTINEL \u0026 EGGER](https://travisbreaks.org/transmissions/053-sentinel-and-egger/) — the narrative of how this setup came to be, including the time swap hit 98% and SSH froze, and what it was like to read a report that opened with \"Mode: sleeping.\"\n\n---\n\n## License\n\nMIT\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftravisbreaks%2Fopenclaw-ec2-sandbox","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftravisbreaks%2Fopenclaw-ec2-sandbox","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftravisbreaks%2Fopenclaw-ec2-sandbox/lists"}