Skip to content

feat(deploy_agent): deliver the role to the agent or fail the step - #146

Merged
earakely-scale merged 5 commits into
mainfrom
feat/deploy-agent-delivers-role
Oct 10, 2026
Merged

earakely-scale merged 5 commits into
mainfrom
feat/deploy-agent-delivers-role

Conversation

@earakely-scale

@earakely-scale earakely-scale commented Oct 9, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

DeployAgentTaskStep filtered a requested role against the agent card's agent-config supported list and dropped it silently when the card did not list it, then recorded it on DeployedAgent anyway. An agent the task believed to be an executor, or a person, called the env as the default role, and register_env_triggers trusted a role the agent never received. Once the role is the agent's identity in every server (companion gateway and synthetic-pipelines changes), a dropped role is a silent identity swap to the pinned user.

  • The role goes into the MCP registration headers (AgentEnv-Role) of every env entry when the card's mcp-config add accepts headers (card_request_accepts), merged onto the sandbox headers as a fresh dict. deploy_agent does not judge what the env does with it: a gateway forwards it, a protocol server reads it from the header itself, anything else ignores a header it does not know.
  • Identity negotiation uses the existing A2AAgent.negotiate_agent_config instead of the hand-rolled filter; role reaches agent-config exactly when the card lists it.
  • AGENT_ENV_ROLE is set in the agent's environment so the generated MCP CLI stops calling as its default cli role.
  • When a role is requested and neither path can carry it, the step raises before any request to the agent, naming the agent and both missing capabilities. Otherwise one info line names the path or paths that carry it. role or None so an empty string is not "delivered".
  • _mcp_add_body gates the card name on the same capability read as the header, so a hand-written supported list behaves like a protocol-rendered card.

DeployedAgent.role is therefore always a role the agent received.

Behaviour change. A task that sets a role on an agent image that cannot carry it now fails at deploy instead of running as default. In dev, claude-code-cli and codex-cli list role in agent-config and accept headers; a2a-default does neither and will fail loudly when given a role. Ships last, after the gateway and server changes.

Testing

Unit: new tst/unit/task_step/test_deploy_agent_role_delivery.py (20; review fix: the header decision judges the fields the registration actually carries, so a card that requires name and lists headers as optional takes the role): both paths, header only, config only, neither (raises before any request, agent closed, nothing recorded), an env not behind a gateway gets the header too, the header lands on every registration, sandbox headers not mutated, card-shape matrix, role None byte-identical to today, empty string, AGENT_ENV_ROLE set and an explicit value kept. test_deploy_agent_mcp_alias.py +1 (hand-written supported relays the name). tst/unit/task_step + tst/unit/env/task_step: 1648 → 1667 passed, the same 8 pre-existing environment-gated failures.

End to end in dev: two claude-code-cli agents deployed with email roles into one env behind the companion gateway:

Both agents were deployed with their emails as roles against the real claude-code-cli card (which lists role in agent-config and accepts registration headers), so both delivery paths carried the role and the refusal path did not trigger. The two prompt_agent steps ran in parallel (103 s and 101 s); every attribution check read back through role sessions passed (6 of 6): each agent's mail arrived in the other's inbox from the right sender, each primary calendar holds its own agent's event, and #general carries one post per agent under its own Slack user id. A first attempt with Sonnet failed at the first turn with "Prompt is too long" (270 tool schemas), unrelated to this change; Opus completed.

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 5/5

No new blocking finding was established.

Summary

DeployAgentTaskStep sends the requested role through MCP headers, agent-config, or both. It fails before configuration requests when neither path can carry it.

  • Deploy steps deliver the requested role or stop.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  A[Deploy agent and read card] --> B[Build MCP registrations]
  B --> C[Negotiate agent-config fields]
  C --> D{Requested role has a delivery path?}
  D -->|No| E[Close agent and fail]
  D -->|Yes or no role requested| F[Send registrations and configuration]
  F --> G[Record deployed agent]
Loading

Reviews (5) · Last reviewed commit: "Merge main into feat/deploy-agent-delive..." · Reviewed by Greptile

A role the agent card could not take was filtered out silently and still
recorded on DeployedAgent, so an agent believed to be an executor or a person
called the env as the default role. The step now puts AgentEnv-Role into the
MCP registration headers for the gateway env when the card's mcp-config add
accepts headers, negotiates it through agent-config when the card lists it,
sets AGENT_ENV_ROLE for the generated MCP CLI, and raises before any request
when neither path can carry a requested role. The recorded role is therefore
always one the agent received.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@earakely-scale
earakely-scale requested a review from a team as a code owner October 9, 2026 20:01
Comment thread src/agent_env/task_step/task_steps/deploy_agent.py Outdated
earakely-scale and others added 2 commits October 9, 2026 13:11
…t actually receives

A card that requires `name` on mcp-config add and lists `headers` as optional
was refused the role header, because the capability check asked about `url`
and `headers` alone while the registration also carried the gateway card's
name. The check now covers the fields the body will contain.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… gateway envs

What an env does with the role is the env's business: a gateway forwards
it, a protocol server reads it from the header itself, anything else ignores
a header it does not know. Judging deliverability by env type refused a
header-capable agent on an env that would have honoured the header.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@earakely-scale

Copy link
Copy Markdown
Collaborator Author

In a7bb965 the role header goes on every MCP registration whose card takes headers, not only on gateway envs. deploy_agent no longer judges deliverability by env type: a gateway forwards the header, a protocol server reads it itself, anything else ignores a header it does not know. Tests: an env not behind a gateway gets the header; the header lands on every registration; deploy_agent suites 72 passed.

…one log line

_mcp_add_body already decides whether the card's `add` takes the env card
name; the role header is one more field it may take, judged on the field set
actually sent. execute() builds every registration body first, then asks
which paths carry the role and raises if none does. The two warnings for a
single path go: the one info line names the paths that carried it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@earakely-scale

Copy link
Copy Markdown
Collaborator Author

Simplified in 8d9b4ae: _mcp_add_body now carries the role header itself (one more field the card's add may take, judged on the exact field set sent), execute() builds every registration body before the decision and sends them after, and the decision is one list of the paths that carry the role: raise if empty, else one info line. The two single-path warnings are gone. Source diff against main is +66/−20 (was +86/−20); deploy_agent suites 70 passed.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@earakely-scale

Copy link
Copy Markdown
Collaborator Author

The Integration Tests status (CodeBuild project agentenv-framework-codebuild) failed on a7bb965 and 8d9b4ae before any test ran: the latest agentenvhub-sdk (0.33.11) needs agentenv-framework>=0.9.1306 and this branch was still 0.9.1305, so the sdk bootstrap raised. Merged main (0.9.1307) in 889f7f7; no code change.

@earakely-scale
earakely-scale merged commit f58a4ba into main Oct 10, 2026
13 checks passed
@earakely-scale
earakely-scale deleted the feat/deploy-agent-delivers-role branch October 10, 2026 16:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant