docs: the door is deployed, and what it does not buy

Proven against the real binary in the runtime container — connect, list
(58 resources) and read (`# Mission repo + commit protocol`, the correct
first heading). That probe is a two-minute loop; I reached for the
ten-minute rebuild-and-run-a-mission one first, and it would have found
the container-name bug sooner.

No `--allowedTools` change was needed. Recorded because the guess would
have been wrong in an expensive way: with no config read on the daemon,
"adding" the MCP tools meant overwriting the seed's `tools` list and
stripping Write and Bash from every mission agent — to solve a problem
that does not exist.

The §3 claim that this was "config, not code" is corrected in place: it
needed a credential narrow enough to leave in a container an untrusted
agent reads, and the measured proof that the credential IS narrow (same
token: 58 skills from /mcp/skills, 401 from /api/missions).

And what it does not buy, stated plainly: Trigger is still unmeasured,
because delivery still inlines. The door makes retrieval possible; making
Trigger real means switching to progressive disclosure, which could
regress Compliance and so wants an A/B rather than a flip.

Co-Authored-By: Claude Opus 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_018i9Ten1LU4jUr5d7TAWda9
This commit is contained in:
Omar Sobh
2026-08-21 11:59:34 -07:00
co-authored by Claude Opus 5
parent 3aeee070b8
commit 02d5f5a8c9
+54 -1
View File
@@ -86,7 +86,60 @@ event: result success
The calls are fully observable. We ask for the wrong output format. The calls are fully observable. We ask for the wrong output format.
## The door already exists, and we never plugged it in ## The door is deployed — 2026-08-21
Proven against the real binary in the runtime container, which is a two-minute
loop rather than the ten-minute rebuild-and-run-a-mission one I reached for
first:
```
claude -p --mcp-config <doc> --strict-mcp-config "List the MCP resources you can see"
→ 58 MCP resources. First two: skill:global/a11y-checklist, …
claude -p … "Read skill:global/workspace-repo-commit-protocol, reply with its first heading"
→ # Mission repo + commit protocol
```
Connect, list and **read** all work. `mission_orchestrator::install_skills_door`
writes the document at launch (mode `0600`, under `/root`, never the checkout)
and points the daemon at it in the same step.
**No `--allowedTools` change was needed.** That question was left open rather
than guessed, and the guess would have been wrong in an expensive way: the
daemon exposes no config read, so "adding" the MCP tools would have meant
overwriting the seed's `tools` list and stripping `Write` and `Bash` from every
mission agent — to solve a problem that does not exist.
### It needed a credential first, and that was not "config"
The section below called this "config, not code". That was wrong, and the reason
is authentication. `/mcp/skills` authenticated via `AuthService::authenticate`,
which returns a full `AuthedUser` carrying the user's role; there was no
narrower credential in the system. The document sits in a file the agent can
`cat` — it runs `Bash` with egress — so the documented approach meant handing an
**owner-privileged API token to something explicitly untrusted**. Checked before
concluding: no such credential was in a mission container, so it would have been
a new exposure rather than an existing one.
`auth_sessions.scope` fixes it. `authenticate` delegates to
`authenticate_scoped(token, SCOPE_FULL)`, so every existing caller rejects a
narrow token and a route opts in by name; `/mcp/skills` is the only opt-in.
Measured live with one token:
```
POST /mcp/skills → 58 skill resources
GET /api/missions → 401
```
### What this does NOT yet buy
**Trigger is still not measured.** `pinned_skills_text` still inlines full skill
bodies, so the agent is still handed skills rather than reaching for them. The
door makes retrieval *possible*; Trigger becomes real only when delivery
switches to progressive disclosure — and that could regress Compliance, so it
wants an A/B rather than a flip.
## How it looked before it was plugged in
`claude_cli.rs` is **ours** — upstream `zeroclaw-labs/zeroclaw` has no such file. `claude_cli.rs` is **ours** — upstream `zeroclaw-labs/zeroclaw` has no such file.
So is the feature that solves this, our own commit So is the feature that solves this, our own commit