@agent.tool: tools with access to runtime state
Lesson 6's add tool needed nothing but its own arguments. Most real tools need something more: a database connection, an API client, the current user's permissions. @agent.tool, no _plain, gives the tool function a RunContext[Deps] as its first parameter, the same mechanism Lesson 5 used for dynamic system prompts.
from dataclasses import dataclass, field
from dotenv import load_dotenvfrom pydantic_ai import Agent, RunContext
load_dotenv()
@dataclassclass AppDeps: notes: dict[str, str] = field(default_factory=dict)
agent = Agent("google:gemini-3.5-flash-lite", deps_type=AppDeps)
@agent.tooldef save_note(ctx: RunContext[AppDeps], title: str, body: str) -> str: """Save a note under a title.
Args: title: A short title for the note. body: The note's content. """ ctx.deps.notes[title] = body return f"Saved note '{title}'."The model never sees ctx, it only sees the schema for title and body. Pydantic AI injects the RunContext for you when it calls the function. This is the exact analog of a LangChain tool that closes over a shared object, except here the dependency is explicit, type-checked, and swappable per run instead of baked into the tool at definition time.
Why this beats a closure
Because deps are supplied fresh on every run_sync call, the same agent and the same tool definitions can serve completely different runtime state, a different user's notes dict, a different database connection, per request, without redefining any tools.
def main() -> None: deps = AppDeps() result = agent.run_sync( "Save a note titled 'groceries' with the body 'milk, eggs, bread'.", deps=deps, ) print("Output:", result.output) print("Notes after the run:", deps.notes)That's the practical payoff of dependency injection: one agent definition, many isolated runs.
Checkpoint
@agent.tool, nottool_plain, gives the function aRunContext[Deps]first parameter.ctx.depsinside a tool is the samedeps=value passed torun_sync, never exposed to the model itself.- One agent definition can serve many isolated runs, each with its own deps, no redefinition needed.
If anything here still feels unclear, ask before moving to Lesson 8, where we validate what a tool, or the model, hands back.