runner_mcp — AgentTeamsModule¶
Goose is refused under
write_policy: "orchestrator-only"(biographies pilot 1, finding G1, 2026-10-12): Goose 1.37 delegates inherit the orchestrator'swrite/edit/shellextensions instead of their sub-recipe's narrowedextensions:, so the policy can't be enforced there.write_policy.resolveand--check-wiringrefuse it; use the switch on Claude (or Codex through its launcher).
The agentteams_runner MCP server: under write_policy: "orchestrator-only", the only way a granted
non-orchestrator agent writes or executes (phase R4 of references/plans/mcp-mediated-agent-writes.plan.md). Its
tools queue requests for the out-of-session runner, which holds the ledger key, checks each request against that
agent's policy, and alone writes the project or runs commands.
Source:
agentteams/runner_mcp.py(install and launch facts) andagentteams/data/agentteams-runner-mcp.py(the server: stdlib only, noagentteamsimport). Both are integrity-pinned. Granted in the brief'smcp_grants.
Generation (R6)¶
Under write_policy: "orchestrator-only", an agent named in mcp_grants is rendered as follows:
- Claude: its narrowed read tools, plus the exact mcp__agentteams_runner__<tool> names, plus the canonical
inline mcpServers block, scoped to that subagent alone.
- Goose: the canonical stdio extension with an exact available_tools list.
- Its write-policy section tells it to write and execute through these tools.
- The orchestrator's section gains the staged-approval commands.
- The server is installed into .agentteams/bin/.
- The audit (AR_WRITE_POLICY) allows exactly that rendering and nothing else. Any shell on a granted agent is
an error (@security C15). Agents without a grant, and teams without mcp_grants, render unchanged.
Tools¶
| Tool | Does | Runner kind |
|---|---|---|
read_file_hashed(path) |
Read a UTF-8 project file, up to 256 KiB, with its sha256: the base_sha256 writes need. A missing file returns "absent". Refuses VCS internals, the control plane, the queue and secrets. The deny list applies to both the requested and the resolved path, so an in-project link to .env is refused, and the opened file must be the one checked (device and inode). |
none (local read) |
write_file(dispatch, path, content, base_sha256, rationale, gates?) |
Write a whole file. Always submitted for staging. The runner applies it at once only when the agent's operator-signed grant verified; otherwise it waits for the orchestrator. | stage-proposal |
delete_file(dispatch, path, base_sha256, rationale) |
Ask to delete. Always staged, even for an agent with a direct grant. | stage-proposal |
run_command(dispatch, argv, purpose, cwd?, expected_writes?, stdin_path?, stdin_content?) |
Run an allowlisted command in the agent's sandbox. Stdin goes only through stdin_from_content and its gates. |
run-request |
request_status(request_id) |
A result this instance queued earlier, or queued. Only for a call that returned a request_id (a long run_command): a write_file/delete_file receipt is final, and a staged-only grant can omit this tool. |
none |
Each call waits up to --wait seconds (20 by default, 120 at most) for the runner's result. Otherwise it returns a
request_id to poll.
Safety model¶
- No key, no project writes. The server's only writes are new files in
.agentteams-queue/mcp-requests/and acknowledgements in.agentteams-queue/acks/. Both are created relative to directory descriptors opened one component at a time without following links, so a linked-in queue directory can't redirect them. - Not a trust boundary. The runner re-validates everything: identity from the nonce, the agent's scopes and allowlist, gates and base hashes.
- Bound to one agent.
--agentis sent asvia_agent. The runner refuses a nonce that belongs to another agent, andrequest_statusanswers only for ids this instance queued. - Only granted tools exist.
--toolslists them; the rest are neither advertised nor callable. - Launch integrity. Generation installs the copy at
.agentteams/bin/agentteams-runner-mcp.py, which is session-write-denied, and agents launch it withpython3 -I -S. The runner checks it againstSHA256on every poll (proposal_runner.check_installed_servers) and refuses to serve on a mismatch.
Public surface (runner_mcp)¶
| Name | Purpose |
|---|---|
SERVER_NAME, TOOLS |
agentteams_runner, and the five tool names; they match proposal_policy.MCP_RUNNER_TOOLS. |
PROTECTED_PATH, PYTHON_FLAGS, SHA256 |
Install path, launch flags and the pinned hash. |
server_content() -> str |
The shipped server's source. |
launch_args(agent, tools, approval="staged") -> list[str] |
The canonical arguments for one agent's instance. There is no --root: the server derives the project root from its install location (<root>/.agentteams/bin/), so a launch from anywhere else fails to start and no machine path is written into the team. |
SYSTEM_PYTHONS, interpreter() -> str |
The absolute, system-wide python3 agents launch the server with (/usr/bin/python3 first). Never one under a home directory or found via PATH. |
tool_names(tools) -> list[str] |
Claude's exact names, mcp__agentteams_runner__<tool>. |
claude_block(agent, tools, approval, python) -> str |
The canonical inline mcpServers: block for a granted Claude agent. |
goose_extension(agent, tools, approval, python) -> dict |
The canonical Goose stdio extension, with an exact available_tools list. |
SHADOW_FILES, MANAGED_MCP_FILES, REQUIRED_CLAUDE_DENY, wiring_problems(root, framework, manifest, *, home=None) -> list[str] |
Live-wiring checks run by --check-wiring for a team with grants (R7). See the CLI reference for the full list. |
install_files(manifest) -> list |
The server install (../../.agentteams/bin/...) when the switch is on and mcp_grants names someone. It refuses a packaged copy whose hash doesn't match. |
Reads are not write-scoped. read_file_hashed reads any project file the session sandbox allows; write_scopes
bound only writes. Staging receipts show expiry in UTC.