Releases: Artui/django-pydantic-agent
Release list
v0.4.4
Changed
-
[drf-mcp]→djangorestframework-mcp-server>=0.17,<0.22, taking in both
0.20.0 and 0.21.0. Two consumer-reported blockers, neither of which touches
this bridge:- 0.21.0 —
DjangoOAuthToolkitBackendrejected every bearer token once a
resource URL was configured. Audience enforcement read aresourcefield
that DOT'sAccessTokendoes not have, so it could never succeed;
enforcement is now the separateENFORCE_AUDIENCE, default off. - 0.20.0 — dynamically registered clients could not be issued an ID token
(Application.algorithmwas never set), so the token endpoint 500'd whenever
the advertisedopenidscope was requested.
Both are confined to drf-mcp's OAuth surface. This bridge consumes
MCPServer
and the tool registry in-process, so no adaptation was needed — verified with
the lock updated to 0.21.0 and the suite green.0.20.0 also added
UndescribedToolWarningfor a tool registered with no
description. Two bridge fixtures do that on purpose, to cover the
no-description path, so the warning is filtered inpyproject.tomlrather
than papered over by giving fixtures descriptions they are meant to lack. - 0.21.0 —
v0.4.3
Changed
-
[drf-mcp]→djangorestframework-mcp-server>=0.17,<0.20, so 0.19.0 is
installable. That release fixes dynamic client registration, which issued
credentials that could never authenticate:token_endpoint_auth_methodwas
not modelled, so every registration silently became a confidential client, and
theclient_secrethanded back was the stored PBKDF2 digest rather than the
secret. Both are confined to drf-mcp'scontrib.oauth— the bridge this
extra backs consumesMCPServerand the tool registry, neither of which
changed, so the widening is purely a ceiling lift and this package's own
behaviour is unaffected.Verified rather than assumed: the suite runs green against 0.19.0 with the
lock updated.
v0.4.2
Changed
-
Widened the two agent-tool integration pins so the current majors of both
backing packages are installable:[spec-tools]→djangorestframework-pydantic-ai>=0.9,<0.11. 0.10.0 adds
SpecToolset(host=…), the origin that makes DRF'sFileField/
Hyperlinked*fields render absolute URLs off the HTTP path. Nothing here
uses it — the widening is what lets a consumer pass it.[drf-mcp]→djangorestframework-mcp-server>=0.17,<0.19. 0.18.0 fixes two
reported crashes: serializer-context providers called positionally
(TypeErrorfor any provider not leading withview, request) and the
missing DRF baseline context (KeyError: 'request'). It also carries a
deliberate break — a provider whose first two parameters are named something
other thanview/requestnow raises — but that is a change to user
provider signatures, not to anything this package calls.
The floors stay at
0.9/0.17: neither integration uses new API, so both
ranges are honestly satisfiable, and a project already pinned to an older
release isn't forced forward.
v0.4.1
Documentation
- Document the
[harness]extra — it has existed since the CodeMode work but
integrations.mdonly covered the two DRF bridges, so the third extra was
undiscoverable. The new section covers the seam (AgentConfig.capabilities
takes live capability instances, so a harness capability composes exactly like
a first-party one), compaction for long tool-heavy runs, and agent
skills for progressive disclosure.- Compaction is presented by cost, since that is the real choice:
SlidingWindow/ClearToolResultsare free and transparent,
SummarizingCompactionspends a model call per compaction,
TieredCompactionescalates between them. - Two gotchas found by running the snippets rather than reading the source:
SummarizingCompactionrequiresmax_messagesormax_tokens, and
Skills(include=…/exclude=…)validates the names against what discovery
actually found — a name that matches no skill raises rather than being
ignored. - Also recorded: nothing is emitted when a compaction fires, so a transport
that wants to tell the user "earlier turns were condensed" has to observe it
via the one-methodCompactionStrategyprotocol.
- Compaction is presented by cost, since that is the real choice:
v0.4.0
Added
-
⚠ New migration
0002_snapshot_state— runmigratewhen upgrading.
StoredSnapshotgains astatecolumn mirroring the harness'sSnapshotState
(complete/interrupted, defaulting tocomplete, so existing rows keep
today's behaviour).A
completesnapshot sits at a boundary where every tool call has a matching
return, so resuming from it is always safe. Aninterruptedone is a rescue
point captured mid-tool-cycle — pending calls may be re-executed or closed out
with synthesized returns — solatest_snapshot()now skips interrupted rows
unless asked for them. The state has to be stored rather than inferred:
by the time a resume is attempted, the run that produced the row is gone.When several snapshots exist, the state filter applies after ordering, so
the newestcompleterow wins even when newerinterruptedrows sit above it
— the same walk-back-from-newest the harness reference stores do.
Changed
[harness]now requirespydantic-ai-harness>=0.12,<0.13(was>=0.7,<0.8).
The ceiling had gone five minors stale, which on a 0.x library is where breakage
accumulates — and it did. It also gated
pydantic_ai_harness.skills,
which does not exist below 0.11 and is the prerequisite for adopting agent skills.- ⚠
DefaultStepStore.latest_snapshot()gained aninclude_interrupted
keyword, matching the harness'sStepStoreprotocol. Harness's own
continue_run()passes it, so before this the resume path raised
TypeError: latest_snapshot() got an unexpected keyword argument. Any custom
StepStoreimplementation must add the same parameter. - Raise the drf-chain ceilings:
[drf-mcp]→djangorestframework-mcp-server>=0.17,<0.18
(was>=0.15,<0.16) and[spec-tools]→djangorestframework-pydantic-ai>=0.9,<0.10
(was>=0.8,<0.9). The MCP ceiling had gone stale a wave earlier — drf-mcp
0.16.0 (MCP Apps) was already excluded — so two upstream releases were
unreachable from here rather than one. No adaptation was needed, which the
three relevant upstream changes explain:- MCP Apps (drf-mcp 0.16.0) adds
ui://resources and_meta.uilinks on
tool definitions. The bridge readsname/description/inputSchema/
outputSchema/annotationsofftools/listand ignores_meta, so the
addition is inert here. The resource-encoding fix in the same release (non-JSON
resource bodies no longer come back as quoted JSON string literals) touches
the resource surface, which this bridge does not use — it calls tools only. - The shared
UrlKwarg/QueryParam(drf-mcp 0.17.0, PAI 0.9.0) are
re-exported fromdjangorestframework-servicesrather than defined locally,
behind permanently preserved import paths. Neither is imported here. PAI's
switch fromValueErrortoImproperlyConfiguredfor a bad channel
declaration is likewise unreachable:SpecCapabilityis constructed with a
spec mapping and no channel registrations. InputRequiredenforcement (drf-services 0.28) makes a missing
marked-required input raiseServiceValidationErrorat dispatch. Over the
MCP bridge that already arrives as anisErrorresult with
type == "validation_error", whichcall_toolmaps toModelRetry— so a
spec adopting the marker gets a model-correctable failure through this path
with no change here.
- MCP Apps (drf-mcp 0.16.0) adds
v0.3.0
Added
AgentDeps— typed, per-run dependencies threaded through the agent.
build_agentnow returnsAgent[AgentDeps, Any](deps_type=AgentDeps), so
a transport hands each run anAgentDeps(user=request.user)and every tool,
toolset and capability reads request-scoped values offRunContext.deps—
pydantic-ai's own seam for exactly this.- The acting user binds natively.
djangorestframework-pydantic-ai's
SpecToolsetalready defaults to readingctx.deps.user; until now this
package overrode that with a closure over the request, so the upstream
default could never fire. - It unblocks reusing a built agent. A capability that closes over a
request can only serve that request, forcing a per-request rebuild —
schemas and all. Request-independent collaborators are the precondition for
building once and binding the user per run. - AG-UI shared state now has somewhere to land.
AgentDepssatisfies
pydantic-ai'sStateHandlerprotocol, so a run'sRunAgentInput.stateis
validated intodeps.staterather than dropped with aUserWarning. Seed
statewith a Pydantic model instance to get it validated against that
model. Inbound only — nothing emitsSTATE_SNAPSHOT/STATE_DELTA
back yet; a tool must return those asToolReturnmetadata. AgentDepsis the one record here that is not frozen: the UI adapter
assignsdeps.state = …directly (it does not usedataclasses.replace,
despite what the protocol's own comment suggests). Deps are per-run and
never shared, so the mutability is contained.
- The acting user binds natively.
Changed
AgentFactoryFnnow returnsAgent[AgentDeps, Any]. A project using the
agent_factory=escape hatch must build its agent with
deps_type=AgentDeps; one that omits it produces an agent whose tools see no
acting user. Type checkers flag it, which is how this surfaced.build_spec_capability(specs, request, …)→build_spec_capability(specs, …)
— therequestargument is gone. It existed solely to supply
get_user=lambda _ctx: request.user; with typed deps,SpecToolset's own
default readsctx.deps.userand the override is unnecessary. Breaking for
direct callers (pre-1.0): drop the positionalrequest, and make sure runs
are givendeps=AgentDeps(user=…), or spec tools will act asNone.
v0.2.0
Added
- Spec composition accepts a spec registry, not just a mapping.
build_spec_capability(specs, request, …)now takes either a
name -> specmapping or a spec registry — drf-services 0.27's
SpecRegistry, the one declaration site for a project exposing the same specs
over more than one transport. Newresolve_spec_mapping()helper and a
SpecSourceProtocol.- Matched structurally, not imported.
SpecSourcedeclares only
specs() -> dict, so this substrate still names no drf-services type and
still depends onpydantic-ai-slimalone — drf-services arrives only with
the optional[spec-tools]extra. NamingSpecRegistryin a signature
would either force the dependency on every install (including projects whose
tools are plain@toolfunctions) or bury the type behind a lazy import
where it cannot appear in a signature at all. drf-services duck-types
SelectorSpec.filter_setfor the same reason. resolve_spec_mapping()is public on purpose. A transport needs the
same normalisation before the builder runs: iterating a registry yields
RegisteredSpecrecords, not names, so a transport reserving tool names by
iterating the raw argument would fill its collision-detection set with
dataclasses and silently stop detecting duplicates between the@tool
registry, the drf-mcp bridge and the spec tools.
- Matched structurally, not imported.
Changed
[spec-tools]now requiresdjangorestframework-pydantic-ai>=0.8,<0.9
(was>=0.5,<0.6) — registry support landed in PAI 0.8.[drf-mcp]now requiresdjangorestframework-mcp-server>=0.15,<0.16
(was>=0.12,<0.13). This is not optional housekeeping: drf-mcp 0.12 caps
drf-services at<0.26while PAI 0.8 requires>=0.27, so moving only
[spec-tools]makes the two extras mutually uninstallable. They have to
advance together.
Documentation
- A real documentation site. The package shipped in 0.1.0 with 36 exported
symbols, a one-paragraphdocs/index.mdand a nav containing only "Home" — no
reference at all. It now has five narrative pages (Concepts, Tools, Storage,
Policy, Integrations) and a five-page autodoc reference covering the whole
public surface, plus a README that says what the package does rather than only
what it is. Highlights the things the source knows but nothing surfaced:
capabilities compose order-independently;categoryis advisory while
destructivedrives the gate; thex-*schema keys reach the client while
DESTRUCTIVE_METADATA_KEYis read server-side; stores key on
(owner_id, thread_id), so two transports sharing one store share a user's
thread list unless wrapped inScopedConversationStore. - Corrected a claim that this package declares a
StepStoreprotocol. It
does not —DefaultStepStorestructurally satisfiespydantic-ai-harness's
protocol, which is upstream's. The repo conventions asserted otherwise in two
places.
Fixed
- The spec-conventions test asserted behaviour that changed in PAI 0.6.0.
SpecCapability.get_instructions()has returnedNonesince the conventions
moved ontoSpecToolset.get_instructions()(so they reach the model whether a
toolset is attached directly or wrapped, and are collected exactly once). The
stale[spec-tools]ceiling pinned this package below 0.6, so the assertion
kept passing against an API two minors old; raising the pin surfaced it. The
test now reads the instructions off the toolset, and
build_spec_capability's module docstring no longer claims the capability
emits them.
v0.1.0
Added
- Initial extraction of the settings-agnostic agent-host substrate from
django-ag-ui:AgentConfig+build_agent, theToolRegistry/@tool
registry and typed schema derivation, toolset & capability composition
(including the optional[drf-mcp]bridge and[spec-tools]capability), the
AuditLoggerprotocol and audit capability, theToolGuardpolicy, the
get_user/ authorization helpers, theConversationStore/AttachmentStore
/StepStorecontracts, and the referencecontrib.storemodels and stores. - The core reads no Django settings. Anything that previously resolved from
DJANGO_AG_UIis now an explicit argument — notably the model stores take
allow_anonymous: bool = Falserather than consulting a settings key. Conversation.messagesis transport-owned. The core persists and returns
JSON-serialisable message records verbatim and never interprets them, so it
carries no dependency on any wire format; the calling transport validates its
own shape (and its message ids survive a round trip untouched).