cc5e772519
test / run tests/run_tests.py (push) Successful in 13s
Cleans up references to the pre-refactor bash layout (cli.sh, lib/*.sh, scripts/*.sh) across README, Dockerfile, the pipelock PRD, and research notes. Refreshes line numbers in the oauth-token note against the current cli/start.py. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
59 lines
4.1 KiB
Markdown
59 lines
4.1 KiB
Markdown
# Host Dispatch to Container Agents
|
|
|
|
## Question
|
|
|
|
Can host Claude decide which claude-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 claude-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/claude-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 claude-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 claude-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 claude-bottle-dispatch` — makes the dispatch agent the primary agent for the session.
|
|
- Set `agent: claude-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.
|