Skip to content

Releases: Artui/django-ag-ui

v0.26.3

Choose a tag to compare

@github-actions github-actions released this 30 Jul 08:01
805daa3

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 transport:

    • 0.21.0DjangoOAuthToolkitBackend rejected every bearer token once a
      resource URL was configured. Audience enforcement read a resource field
      that DOT's AccessToken does not have, so it could never succeed;
      enforcement is now the separate ENFORCE_AUDIENCE, default off.
    • 0.20.0 — dynamically registered clients could not be issued an ID token
      (Application.algorithm was never set), so the token endpoint 500'd whenever
      the advertised openid scope was requested.

    Both are confined to drf-mcp's OAuth surface. The bridge consumes MCPServer
    in-process and never constructs an auth backend, so no adaptation was needed —
    verified with the lock updated to 0.21.0 and the suite green.

    0.20.0 also added UndescribedToolWarning, which the bridge fixtures trip by
    registering throwaway tools; filtered in pyproject.toml.

v0.26.2

Choose a tag to compare

@github-actions github-actions released this 29 Jul 13:03
4b4584a

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_method was
    not modelled, so every registration silently became a confidential client, and
    the client_secret handed back was the stored PBKDF2 digest rather than the
    secret. Both are confined to drf-mcp's contrib.oauth — the bridge this
    extra backs consumes MCPServer, which did not change, so the widening is
    purely a ceiling lift and this transport's own behaviour is unaffected.

    Verified rather than assumed: the suite runs green against 0.19.0 with the
    lock updated.

v0.26.1

Choose a tag to compare

@github-actions github-actions released this 29 Jul 10:14
ff06ae3

Changed

  • Widened the two agent-tool integration pins, matching
    django-pydantic-agent 0.4.2, 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's FileField /
      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
      (TypeError for any provider not leading with view, request) and the
      missing DRF baseline context (KeyError: 'request'). Its one deliberate
      break — a provider whose first two parameters are named something other than
      view / request now raises — applies 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.26.0

Choose a tag to compare

@github-actions github-actions released this 28 Jul 12:12
1e51d87

Added

  • CompactionObserver — tell the client when history was compacted.
    Compaction is deliberately invisible upstream: it runs inside
    before_model_request, mutates the message list, and emits nothing. Right
    for the model, wrong for a person watching a long run, who sees earlier turns
    quietly stop informing the answers with no explanation. Wrapping the capability
    is the seam for saying so:

    capabilities=[CompactionObserver(SlidingWindow(max_messages=80, keep_messages=40))]

    Opt-in by construction — pass the strategy unwrapped and nothing is emitted.

    • The compaction itself is untouched. CompactionObserver subclasses
      pydantic-ai's WrapperCapability and overrides one hook, so ordering,
      deferral and hook introspection all delegate to the wrapped capability.
      A hand-rolled proxy would have silently dropped those.
    • The wire stays vanilla AG-UI. Each firing emits a standard
      ACTIVITY_SNAPSHOT with activityType: "compaction" (exported as
      COMPACTION_ACTIVITY_TYPE) and content carrying removed / before /
      after — not a CUSTOM event, so any AG-UI client can render it and ours
      is not privileged. Handle it with @ag-ui/client's
      onActivitySnapshotEvent.
    • Placed where a reader wants it: interleaved immediately before the
      events of the turn that ran with the shortened history, with a fresh
      messageId per firing — a compaction is a distinct occurrence, not a
      mutation of a previous one.
    • What it does not detect, stated plainly: detection is a message-count
      comparison across the wrapped call, because that is all the upstream
      contract exposes. A strategy that rewrites history without shortening it
      does not register — matching what the indicator claims (turns were dropped)
      rather than over-promising a general "history changed" signal.
    • Per-run, not per-instance. The observer records into a ContextVar. A
      consumer builds the capability once at configuration time and the same
      instance serves every request, so instance state would interleave concurrent
      runs into each other's transcripts.

Documentation

  • New page: Compaction & skills — adopting the harness
    compaction and Skills capabilities through the CAPABILITIES seam (pure
    composition, no code here), then the indicator above. Strategy-by-strategy
    guidance lives in django-pydantic-agent's integrations guide, where the seam
    itself is documented.

v0.25.0

Choose a tag to compare

@github-actions github-actions released this 28 Jul 11:23
d5a0d99

Changed

  • [harness] now requires pydantic-ai-harness>=0.12,<0.13 (was >=0.7,<0.8)
    and django-pydantic-agent>=0.4,<0.5 (was >=0.3,<0.4). The harness ceiling
    had gone five minors stale, which also gated
    pydantic_ai_harness.skills
    it does not exist below 0.11.
  • The resume / fork path needed the substrate fix that came with it. Harness's
    StepStore protocol gained latest_snapshot(*, run_id, include_interrupted=False)
    and its continue_run() passes the new argument, so resume/<run_id>/ raised
    TypeError: latest_snapshot() got an unexpected keyword argument under 0.12
    until DefaultStepStore was adapted (django-pydantic-agent 0.4.0, which adds a
    state column and migration 0002 — run migrate when upgrading).
    This package's resume tests are what surfaced the break; the substrate's own
    suite could not see it, because it calls latest_snapshot() directly rather
    than through the harness helper that drives the protocol.
  • GET runs/'s continuable flag still means what it says. It is computed
    from latest_snapshot(run_id=…) precisely because that is the call resume
    makes, and both now default to include_interrupted=False — so a run reported
    continuable is still exactly a run resume would find a snapshot for. Had the
    new argument defaulted the other way upstream, the flag would have started
    lying about interrupted-only runs rather than failing loudly.
  • StepPersistence's constructor is unchanged at 0.12, so the capability wiring
    needed no adaptation.
  • 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.ui links on
      tool definitions. The bridge reads name / description / inputSchema /
      outputSchema / annotations off tools/list and 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 from djangorestframework-services rather than defined locally,
      behind permanently preserved import paths. Neither is imported here. PAI's
      switch from ValueError to ImproperlyConfigured for a bad channel
      declaration is likewise unreachable: SpecCapability is constructed with a
      spec mapping and no channel registrations.
    • InputRequired enforcement (drf-services 0.28) makes a missing
      marked-required input raise ServiceValidationError at dispatch. Over the
      MCP bridge that already arrives as an isError result with
      type == "validation_error", which call_tool maps to ModelRetry — so a
      spec adopting the marker gets a model-correctable failure through this path
      with no change here.

v0.24.0

Choose a tag to compare

@github-actions github-actions released this 27 Jul 14:18
081ad05

Added

  • Every run is given typed dependencies. The endpoint now builds an
    AgentDeps(user=request.user) per run and passes it to the agent, so tools,
    toolsets and capabilities read request-scoped values off RunContext.deps
    pydantic-ai's own seam — instead of the agent closing over the request:

    @tool(registry)
    def whoami(ctx: RunContext[AgentDeps]) -> str:
        """Report the acting user."""
        return str(ctx.deps.user)
    • Spec tools bind the user natively. SpecToolset's default extractor
      reads ctx.deps.user; this package used to override it with a closure,
      purely because deps were None.
    • AG-UI state now lands somewhere. AgentDeps satisfies pydantic-ai's
      StateHandler protocol, so a run's RunAgentInput.state is validated into
      deps.state rather than dropped with a UserWarning. Emitting state back
      is a tool's job — see the new Shared state
      guide.
    • It is the precondition for reusing a built agent. A capability that
      closes over a request can only serve that request, which is why the agent
      (and every tool's JSON schema) is rebuilt on each call today. Nothing about
      that caching changes here; this removes the blocker.
    • A request served without Django's auth middleware has no user attribute
      at all — that is an anonymous run (deps.user is None), matching what
      materialize_request_user already does, not an AttributeError.
  • deps_factory — supply the run's AgentDeps yourself. A
    request -> AgentDeps callable on AGUIServer / DjangoAGUIView replacing
    the default, which binds only the acting user.

    • This closes a gap the previous release opened. Typed deps shipped, but
      the endpoint constructed AgentDeps(user=request.user) itself with no hook,
      so a project could not carry its own per-run context — and, specifically,
      could not have AG-UI's inbound shared state validated, since pydantic-ai
      validates it against type(deps.state) and that requires the deps to arrive
      pre-seeded with a model instance. Now:
      deps_factory=lambda request: AgentDeps(user=request.user, state=DocumentState()).
    • Subclass AgentDeps to carry anything else per run (a tenant, a
      feature-flag snapshot); whatever the factory returns reaches every tool,
      toolset and capability as ctx.deps.

Changed

  • django-pydantic-agent floor raised to >=0.3,<0.4AgentDeps and the
    request-free build_spec_capability come from there.
  • AgentSession(...) now requires a keyword-only deps. Deliberately
    required rather than defaulted: a forgotten deps would mean spec tools
    silently acting as nobody. Only affects code constructing AgentSession
    directly; going through DjangoAGUIView / AGUIServer needs no change.

Documentation

  • New Shared state guide.
    AG-UI's state channel works end to end and needed no package code — a tool
    reads ctx.deps.state and writes back by returning a StateSnapshotEvent as
    ToolReturn metadata, which pydantic-ai's adapter streams verbatim. The guide
    is the recipe joining the two ends, plus when to prefer a tool instead: a
    tool call is visible in the transcript and can be gated by a confirmation
    card, which state events cannot.

    • Writing it surfaced the missing deps_factory seam (above) — without which
      inbound state could not be validated into a model at all. The guide shows
      the validated shape rather than a workaround.
  • CI now checks doc snippets against the installed packages
    make docs-check / scripts/check_docs_snippets.py, wired into the docs job.
    Every Python fence in docs/ and README.md must parse, every
    from X import Y must resolve, and every keyword argument at a resolvable
    call must exist in the real signature.

    • It closes the one gap the other gates share: ruff, ty and
      mkdocs --strict all stop at this package's boundary, so nothing checked a
      claim about a dependency's API — where drift is likeliest, since the
      dependency moves on its own schedule and no test imports the snippet.
    • A module the reader is meant to own is told apart from one that moved by
      whether its root package is installed, not by a name list and not by
      "the import failed". myproject.agent is a placeholder; a dependency
      submodule that has been relocated is a failure — which is the shape of a
      real, boot-breaking defect found in these docs before.
    • Verified against known-bad snippets, not just a clean run: a moved
      dependency module, a bad keyword on one of our types, and a bad keyword on a
      dependency's type are each caught.
    • Limits, stated so nobody over-trusts it: it does not execute snippets or
      check semantics, and it cannot see non-Python fences — a JavaScript
      example remains a matter for review.

v0.23.0

Choose a tag to compare

@github-actions github-actions released this 27 Jul 10:35
8169163

Added

  • GET runs/ — the discovery half of resume/fork. Mounted alongside
    resume/<run_id>/ and fork/<run_id>/ whenever a step_store is configured,
    the same way threads/ mounts with a conversation store.

    Both existing endpoints address a run by id, so on their own a client can
    only continue a run whose id it still holds — which rules out resuming after a
    page reload or from another device, most of what durable step persistence is
    for. list_runs() was already implemented on the store; nothing exposed it.

    Each row carries run_id, thread_id, parent_run_id, started_at and
    continuable — whether the run has a snapshot to seed from, answered by
    making the same latest_snapshot call resume itself makes rather than
    inferring it from event counts. A run that never reached a provider-valid
    boundary has no snapshot, so resuming it would start from nothing: a client
    should offer the action only where continuable is true and treat the rest as
    informational. parent_run_id exposes fork lineage so a UI can show a branch
    rather than listing near-identical transcripts.

    Owner-scoped by the store, and nothing on the wire names an owner — another
    user's runs are simply absent, never a 403 that would confirm the id exists.
    Carries the same authentication seam as every other mounted view, and
    authorization runs before the store is built, so a denied request never
    reaches the database.

v0.22.0

Choose a tag to compare

@github-actions github-actions released this 27 Jul 10:16
843b47b

Added

  • AGUIServer(service_specs=…) accepts a SpecRegistry as well as a
    name -> spec mapping (djangorestframework-services 0.27+). A project
    exposing the same specs over this endpoint and an MCP server and HTTP views
    declares them once in the registry; each transport reads that one source
    instead of repeating the list. A filtered view (by_tag / subset) is itself
    a registry, so two endpoints can be given different projections with no shared
    state.
    • Normalised once, at construction, into a plain dict — which is the whole
      design. Three things downstream consume this value, and a registry reaching
      any of them unresolved fails differently: build_tool_catalog calls
      .items() (AttributeError), and the view's tool-name reservation
      iterates it — a registry yields RegisteredSpec records rather than name
      strings, which would fill the collision set with dataclasses and silently
      stop detecting duplicate tool names between the @tool registry, the drf-mcp
      bridge and the spec tools. Resolving at the entry point means nothing
      downstream changes.
    • The mapping is copied, so a caller mutating theirs afterwards no longer
      leaks into a configured server.

Changed

  • Dependency pins advance together: django-pydantic-agent >=0.2,<0.3
    (for resolve_spec_mapping / SpecSource), [spec-tools]
    djangorestframework-pydantic-ai>=0.8,<0.9, [drf-mcp]
    djangorestframework-mcp-server>=0.15,<0.16. The last two are not optional
    housekeeping: drf-mcp 0.12 capped drf-services at <0.26 while PAI 0.8
    requires >=0.27, so raising one without the other leaves the two extras
    mutually uninstallable.

Fixed

  • The persistence docs pointed at a module that no longer exists. Four pages
    told readers to add "django_ag_ui.contrib.store" to INSTALLED_APPS and to
    import DefaultConversationStore / DefaultAttachmentStore /
    DefaultStepStore from it. That app moved to
    django_pydantic_agent.contrib.store in 0.21.0's extraction and the docs were
    not updated, so following them raised ModuleNotFoundError at startup. All
    references repointed.

Documentation

  • The service_specs= reference was three ways stale. It described the
    argument as "a dotted path" — import_string was removed package-wide in
    0.19.0, and the value has been the mapping itself since. Its example built
    SelectorSpec(serializer=…, queryset=…), and neither is a field (nor was the
    required kind present), so the snippet raised TypeError as written. And it
    credited SpecCapability with emitting the spec conventions, which moved onto
    SpecToolset.get_instructions() in PAI 0.6.0 — the capability delegates so the
    block reaches the prompt exactly once. All three corrected, plus a section on
    declaring specs once across transports.
  • A snippet in the agent_factory= section put a bare ... after a keyword
    argument, which is a SyntaxError — so it could not be copied, and it made
    the whole fence invisible to doc-checking tooling. That is how the broken
    SelectorSpec example above survived in the same file.

v0.21.0

Choose a tag to compare

@github-actions github-actions released this 23 Jul 10:34
55c5342

Changed

  • The agent-host substrate now lives in
    django-pydantic-agent
    , a new
    settings-agnostic package this release depends on. Agent construction, the tool
    registry, toolset/capability composition, audit, the tool guard, user resolution,
    the storage contracts and the reference store models moved there; this package
    keeps the AG-UI transport — the view, the SSE stream, AGUIServer.urls, the
    browser-facing sub-views, skills and transcription. It is the lift-down that lets
    a second transport share one substrate.

    The public surface is unchanged: every moved symbol is permanently
    re-exported
    , so from django_ag_ui import ToolRegistry (and friends) keeps
    working and downstream projects need only a version bump.

Breaking

  • INSTALLED_APPS: the reference store app moved — replace
    "django_ag_ui.contrib.store" with "django_pydantic_agent.contrib.store".
  • The model stores no longer read DJANGO_AG_UI["ALLOW_ANONYMOUS"]. A
    settings-agnostic substrate cannot read a transport's settings key, so pass
    allow_anonymous=True to the store constructor instead.
  • Conversation.messages holds JSON-serialisable records, not ag_ui Message
    objects.
    The substrate persists transport-owned records verbatim (client
    message ids survive untouched) and the AG-UI wire shape is converted at this
    package's boundary. Code that read message.content off a loaded conversation
    now reads message["content"]; messages_to_jsonable / messages_from_jsonable
    live in django_ag_ui.persistence.utils.
  • The attachment toolset's internal id is now django-pydantic-agent-attachments.
    Tool names (read_attachment) and the wire are unaffected.

v0.20.0

Choose a tag to compare

@github-actions github-actions released this 22 Jul 10:27
0cd7141

Added

  • Durable step persistence — a model-backed, owner-scoped store for
    pydantic-ai-harness's StepPersistence capability. Pass
    AGUIServer(step_store=DefaultStepStore) (the constructor is the
    request -> StepStore factory — the harness protocol carries no request, so
    the store binds one and is built per run) and every run records an append-only
    event log, a (run_id, tool_call_id) tool-effect ledger, and a continuable
    snapshot at each provider-valid boundary, keyed on the AG-UI run_id. Four new
    models under django_ag_ui.contrib.store (StoredRun / StoredStepEvent /
    StoredSnapshot / StoredToolEffect, migration 0003) back
    DefaultStepStore, which structurally satisfies the harness StepStore
    protocol. Every row filters by the resolved owner, so a run_id from one user
    can't read another's runs; an anonymous request without ALLOW_ANONYMOUS
    degrades to no-op rather than aborting the run. Requires the [harness] extra.
    A custom backend is any request -> StepStore callable. See
    Durable step persistence.
  • Resume / fork endpoints — configuring a step_store also mounts owner-scoped
    resume/<run_id>/ and fork/<run_id>/ endpoints. Both seed a new run from a
    prior run's last continuable snapshot: the server loads it (a run_id from
    another owner is a clean 404), injects it as the run's message_history
    (AgentSession gained the seam; run_stream_native composes it ahead of the
    client's new turn), and records the new run with parent_run_id pointing back
    at the source — so the parent is never mutated. resume and fork are two
    names for one mechanism (the harness's continue_run / fork_run are
    data-identical). The web-component checkpoint UI rides a downstream release.