What agents should know before contributing
pydantic/pydantic-ai
How Python does AI. Agents, realtime voice, image generation, embeddings. Every model, every interface, typed end to end.
Read by Bluet from 9 files written for agents and 300 merged pull requests on 2026-09-24. Not reviewed by the maintainers.
Rules its files tell agents (18)
- docs/agent.md
mustName a part relative to what you own — 'limits', not 'toolset:weather:limits' — and the source contributing it…
You declare a [name][pydantic_ai.messages.InstructionPart.name]; the framework issues an [id][pydantic_ai.messages.InstructionPart.id]. Name a part relative to what you own — 'limits', not 'toolset:weather:limits' — and the source contributing it supplies the rest, so you never repeat your own identity and can never claim another source's key. The [InstructionId][pydantic_ai.messages.InstructionId] you get back pairs the [source][pydantic_ai.messages.InstructionId.source] that contributed the part with the [name][pydantic_ai.messages.InstructionId.name], if there is one, and renders as its seg
- docs/agent.md
mustPydantic AI stops pulling from the stream immediately, so final usage events may never arrive; some provider S…
!!! info "Usage tracking for cancelled streams" Token usage reported by usage after cancellation is partial and provider-dependent. Pydantic AI stops pulling from the stream immediately, so final usage events may never arrive; some provider SDKs may also continue generation server-side after the local stream is closed. Do not rely on cancelled-stream usage for cost-critical accounting. For OpenAI chat completions, [openai_continuous_usage_stats][pydantic_ai.models.openai.OpenAIChatModelSettings] can improve in-stream usage reporting by requesting cumulative usage data with each chunk, but canc
- CLAUDE.md
mustNever report an operation as blocked, unavailable, or not-permitted based on a metadata flag, a config field,…
A restriction is a conclusion you earn from a real failure, not a field you read. Never report an operation as blocked, unavailable, or not-permitted based on a metadata flag, a config field, or a docs claim — attempt it and quote the actual error. (maintainerCanModify: false on a PR does not mean you cannot push: it governs the upstream-maintainer auto-grant, not your own access to the fork.) If you genuinely cannot attempt it, say "not attempted", never "we can't".
- CLAUDE.md
mustNever add yourself (Claude) as a co-author on commits.
Never add yourself (Claude) as a co-author on commits. Commits should be authored as the user only, with no Co-Authored-By trailer referencing Claude.
- docs/changelog.md
shouldProvider-adaptive WebSearch and WebFetch capabilities are now native-only and raise on models that don't suppo…
Provider-adaptive WebSearch and WebFetch capabilities are now native-only and raise on models that don't support them, and MCP(url=...) runs the server locally by default. Restore the V1 fallbacks with WebSearch(local='duckduckgo'), WebFetch(local=True), and MCP(url=..., native=True). Announced via #5331; changed in #5333.
- docs/agent.md
shouldDon't rely on cost_limit as a hard billing guarantee — pair it with [request_limit][pydantic_ai.usage.UsageLim…
!!! note Cost is best-effort: it's None for models and providers genai-prices has no pricing data for, including models released after your install unless you keep prices up to date. With a [cost_limit][pydantic_ai.usage.UsageLimits.cost_limit], a run that could not be priced at all emits [CostNotFoundWarning][pydantic_ai.exceptions.CostNotFoundWarning] rather than being silently unconstrained; an unexpected pricing failure emits [CostCalculationFailedWarning][pydantic_ai.exceptions.CostCalculationFailedWarning]. Don't rely on cost_limit as a hard billing guarantee — pair it with [request_limi
- docs/agent.md
shouldRaising RunCancelled in its place would silently break each of those.
The asyncio environment cancels the task the run happens to be on: asyncio.Task.cancel(), asyncio.timeout() expiring, a [TaskGroup][asyncio.TaskGroup] tearing down after a sibling failed, a server shutting down, workflow cancellation under durable execution. All of these deliver the very same CancelledError signal, so Pydantic AI cannot tell a stop button from a timeout -- and the exception's type is load-bearing for everything built on it: asyncio.timeout() only produces TimeoutError, a TaskGroup only treats the task as cleanly cancelled, and Temporal only ends the workflow as Cancelled if Ca
- docs/agent.md
shouldSpell the name out as well when the tag has to match something you don't control.
Spell the name out as well when the tag has to match something you don't control. It's the identifier the UI adapters put on the wire, as the AG-UI event's name and the Vercel AI chunk's data-{name} type, and derivation only ever produces snake case: a frontend that already expects data-indexProgress or a dotted ui.progress needs name='indexProgress' or name='ui.progress' rather than a class renamed to suit it.
- CONTRIBUTING.md
shouldA heading's anchor is generated from its text, so renaming one silently breaks every link pointing at it.
CI checks that every link between doc pages resolves, anchor included, and fails on Cannot find fragment. A heading's anchor is generated from its text, so renaming one silently breaks every link pointing at it. Where a heading is linked to, pin its anchor with {#custom-id} — the heading text is then free to change without moving the anchor.
- CONTRIBUTING.md
shouldDo not prefix either value with /ai or a leading slash.
All routes in docs/navigation.yml are relative to the Pydantic AI documentation root. Give each page its complete canonical route in slug; use aliases only for redirect sources. Do not prefix either value with /ai or a leading slash.
- CONTRIBUTING.md
shouldDo not force-push updates to an open PR.
Do not force-push updates to an open PR. Rewriting its commits invalidates previous reviews; push follow-up commits instead. We will squash them when merging.
- CONTRIBUTING.md
shouldWe do not automatically triage every new PR.
We do not automatically triage every new PR. PRs on issues we have not pre-aligned on are not in our review queue, regardless of how well written they are. If no maintainer has agreed to the change on an issue and assigned it to you, assume we have not seen your PR.
- CONTRIBUTING.md
shouldPlease don't spend effort chasing green CI, addressing every automated review comment, or rebasing for merge c…
Please don't spend effort chasing green CI, addressing every automated review comment, or rebasing for merge conflicts on a PR we haven't pre-aligned on. If we take the change forward, that polish gets thrown away when we rewrite. Get the approach working, then stop and ping us on Slack.
- CONTRIBUTING.md
shouldDo not start with code.
Want a feature or API change? Open an issue describing the problem you're solving. Do not start with code.
- CLAUDE.md
shouldDo not use subprocesses to test logic that can run in-process.
Do not use subprocesses to test logic that can run in-process. Starting another interpreter and importing the project again makes the full test suite materially slower. Reserve subprocesses for behavior that depends on the process boundary, such as CLI invocation, interpreter startup, or import isolation.
- CLAUDE.md
shouldDo not leave work uncommitted.
Do not leave work uncommitted. Don't end a turn with unstaged or uncommitted local changes unless the user's own instructions say otherwise.
- CLAUDE.md
shouldAfter you push, do not go idle.
Pushing is not the end of the task. After you push, do not go idle. The work is done when CI is green and there are no unresolved comments — see the pushing-commits-to-the-repo skill for the full loop.
- CLAUDE.md
shouldDo not refactor a shared protocol, helper, or abstraction to fix one caller unless the narrow fix is unavailab…
be scoped to the problem you are solving: for a bug fix, make the narrowest change that resolves the reported, reproduced behavior -- often one line plus one regression test -- and stop. The preference for general primitives and powerful abstractions (see Philosophy) is for shaping designs and new public surface, not for widening a bug fix. Extend a fix to sibling fields, providers, or models only after confirming, by reproducing, that they share the same defect; an "others might also be affected" hunch is unacceptable: if a concern is verified, it can be included in the same PR if it doesn't
Decisions its pull requests settled (40)
- #7758Make `RunContext.enqueue()` safe from worker threads
- #7759Preserve explicit empty `Tool` description instead of falling back to the function docstring
- #7794Let capabilities emit typed `CapabilityEvent`s and subscribe to run events with `@on_event`
- #7823Validate `ToolReturnContent` without a Python call per JSON node
- #7830Attribute tool-returned media to its originating tool call when it can't ride the tool result
- #7870Handle barge-in in the session: `handle_barge_in=True`, `interrupt(played_bytes=...)`, and `played_audio_bytes`
- #7864Fix `CodeExecutionTool` uploads never reaching a fresh Anthropic container on a multi-turn history
- #7887Add `glm-5.3-flash` and rework the Z.AI test suite onto cassettes
- #7936Document that a `Case` with `expected_output=None` cannot fail under `EqualsExpected`
- #7959Document which docstring sections reach the model
- #7972Stop rebuilding unmanaged models in durable capability operations
- #7989Add Claude Fable 5.1 (`claude-fable-5-1`) and Claude Mythos 5.1 (`claude-mythos-5-1`) support
- #8059Add a `GitHubCopilotProvider` for GitHub Copilot's OpenAI-compatible API
- #8056Resolve non-object `$ref` definitions inline in code-mode function signatures
- #8062Emit speech finalized by a tool call on `stream_transcripts()`
- #8071Run a capability's `prepare_tools` over every tool the availability gate admits
- #8081Reject invalid `DeferredToolResults.approvals` values
- #8099Let a capability cache derived state again, and refuse a collection the merge cannot rebuild
- #8119Make a capability's auto-assigned registry key look like the run-local handle it is
- #8105Let a realtime tool hang up with `ctx.realtime_session.close()` instead of wedging itself
- #8133Bound the realtime session event queue while nothing is iterating it
- #8138Surface realtime receive-side failures to every consumer shape, and end `send_audio(iterable)` cleanly on close
- #8142Let `RealtimeSession.close()` finish its teardown when the closing task is cancelled
- #8257key tool opt-out checks on operation kind, not an MCP import
- #8395Dispatch a capability `@durable_operation` called from a per-request hook, instead of silently running it inline
- #8396Require a JSON `Content-Type` on UI adapter requests
- #8398Document that `prepare` functions must modify the `ToolDefinition` they're given
- #8407Compare `web_fetch_tool` domain lists in the form the resolver uses
- #8417Report a run's own usage on its span, not the conversation total
- #8419Check `include_content=False` over every content channel at once
- #8428Close two `include_content` redaction bypasses and make the content sweep prove itself
- #8429Close two `include_content` redaction bypasses and make the content sweep prove itself (v1 backport)
- #8437Put the enqueued-messages event on the queue the session now uses
- #8438Re-arm coverage for code already seen when the dataclass walk resumes it
- #8450Add `TypeSafeModel` for TypeSafe's Jev
- #8463Hold one MCP server session per durable run instead of one per durable unit
- #8479Describe an `Enum`'s options by its member docstrings when it mixes in `UseEnumMemberDocstrings`
- #8484Refuse more options than Jev can pick from in `TypeSafeModel`
- #8500Check `docs/navigation.yml` against the released Pydantic AI Harness
- #8586Add `BoolCriteria` to say what a `bool` field's yes and no mean, and let a `True`/`False` `Enum` do the same