Skip to content

Configuration

The sidebar reads two settings dicts: its own DJANGO_ADMIN_AGENT (admin sidebar presentation + endpoint wiring) and the inherited DJANGO_AG_UI from django-ag-ui (the agent model, the conversation store, the optional MCP bridge, …). Both are read fresh on every access, so test overrides and runtime changes take effect immediately.

DJANGO_ADMIN_AGENT

Read by django_admin_agent.conf.get_settings() into a frozen AdminAgentSettings snapshot. All keys are optional.

Key Default Purpose
TITLE "Admin Copilot" Header text shown on the sidebar chat panel.
AUTO_CONFIRM False When True, destructive frontend tools run without the confirmation modal. Passed to the Web Component as autoConfirm.
TOOL_DISPLAY "compact" How much detail tool-call cards show: "minimal", "compact", or "full". Rendered as the data-tool-display attribute.
THEME unset Web Component theme: "light", "dark", "auto", or "code". Rendered as the theme attribute; left off (component default, light) when unset.
DENSITY unset Layout density: "comfortable" or "compact". Rendered as the density attribute; left off when unset.
PLACEMENT unset Where the panel sits: "bottom-left", "side", "sidebar", "full", or "embedded". Rendered as the placement attribute; left off for the default floating bottom-right. "sidebar" is a full-height docked panel that collapses to an icon rail (pair it with SIDE).
TEXT_ANIMATION unset Incoming-text animation: "none", "fade", or "word". Rendered as the data-text-animation attribute; left off (default none) when unset.
SIDE unset For PLACEMENT="sidebar": which edge it docks to — "left" or "right". Rendered as the data-side attribute; left off (component default, right) when unset.
THEME_TOGGLE False Show the Web Component's built-in light⇄dark header toggle (it flips theme and persists per tab). Rendered as the data-theme-toggle attribute. Off by default, since the admin's own theme usually governs.
ICON_URL unset URL of a header/launcher icon image. Rendered as the data-icon-url attribute; left off (icon-less) when unset.
STRINGS unset Localized UI-string overrides for the Web Component (a partial dict merged over its English defaults). Rendered as the data-strings attribute (serialized JSON). Wrap values in gettext_lazy so the sidebar follows the admin's active language; left off when unset.
SKILLS unset Override for the slash-command / chip catalog (a list of Skill dicts). Leave unset to use the built-in admin catalog. See Skills.
SHELL_FIELD_REDACTION True Redact sensitive fields in shell.query_model / shell.get_model_instance output. True uses the built-in denylist (password\|token\|secret\|key\|hash); False disables it; a regex str overrides the pattern. See Access control.
settings.py
DJANGO_ADMIN_AGENT = {
    "TITLE": "Acme Copilot",
    "AUTO_CONFIRM": False,
    "TOOL_DISPLAY": "compact",
    "THEME": "auto",
    "DENSITY": "compact",
}

AUTO_CONFIRM is the autopilot toggle

With AUTO_CONFIRM = True, the agent can fill and submit forms, run bulk actions, and apply filters without pausing for a confirmation click. Leave it False (the default) unless you trust the agent to act unattended.

Presentation

TOOL_DISPLAY, THEME, DENSITY, PLACEMENT, and TEXT_ANIMATION all flow straight through build_sidebar_context() onto the <ag-ui-chat> element as attributes; the Web Component reads them. Their accepted values mirror the component's own attribute values:

Setting Attribute Values Default
TOOL_DISPLAY data-tool-display minimal · compact · full compact
THEME theme light · dark · auto · code component default (light)
DENSITY density comfortable · compact component default
PLACEMENT placement bottom-left · side · sidebar · full · embedded floating bottom-right
TEXT_ANIMATION data-text-animation none · fade · word none
THEME_TOGGLE data-theme-toggle True · False False

THEME_TOGGLE adds the Web Component's built-in light⇄dark header toggle (it flips theme and persists per tab) — off by default, since the admin's own theme usually governs. TOOL_DISPLAY always renders (it defaults to "compact"); the other four are rendered only when set, so leaving them unset keeps the component's own defaults. data-slash-commands="true" is always emitted, enabling the skill palette.

Skills

The sidebar ships a small catalog of pre-defined prompts surfaced as chips above the composer and in the /-command palette. They are embedded into the page as a json_script block (#django-admin-agent-skills) and read by the bootstrap module, which calls el.setSkills(...). This needs no server-side Skills endpoint — the catalog is purely client-side.

Each skill is a plain dict in the client Skill shape (camelCase-free here — the keys are name, title, description, prompt):

{
    "name": "summarize-page",
    "title": "Summarize this page",
    "description": "Recap what's currently shown.",
    "prompt": "Summarize what's shown on the current admin page.",
}

The built-in catalog (django_admin_agent.admin.build_skills.build_skills()) ships four placeholder-free skills: Summarize this page, Summarize this changelist, Draft a description, and Explain this model.

A skill prompt may contain {placeholder} tokens. The bootstrap module supplies a skillContext provider derived from the current admin page — {path} (the current pathname) and {selected_ids} (the comma-joined pks of ticked changelist rows) — which the Web Component substitutes before sending. The built-in skills are placeholder-free so they work on any page.

Override the whole catalog with the SKILLS setting:

settings.py
DJANGO_ADMIN_AGENT = {
    "SKILLS": [
        {
            "name": "triage-orders",
            "title": "Triage open orders",
            "description": "Group the visible orders by status.",
            "prompt": "On {path}, group the visible orders by status and flag any stuck ones.",
        },
    ],
}

Setting SKILLS to an empty list ([]) ships no skills; leaving it unset uses the built-in catalog.

Access control

AdminAgentServer is fail-closed by default: every mounted route (the agent endpoint, the tool catalog, and — when configured — the thread index, uploads, and transcription) requires an authenticated, active staff user, and the agent endpoint is CSRF-protected.

  • require_authenticated=True (default) → an anonymous request gets 401.
  • authorize=staff_required (default) → a non-staff user gets 403. Both are JSON, not an HTML login redirect (which would corrupt an SSE stream or a JSON fetch), so admin_view() is deliberately not used.
  • csrf_exempt=False (default) → the agent POST is CSRF-checked; the sidebar bootstrap already sends the token.
from django_admin_agent import AdminAgentServer

urlpatterns = [
    path("admin/", admin.site.urls),
    # Locked to staff by default; tighten or relax deliberately:
    path("admin-agent/", AdminAgentServer(authorize=lambda r: r.user.is_superuser).urls),  # superusers only
    # path("admin-agent/", AdminAgentServer(require_authenticated=False, authorize=None).urls),  # fully open (not advised)
]

The default authorize gate is staff_required, importable from django_admin_agent if you want to compose it.

Without this an unauthenticated visitor could drive the agent and, via the shell.query_model tool, stream model rows — including auth.User password hashes — back over SSE.

Sensitive-field redaction

Independently of who is calling, the shell.query_model and shell.get_model_instance tools redact any field whose name matches SHELL_FIELD_REDACTION (default password|token|secret|key|hash, case-insensitive) before the row reaches the model — even a legitimate staff query shouldn't ship a password hash to a third-party LLM. Set it to False to disable, or to a regex str to use your own denylist.

Inherited DJANGO_AG_UI

The agent model, persistence, and toolset composition are configured on django-ag-ui's settings dict. The sidebar inherits all of it. The keys most relevant to the admin sidebar:

Key Default Purpose
MODEL unset Pydantic-AI model string, e.g. "anthropic:claude-sonnet-4.6". Can instead be passed per-mount as AdminAgentServer(model=...).
AUTO_CONFIRM False The django-ag-ui-level destructive-confirmation flag surfaced to the frontend.
ATTACHMENT_MAX_BYTES / ATTACHMENT_ALLOWED_TYPES 10 MiB / any Server-side upload size cap and content-type allowlist (enforced by AttachmentsView).
MODEL_SETTINGS, RETRIES Advanced Pydantic-AI configuration; see the django-ag-ui docs.
settings.py
DJANGO_AG_UI = {
    "MODEL": "anthropic:claude-sonnet-4.6",
}

Collaborators are constructor arguments, not settings

Stores, backends, loggers, toolsets and the MCP server are passed to AdminAgentServer(...) in urls.py — a settings dict can't hold a live object, and urls.py can. django-ag-ui 0.19 removed the dotted-path keys that used to name them, and raises ImproperlyConfigured if one is still present rather than silently ignoring it: CONVERSATION_STORE, ATTACHMENT_STORE, TRANSCRIPTION_BACKEND, AUDIT_LOGGER, DRF_MCP_SERVER, SERVICE_SPECS, AGENT_FACTORY, TOOLSETS, CAPABILITIES, PROVIDER. If an older guide had you set any of these, move them to the constructor:

urls.py
AdminAgentServer(conversation_store=DjangoSessionConversationStore())

Conversation persistence and the reload model

Because the Django admin reloads the whole page on every save, filter, and navigation, the agent's run loop must survive reloads. The Web Component's client-side store (per-tab sessionStorage) keeps the conversation continuous out of the box. Passing a conversation_store adds server-side persistence on top — keyed by AG-UI thread_id, owner-scoped per user — so a conversation durably survives across tabs and devices, and the resume checkpoint becomes derivable from the stored history.

For admin deployments — where AdminAgentServer requires an authenticated staff user, so sessions always exist — DjangoSessionConversationStore is the natural choice (no migration, per-user durability). Passing no store keeps the server stateless; the client store still provides single-tab continuity.

urls.py
from django_pydantic_agent import DjangoSessionConversationStore

AdminAgentServer(conversation_store=DjangoSessionConversationStore())

For durable, cross-device history, opt into the reference store instead: add "django_pydantic_agent.contrib.store" to INSTALLED_APPS, run migrate, and pass DefaultConversationStore.

The chat-history drawer

When a conversation store is configured, AdminAgentServer mounts an owner-scoped thread index at <prefix>threads/ (list) and <prefix>threads/<id>/ (load / rename / delete), and the sidebar passes its URL to the Web Component as data-threads-url — the data behind a chat-history drawer of the admin user's past conversations. The index uses whatever the store passed to AdminAgentServer(conversation_store=...). Without a store the sub-view isn't mounted and the drawer falls back to the client's per-tab sessionStorage threads; configure a durable store to list server-backed threads.

Continuing a run

When a step store is configured, AdminAgentServer also mounts an owner-scoped run index at <prefix>runs/ beside resume/<run_id>/ and fork/<run_id>/, and the sidebar passes its URL to the Web Component as data-runs-url — so the sidebar header gains a ⭯ Continue a run panel.

A run that stopped part-way (a crash, a closed tab) can then be picked up from its last server-side checkpoint rather than restarted: type the next turn, then Resume to carry on or Fork to branch without touching the original. Only runs the server marks continuable are offered — one that never reached a provider-valid boundary has no snapshot to seed from.

urls.py
from django_pydantic_agent.contrib.store.default_step_store import DefaultStepStore

AdminAgentServer(step_store=DefaultStepStore)

DefaultStepStore's constructor is the request -> StepStore factory the capability needs, so pass the class itself. Requires django-ag-ui's [harness] extra. Without a step store the sub-views aren't mounted and no panel appears.

File uploads

When an attachment store is configured, AdminAgentServer mounts an owner-scoped upload endpoint at <prefix>attachments/ (POST upload) and <prefix>attachments/<id>/ (GET download, DELETE), and the sidebar passes its URL to the Web Component as data-attachments-url — so the composer gains a 📎 picker + drag-and-drop. Files upload out-of-band and travel as lightweight refs; the agent reads their contents server-side via the built-in read_attachment tool (the AG-UI message stream stays free of file bytes).

Uploads are off by default — with no attachment store the sub-view isn't mounted (no 📎 affordance). Turn them on by passing one. For durable, per-admin-user files, opt into the reference store: add "django_pydantic_agent.contrib.store" to INSTALLED_APPS, run migrate, then

urls.py
from django_pydantic_agent.contrib.store.default_attachment_store import (
    DefaultAttachmentStore,
)

AdminAgentServer(attachment_store=DefaultAttachmentStore())
settings.py
DJANGO_AG_UI = {
    # Optional server-side guards. When uploads are mounted the admin sidebar
    # forwards these to the composer (data-attachment-max-bytes /
    # data-attachment-accept) so oversized or wrong-type files are rejected
    # before upload — the server stays authoritative:
    "ATTACHMENT_MAX_BYTES": 10 * 1024 * 1024,
    "ATTACHMENT_ALLOWED_TYPES": ["image/png", "image/jpeg", "application/pdf", "text/plain"],
}

The reference store keeps bytes in Django Storage (filesystem by default; point STORAGES/DEFAULT_FILE_STORAGE at S3/GCS for production) and metadata in a row, scoped to the admin user — one admin never sees another's files. Pass an explicit store with AdminAgentServer(attachment_store=...) to override. Validation is server-authoritative: oversize → 413, disallowed type → 415, and downloads stream through the owner-checked GET (never a guessable public URL).

Voice input

When a transcription backend is configured, AdminAgentServer mounts a transcription endpoint at <prefix>transcribe/ (POST an audio clip → {"text": ...}), and the sidebar passes its URL as data-transcribe-url — so the composer gains a 🎤 mic button (record, then drop the transcript into the input). Voice is off by default — with no TRANSCRIPTION_BACKEND the sub-view isn't mounted (no mic affordance); enable it by pointing DJANGO_AG_UI["TRANSCRIPTION_BACKEND"] at a backend — django-ag-ui ships an opt-in OpenAITranscriptionBackend over any OpenAI-compatible endpoint (the [openai] extra):

settings.py
DJANGO_AG_UI = {
    "TRANSCRIPTION_BACKEND": (
        "django_ag_ui.contrib.transcription.openai_transcription_backend"
        ".OpenAITranscriptionBackend"
    ),
}

Pass an explicit backend with AdminAgentServer(transcription_backend=...) to override. Model reasoning needs no admin wiring: when DJANGO_AG_UI["MODEL_SETTINGS"] enables a thinking budget, the sidebar renders the streamed chain-of-thought in a collapsible "thoughts" region automatically.

DRF_MCP_SERVER and the [mcp] extra

Installing django-admin-agent[mcp] and pointing DRF_MCP_SERVER at a djangorestframework-mcp-server MCPServer instance exposes that server's tools to the agent in-process — no network MCP hop — alongside the built-in shell.* / introspect.* tools. The bridge is imported lazily by django-ag-ui; the dependency is never loaded unless the setting is configured.