Repository navigation
feat(agents)!: ship Claude Code in the wheel as the default agent - #257
earakely-scale wants to merge 9 commits into
Conversation
…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>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
| 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) |
There was a problem hiding this 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.
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.There was a problem hiding this comment.
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.
…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>
Why
agent-env shipped no agent. A
deploy_agentstep or an agent judge that named no agent deployeda2a-default, which a fresh install doesn't have, and wrapping a harness was left to users.What
The
agentsbundle, in the wheel besidehelloandecho-mcp. Its one agent folder,agents/claude-code/, is@local/agentenv-framework/agents/claude-code, and its smoke task runs withagent-env run agents.python:3.11-slimat a pinned multi-arch digest, plus Anthropic's native Claude Code binary at a pinnedCLAUDE_CODE_VERSION, checked against the release manifest's sha256.USER(IS_SANDBOX).CLAUDE_CONFIG_DIRis fixed, so the CLI's state doesn't follow the uid. The auto-updater and nonessential traffic are off.a2a_server.py,claude_bridge.py,kit.py): anAgentEnvAgentrunningclaude -p --output-format stream-json, ported from Scale's internal Claude Code wrapper.--mcp-configwith the role header;max_turns,max_thinking_tokens, and an error that explains a 404 from the endpoint.[model]reaches the CLI asANTHROPIC_BASE_URLand a bearerANTHROPIC_AUTH_TOKEN. The API key is blanked, and the key isn't also passed asLITELLM_API_KEY.[model.headers], and the run's project and task attribution, go on every call (ANTHROPIC_CUSTOM_HEADERS).NODE_EXTRA_CA_CERTS.requirements.txtis the protocol alone, and a release pins it.publish-pypi.ymlnow rewrites every shipped example's requirements, not only echo-mcp's.The default agent is now this one.
_DEFAULT_A2A_AGENT_IDchanges. A bundle run pulls the agent from the installed bundle for any agent field left unset. So a baredeploy_agentdeploys Claude Code, and so does arubrics_verifierthat names no judge agent, since it's an agent judge by default.use_agent_judge: false) still pulls it, but nothing is built for it.Breaking: a store agent named
a2a-defaultis 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_agentwith this agent fails, saying the agent declares no install extension.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.pyruns 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:[model.headers]and the model reaching the endpoint;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
Snapshot loads can still roll back another conversation's history, so the PR needs that fix before merging.
Fix with agent prompt
Summary
Ships Claude Code in the wheel as the default agent, with an A2A server, model endpoint settings, snapshots, and trajectories.
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]Reviews (3) · Last reviewed commit: "fix(agents): a snapshot load keeps the o..." · Reviewed by Greptile