Skip to content

feat(bundle): name bundled agents by alias, as claude-code for the shipped Claude Code - #304

Merged
earakely-scale merged 6 commits into
mainfrom
feat/agent-aliases
Oct 22, 2026
Merged

earakely-scale merged 6 commits into
mainfrom
feat/agent-aliases

Conversation

@earakely-scale

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

Copy link
Copy Markdown
Collaborator

Why. A bundle task with "a2a_agent_id": "claude-code" is refused today (there is no agent 'claude-code' in the store), since the shipped Claude Code's id is @local/agentenv-framework/agents/claude-code. The website's examples write claude-code. Came up while fixing #291.

Change

  • A bundle can give its agents aliases in a root aliases.toml, [agents] <folder> = ["alias", ...]. agent-env's agents bundle gives claude-code and pi. It sits at the root, not in agent.toml, which is in the image's build context.
  • A name means, in order: the run's own bundle's entity, an installed bundle's full id, the store's agent of that id, an installed alias. So your own claude-code wins, pinned or not. An alias takes no pin.
  • The planner pulls the alias's agent in and writes the task with its full id, reading the store by exact id itself. Everywhere else, A2AAgent.get resolves the name in the same order, so every step and command that looks an agent up takes an alias: deploy, install, the judge, the validator steps, --a2a-agent-id, the configured default, and a2a-agent get/deploy/validate. The built-in default stays the full id.
  • agent-env's own aliases are reserved. An alias two plugins both give is refused, and the error names both.
  • Runs and dry runs print what each alias named, agent-env run lists each bundle's aliases, and plugin show / plugin list --json report a refused claim as alias-refused.
  • agent-env a2a-agent list lists the agents a task can name: each one the installed bundles ship, with its aliases (marking one the store shadows), then the store's, newest first, 50 unless --limit says otherwise; --json for scripts. a2a-agent get --id claude-code prints the agent a step naming it deploys, and a name close to an alias (claude_code) says which alias it may mean.
  • Installed-bundle discovery moves to agent_env/plugins/_bundles.py (re-exported from bundle/installed.py), so a step can read aliases without the bundle → run → deploy_agent import cycle.

Verified. Unit tier passes (9,086). A slow integration test builds the shipped Claude Code with real Docker against a fake model endpoint: a bundle task and a step outside one both deploy it by its alias, then both deploy the store's agent once one is put as claude-code. End to end on an admin VM, from a clean install of this branch's wheel, with a real model: see the comment below.

For consumers. A2AAgent.get(x) can now return an agent whose id isn't x (when x is an alias), and a pinned alias raises NotFoundError. In the hub, only the validate route reuses the requested id after a get (it pairs it with the returned version); it should use agent.id, in the pin bump that picks this up.

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 5/5

The PR appears safe to merge; no new blocking issue remains.

Summary

Bundles can give agents short aliases, including claude-code and pi for the shipped agents.

  • Tasks can name bundled agents by short aliases.
  • Agent lookups can resolve aliases to the agent they name.
  • a2a-agent list shows installed and stored agents.
  • Bundle listings and plugin reports explain alias claims.
  • Installed-bundle discovery now lives in the plugin bundle module.

No new actionable issues were found. The five earlier, unnumbered findings are addressed in the current code. This review checked code and tests without running them.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Task names an agent] --> B{Defined in this bundle?}
    B -->|Yes| C[Use this bundle's agent]
    B -->|No| D{Installed full id?}
    D -->|Yes| E[Pull installed agent into run]
    D -->|No| F{Stored agent with this id?}
    F -->|Yes| G[Use stored agent]
    F -->|No| H{Accepted installed alias?}
    H -->|Yes, without pin| E
    H -->|No, or pinned| I[Report why the name cannot be used]
    E --> J[Save explicit task reference by full id]
Loading

Reviews (6) · Last reviewed commit: "refactor(a2a-agent): A2AAgent.get resolv..." · Reviewed by Greptile

…ipped Claude Code

A bundle task writing "a2a_agent_id": "claude-code" was refused unless the store held an agent of that id: the shipped
Claude Code is @local/agentenv-framework/agents/claude-code. A bundle can now give its agents aliases in an
aliases.toml at its root, and agent-env's agents bundle names Claude Code claude-code (and pi, pi).

- A reference to an alias names the agent its installed bundle gives it, unless the run's bundle has an entity of
  that name or the store an agent of that id, which wins. The planner pulls the agent in and writes the task naming
  it by its id; an alias takes no pin. A configured default that's an alias is resolved the same way.
- A step deploying, installing or judging with an agent outside a bundle run looks it up with A2AAgent.resolve:
  the store's agent of that id, else the alias's agent.
- agent-env's own bundles' aliases are theirs; an alias two other bundles give is refused, naming both. The run and
  the dry run say which agent each alias names, `agent-env run` lists each bundle's aliases, and `agent-env plugin
  list` reports a refused claim (alias-refused).
- The installed-bundle discovery moves to agent_env/plugins/_bundles.py, which imports nothing from agent_env.bundle,
  so a task step can read the aliases without the import cycle the bundle package's run would make; os_reason moves
  to agent_env/utils/paths.py with it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comment thread src/agent_env/bundle/plan.py Outdated
Comment thread src/agent_env/plugins/_bundles.py Outdated
Comment thread src/agent_env/plugins/_bundles.py Outdated
Comment thread src/agent_env/cli/plugin.py Outdated
…s's rules

A pinned reference to a stored agent of an alias's id, or one whose alias two
bundles clash on, now names that agent instead of being refused, at plan time
and at run time. An aliases.toml that's a folder or a broken symlink is
refused rather than ignored, an alias can't end in a newline, and a bundle run
by its qualified name still says which aliases it's refused.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@earakely-scale

earakely-scale commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator Author

End to end at 2a37e04: one m5.2xlarge (linux/amd64) admin VM, a clean venv with this branch's wheel, claude-haiku-5-5 via LiteLLM, each case in its own store. The shipped bundles' smoke prompt (echo $((6 * 7)), checked for 42).

Case Result
Bundle task naming claude-code, --sandbox local prints claude-code → @local/agentenv-framework/agents/claude-code (the alias agentenv-framework's agents bundle gives it); passed; the stored task names the full id
Same, --sandbox modal_vm passed (73 s)
task run --id <that task> --a2a-agent-id claude-code completed, score 1.0
Task stored with task create (not a bundle), its deploy_agent and verify_a2a_agent_card both naming claude-code completed: both steps got the shipped agent through A2AAgent.get
Store has its own claude-code (a2a-agent put --image-ref of the published image) prints the store's agent 'claude-code' v1, which shadows the alias …; passed on it; the stored task keeps claude-code; pinning v1 passes too
[agents] default_a2a_agent_id = "claude-code", a step naming no agent prints the alias line; passed
Two plugins: acme-kit gives its agent claude-code and helper, zeta-tools gives helper plugin show acme-kit says both are refused (alias-refused); acme-kit's own task naming claude-code gets agent-env's; a task naming helper is refused, naming both bundles; once the store has its own helper, that task passes on it
a2a-agent list lists claude-code and pi with their aliases; marks claude-code (shadowed) beside the store's own; lists the plugins' agents and the store's, and the refused helper
a2a-agent get --id claude-code / deploy --id claude-code prints the shipped agent's JSON, with the alias line on stderr; deploys it; with the store's own, prints that one and no note
a2a-agent get --id claude_code / --id claude-code --version 1 did you mean the alias 'claude-code'? / … so it has no fixed version; drop the pin

No Modal sandboxes were left behind; the one container left was the a2a-agent deploy sandbox, up for its ttl, ended with the VM. The results hold no secret text.

Comment thread src/agent_env/plugins/_bundles.py
earakely-scale and others added 4 commits October 22, 2026 00:30
…reading it

A named pipe there would block every command that reads the installed aliases.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…store has its own

A bundle run and a step outside one both name the shipped Claude Code by its alias, and both name the store's agent
once one is put under that id; the stored tasks hold the id each run resolved.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…esolves an alias

`agent-env a2a-agent list` lists each agent the installed bundles ship, with the aliases that name it (marking one the
store's agent of that id shadows), then the store's agents newest first, 50 unless --limit says otherwise; --json for
scripts. `a2a-agent get --id <alias>` prints the agent a step naming it deploys. A name close to an alias that names
no agent says which alias it may mean, at plan time and at run time.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…does

One get replaces get and resolve: the store's agent of the id, else the agent an installed alias names, so the
validator steps and `a2a-agent deploy`/`validate` take an alias as the deploy, install and judge steps do. The
planner reads the store by exact id itself (STORE_GETTERS), as it weighs aliases against the store on its own. A
pinned alias is now NotFoundError like any other miss, and an id that can't be an alias skips the alias table.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@earakely-scale
earakely-scale merged commit 8b0a880 into main Oct 22, 2026
15 checks passed
@earakely-scale
earakely-scale deleted the feat/agent-aliases branch October 22, 2026 17:35
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