Skip to main content

django-agent-actions

Expose safe, auditable backend actions to LLM agents — as a Django app.

django-agent-actions is a reusable Django app that lets you define normal Python functions with @action and expose them to AI agents through a built-in REST API. Every call flows through a consistent pipeline: input validation → scope check → policy → idempotency → execution → audit log.


Features

Feature Status
@action decorator with typed Pydantic schemas ✅
Policy engine (allow / deny / require_approval) ✅
Required scopes per action ✅
Approval workflow (pending → approved / rejected) ✅
Approver identity captured on every decision ✅
Audit logging with sensitive field redaction ✅
Idempotency (per tenant, DB-backed duplicate suppression) ✅
Thread-safe runtime for concurrent requests ✅
Atomic approval and idempotency state transitions ✅
Django ORM models + migrations included ✅
REST API (Django views) ✅
Request context (actor_id, roles, tenant_id) ✅
Pluggable AuthBackend interface ✅

Installation

pip install django-agent-actions

Quickstart

1. Add to INSTALLED_APPS and include URLs

# settings.py
INSTALLED_APPS = [
    ...
    "django_agent_actions",
]
# urls.py
from django.urls import include, path

urlpatterns = [
    path("agent-actions/", include("django_agent_actions.urls")),
]

2. Run migrations

python manage.py migrate

3. Define and register actions

# myapp/actions.py
from django_agent_actions import action, registry
from django_agent_actions.context import RequestContext

@action(
    name="get_invoice",
    description="Fetch an invoice by ID",
    risk="low",
    required_scopes=["finance"],
)
def get_invoice(invoice_id: str, ctx: RequestContext):
    return {"invoice_id": invoice_id, "status": "open", "amount": 149.99}

@action(
    name="issue_refund",
    description="Issue a full refund — requires human approval",
    risk="high",
    approval_required=True,
    required_scopes=["finance"],
)
def issue_refund(invoice_id: str, reason: str, ctx: RequestContext):
    return {"invoice_id": invoice_id, "refunded": True, "reason": reason}

registry.register(get_invoice)
registry.register(issue_refund)

4. Load actions at startup

# myapp/apps.py
from django.apps import AppConfig

class MyAppConfig(AppConfig):
    name = "myapp"

    def ready(self):
        import myapp.actions  # noqa: F401  — registers actions once

Configuration

All keys are optional. Set AGENT_ACTIONS in settings.py:

from django_agent_actions.policies import RiskBasedPolicy

AGENT_ACTIONS = {
    # App-level policy applied to actions with no per-action override.
    # Default: allow everything.
    "DEFAULT_POLICY": RiskBasedPolicy(),

    # Auth backend for inline credential validation.
    # Default: trust X-Actor-Id / X-Roles / X-Tenant-Id headers from middleware.
    "AUTH_BACKEND": None,
}

HTTP API

All endpoints are mounted under the prefix you chose in urls.py (e.g. /agent-actions/).

Method Path Description
GET health/ Liveness check
GET actions/ List registered actions and their input schemas
POST actions/<name>/execute/ Invoke an action
GET approvals/ List approvals (?status=pending)
POST approvals/<id>/approve/ Approve and execute a pending action
POST approvals/<id>/reject/ Reject a pending action
GET audit-logs/ Paginated audit log

Execute an action

curl -s -X POST http://localhost:8000/agent-actions/actions/get_invoice/execute/ \
  -H "Content-Type: application/json" \
  -H "X-Actor-Id: alice" \
  -H "X-Roles: finance" \
  -H "X-Tenant-Id: acme" \
  -d '{"inputs": {"invoice_id": "INV-001"}}' | jq

Idempotent execution

curl -s -X POST http://localhost:8000/agent-actions/actions/issue_refund/execute/ \
  -H "X-Actor-Id: alice" \
  -H "X-Roles: finance" \
  -d '{"inputs": {"invoice_id": "INV-001", "reason": "duplicate"}, "idempotency_key": "refund-INV-001"}' | jq

Idempotency keys are scoped per (action, key, tenant_id). Under concurrent requests only one caller executes the action; the other receives the stored result.

Approve with approver identity

# Approver identity is read from X-Actor-Id on this request
curl -s -X POST http://localhost:8000/agent-actions/approvals/<id>/approve/ \
  -H "X-Actor-Id: manager-bob" | jq

The approver's identity is stored on the Approval row and in the audit log.


Authentication and context

Default: trust X-* headers (gateway / middleware pattern)

Header Field Default
X-Actor-Id ctx.actor_id "anonymous"
X-Roles ctx.roles []
X-Tenant-Id ctx.tenant_id None

ctx.authenticated is True when actor_id != "anonymous".

Inline token validation with AuthBackend

from django_agent_actions import AuthBackend

class ApiKeyBackend:
    def authenticate(self, credential: str) -> dict:
        # credential = raw "Authorization" header value
        if not credential.startswith("ApiKey "):
            raise PermissionError("Expected ApiKey scheme.")
        key = credential[len("ApiKey "):]
        identity = API_KEY_STORE.get(key)
        if identity is None:
            raise PermissionError("Unknown API key.")
        actor_id, roles, tenant_id = identity
        return {"actor_id": actor_id, "roles": roles, "tenant_id": tenant_id}
# settings.py
AGENT_ACTIONS = {
    "AUTH_BACKEND": ApiKeyBackend(),
}

Invalid credentials return HTTP 401. No action code is reached.


Policies

from django_agent_actions import RoleBasedPolicy, RiskBasedPolicy, action

# Per-action override
@action(
    name="delete_invoice",
    description="Delete an invoice",
    risk="high",
    approval_required=True,
    required_scopes=["admin"],
    policy=RoleBasedPolicy(allowed_roles=["admin"]),
)
def delete_invoice(invoice_id: str):
    ...

Built-in policy rules:

Class Behaviour
DefaultPolicy Always ALLOW
DenyPolicy Always DENY
RequireApprovalPolicy Always REQUIRE_APPROVAL
RoleBasedPolicy(allowed_roles) ALLOW if actor holds a matching role, else DENY
RiskBasedPolicy(risk_map) Map action risk level to a decision

Implement the PolicyRule protocol to plug in OPA, Casbin, or any custom engine.


Sensitive data redaction

All action inputs are redacted before being written to the audit log or stored in approval records. The following field names are redacted regardless of nesting depth:

password, passwd, secret, token, api_key, access_token, refresh_token, authorization, client_secret, private_key, credential, ssn, credit_card, cvv, and more.

Extend SENSITIVE_KEYS for application-specific fields:

import django_agent_actions.redaction as r
r.SENSITIVE_KEYS = r.SENSITIVE_KEYS | {"my_secret_field"}

Raw Authorization header values are never stored on RequestContext.


Project structure

django_agent_actions/
├── __init__.py        Public API
├── apps.py            Django AppConfig
├── models.py          Django ORM models (Approval, AuditLog, IdempotencyRecord)
├── migrations/        Database migrations
├── decorators.py      @action decorator
├── registry.py        ActionRegistry + ActionDef
├── context.py         RequestContext, AuthBackend, ContextResolver
├── redaction.py       Sensitive-field redaction
├── policies.py        PolicyEngine + built-in rules
├── runtime.py         ActionRuntime + module-level registry/get_runtime()
├── audit.py           AuditLogger
├── approvals.py       ApprovalService
├── idempotency.py     IdempotencyService (tenant-scoped)
├── views.py           Django views
└── urls.py            URL patterns
examples/
└── billing/           Full billing example (get, list, pay, refund)
tests/
├── settings.py        Test Django settings
├── conftest.py
├── test_registry.py
├── test_policy.py
├── test_approval.py
├── test_idempotency.py
└── test_concurrency.py

Security model

Concern How it's handled
Auth AuthBackend protocol; default trusts X-* headers from middleware
Anonymous access Allowed by default; restrict with RoleBasedPolicy or required_scopes
Scope enforcement required_scopes on ActionDef; checked before policy, audited on deny
Authorization PolicyRule per action or app-level default
Approval safety pending → approved/rejected; approver identity stored; no replay
Audit completeness Every invoke path (allow/deny/approval/error) writes an audit record
Secret leakage redact_dict applied to all inputs before storage
Tenant isolation tenant_id in all audit/approval/idempotency records
Concurrent duplicate execution DB-backed idempotency — same-key races execute side effects once
Approval race safety Atomic DB updates — only one terminal outcome wins
Thread safety Per-request context, per-operation DB queries, no shared request globals

Publishing

Releases are published to PyPI automatically via GitHub Actions using PyPI Trusted Publishing (OIDC). No API tokens or secrets are required.

See PUBLISHING.md for setup instructions and the release process.


Running tests

pip install -e ".[dev]"
pytest tests/ -v

The test suite covers concurrency (idempotency races, approval races, context isolation under parallel requests) and requires pytest-django.

Metadata

Release files for django-agent-actions 0.2.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for django-agent-actions 0.2.0
File Size Uploaded Provenance
django_agent_actions-0.2.0.tar.gz 27.7 kB With provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for django-agent-actions 0.2.0
File Interpreter ABI Platform
django_agent_actions-0.2.0-py3-none-any.whl Python 3 none any Details

Total release size: 53.2 kB

Release files / django_agent_actions-0.2.0.tar.gz

Download URL django_agent_actions-0.2.0.tar.gz
Size 27.7 kB
Tags Source
SHA-256 checksum
How to use checksums
43d6cd03f9c7d25c708a4433adf4f41fac82b7712dc573e625246e1f0cd4eae3
BLAKE2b-256 checksum
How to use checksums
cf6ef86bc24f7d4805fa705af4b1f55452288a0acb34ab01383c00b28d393016
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Mar 16, 2026.

Transparency log

Release files / django_agent_actions-0.2.0-py3-none-any.whl

Download URL django_agent_actions-0.2.0-py3-none-any.whl
Size 25.5 kB
Tags Python 3
SHA-256 checksum
How to use checksums
1eb0bd38406b1d1af40583e66be2f2dd83dc9293c3b68e4083d30b58ad2f008f
BLAKE2b-256 checksum
How to use checksums
796f4a9626573d65a46f7f5d92fe961f7795fb4cb9d6b8d48a00fdaf7e2fe083
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/6.1.0 CPython/3.13.7

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Mar 16, 2026.

Transparency log

Security for django-agent-actions 0.2.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for django_agent_actions-0.2.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Mar 16, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.2.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page