Skip to content

agent sandbox - #1

Merged
jackharrhy merged 19 commits into
mainfrom
feat/agent-sandbox
Apr 19, 2026
Merged

jackharrhy merged 19 commits into
mainfrom
feat/agent-sandbox

Conversation

@jackharrhy

Copy link
Copy Markdown
Member

No description provided.

Two-user (app/agent) filesystem permission model inside a single
container, supervisor-managed process lifecycle with crash-loop
auto-revert, auto-commit on agent edits, and a split AGENTS.md
(immutable rules + agent-editable knowledge base) to teach the
agent its environment.

Scope is the runtime sandbox only; broader hardening (CI, prod
start scripts, dependency pinning) is orthogonal and tracked
separately.
15 tasks across branch setup, repo reshuffle, infra consolidation,
supervisor implementation (TDD per module), AGENTS.md files, Dockerfile
rewrite with two users + healthcheck, and end-to-end revert smoke test.

Fallback priv-drop approach; coarse supervisor-watcher auto-commit;
clean-slate assumption (no existing-volume migration). Phase-2 polish
(OpenCode plugin auto-commit, ops-channel notifications, salvage logic,
migration) deferred.
Debian has predictable glibc, setpriv, and addgroup behavior; alpine's
busybox utilities are inconsistent for user/permission setup we're
about to add.
Groundwork for the agent sandbox. src/ will be the agent-editable
tree; everything outside it will be off-limits to the agent.
Moves Dockerfile, compose.yml, and entrypoint.sh under infra/.
Updates paths to reference src/website and src/discordbot. Tighter
.dockerignore. Repo-root compose.yml is a symlink for convenience.
Bun project under infra/supervisor with a typed loadConfig()
reading env vars with defaults. Test-driven.
ensureWorkspaceRepo is idempotent; commitAll uses per-commit author
env so agent vs supervisor attribution is preserved; hardResetTo and
a private refs/supervisor/good ref track the last known good commit.
Sliding window over recent non-zero exits; once threshold hit within
window the process is marked bricked and stops auto-restarting.
clearCrashes() resets so the orchestrator can unbrick after a revert.
Spawns opencode, website, and bot; watches src/ for changes; debounced
auto-commits as 'agent'; debounced restarts; periodic tick updates the
good ref when revertables have been healthy, and auto-reverts bricked
revertables to the good ref with a supervisor-authored revert commit.
Halts recovery after 3 consecutive failed reverts.
Adds allowEmpty option to commitAll so the orchestrator can record
supervisor-initiated revert events as explicit git commits even when
the working tree matches the target sha. Also fixes four strict-mode
typecheck errors uncovered by running tsc --noEmit over the package:
Bun server.port is number|undefined (assert), Response.json() returns
unknown (cast), and a let+assign split in the batcher's IIFE to satisfy
control-flow analysis.
infra/AGENTS.md is the source of truth for what the agent is allowed
to do; entrypoint will concatenate it with AGENTS.md.tmpl to produce
/workspace/AGENTS.md on first boot.
- Seeds /workspace on first boot without leaking .git/.github/Dockerfile.
- Regenerates /workspace/AGENTS.md top section from infra/AGENTS.md on
  every boot; salvages previous editable content if sentinel missing.
- Sets ownership so agent user can write src/ and .git/ but not infra/.
- Installs deps if missing (first-boot volume).
- Drops to 'app' user and execs the supervisor.
- Renames the default bun user (uid 1000) to 'app' so we keep the UID.
- Adds 'agent' user at uid 1001 and puts app in the agent group.
- Installs git, util-linux (setpriv), curl.
- Pre-installs supervisor deps into the seed.
- HEALTHCHECK pings supervisor at :3002.
- Removes :4096 exposure; OpenCode is internal-only.
- compose.yml: paths relativized to infra/, restart: unless-stopped,
  DISCORD_BOT_PORT var respected, OPENCODE_URL to loopback.
Five real issues surfaced by the first end-to-end smoke test:

1. The opencode binary copied from ghcr.io/anomalyco/opencode:latest is
   musl-linked for alpine; on debian it fails with
   'libc.musl-*.so.1 => not found'. Switched to the official
   opencode.ai/install script which picks the correct glibc build.
   Adds ca-certificates and tar to the apt install list.

2. cp -a of the seed included every src/node_modules, which ballooned
   seed time to ~40s and made 'git add -A' in the workspace repo
   glacial. Switched to rsync with --exclude node_modules/dist and
   ship a workspace.gitignore so the workspace git repo never tracks
   node_modules.

3. The website ManagedProcess spawned 'bun run src/server.tsx'
   directly, which skips the build:css step. The old setup used
   'bun run dev' which built CSS first. Wrap the cmd in
   'bash -c build:css && exec bun run src/server.tsx' so CSS is
   built before the server starts.

4. oven/bun:1 ships a pre-existing user at uid 1000; our 'app'
   useradd collided. Rename the existing bun user to app rather
   than creating a new one (preserves the uid).

5. Bind-mounting ./volumes/clawscibois_data on macOS uses grpcfuse
   which does not enforce Linux uid/perm semantics — root-owned
   755 dirs become writable by non-root users. Switched to a
   Docker-managed named volume (ext4 inside the VM) so perms are
   real. Fix is also preferable on Linux for isolation.

Also: supervisor no longer lives in the workspace volume — it runs
from /opt/workspace-seed/infra/supervisor (root-owned, image-baked).
That removes the chicken-and-egg 'chown then su app bun install'
ordering problem and keeps supervisor code fully out of the
agent-editable surface.

Verified end-to-end:
- opencode, website, supervisor all boot cleanly
- discordbot correctly crash-loops on missing real Discord env (expected)
- curl http://localhost:3000 serves the React SSR page
- docker exec -u agent cannot write to /workspace/infra/ (EACCES)
- docker exec -u agent cannot modify /workspace/infra/AGENTS.md
- docker exec -u agent cannot touch /opt/workspace-seed/
- docker exec -u agent CAN write to /workspace/src/ and /workspace/AGENTS.md
- file change triggers: watch event -> restart website -> auto-commit
  as agent author, all with the expected debounces.
@jackharrhy
jackharrhy merged commit dd45b4a into main Apr 19, 2026
@jackharrhy
jackharrhy deleted the feat/agent-sandbox branch April 19, 2026 04:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant