June 14, 2026 ยท 2 min read
Fixing tenant-aware tools in Pydantic AI
How Codex fixed Pydantic AI tools that ignored per-run tenant data.
We tested a Pydantic AI support agent that routes tickets for several tenants. Each run receives its own tenant, region, priorities, and escalation policies through Pydantic AI dependencies.
The agent declared deps_type=SupportDeps, but both tools used @agent.tool_plain. They could not access RunContext, so every request fell back to global values.
Both runs used Codex GPT-5.5 against pydantic-ai 2.0.0b7 with the same prompt:
Fix this Python fixture so `pytest` succeeds, preserving tenant-aware Pydantic AI routing tools.
Case study replayReal agent replays
Pydantic AI tenant routing tools
model Codex GPT-5.5Fix this Python fixture so `pytest` succeeds, preserving tenant-aware Pydantic AI routing tools.
Without GitHits
- tokens
- 0
- time
- 0s / 189s
- Ready. Click "Watch Replay" to start.
- The Pydantic AI tools now use @agent.tool with RunContext[SupportDeps], so tenant, region, priorities, and escalation policies come from the active per-run dependencies.
With GitHits
- tokens
- 0
- time
- 0s / 99s
- Ready. Click "Watch Replay" to start.
- Used GitHits to confirm Pydantic AI 2.0.0b7's context-aware tool pattern, then changed the two tools from tool_plain to tool with RunContext[SupportDeps].
Result
| Run | Time | Tokens | Tools |
|---|---|---|---|
| With GitHits | 99s | 393,469 | 21 |
| Without GitHits | 189s | 901,661 | 28 |
Both runs produced a passing patch. The GitHits run finished 90 seconds sooner and used 508,192 fewer processed tokens.
The bug
The tools were registered like this:
@agent.tool_plain
def lookup_ticket(ticket_id: int) -> str:
return f"{DEFAULT_TENANT}:{ticket_id}:{DEFAULT_PRIORITY}"
@agent.tool_plain
def escalation_policy(ticket_id: int) -> str:
return (
f"{DEFAULT_TENANT}:{DEFAULT_PRIORITY}:"
f"{DEFAULT_POLICY}:{DEFAULT_REGION}"
)
tool_plain is for tools that do not need the run context. These tools did. The tests expected acme and globex requests to route differently, with fallbacks that still kept the active tenant and region.
The fix
Both tools needed @agent.tool and a typed RunContext:
from pydantic_ai import Agent, RunContext
@agent.tool
def lookup_ticket(ctx: RunContext[SupportDeps], ticket_id: int) -> str:
priority = ctx.deps.priorities.get(ticket_id, DEFAULT_PRIORITY)
return f"{ctx.deps.tenant}:{ticket_id}:{priority}"
@agent.tool
def escalation_policy(ctx: RunContext[SupportDeps], ticket_id: int) -> str:
priority = ctx.deps.priorities.get(ticket_id, DEFAULT_PRIORITY)
policy = ctx.deps.escalation_policies.get(priority, DEFAULT_POLICY)
return f"{ctx.deps.tenant}:{priority}:{policy}:{ctx.deps.region}"
The change is small: use the context-aware decorator and read the active dependency object through ctx.deps. Fixing only one tool would still leave routing inconsistent.
What GitHits changed
Both runs produced the same correct patch. Here, GitHits improved efficiency: its run finished 90 seconds sooner, used 508,192 fewer processed tokens, and needed seven fewer tool calls.
GitHits found the relevant Pydantic AI docs for version 2.0.0b7, where @agent.tool and ctx.deps are shown together. A source read then confirmed the Agent.tool API before the edit.
Without GitHits, Codex had to locate the installed package, inspect its exports, read the agent implementation, and trace the run-context types to recover the same contract. GitHits removed that package discovery and source navigation; it did not change the final fix.