Pydantic AI
Verified as a sealed mission 2026-09-27 — general questions, live weather and BBC News through HexaEight-verified services, search of this machine’s documents, “who am I”, and conversation memory across turns.
As a sealed mission
Section titled “As a sealed mission”HexaEight decides who may ask and which command may run; Pydantic AI does the work. It is a
direct mission — the same one-card mission every framework
uses — and the answer is Pydantic AI’s, headed Answered by Pydantic AI.
hexaeight-activate enable pydanticai # from your agent's folderhexaeight-activate enable pydanticai --venv ~/my-env # or use an environment you already haveThen choose runmission → PydanticAI_v1_Runner in the workspace. Requires agent r30, harness engine r41 and
Activate 1.0.68 or later, and Python 3.10–3.13 (or uv). The mission
bundle is the integration-pydanticai-v1 release; the runner, pinned requirements and install-by-hand steps are in
integration_examples/pydanticai.
| the one command | python3 runner.py --question-file question.txt — pinned to runner.py’s sha256 |
| the Pydantic AI half | an Agent with the tools as plain functions, and the conversation so far as native message history |
| model | AnthropicModel over an AsyncAnthropic client on the turn’s route, with the turn’s token as a bearer header — no provider key in the file |
| pinned | pydantic-ai-slim[anthropic]==2.51.0, anthropic==1.8.0 |
| its own output | to pydanticai-run.log in the turn’s folder, with one [tool] line per tool call |
To serve it to other people’s software, run the same enable inside a runner’s folder — see
Serving it to other people and systems.
As a process engine
Section titled “As a process engine”Verified 2026-08-19 — PASS, 4s. Session persistence: message_history round-trip.
Pydantic AI exposes the full typed message list, so persistence is one adapter call each way.
This is one of the two frameworks that appeared to fail in an earlier test run. It was never a framework fault — the transport underneath had a poisoned session. See sessions-and-swarm.md.
The runner
Section titled “The runner”import sys, os, json, pathlibsys.path.insert(0, os.path.expanduser("~/agenttests"))import azcfg, hetoolfrom pydantic_ai import Agentfrom pydantic_ai.models.openai import OpenAIChatModelfrom pydantic_ai.providers.openai import OpenAIProviderfrom pydantic_ai.messages import ModelMessagesTypeAdapter
SESS = pathlib.Path(os.path.expanduser("~/agenttests/pydanticai/sessions"))SESS.mkdir(parents=True, exist_ok=True)
def get_working_folder() -> str: """Return the agent's current working folder (absolute path).""" return hetool.get_working_folder()
def run_shell(command: str) -> str: """Run a shell command in the working folder and return its output.""" return hetool.run_shell(command)
def invoke(sid, message): c = azcfg.load() model = OpenAIChatModel(c["model"], provider=OpenAIProvider(base_url=c["base_url"], api_key=c["api_key"])) agent = Agent(model, system_prompt="Be concise. Use tools for operational questions.", tools=[get_working_folder, run_shell])
f = SESS / f"{sid}.json" history = ModelMessagesTypeAdapter.validate_json(f.read_text()) if f.exists() else None result = agent.run_sync(message, message_history=history) f.write_bytes(ModelMessagesTypeAdapter.dump_json(result.all_messages())) return str(result.output).strip()Note run_sync — no async needed, though agent.run is available if you prefer.
Engine definition
Section titled “Engine definition”{ "type": "process", "file": "/home/you/pydanticai/.venv/bin/python3", "nativeSession": true, "argsNew": ["/home/you/he_adapter.py", "/home/you/pydanticai_session.py", "", "{message}"], "argsResume": ["/home/you/he_adapter.py", "/home/you/pydanticai_session.py", "{sessionId}", "{message}"], "replyField": "result", "sessionIdField": "session_id", "approve": false, "workdir": "/home/you/work", "proxy": { "shape": "openai", "baseUrlEnv": "OPENAI_BASE_URL", "keyEnv": "OPENAI_API_KEY", "modelEnv": "OPENAI_MODEL", "model": "<route>|<provider model>" }}Adoption
Section titled “Adoption”- Keep your agent, tools and result types as they are.
- Persist
result.all_messages()keyed onsid; pass it back asmessage_history. - Build
OpenAIChatModelwith anOpenAIProviderfromazcfg.load(). - Seal the engine, point
external.engineat it, restart.
Gotchas
Section titled “Gotchas”ModelMessagesTypeAdapter both ways — do not hand-roll JSON. The message list is typed
(tool calls, tool returns, parts), and dump_json / validate_json preserve it exactly. Serialising
it yourself loses structure and breaks resume in ways that surface much later.
dump_json returns bytes, hence write_bytes paired with read_text for the parse.
Provider goes through OpenAIProvider, not constructor kwargs on the model.
result.output, not result.data. The attribute was renamed; older examples on the web still use
data and will raise.
Typed outputs still work. If your agent declares a Pydantic result type, result.output is that
type — stringify it however your product needs before returning, since the adapter contract expects a
string.