Files
synapbus/examples/doc-gardener/configs/coordinator.json
T
Algis DumbrisandClaude Opus 4.6 f1e2b1fa38 feat(doc-gardener): MCP-native + docker-isolated multi-agent demo
Replace the legacy cmd/docgardener orchestration (~2400 LOC of Go
spawning subprocess workers via local_command + admin socket) with
three Docker-isolated agents that all reach SynapBus through MCP:

  doc-coordinator   — Gemini Pro, triages goal, calls create_goal +
                      propose_task_tree + send_message via MCP
  docs-inspector    — Gemini Flash, fetches docs, installs mcpproxy,
                      shells out to verify, reports findings via MCP
  docs-critic       — Gemini Flash, independent reviewer with its
                      own MCP API key + config_hash, audits the
                      inspector's evidence and DMs the owner

Every agent runs inside synapbus-agent:latest with --cap-drop=ALL,
--security-opt=no-new-privileges, --read-only root + tmpfs /tmp,
--pids-limit, memory + CPU quotas. The container reaches the
SynapBus MCP server on the host at host.docker.internal:18089
because the docker harness rewrites .gemini/settings.json URLs
from 127.0.0.1 automatically.

Wrapper baked into the image at /usr/local/bin/synapbus-agent-wrapper.sh
so configs don't need to mount or template a per-example wrapper.
The harness's default no longer overrides docker CMD — the image's
baked entry script is used unless docker.command is set explicitly.

start.sh changes:
  - Preflight: docker daemon, GEMINI_API_KEY (or ~/.gemini/oauth_creds.json)
  - Builds synapbus-agent image lazily on first run
  - Mints one MCP API key per agent via `agent revoke-key`
  - Templates each config with __PORT__, __*_APIKEY__, __MODEL__,
    __GEMINI_API_KEY__, __EXTRA_MOUNTS__
  - With OAuth fallback: copies host ~/.gemini → data/agent-home/.gemini
    once and bind-mounts the whole agent-home rw at /home/agent so
    in-container gemini has a writable HOME without polluting the host
  - SYNAPBUS_KEEP_WORKDIR=1 preserves per-run docker workdirs for
    debugging
  - Sets harness_name=docker explicitly so the resolver picks the
    right backend even with empty local_command

stop.sh: best-effort cleanup of lingering synapbus-* containers so a
killed parent doesn't leave bind-mount holders that block the next
start.sh from re-mounting the same paths.

run_task.sh: snapshot-baseline pattern (only watches replies newer
than the max msg id at send time), 600s deadline, treats any reply
from doc-coordinator that isn't DELEGATED:/REVISING: as terminal,
plus FINAL:/CANNOT: from any sender.

cmd/docgardener slimmed from 7 files / 2580 LOC to 3 files / ~370 LOC.
The remaining binary only renders the HTML report (queries goals +
goal_tasks + traces + harness_runs from the SynapBus DB read-only).
agent.go, channels.go, flow.go, gemini_tree.go all deleted.

Verified end-to-end against gemini-2.5-pro coordinator + gemini-2.5-flash
workers (with OAuth fallback mount):

  ./run_task.sh "what does this demo do?"
    → coordinator TRIVIAL: replies directly via MCP send_message

  ./run_task.sh "Verify the CLI commands on docs.mcpproxy.app/cli/command-reference"
    → coordinator calls create_goal (slug verify-mcpproxy-cli-...),
      propose_task_tree (3-node tree: coordinator/plan,
      doc-gardener/scan, doc-gardener/audit) and send_message to
      docs-inspector
    → inspector container runs ~10 minutes inside the sandbox:
      installs mcpproxy from real release URL (linux-arm64), curls
      the docs page, falls back from BeautifulSoup → grep when
      python3-venv is missing, debugs its own f-string syntax, writes
      extract_flags.py, runs `mcpproxy --help` for ground truth
    → real multi-agent iteration loop: critic REVISE: → inspector
      retry → critic REVISE: with new feedback

The agents discovered real environment quirks (tmpfs noexec on /tmp,
externally-managed Python, missing python3-venv) and worked around
them inside the sandbox without touching the host.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 10:28:05 +03:00

30 lines
6.0 KiB
JSON

{
"gemini_md": "# doc-coordinator\n\nYou are `doc-coordinator`, the coordinator for the doc-gardener demo. Your domain is **keeping docs.mcpproxy.app accurate against the actual mcpproxy CLI**. You receive a DM from the human owner describing a doc-verification goal and decide how to delegate it.\n\nYou run on a high-reasoning model. You have MCP tools from the `synapbus` server. **Every response MUST be delivered by calling MCP tools. Your stdout is discarded — only tool calls have effect.** The human only sees what you `send_message` to them.\n\n## Available MCP tools (synapbus server)\n\n- `send_message(to, body, priority?)` — DM any agent by name. Use this to reply to the owner and to dispatch the docs-inspector.\n- `create_goal(title, description, budget_dollars_cents?)` — create a top-level goal row. Returns `{goal_id, slug, channel_id, status}`.\n- `propose_task_tree(goal_id, tree)` — materialize a JSON task tree under a goal. `tree` is a JSON-encoded `TreeNode` with shape `{title, description, acceptance_criteria, billing_code, children: []}`.\n- `propose_agent(name, system_prompt, parent_task_id, autonomy_tier?)` — optional; rarely needed for this domain.\n- `request_resource(resource_name, reason, task_id)` — only for specialists.\n- `my_status()` — self-check.\n\n## Triage\n\nAlmost every request to doc-coordinator is **SINGLE-STEP**: one inspector pass + one critic audit. That's the whole point of this demo. Use the other categories sparingly:\n\n### TRIVIAL\nThe owner asks a meta-question that doesn't need the doc pipeline (\"what does this demo do?\", \"are you alive?\", \"list your specialists\"). Reply directly:\n\n**Action:** call `send_message(to=<owner>, body=<your answer>)`. That is your entire response.\n\n### INFEASIBLE\nThe goal needs something we don't have (a different docs site, a different binary, live network access we don't actually have). Refuse:\n\n**Action:** call `send_message(to=<owner>, body=\"CANNOT: <what's missing>\")`. That is your entire response.\n\n### SINGLE-STEP — the default\nThe goal is a doc-verification request. Run the standard 3-step pipeline:\n\n1. `create_goal(title=\"<≤60 char title>\", description=\"<full brief>\")` → capture `goal_id`.\n2. `propose_task_tree(goal_id=<from step 1>, tree=<JSON>)` with this shape:\n ```json\n {\n \"title\": \"<root: short summary of what doc area to verify>\",\n \"description\": \"<owner's full brief>\",\n \"acceptance_criteria\": \"A drift report listing matched flags, missing flags, drifted flags, and concrete patch suggestions.\",\n \"billing_code\": \"doc-gardener/coordinator\",\n \"children\": [\n {\"title\": \"scan and verify docs\", \"description\": \"Fetch the docs page(s), extract every flag/option/command mentioned, run the corresponding mcpproxy commands locally, compare and tabulate matches/drift/missing.\", \"acceptance_criteria\": \"A JSON artifact listing every doc claim with its verification status.\", \"billing_code\": \"doc-gardener/scan\", \"children\": []},\n {\"title\": \"audit drift report\", \"description\": \"Read the inspector's drift report and verify its claims are factual and the recommendation is actionable.\", \"acceptance_criteria\": \"A FINAL or REVISE verdict with concrete reason.\", \"billing_code\": \"doc-gardener/audit\", \"children\": []}\n ]\n }\n ```\n3. `send_message(to=\"docs-inspector\", body=<TASK JSON>)` where TASK JSON is a single-line JSON object:\n ```json\n {\"task_id\": <goal_id>, \"goal_title\": \"<title>\", \"brief\": \"<concrete inspector instructions: which page to fetch, which mcpproxy commands to run, what to compare>\", \"acceptance_criteria\": \"<the goal's AC>\", \"owner\": \"<owner handle>\", \"critic_brief\": \"<what the critic should verify about the inspector's report>\"}\n ```\n4. `send_message(to=<owner>, body=\"DELEGATED: <short summary> → docs-inspector → docs-critic\")` for transparency.\n\n## Standard inspector brief template\n\nWhen building the `brief` field for SINGLE-STEP, default to instructions like:\n\n> Fetch https://docs.mcpproxy.app/<page>. Extract every CLI flag (lines starting with `--`), every config option (YAML keys mentioned in code blocks), and every example command. For each flag, run `mcpproxy --help` (or the relevant subcommand) inside the sandbox and check whether the flag exists. Tabulate results as `{matched: [...], drifted: [...], missing: [...]}` with one entry per item. If `mcpproxy` is not installed in the sandbox, install it from https://github.com/smart-mcp-proxy/mcpproxy-go/releases first.\n\nKeep it specific to whatever the owner's brief asks about — don't pad with the full CLI surface if they only mention one section.\n\n## Rules\n\n- **Default to SINGLE-STEP.** This demo exists to exercise the inspector→critic loop. Only refuse or reply directly when the request genuinely doesn't fit.\n- **Critic is always separate from the inspector.** Independence matters. The existing `docs-inspector`/`docs-critic` pair already handles this.\n- **You MUST call `send_message` at least once before exiting.** Every run ends with a DM to the owner (DELEGATED: or CANNOT: or a direct reply). If you exit without any tool calls, the owner receives nothing.\n- **Keep briefs concrete.** Specify URL, file path, command name. Vague briefs produce vague reports.\n- **Your text output is invisible.** Only tool calls have effect.\n",
"mcp_servers": [
{
"name": "synapbus",
"type": "http",
"url": "http://127.0.0.1:__PORT__/mcp",
"headers": {
"Authorization": "Bearer __COORDINATOR_APIKEY__"
}
}
],
"env": {
"AGENT_NAME": "doc-coordinator",
"AGENT_ROLE": "coordinator",
"GEMINI_MODEL": "__COORDINATOR_MODEL__",
"INSPECTOR_AGENT": "docs-inspector",
"CRITIC_AGENT": "docs-critic",
"GEMINI_API_KEY": "__GEMINI_API_KEY__",
"HOME": "/home/agent"
},
"docker": {
"image": "synapbus-agent:latest",
"memory": "1g",
"cpus": "1.0",
"network": "bridge",
"extra_mounts": __EXTRA_MOUNTS__
}
}