Documentation
A guide to running coding agents you don't fully trust — safely, and at scale. It explains not just how each piece works, but why it works that way.
The idea
Coding agents are at their best with the guardrails off — free to edit any file and run any command without stopping to ask. That's also exactly when you don't want one running loose on your laptop.
Coop's answer is simple: run the agent inside a
disposable container that can only see the one repo
you're working on. Your home directory, SSH keys, cloud
credentials, and every other project simply aren't in
there. The repo's own secrets — .env files,
private keys, a secrets/ folder — are
blanked out before the agent ever starts.
The container is the boundary — not a list of permissions you have to get exactly right. You don't audit what the agent is allowed to do; you put it somewhere it can't do harm. The worst an off-the-rails agent can manage is a mess in one repo you can restore from git.
Everything else is built on that one box. Run an agent interactively, hand work off like a pull request, point a whole fleet at a backlog overnight, or convene a council of models on a hard problem — it's the same cage every time.
Install
One binary, no Go toolchain, no clone. A container runtime is the only requirement.
curl -fsSL https://raw.githubusercontent.com/AndrewDryga/coop/main/install.sh | sh
This drops coop into
~/.local/bin, and — if a container runtime
is already installed — builds the sandbox image and runs
coop doctor for you. If no runtime was
found, do that one step yourself once a runtime is in
place:
coop build && coop doctor
For the runtime, take your pick: Apple
container
(macOS 26+), Docker, or Podman — coop auto-detects
whichever you have. The coop binary itself
is fully static with no other dependencies.
PATH it checks a keyless Sigstore
signature; otherwise it falls back to a SHA-256 check
and tells you the signature wasn't verified. Prefer Go?
go install github.com/AndrewDryga/coop@latest
works too.
Quickstart
The shortest path from nothing to a sandboxed agent that lands reviewed work.
cd ~/code/your-repo # 1. any git repo coop doctor # 2. prove the isolation holds coop login claude # 3. sign in once — paste a code, no browser coop claude # 4. a sandboxed agent, brakes off, secrets hidden
That's the whole everyday flow. When you'd rather not work in your live checkout — for a bigger or riskier change — hand it to a fork instead and review the result like a PR:
coop fork feature claude # agent works in a throwaway clone… coop fork review feature # …you read the diff… coop fork merge feature # …and land it on your branch
Anything you type after the agent's name is passed
straight through to it, so
coop claude --continue resumes the last
session, still sandboxed. From here, the rest of this
guide goes feature by feature — but you already know
enough to be useful.
The sandbox
What's inside the box, what's deliberately left out, and why that line is the entire security model.
Your repo is bind-mounted into the container at the same path it has on your machine. That matters for two reasons: the agent edits your real files (you watch the changes land live in your editor), and absolute paths line up between coop, the loop, and your IDE. Everything outside that one directory — the rest of your disk — is just not present in the container.
A fixed /workspace mount would break
tools that record absolute paths (agent session
files, your editor's project state). Mounting at the
real path means a session you start in the loop is
the same one you can resume later from Zed.
Your git identity, not the box's
The container has no ~/.gitconfig of its
own, so coop mounts a curated one: your name and email
(commits are authored as you), your global
gitignore, and commit.gpgsign=false. Your
signing key never enters the box, so commits made inside
it are unsigned — but work landed through a fork can
still be signed on the host, where your key lives.
More on that below.
Secrets stay out
Two layers keep credentials away from the agent: secrets are hidden by name, and a content scan catches the ones hiding inside ordinary files.
Files and folders that look like secrets —
.env, *.pem,
*.tfvars, secrets/,
.ssh, and a long list of well-known names —
are shadowed: an empty in-memory
filesystem covers secret directories, and a blank
read-only file covers secret files. The agent sees that
the file exists, but it's empty and can't be written.
Templates (*.example,
*.sample, *.template) stay
visible on purpose, so the agent can still learn the
shape of your config.
.coopignore, not
.gitignore.
A normal run mounts your whole working tree, so
a secret that's gitignored-but-still-on-disk (a real
serviceAccount.json, say) is fully visible
to the agent — gitignoring it does not hide it.
Anything that must live in the tree but stay secret
belongs in .coopignore.
The built-in list can't know your repo holds a
config/credentials.yaml. Drop a
.coopignore at the repo root — one pattern
per line — and those paths are hidden everywhere the box
can see:
prod.yml # basename — matched at any depth config/credentials.yaml # a slash makes it a repo-relative path vault/ # a directory — hidden whole
That handles secrets identified by their name. But a
token can also hide inside an ordinary file.
coop check-secrets reads your files looking
for secret-shaped content — known provider token formats
and high-entropy strings — and reports each as
file:line, exiting non-zero on a hit so it
doubles as a pre-flight or CI gate. The same scan runs
automatically every time you merge a fork.
Prove it
Security you can't verify is just a promise.
coop doctor attacks its own box and shows
you, check by check, that it holds.
Run it any time — and especially after you change
config. doctor plants a decoy secret in
your repo, launches the box, and then tries to break in:
from inside the container it confirms the
secret is unreadable and unwritable while ordinary
source stays readable, and on the host it
confirms a fork carries neither the secret nor anywhere
to push to. If anything fails, it tells you exactly
what.
Run an agent
The everyday command: a sandboxed agent in your repo, brakes off.
coop claude # Claude — no permission prompts, secrets hidden coop codex # the same box, Codex instead coop gemini # …or Gemini coop shell # just a shell in the box, to look around coop run -- npm test # run one command in the box and exit
Each agent launches with its most autonomous flags on purpose. Outside a sandbox that's reckless; inside one it's the whole point — you get an agent that doesn't nag you for permission on every step, and the box makes sure it can't reach anything that matters.
Forks — hand off work like a PR
Give a task to an agent the way you'd give it to a contractor: it works in its own copy, you review the diff, and you decide what lands.
A fork is a throwaway local clone of your repo, handed
to the agent instead of your live working tree. Because
its origin is a local path, the agent has
nowhere to push — and since a clone only carries
committed content, gitignored secrets never even come
along.
Use it for anything bigger or riskier than a quick edit, and any time you want to run several attempts at once. The agent never touches your checkout, so you can keep working while it does — and you stay the only person who lands anything.
The lifecycle mirrors a pull request: open → work → review → land. You review the diff in your terminal (a brief, then the changes) or in your IDE. Landing rebases the fork onto your current branch for linear history — and because that rebase runs on the host where your key lives, the commits can be signed even though the box committed them unsigned.
Set a gate and every landing earns its place:
COOP_GATE="make check" re-runs your tests
in the box on the rebased tree and rolls back if they go
red. A content scan also flags any secret-shaped or
oversized files on the way in.
Fusion — a council of models
For a genuinely hard call, don't bet the run on one model's blind spots. Let one model lead and the others weigh in.
In coop fusion, one model is the
governor: it does the real work — edits, runs
the gate, commits. The other two are consulted read-only
and in parallel on the decisions that matter, and the
governor synthesizes the strongest parts of all three.
The combined answer reliably beats any single model
working alone.
There's no extra service or protocol — the leader simply asks its peers from its own shell, and only on hard questions (it's told to skip the council for trivial steps). Each consult is two quick read-only runs, so you spend the extra tokens exactly where a second opinion is worth it.
coop fusion # the default governor leads coop fusion claude # …or pick who leads coop claude --consult # lighter, opt-in: a second opinion on hard calls only
The loop — work the queue while you sleep
Give the agent a checklist and let it work the list on its own — through the night, through rate limits, without your hand on the wheel.
Most real agent work isn't one big task — it's a queue of small, well-defined ones. Driving the agent through them by hand, one at a time, is the bottleneck. And a single marathon session quietly degrades: the context window fills with old work, the agent loses the plot, and quality slides. The loop sidesteps both.
You write tasks as checkboxes in
.agent/TASKS.md.
coop loop then starts a
fresh agent for each one — so every
task begins with a clean head, no leftover context to
rot. The agent claims a task, does it, runs your gate,
commits the result, and moves to the next. It won't stop
while a single [ ] remains.
coop init # scaffold AGENTS.md, the .agent/ folder, and the hooks # …fill .agent/TASKS.md with checkbox tasks… coop loop # disposable agents drain the queue, then audit
It checks its own work. When the list is empty, coop starts one more agent as an auditor. It re-reads every task marked done and confirms the git history actually backs it up — reopening anything that doesn't. "Done" ends up meaning verified, not just claimed.
It survives the daily cap. Long runs hit subscription rate limits. Rather than failing, the loop reads the reset time from the agent's own output and waits it out — or, if you've added a second account, switches to it and keeps going. An overnight run rides straight through the limit instead of dying on it.
A backlog of small refactors. Migrating one pattern across dozens of files. Working down a test-coverage or lint list. A "Friday cleanup" queue you'd never get to by hand. Anything that's many small, checkable steps.
The .agent/ folder
coop init creates a small, tool-neutral
working folder the agent reads back on every start (and
after each compaction). It's all local working state and
git-ignored — except rules/, a shared
knowledge base, which is committed.
| File | What it's for |
|---|---|
TASKS.md |
the work queue — [ ] todo,
[w] claimed, [x] done,
[B] blocked
|
LOG.md |
the agent's running notes (what & why), so intent survives a fresh start |
BACKLOG.md |
work spotted outside the current task — captured, not auto-worked |
rules/ |
the taste knowledge base — corrections graduate into rules here |
A fleet — many agents at once
One loop is one agent working one queue. A fleet is several — often different models — each on its own slice of the work, all running at the same time.
Split the backlog into separate task files and hand each
fork one. coop fleet up starts them all in
the background; coop fleet watch is the
live board, showing each fork's progress, the task it's
on, and who's finished.
coop fleet up # start every fork in .agent/fleet, detached coop fleet watch # the live dashboard coop fork merge --all # land the whole fleet through a revalidating rebase
Once generating code is cheap, reviewing it becomes the bottleneck — so the move is to parallelize generation and batch the review. Add agents until your review queue, not the agents, is what's keeping you busy.
Profiles & failover
Hold more than one account for an agent so a long run never has to park on a rate limit.
Sign in to several subscriptions as named profiles. When a loop hits a limit on one, it rotates to another that's still fresh and keeps going — only ever waiting once every profile is capped.
coop login claude --profile work # a second account… coop login claude --profile personal # …and a third coop profiles # list them and which are signed in
Only the active profile is ever mounted into the box, so a running agent sees just the account it's using — never your whole vault. Switching mid-run loses nothing: each iteration is a fresh run, and the queue plus git carry the progress.
Editors (Zed)
Prefer your editor to a terminal? Drive the sandboxed agent from Zed's agent panel — Zed is the cockpit, the box stays the cage.
Coop speaks ACP, so you can register it as a custom agent and steer it from Zed while it runs in the container. Point one entry at each agent you use:
{
"agent_servers": {
"coop": {
"type": "custom",
"command": "coop",
"args": ["acp", "claude"] // or codex / gemini / fusion
}
}
}
Because the repo mounts at its real path, sessions line
up across tools — a thread you started with
coop loop is right there to resume in Zed.
PATH. If Zed can't find coop,
use the absolute path from
command -v coop as the
command.
Toolchain & services
Real projects need a language toolchain and a database. Declare them once, instead of having the agent reinstall them on every run.
If your repo pins versions in a
.tool-versions (asdf), the box provisions
that toolchain the first time and caches it — so a repo
with nothing but a .tool-versions just
works. For a fully reproducible image,
coop init --stack asdf bakes the same
versions into a Dockerfile.agent at build
time.
Need a Postgres or Redis alongside? They're opt-in —
coop init asks, or pass
--services postgres,redis — and run as
their own throwaway containers the box can reach by
name:
coop up # start the services, wait until healthy coop claude # the box reaches them by name (db, redis) coop down -v # stop them and wipe the throwaway data
To pull the latest agent CLIs and base image,
coop update rebuilds the box fresh;
coop build is the stable, pinned rebuild.
Configuration
Sensible defaults out of the box; a handful of knobs for when you need them.
Set these as environment variables or in
~/.config/coop/coop.conf. The ones you're
most likely to touch:
| Variable | What it does |
|---|---|
COOP_GATE |
a command re-run in the box before a fork merge
lands (e.g. make check)
|
COOP_EGRESS |
none cuts the box off the network
entirely — no outbound, nothing to exfiltrate
|
COOP_RUNTIME |
force container /
docker /
podman instead of auto-detect
|
COOP_MEMORY ·
COOP_CPUS
|
cap the box's resources (e.g. 4g,
2)
|
COOP_FUSION_GOVERNOR |
which model leads coop fusion by
default
|
On Docker and Podman the box additionally runs with every Linux capability dropped, no-new-privileges, and a process cap. The complete list of variables lives in the README reference.
Troubleshooting
The handful of things that trip people up, and the quickest fix for each.
| Symptom | Fix |
|---|---|
| "no container runtime found" |
Install Apple container, Docker, or
Podman, then
coop build && coop doctor.
|
| "image isn't built" |
Run coop build once (or
coop doctor, which builds it too).
|
| Login hangs / "usage limit reached" |
Re-run coop login <agent>. A
subscription cap resets on a schedule — a loop
waits it out on its own.
|
| A detached loop won't quit |
coop fork logs <name> -f to
watch it;
coop fork stop <name> to stop
it.
|
| A secret is still visible |
Run coop doctor to see what's
shadowed, then add the path to
.coopignore.
|
The full troubleshooting guide and every command's flags
are in the
README
— or run coop help (and
coop <command> --help) for the live
reference.
Stuck on something not here? Open an issue · ← back home