Skip to content

feat(agents): add the Pydantic AI agent - #338

Merged
earakely-scale merged 6 commits into
scaleapi:mainfrom
mpfaffenberger:pydantic-agent
Oct 28, 2026
Merged

earakely-scale merged 6 commits into
scaleapi:mainfrom
mpfaffenberger:pydantic-agent

Conversation

@mpfaffenberger

@mpfaffenberger mpfaffenberger commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Ship a Pydantic AI agent in the agents bundle, beside Claude Code and the others, at
@local/agentenv-framework/agents/pydantic-ai (alias pydantic-ai).

The agent-env protocol SDK serves the A2A endpoint and its extensions; each turn runs a Pydantic AI
Agent in process with the Pydantic AI Harness Coder tools (shell, file tools, sub-agents) in a
LocalWorkspace, the task's MCP servers as MCPToolsets and installed skills through the harness's
Skills. A context's message history is persisted, so later turns continue it, its messages are the
live trajectory, and snapshots carry it with the files attached in it. The model is called through
the [model] endpoint as an OpenAI-compatible one; a vllm: prefix picks Pydantic AI's vLLM provider.

Pydantic AI's own Agent.to_a2a is not used: it serves plain A2A, without the agent-env extensions
the validator checks.

Also: the fake OpenAI endpoint answers streamed requests with server-sent chunks, which Pydantic AI
always asks for.

Verified: unit tests (tst/unit/agents/pydantic_ai, 34), tst/integration/cli/run_bundle_pydantic_ai_test.py (6, real Docker against the fake OpenAI endpoint), ruff check, and agent-env a2a-agent validate locally against an OpenAI-compatible vLLM endpoint: every extension the agent advertises passed (agent-config, mcp-config, trajectory, skill-config, snapshot, peer-agents, install/v1); the snapshot changelog is not implemented, as for OpenHands.

RetriggerView in GreptileConfidence Score: 4/5

Fix the overly broad snapshot exclusion before merging.

Fix All in CursorFindings

  1. P1 Snapshots skip project files ▶
Fix with agent prompt
### Issue 1
src/agent_env/agents/agents/pydantic-ai/pydantic_ai_agent.py:579
`_entry_holding` selects the first directory under the workspace, not just the agent's state directory. If the shipped image runs with `PYDANTIC_AI_A2A_WORKSPACE=/home`, the state lives at `/home/agent/.pydantic-ai-a2a`, but saving skips all of `/home/agent`, including project files there. Loading also leaves that whole directory unchanged. Both calls can return success without saving or restoring those files.

Exclude and preserve only the agent's state directory, not its whole ancestor.

---

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

Summary

Adds Pydantic AI as a bundled agent, with coding tools, MCP servers, skills, saved conversations, snapshots, and installation support.

  • Pydantic AI tasks use coding tools and continue their context's conversation.
  • Pydantic AI conversations and workspace files can be saved and restored.
  • The Pydantic AI agent is available as a bundled, installable agent.
  • The fake OpenAI endpoint sends chunks for streamed requests.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  Task[A2A task] --> Agent[Pydantic AI agent]
  Agent --> Tools[Coding tools and MCP servers]
  Agent --> History[Saved conversation]
  History --> Snapshot[Snapshot]
  Workspace[Workspace files] --> Snapshot
Loading

Reviews (6) · Last reviewed commit: "fix(agents): keep the Pydantic AI agent'..." · Reviewed by Greptile

Ship a Pydantic AI agent in the agents bundle, beside Claude Code and the others, at
@local/agentenv-framework/agents/pydantic-ai (alias `pydantic-ai`).

The agent-env protocol SDK serves the A2A endpoint and its extensions; each turn runs a Pydantic AI
Agent in process with the Pydantic AI Harness Coder tools (shell, file tools, sub-agents) in a
LocalWorkspace, the task's MCP servers as MCPToolsets and installed skills through the harness's
Skills. A context's message history is persisted, so later turns continue it, its messages are the
live trajectory, and snapshots carry it with the files attached in it. The model is called through
the [model] endpoint as an OpenAI-compatible one; a `vllm:` prefix picks Pydantic AI's vLLM provider.

Pydantic AI's own Agent.to_a2a is not used: it serves plain A2A, without the agent-env extensions
the validator checks.

Also: the fake OpenAI endpoint answers streamed requests with server-sent chunks, which Pydantic AI
always asks for.
@mpfaffenberger
mpfaffenberger requested a review from a team as a code owner October 26, 2026 21:47
Comment thread src/agent_env/agents/agents/pydantic-ai/pydantic_ai_agent.py Outdated
Comment thread src/agent_env/agents/agents/pydantic-ai/pydantic_ai_runtime.py Outdated
…hreaded writes before a cancel lets go of the locks

A snapshot whose messages.json was JSON but not Pydantic AI messages replaced the conversation and
workspace, then failed the next turn; the runtime now validates the messages before anything is
replaced. A turn or snapshot cancelled while a thread wrote its files released its locks before the
write ended; to_completion waits for the thread before re-raising.
@earakely-scale

Copy link
Copy Markdown
Collaborator

🤖 Automated Claude routine — Env Pod PR Context Review. This is a bot, not Edgar Arakelyan; not a human review.

Context from a sibling PR and from main, not about this diff's own logic.

#345 adds a second agent (letta-code) and lands on the same three shared registration points this PR does. Two of them are exhaustive assertions, so they don't merge additively:

  • tst/unit/bundle/shipped_bundles_test.py:45 — assert {alias: … for …} == { … } over the whole alias table. This PR's expected dict is the six on main plus pydantic-ai; feat(agents): add a Letta Code agent #345's is the same six plus letta-code. Once both land the real table holds eight, so whichever expected dict survives the merge is short one entry and the test fails. It needs both names, not whichever side wins the conflict.
  • src/agent_env/agents/aliases.toml — main ends at openhands = ["openhands"] and both PRs append a line after it, so that's a textual conflict either way.
  • .github/scripts/infra_build.py — both add an _IMAGES entry and a _SMOKES entry; those are dict keys, so they do merge additively.

Separately, this branch looks behind main (a4ae4f6) on the third one. Your hunk for tst/unit/test_infra_images_script.py:62 shows the variable as agents and the expected list ending "agentenv-pi", "agentenv-proxy"], while main now reads:

    bundles = [image.name for image in BUNDLE_IMAGES]
    …
    assert "agentenv-gateway" in planned["infra"] and bundles == [
        "agentenv-claude-code", "agentenv-codex", "agentenv-gemini", "agentenv-opencode", "agentenv-openhands",
        "agentenv-pi", "agentenv-proxy", "agentenv-mail-mcp"]

So after a rebase the list also needs agentenv-mail-mcp, and the name is bundles. Worth noting the order is BUNDLE_IMAGES declaration order rather than alphabetical (agentenv-mail-mcp sits last, after proxy), so where agentenv-pydantic-ai belongs follows wherever the image gets declared.

Each reviewer sees one diff at a time, so none of this shows up on either PR on its own. Offering the comparison, not a verdict on merge order.


Generated by Claude Code

Keeps both sides of the image registrations main added beside this one (the mail-mcp example env and the
ubuntu-24.04 base computer) in infra_build.py, dependabot.yml and the infra images test.

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

socket-security Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedpypi/​pydantic-ai-harness@​0.61.096100100100100
Addedpypi/​pydantic-ai-slim@​2.61.097100100100100

View full report

Comment thread src/agent_env/agents/agents/pydantic-ai/pydantic_ai_agent.py
earakely-scale and others added 2 commits October 28, 2026 10:17
With neither agent-config's model nor PYDANTIC_AI_A2A_MODEL set, every task
failed as pydantic_ai.no_model, so an agent other agents message directly,
like the A2A validator's peer, never answered. It now falls back to
anthropic/claude-sonnet-4-6, as the other bundled agents have a default.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Keeps the Letta Code agent main added beside this one in the shipped aliases and the bundle and infra image tests.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Comment thread src/agent_env/agents/agents/pydantic-ai/pydantic_ai_agent.py Outdated
…store, and bound inline attachments

Installed in a home directory, the agent works there, so its own
~/.pydantic-ai-a2a is inside the workspace. A workspace restore deleted it,
the staged conversation with it, and the restore failed after the workspace
had changed, with every other conversation and skill gone. Workspace snapshots
now leave that entry out and restores keep it.

An inline file part was decoded and written on the event loop with no size
check; it now gets the 256 MiB limit downloaded ones have, checked before it is
decoded, and is written in a thread.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
"""The name of ``directory``'s entry that ``path`` is in, as the agent's home is in a workspace an install in a
home directory works in; None when ``path`` is outside ``directory``."""
try:
return path.resolve().relative_to(directory.resolve()).parts[0]

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 Snapshots skip project files

_entry_holding selects the first directory under the workspace, not just the agent's state directory. If the shipped image runs with PYDANTIC_AI_A2A_WORKSPACE=/home, the state lives at /home/agent/.pydantic-ai-a2a, but saving skips all of /home/agent, including project files there. Loading also leaves that whole directory unchanged. Both calls can return success without saving or restoring those files.

Exclude and preserve only the agent's state directory, not its whole ancestor.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/agent_env/agents/agents/pydantic-ai/pydantic_ai_agent.py
Line: 579

Comment:
**Snapshots skip project files**

`_entry_holding` selects the first directory under the workspace, not just the agent's state directory. If the shipped image runs with `PYDANTIC_AI_A2A_WORKSPACE=/home`, the state lives at `/home/agent/.pydantic-ai-a2a`, but saving skips all of `/home/agent`, including project files there. Loading also leaves that whole directory unchanged. Both calls can return success without saving or restoring those files.

Exclude and preserve only the agent's state directory, not its whole ancestor.

---

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

@earakely-scale
earakely-scale merged commit daaa52c into scaleapi:main Oct 28, 2026
25 of 26 checks passed
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.

2 participants