Why the spec file alone isn’t enough
agent.enabled: false on a task removes it from what meriadoc advertises over MCP — it does not, by itself, stop anything. If the agent calling meriadoc also has its own native shell tool (most coding agents do), it doesn’t need meriadoc’s commit task at all; it can just run git commit directly. This isn’t hypothetical — it’s a filed bug against a major agent CLI (“MCP tools don’t respect sandboxing,” openai/codex#7635): a tool hidden at the MCP layer is trivially bypassed by any other tool the same agent process has access to. The fix is running the entire agent process, not just meriadoc, inside a boundary that makes the disallowed action physically impossible.
One file does the whole job
meriadoc isn’t just describing the sandbox here — it builds and launches it. sandbox-build writes the Dockerfile (and a discovery config for the container’s own meriadoc) to a throwaway build context and builds the image; sandbox-start runs it. meriadoc job sandbox-review does both in one command. The review policy — what the agent is actually allowed to call once it’s inside — lives in the same file as the tasks it protects, exactly like a real project’s meriadoc.yaml would.
version: v1
tasks:
# ── Policy: what the agent may do once it's inside the sandbox ──
# In a real project these sit alongside your normal build/test/deploy
# tasks — this is just the review-relevant subset.
status:
description: "Show working tree status"
agent:
risk_level: low
cmds:
- git status
diff:
description: "Show uncommitted changes"
agent:
risk_level: low
cmds:
- git diff
log:
description: "Show recent commit history"
agent:
risk_level: low
cmds:
- git log --oneline -20
run-tests:
description: "Run the test suite"
agent:
risk_level: low
cmds:
- npm test
commit:
description: "Commit changes (human only — not exposed to the review agent)"
agent:
enabled: false
cmds:
- git commit
# ── Sandbox: meriadoc builds and launches the container that enforces the policy above ──
sandbox-build:
description: "Build the agent-review sandbox image for this project"
env:
AGENT_CLI_PATH:
type: filepath
required: true
MERIADOC_BIN_PATH:
type: filepath
default: "/usr/local/bin/meriadoc"
preconditions:
- cmds:
- docker --version
on_failure:
continue: false
cmds:
- echo "docker not found. Install Docker Desktop from https://www.docker.com/products/docker-desktop"
cmds:
- |
set -e
BUILD_DIR=$(mktemp -d)
cp "$MERIADOC_BIN_PATH" "$$BUILD_DIR/meriadoc"
cp "$AGENT_CLI_PATH" "$$BUILD_DIR/agent"
cat > "$$BUILD_DIR/config.yaml" <<'CONFIG_EOF'
discovery:
roots:
- path: /workspace
enabled: true
CONFIG_EOF
cat > "$$BUILD_DIR/Dockerfile" <<'DOCKERFILE'
FROM debian:bookworm-slim
RUN apt-get update && apt-get install -y --no-install-recommends \
git nodejs npm \
&& rm -rf /var/lib/apt/lists/*
COPY meriadoc /usr/local/bin/meriadoc
COPY agent /usr/local/bin/agent
COPY config.yaml /etc/meriadoc/config.yaml
RUN chmod +x /usr/local/bin/meriadoc /usr/local/bin/agent
ENV MERIADOC_CONFIG=/etc/meriadoc/config.yaml
WORKDIR /workspace
DOCKERFILE
docker build -t agent-review-sandbox "$$BUILD_DIR"
rm -rf "$$BUILD_DIR"
echo "Built agent-review-sandbox"
sandbox-start:
description: "Launch the agent inside the sandbox — network-less, read-only repo mount"
cmds:
- |
docker run --rm -it \
--network none \
--read-only \
--tmpfs /tmp \
-v "$(pwd)":/workspace:ro \
agent-review-sandbox \
agent --mcp-server "meriadoc serve"
jobs:
sandbox-review:
description: "Build (if needed) and start a sandboxed review session for this project"
tasks:
- sandbox-build
- sandbox-start
Run it
meriadoc job sandbox-review --env AGENT_CLI_PATH=/usr/local/bin/claude
AGENT_CLI_PATH is the one thing meriadoc can’t know for you — point it at your agent’s actual binary (the Claude Code CLI, Aider, Codex CLI, etc.) and everything else — the Dockerfile, the container’s own discovery config, the build, the run — is generated and executed by meriadoc itself. run-tests needs your dependencies (e.g. node_modules) already installed in the repo before you run this, since /workspace is mounted read-only inside the container — there’s no npm install step to fall back on. commit is unreachable two ways: meriadoc won’t offer it, and even a direct git commit from the agent’s own shell hits a read-only filesystem.
Re-running meriadoc job sandbox-review after the first time is fast — Docker’s layer cache means sandbox-build mostly just confirms nothing changed.