# Host Dispatch to Container Agents

## Question

Can host Claude decide which bot-bottle container to spin up for a task, while guaranteeing the work executes in the container and not on the host?

## Claude Code Agent Mechanisms

Claude Code provides two mechanisms for defining reusable agent behavior:

**Skills** (`.claude/skills/<name>/SKILL.md`) run inline in the main conversation context. They're reusable workflows invoked via `/skill-name`, with optional tool pre-approval.

**Subagents** (`.claude/agents/<name>.md`) run in an isolated context window with a custom system prompt and a declared tool allowlist. They're invoked by natural language, `@agent-name`, or `claude --agent`. The `tools:` frontmatter is enforced — the subagent cannot call tools not in the list. (See [Claude Code subagents docs](https://code.claude.com/docs/en/sub-agents.md), "Choose the subagent scope" and "Write subagent files" sections.)

"Isolated context window" means only conversational isolation (fresh LLM state, summarized output). It is not process, filesystem, or network isolation. Subagents still run on the host with full user permissions.

## The Reliability Problem

The previous approach used an MCP server to bridge host Claude and bot-bottle containers. It failed because host Claude had both work-capable tools (Edit, Write, Bash) and MCP dispatch tools. Claude could choose to do the work itself rather than dispatch, with no enforcement mechanism to prevent it.

## Why Tool Restriction Solves It

Claude Code's subagent `tools:` allowlist is architecturally enforced — not a prompt-level suggestion. If the host subagent is defined with only container-dispatch tools and no Edit/Write/Bash, it is incapable of doing implementation work. Dispatch becomes the only available path.

## Reliable Dispatch Architecture

Three pieces in combination give a 100% guarantee:

1. **Restricted host subagent** — a `.claude/agents/bot-bottle-dispatch.md` with `tools:` limited to MCP container tools and git-read operations. No Edit, Write, or arbitrary Bash.

2. **MCP server** — exposes tools the restricted host can call:
   - `list_agents()` — available agents from the manifest (host Claude decides which to use)
   - `run_agent(agent_name, task)` — starts a container non-interactively, returns a job ID
   - `get_status(job_id)` — check running/done
   - `get_output(job_id)` — read results

3. **Non-interactive container run mode** — `cli.py run <agent> "<task>"` passes the task to `claude --print` inside the container and captures output. Currently `cli.py start` is interactive only; this mode does not yet exist.

## Proposal

Build host-dispatch-to-container in two deliverables:

**Deliverable 1: Non-interactive run mode for bot-bottle**

Extend `cli.py` with a `run <agent> <task>` subcommand. Starts the container, writes the task prompt to a file inside it (same `docker cp` pattern used for `--append-system-prompt-file`), invokes `claude --print` with the prompt, streams stdout back to the host, and exits when Claude finishes. Results committed and pushed from inside the container as usual.

**Deliverable 2: MCP server wrapping bot-bottle**

A minimal MCP server (bash or node) exposing `list_agents`, `run_agent`, `get_status`, `get_output`. Registered in the host Claude Code settings so a restricted dispatch subagent can call it.

The combination enforces the container boundary at the tool layer, not the prompt layer — making it structurally impossible for host Claude to do implementation work itself.

**Critical:** the tool restriction only applies within the dispatch agent's context. A normal Claude session has its full toolset and may never invoke the dispatch agent regardless of its description. The dispatch agent must be the *entry point* for the session, not an optional subagent a full-tool host might call. Two ways to enforce this:

- Launch with `claude --agent bot-bottle-dispatch` — makes the dispatch agent the primary agent for the session.
- Set `agent: bot-bottle-dispatch` in the project `.claude/settings.json` — same effect automatically for any `claude` invocation in that directory.

Without one of these, the guarantee does not hold.
