Skip to content

feat(agents)!: ship Claude Code in the wheel as the default agent - #257

Open
earakely-scale wants to merge 9 commits into
mainfrom
feat/claude-code-agent
Open

earakely-scale wants to merge 9 commits into
mainfrom
feat/claude-code-agent

Conversation

@earakely-scale

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

Copy link
Copy Markdown
Collaborator

Why

agent-env shipped no agent. A deploy_agent step or an agent judge that named no agent deployed a2a-default, which a fresh install doesn't have, and wrapping a harness was left to users.

What

The agents bundle, in the wheel beside hello and echo-mcp. Its one agent folder, agents/claude-code/, is @local/agentenv-framework/agents/claude-code, and its smoke task runs with agent-env run agents.

  • The image.
    • python:3.11-slim at a pinned multi-arch digest, plus Anthropic's native Claude Code binary at a pinned CLAUDE_CODE_VERSION, checked against the release manifest's sha256.
    • It runs as a non-root user, or as root where the provider ignores USER (IS_SANDBOX). CLAUDE_CONFIG_DIR is fixed, so the CLI's state doesn't follow the uid. The auto-updater and nonessential traffic are off.
    • 183 MB, built natively on x86 and arm64 in about 35 s.
  • The agent (a2a_server.py, claude_bridge.py, kit.py): an AgentEnvAgent running claude -p --output-format stream-json, ported from Scale's internal Claude Code wrapper.
    • It keeps:
      • the task env's MCP servers, through --mcp-config with the role header;
      • live and context trajectories;
      • skills, snapshots and triggers;
      • a resume after a transient API error, within the caller's budget;
      • cancellation that kills the CLI's whole process tree.
    • New: max_turns, max_thinking_tokens, and an error that explains a 404 from the endpoint.
  • The model endpoint.
    • [model] reaches the CLI as ANTHROPIC_BASE_URL and a bearer ANTHROPIC_AUTH_TOKEN. The API key is blanked, and the key isn't also passed as LITELLM_API_KEY.
    • Claude Code's model aliases are pinned to the run's model on that endpoint.
    • [model.headers], and the run's project and task attribution, go on every call (ANTHROPIC_CUSTOM_HEADERS).
    • It needs an endpoint serving Anthropic's Messages API, such as a LiteLLM gateway.
  • TLS checks stay on. The on-machine providers already give agent containers NODE_EXTRA_CA_CERTS.
  • requirements.txt is the protocol alone, and a release pins it. publish-pypi.yml now rewrites every shipped example's requirements, not only echo-mcp's.

The default agent is now this one.

  • _DEFAULT_A2A_AGENT_ID changes. A bundle run pulls the agent from the installed bundle for any agent field left unset. So a bare deploy_agent deploys Claude Code, and so does a rubrics_verifier that names no judge agent, since it's an agent judge by default.
  • An explicit direct LLM judge (use_agent_judge: false) still pulls it, but nothing is built for it.
  • The Explorer features the agent, and AGENTS.md and the example config say what the default is.

Breaking: a store agent named a2a-default is no longer the default. Set [agents] default_a2a_agent_id = "a2a-default" to keep it. A host that sets the default in its config is unaffected.

Not in this PR:

  • install/v1, peer agents, the changelog, and file parts that name a stored object come next. Until then, install_agent with this agent fails, saying the agent declares no install extension.
  • Caching VM-built images comes after that, so each new remote VM builds the image cold, in about 40 s on modal_vm.

Tests

  • Unit: the agent's suite, 150 tests in tst/unit/examples/claude_code/, covers the card, turns, retries and the failure messages, trajectories, snapshots, transfers, the CLI's environment and the image contract. Unit and protocol tiers: 8747 passed.

  • Integration: run_bundle_claude_code_test.py runs the real image and the real CLI against a fake Anthropic Messages endpoint (tst/util/fake_anthropic.py) on local docker, so no model key is needed. It covers:

    • a task naming no agent;
    • the bearer key, [model.headers] and the model reaching the endpoint;
    • a second bundle reusing the image;
    • a call to the tool of echo-mcp's env, through the gateway.

    It passes on fresh x86 and arm64 Linux VMs.

  • Live, with a real model through a LiteLLM gateway, on fresh Linux VMs: the results are in the verification comment.

  • Chaos: the results are in the verification comment.

🤖 Generated with Claude Code

RetriggerView in GreptileConfidence Score: 4/5

Snapshot loads can still roll back another conversation's history, so the PR needs that fix before merging.

Fix All in CursorFindings

  1. P1 Restore deletes other conversations ▶
Fix with agent prompt
### Issue 1
src/agent_env/examples/agents/agents/claude-code/a2a_server.py:499-503
A workspace snapshot can load while another conversation is idle. `_restore_workspace_archive` then clears all of `CLAUDE_STATE_DIR`, but the saved context references remain. The other conversation's trajectory returns 404, and its next turn tries to resume a deleted session. Preserve those session files, or reject a restore that would erase another bound context.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Summary

Ships Claude Code in the wheel as the default agent, with an A2A server, model endpoint settings, snapshots, and trajectories.

  • Tasks without an agent now use the bundled Claude Code agent.
  • Claude Code runs tasks through its command-line tool.
  • Claude Code keeps sessions, trajectories, skills, and snapshots between turns.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
    A[Task names no agent] --> B[Installed agents bundle]
    B --> C[Claude Code image]
    C --> D[A2A server]
    D --> E[Claude CLI]
    E --> F[Messages API endpoint]
    E --> G[Task environment MCP tools]
Loading

Reviews (3) · Last reviewed commit: "fix(agents): a snapshot load keeps the o..." · Reviewed by Greptile

earakely-scale and others added 6 commits October 19, 2026 09:56
…laude-code agent

An agent folder built from its own Dockerfile: Anthropic's native Claude Code binary at a pinned version, checked
against its release manifest, behind an A2A server on agentenv-framework-protocol. The configured endpoint reaches the
CLI as Anthropic's variables, with [model.headers] on every call and the model's aliases pinned to the run's model. A
release pins every shipped example's protocol requirement.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ies, snapshots and environment

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

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A step that names no agent deploys @local/agentenv-framework/agents/claude-code, which a bundle run pulls from the
installed agents bundle. A store agent named a2a-default is no longer the default: set [agents] default_a2a_agent_id
to keep it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…d assert on what a run surely prints

The CLI follows a tool result with system messages, and serves a tool's output as JSON; a rerun on one machine
reuses the agent image without building it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@earakely-scale
earakely-scale marked this pull request as ready for review October 19, 2026 17:17
@earakely-scale
earakely-scale requested a review from a team as a code owner October 19, 2026 17:17
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comment on lines +499 to +503
if self._active_contexts:
raise HTTPException(status_code=409, detail="cannot load a workspace while an agent turn is running")
if has_workspace_archive:
try:
await asyncio.to_thread(_restore_workspace_archive, archive_path)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Restore deletes other conversations

A workspace snapshot can load while another conversation is idle. _restore_workspace_archive then clears all of CLAUDE_STATE_DIR, but the saved context references remain. The other conversation's trajectory returns 404, and its next turn tries to resume a deleted session. Preserve those session files, or reject a restore that would erase another bound context.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agent_env/examples/agents/agents/claude-code/a2a_server.py
Line: 499-503

Comment:
**Restore deletes other conversations**

A workspace snapshot can load while another conversation is idle. `_restore_workspace_archive` then clears all of `CLAUDE_STATE_DIR`, but the saved context references remain. The other conversation's trajectory returns 404, and its next turn tries to resume a deleted session. Preserve those session files, or reject a restore that would erase another bound context.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Fix in Cursor Fix in Claude Code Fix in Codex

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 3bd1981. A load now keeps the transcript of every session this agent has bound (bound_session_refs) that the archive doesn't hold, so another conversation's trajectory and its next turn still find it. Test: test_a_snapshot_load_keeps_the_transcripts_of_the_other_conversations.

Comment thread src/agent_env/examples/agents/agents/claude-code/a2a_server.py
Comment thread src/agent_env/examples/agents/agents/claude-code/a2a_server.py Outdated
Comment thread src/agent_env/examples/agents/agents/claude-code/claude_bridge.py Outdated
Comment thread src/agent_env/examples/agents/agents/claude-code/a2a_server.py
Comment thread src/agent_env/examples/agents/agents/claude-code/kit.py Outdated
earakely-scale and others added 2 commits October 19, 2026 10:53
…its tools

asyncio kills a process cancelled mid-start, but not what it already started, and the bridge had no process to
kill yet. The spawn is shielded, and a cancel waits for it, then kills the whole tree.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…start resumes nothing, and links survive

A load clears the CLI's state, and kept the transcripts of no other bound context: it now keeps them. A turn marks
its context active before writing its attachments, so a load can't clear them, and resumes its session only once the
CLI has written its transcript, so a turn that failed before then doesn't break the next. A timed-out turn keeps its
tool calls. A link to a directory is archived as a link, and a link the load's data filter would refuse is left out.

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

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant