샌드박스
A sandbox is a container you start on your own machine, holding your own agent, the Motir CLI and your checkouts — and nothing else. You bring your own agent credential, mounted read-only; the loop runs inside, so a misbehaving agent reaches your work tree and not the rest of your machine.
Before you start
- Docker, running. Built for
linux/amd64andlinux/arm64, so Apple Silicon is a first-class machine and nothing is emulated. There is no build step — you pull. - Your agent’s own sign-in, on this machine. Its credential mount is read-only, so the container can use a sign-in and cannot renew one. Claude Code on macOS is the exception you will meet: it keeps its token in the login Keychain, so there is no file to mount, and you sign in to
claudeinside the container instead — the image gives it a writable config directory, and that is where the sign-in lands. (Antigravity is the same — step 2 says so when you pick it.) - Your workspace root — the folder that CONTAINS your checkouts. A project usually spans several repositories and the loop runs across all of them.
your machine
~/work/ ← start the container from HERE ├── motir-core/ ← a checkout └── motir-ai/ ← another
Which agent do you use?
Every command below is for Claude Code. Switching rewrites the tag and the credential mount in steps 1, 2 and 2b — the three places they appear.
Set it up
Five steps. Each one is a single thing to do.
Pull the image for your agentCommand
There is no build step — the image is published per agent profile.
pull
docker pull ghcr.io/moooon-b-v/motir-sandbox:claude
Start the container from your workspace rootCommand
Run it from the folder that contains your checkouts, not from any one of them.
run
docker run -it --rm --pull=always \ -v "$PWD:/workspace" \ -v motir-auth:/home/node/.config/motir \ -v "$HOME/.claude:/home/node/.claude:ro" \ ghcr.io/moooon-b-v/motir-sandbox:claude
Using VS Code instead? Steps 2a–2c below replace this one. Everything after is the same either way.
Install the Dev Containers extensionIn your editor
From the Extensions view, or the command palette — ⇧⌘P on macOS, Ctrl+Shift+P elsewhere, F1 on all three — then Extensions: Install Extensions. Two of these three steps happen in the palette, so it is worth pinning now.
Create the dev container configCommand
Run this in the folder you are mounting. One paste: it makes the
.devcontainerfolder and writes the file into it. Do not try to create them from a file picker — Finder and most GUI pickers refuse a name beginning with a dot, and refuse it without saying why.your machine — in the folder you are mounting
mkdir -p .devcontainer cat > .devcontainer/devcontainer.json <<'JSON' { "name": "Motir sandbox (Claude Code)", "image": "ghcr.io/moooon-b-v/motir-sandbox:claude", "workspaceFolder": "/workspace", "workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind", "mounts": [ "source=motir-auth,target=/home/node/.config/motir,type=volume", "source=${localEnv:HOME}/.claude,target=/home/node/.claude,type=bind,readonly" ], "remoteUser": "node", "overrideCommand": true, "postStartCommand": "motir-sandbox-agent-config || true" } JSONA dev container keeps the image it was created from.
--pull=alwaysbelongs to the run command in step 2, not to this route. To move to the current image andmotirCLI: 1. run step 1'sdocker pullin a terminal on your machine; 2. Dev Containers: Open Folder in Container… on this folder, which attaches the window; 3. Dev Containers: Rebuild Container, which recreates the container from the image you just pulled. Rebuild Container only appears in a window attached to the container, which is why step 2 comes first. A rebuild keeps your Motir sign-in (it lives on themotir-authvolume) but not a Claude Code sign-in made inside the container — runclaudeand sign in again.Open the folder in the containerIn your editor
Command palette → Dev Containers: Open Folder in Container…, and pick the folder you just wrote the file into. Its terminal is the same shell step 2 would have dropped you into — carry on at step 3.
Sign in, inside the containerCommand
A code and a URL are printed; approve it in any browser. The sign-in lands on the
motir-authvolume, so you do this once.in the container
motir login
Link the folder to your projectCommand
Swap
ACMEfor your project key. If your workspace has exactly one project, drop the flag — that is the whole step.in the container
motir link --project ACME
Check it — all green is the end of this pageCommand
Auth, link, the agent binary and its credential. This is the only thing that tells you the container actually got what you passed it.
in the container
motir doctor
The file that command writes
Reference, not a step — 2b already wrote it. It is here for the reader who would rather create the file by hand, and because the quotes around <<’JSON’ are load-bearing: they stop your shell expanding ${localWorkspaceFolder} and ${localEnv:HOME} before they reach the file. Those are Dev Containers substitutions and the editor is what resolves them.
.devcontainer/devcontainer.json
{
"name": "Motir sandbox (Claude Code)",
"image": "ghcr.io/moooon-b-v/motir-sandbox:claude",
"workspaceFolder": "/workspace",
"workspaceMount": "source=${localWorkspaceFolder},target=/workspace,type=bind",
"mounts": [
"source=motir-auth,target=/home/node/.config/motir,type=volume",
"source=${localEnv:HOME}/.claude,target=/home/node/.claude,type=bind,readonly"
],
"remoteUser": "node",
"overrideCommand": true,
"postStartCommand": "motir-sandbox-agent-config || true"
}Why it looks like this
What the profile picker changes
Picking an agent rewrites three things and nothing else: the image tag, the credential -v line(s), and the dev container’s image, name and mounts. It is a control rather than a paragraph telling you to swap them yourself, because every command here has a Copy button and a reader who copies is a reader who did not read the swap instruction.
Not every profile has one credential directory. opencode keeps two and takes two -v lines; antigravity keeps its token in the OS keyring and takes none, signing in inside the container instead; and aider binds a file and reads a model key from the environment. The steps say so when you pick them.
On the run command, nothing is kept that could go stale
--pull=always fetches the current image on every start, so a profile tag that has moved reaches you without your having to notice that it moved, and --rm means nothing is kept that could go stale. There is no separate coming-back-to-it path — which is exactly what used to leave people running a motir months older than the page they were reading it from. Your sign-in survives all of that: it is written to the motir-auth volume, which lives outside the container, so you sign in once and every later run picks it up — sign out for good with docker volume rm motir-auth. Working offline? Drop --pull=always: it reaches the registry on every start, so with no network the run fails instead of falling back to the image you already have. All of this is the run command's. A dev container (steps 2a–2c) keeps the image it was created from until you pull, attach with Dev Containers: Open Folder in Container… and choose Dev Containers: Rebuild Container.
What next
motir run takes a SCOPE — one work item, a whole story, or sprint for the active one. motir auto drains the ready set unattended instead, one item at a time onto a session branch. Every flag both accept is on the CLI page.
What it confines — and what it does not
Worth reading before you rely on it, because one of these three is an exception rather than a guarantee.
- Filesystem — confined.
- The only host surfaces inside the container are a writable
/workspaceand your agent’s own credential, mounted read-only. No Docker socket, no other host bind. - Network — OPEN, by design.
- Every agent needs its provider API and every dispatched work item needs git remotes, so the image confines the filesystem blast radius and not egress. If your threat model needs more, reach for Docker’s own network controls — the container will not stop an agent talking to the internet.
- Privileges — unprivileged.
- It runs as the
nodeuser (uid 1000), so files written into the mount stay owned by you rather than by root.
What the environment gives you
- Your folder, mounted.
$PWDbecomes/workspace, so the checkouts the run works in are yours and the commits it makes are on your disk when it exits. - One checkout per work item, on a git worktree. A run does not edit the tree you are sitting in; it adds a worktree per item, so parallel runs cannot collide on a branch checkout.
- Your agent credential, READ-ONLY. The profile’s credential directory is bind-mounted with
:ro. Nothing in the container can rewrite it, and nothing about it is sent to Motir — this is bring-your-own-key, so the agent bill is yours and the API call never passes through us. - The CLI, preinstalled. The image carries
motirand the agent binary the tag names, so there is nothing to install before the first run. - Your agent’s output stays local by default. Only the run’s lifecycle reaches Motir. Passing
--report-logadditionally sends the output’s tail so a failed run shows it on the run page; it is OFF unless you ask, and file contents, paths and diffs are never sent either way.
What the token may do — and what it refuses
A token minted by motir login carries a fixed, narrowed grant. The approval screen shows it and cannot change it — neither wider nor narrower, because a hand-narrowed grant breaks an unattended loop halfway through.
the grant a device-minted token carries
project:browse read the project and its work items lesson:view search the recorded lessons before building lesson:reinforce record that a lesson described what went wrong work_item:edit edit the item it is running, and file a bug comment:add comment on the item ai:plan open a plan
The one it does NOT carry is ai:view_plan, and the refusal that follows is the design rather than a bug. Opening a plan needs only work_item:edit, so a sandboxed run CAN open one — and is then refused on its first append, because that is the key adding proposals asserts. A run executing a work item does not get to reshape the plan it was handed. When you meet that refusal, the agent has done the right thing: it records the correction as a comment, leaves the item blocked, and stops. Nothing is lost, and a person decides what the plan should say.
Two flags narrow this further when you want a quieter run: --disable-log-bug stops the agent filing a bug for a defect it finds elsewhere (it comments instead), and --disable-replan stops it submitting a re-plan for a work item it judges wrong (it comments and stops). On motir auto only, --auto-approve-replan goes the other way: it approves a submitted re-plan and keeps looping, instead of stopping for you.
What a run produces, and where to read it
- A branch and a pull request in each repository the item ships in, pushed with your git credentials from inside the container.
- A link on the work item. The run declares which item each pull request delivers, so merging it moves the card. That link is what the item page’s Development panel shows, and it is what closes the item on merge — not the branch name and not the title.
- Status, as it goes. The item moves to In Progress when the run claims it and to Implemented when the pull request opens. In Review is written by CI when the checks go green, and Done by the merge.
- The terminal. The agent’s own output stays in your terminal unless you passed
--report-log.
When it does not work
- The agent binary is not found
- The tag and the agent disagree. Check which profile you started, or point the run at a different binary with
--agent <cmd>.motir doctorreports this before a run wastes a claim on it. - The agent starts and is not authenticated
- The credential mount is missing or points at the wrong directory — each profile mounts its own. Re-run the
docker runline for the tag you actually pulled. - Nothing is ready to run
- Every candidate has an unmet dependency.
motir readyshows the set;motir showon a work item names what is blocking it. Dispatching anyway is--force, one item only. - The run stops on a submitted re-plan
- The agent judged the work item wrong and proposed a corrected shape. That is the intended stop: read the plan in Motir and approve or decline it. To keep an unattended loop going instead, run
motir autowith--auto-approve-replan. - A run left work behind after it exited
- The worktrees and branches are on your disk, under the folder you mounted — a container that stopped did not take them with it.
motir donecloses out a merged item, or a whole merged session branch with--session <branch>.
What this page does not cover
Every command and every flag — that is CLI, which is generated from the CLI’s own catalogue and cannot drift from it. Wiring an agent to Motir directly, without the CLI, is MCP 서버. Driving the same work loop over HTTP instead of from a terminal is the API 참조. Running the sandbox anywhere other than your own machine is not documented here yet. (The VS Code path IS documented, above — that clause used to say otherwise, and it was recording a deleted section as a decision.)