feat(attachments): carry runtime MCP attachments outside the bound profile - #181
Merged
Conversation
…ofile A dispatching runtime serves its coordination MCP endpoint on an ephemeral port. The only channel that reached the harness beside an exact AgentProfile was `agent_profile.mcp`, which moves the canonical profile digest, so a restarted runtime that rebinds a new port was refused by the exact session binding before it could spend a token. Add `runtime_attachments.mcp` on chat completions, retained creates, and retained turns. Each entry merges into the one MCP resolution every backend already uses, and is excluded from the session AgentProfile binding, from receipt digests, from durable-run identity, and from durable session state. A changed authored profile or model is still refused. `capabilities. runtimeAttachments.mcp` in the root document lets a caller refuse loudly instead of folding the endpoint into the profile.
This was referenced Aug 21, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Why
supervise()in agent-runtime hands the harness its coordination MCP endpoint. Today the only channel that reaches a harness beside an exactagent_profileisagent_profile.mcp, so the runtime injects the endpoint into the profile it sends. That URL carries a process-ephemeral port. A runtime that restarts rebinds a new port, the canonical profile digest moves, andassertSessionProfileBindingcorrectly refuses the resumed turn withsession ... is bound to a different AgentProfile/model— before any token is spent (agent-runtime#774).The endpoint is not authored behavior. It is the platform mounting its own service, and it needs a channel that never enters an identity.
What
runtime_attachments: { mcp: Record<alias, McpServerConfig> }onPOST /v1/chat/completions,POST /v1/sessions, andPOST /v1/sessions/:id/turns. Entries use the same server schema asagent_profile.mcp.resolveMcpServers— the one MCP resolution every backend already materializes — so no backend needed a change and no second mount path exists.SessionProfileBinding, every profile materialization receipt digest (the receipt digests the profile, which is now untouched),durableRunRequestDigest, the retainedcreateRequestDigest, and durable session metadata. The caller sends the attachment on every request that needs the mount; a turn value replaces the created value for the spawn it starts.mcpbesideagent_profile; an attachment alias that collides with a profile or request server; anenabled: falseattachment; a secret-ref inside an attachment (named by key, never by value); an unknown field underruntime_attachments.capabilities.runtimeAttachments: { mcp: true }on the root document, so a dispatcher gates on the flag and refuses loudly rather than falling back to profile injection.Proof
pnpm typecheck— clean.pnpm vitest run tests/runtime-attachments.test.ts— 14 passed (mount lands in the materialized MCP config file the CLI loads; profile digest unchanged; one binding across two turns that differ only in the attachment port; changed profile still 400; durable run re-attaches across a changed port; 6 refusal paths; nothing persisted).pnpm vitest run tests/retained-sessions.test.ts— 3 new attachment tests pass (native spawn receives the attachment, create digest excludes it, a turn rebinds the endpoint, an invalid attachment is refused before durable state).pnpm vitest run tests/server-capabilities.test.ts— 1 passed (flag advertised).commof the two FAIL lists: 0 new). The host failures are docker, bubblewrap-jail, Linux/proc, and missingcodex/pibinaries.Simplification
Simplification: attachments route through the existing
resolveMcpServers+ per-backend materializers instead of a new mount path;profileMcpToSpecgained an error-label parameter rather than a second converter.Net: +250 / -7 lines, 12 files, 0 paths removed — an honest net addition. The wire channel did not exist and cannot be expressed by an existing field:
mcpis refused besideagent_profileby design, and widening it would put the endpoint back inside the bound identity. The collapse this unlocks is on the agent-runtime side, where the profile-rewrite path is deleted.Not done here: peer-mail endpoints ride the same channel later (agent-runtime#889 gap 1); no second attachment kind is added now.