The docker harness now detects and stages host CLI auth files (~/.gemini/oauth_creds.json, ~/.claude/.credentials.json) into a writable agent-home directory mounted at /home/agent. This lets containerized agents reuse the host's Gemini Pro / Claude Pro OAuth sessions without manual secret management or API keys. Only auth files are copied — not the host's settings.json or MCP configs (which contain stale localhost URLs that would hang Gemini CLI inside containers). The staged dir is writable so CLIs can create projects.json, history, etc. alongside the auth files. Also sets GEMINI_DEFAULT_AUTH_TYPE=oauth-personal and GEMINI_CLI_NO_RELAUNCH=true when OAuth creds are detected, writes Claude's hasCompletedOnboarding flag, and simplifies the doc-gardener example to use the harness-level credential staging instead of manual HOME directory seeding. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
doc-gardener — docker-isolated doc verification demo
A real, working multi-agent example that:
- Takes a goal like "Verify the CLI commands on docs.mcpproxy.app/cli/command-reference still exist in the current mcpproxy binary".
- Routes it through
doc-coordinator, which calls SynapBus MCP tools (create_goal,propose_task_tree,send_message) to record the goal and dispatch work. - Spawns
docs-inspectorinside an isolated Docker container to actuallycurlthe docs, install/runmcpproxy, parse output, and tabulate drift. - Forwards the findings to
docs-critic— a separate container with its own MCP key — for an independent audit. - Returns a
FINAL:summary back to the human.
Every agent runs in its own ephemeral container with --cap-drop=ALL, --security-opt=no-new-privileges, --read-only root + tmpfs /tmp, --pids-limit, memory + CPU quotas, and --user set to your host UID. The container can reach the SynapBus MCP server on the host at host.docker.internal:18089 but nothing else of yours unless you mount it in.
Architecture
algis ──DM──▶ doc-coordinator (Gemini Pro, container)
│
│ MCP tools: create_goal, propose_task_tree, send_message
▼
┌── reply ──▶ algis (TRIVIAL)
├── refuse ─▶ algis (CANNOT: …) (INFEASIBLE)
└── delegate ──▶ docs-inspector (Gemini Flash, container)
│
│ shell tools: curl, jq, mcpproxy …
│ MCP: send_message
▼
docs-critic (Gemini Flash, container)
│
│ spot-checks evidence; MCP: send_message
▼
algis (FINAL: … or REVISING: …)
Three independent agents, three MCP API keys, three containers. The critic is structurally separate from the inspector — it has its own config_hash and reputation, and reads only the inspector's findings JSON, not its reasoning trace.
What's actually real (not synthetic)
| Piece | Status |
|---|---|
Three Docker-isolated agent containers (--cap-drop=ALL, read-only root, pids/mem/cpu limits) |
✅ |
MCP-native dispatch — every agent calls send_message directly via Gemini's MCP client |
✅ |
create_goal + propose_task_tree materialize real rows in goals / goal_tasks |
✅ |
| Inspector has shell access inside the sandbox to fetch docs and run CLIs | ✅ |
| Coordinator/inspector/critic each get their own SynapBus API key | ✅ |
Trust model (config_hash, delegation cap, reputation ledger) |
✅ (covered by internal/trust/ tests) |
| Atomic task claim, cost rollup via recursive CTE | ✅ (covered by internal/goaltasks/ tests) |
| Rich HTML report (goal tree / agents / spend / timeline) | ✅ via ./report.sh |
| Secret encryption + scoped env injection | ✅ via internal/secrets/ |
Svelte /goals UI |
✅ at http://localhost:18089/goals |
Prerequisites
- Docker daemon running (
docker versionworks) go,jq,sqlite3,curlon PATH- A Gemini API key from https://aistudio.google.com/apikey:
export GEMINI_API_KEY=...
The first ./start.sh builds the canonical synapbus-agent image (image-build/synapbus-agent/Dockerfile) — Debian slim + Node 22 + gemini, claude, jq, sqlite3, curl, git, python3, tini. ~2-5 minutes the first time, cached afterwards.
Run
export GEMINI_API_KEY=...
./start.sh # builds binary + image, provisions agents
./run_task.sh # default brief: verify mcpproxy CLI flags
./run_task.sh "what does this demo do?" # TRIVIAL path — coordinator answers directly
./run_task.sh "Transfer money from my bank" # INFEASIBLE — coordinator refuses
./report.sh # render rich HTML report
./stop.sh
Web UI at http://localhost:18089 (login algis / algis-demo-pw):
/runs— every reactive harness run, captured prompts + responses, exit codes, durations/goals— goal tree + task state + spend per billing code/agents— three agents, each with its ownconfig_hashand reputation/dm/algis— DM thread withdoc-coordinator
How it isolates
The docker block in each configs/*.json is what makes this happen:
{
"docker": {
"image": "synapbus-agent:latest",
"memory": "1g",
"cpus": "1.0",
"network": "bridge"
}
}
The SynapBus reactor sees the docker.image field, picks the docker harness backend (via internal/harness/docker/), and runs:
docker run --rm \
--workdir /workspace \
--mount type=bind,source=<run-workdir>,target=/workspace \
--security-opt no-new-privileges \
--cap-drop ALL \
--pids-limit 512 \
--read-only --tmpfs /tmp:rw,size=64m \
--memory 1g --memory-swap 1g \
--cpus 1.0 \
--network bridge \
--add-host host.docker.internal:host-gateway \
--user <host-uid>:<host-gid> \
--env GEMINI_API_KEY=... \
--env GEMINI_MODEL=... \
[other -e flags] \
synapbus-agent:latest
The container's CMD is the standard /usr/local/bin/synapbus-agent-wrapper.sh baked into the image — it reads the bind-mounted message.json, loads GEMINI.md, and invokes gemini -p once. Every side effect happens through MCP tool calls inside the Gemini session; the container never reaches the SynapBus admin Unix socket because it doesn't have access to it.
The .gemini/settings.json materialized by the harness already points at the host MCP server with the correct API key — the harness rewrites 127.0.0.1 to host.docker.internal for docker-backed agents automatically.
Customize
| Variable | Default | What it does |
|---|---|---|
SYNAPBUS_PORT |
18089 |
Host HTTP port |
SYNAPBUS_COORDINATOR_MODEL |
gemini-3.1-pro-preview |
Smart triage model (fall back to gemini-2.5-pro if rate-limited) |
SYNAPBUS_WORKER_MODEL |
gemini-2.5-flash |
Fast inspector + critic model |
SYNAPBUS_AGENT_IMAGE |
synapbus-agent:latest |
Container image to run agents in |
GEMINI_API_KEY |
(required) | Forwarded to every container as -e |
Override per-agent docker resources by editing configs/*.json:
docker.memory—512m,1g,2gdocker.cpus—0.5,1.0,2.0docker.network—bridge(default, internet OK),none(air-gapped)docker.cap_add— array of capabilities to grant on top of--cap-drop=ALLdocker.extra_mounts— additional read-only host bind mountsdocker.read_only_root— set tofalseif the agent CLI insists on writing outside/tmpand/workspace
What got removed
The legacy cmd/docgardener Go binary used to contain ~2400 LOC of agent orchestration: a hardcoded 3-task tree, a runDemo flow that wrote directly to the DB, per-role subprocess entry points, a Gemini fallback for tree generation, channel bootstrap, etc. All of that is gone — replaced by:
configs/coordinator.json+configs/inspector.json+configs/critic.json(declarative GEMINI.md + docker block)- The standard
synapbus-agent-wrapper.shbaked into the canonical image - The 6 spec-018 MCP tools that ship with
synapbus serve
cmd/docgardener/ now contains only report.go + template.go + a tiny main.go cobra wrapper. The binary's only job is rendering the HTML snapshot you get from ./report.sh.