AI Agents — Custom Template Integration
Audience: Developers and administrators who add AI assistance to a project or business-app template. Use this document when: You want Ask assistant on a custom page, or you want a page-specific agent outside the built-in Enezaframework list and detail templates. Canonical for: Embedding Agent Assistance in custom Django templates and registering app-specific AI metadata. Related: AI Agents Index, Administrator Guide, End User Guide.
This guide explains how to add an AI agent to a custom Django template outside the built-in templates in the system. A custom template is a page owned by your project or business app, such as a custom workspace, a tree view, a special dashboard, or a report page.
There are three integration paths:
| Path | Use when | What you change |
|---|---|---|
| A. Model-backed page | The page shows one registered model or a list of one model. | The template, and optionally the view context. |
| B. Non-model page with a page-specific agent | The page is not about one model and needs its own agent. | assistant/pages.py, views/assistant.py, and the agent configuration. |
| C. App metadata and code-registered agents | The app or model must be visible to the AI registry, or an agent must be registered from code. | <app>/ai.py. |
Start with Path A whenever the page is about a model. It is the smallest and safest change.
Path A — model-backed custom template
A1. Template-only form
Load the tag library and place the tag once in the template:
{% load ai_assistant_extras %}
{% ai_assistant model="humanresource.department" view="list" %}
For a detail page:
{% load ai_assistant_extras %}
{% ai_assistant model="humanresource.employee" record=employee view="detail" %}
The model argument is an app_label.model_name string or a model class. The record argument is the record instance. If view is omitted:
recordpresent →detail.- No
record→list.
The tag renders the existing assistant launcher and, when the user is allowed, the Ask assistant panel.
A2. View-supplied context form
If the view already knows the model and record, build the assistant context in the view and let the tag fall back to it:
from django.views.generic import TemplateView
from enezaagents.assistant.context import build_assistant_context_for
from humanresource.models import Employee
class EmployeeWorkspaceView(TemplateView):
template_name = "humanresource/employee_workspace.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
employee = Employee.objects.get(pk=self.kwargs["pk"])
context["employee"] = employee
context["ai_assistant"] = build_assistant_context_for(
self.request,
Employee,
record=employee,
view_kind="detail",
)
return context
Then include the tag without arguments:
{% load ai_assistant_extras %}
{% ai_assistant %}
build_assistant_context_for returns None when the user is not signed in, lacks model view access, lacks the assistant permission, or the model is not registered for AI. The tag then renders nothing.
A3. Model registration requirement
The assistant needs a model schema. A model is visible to the assistant only when it is registered in <app>/ai.py:
from enezaagents.models.choices import FieldPolicyType
from enezaagents.registry.api import registry
from myapp.models import MyModel
registry.register_application(
code="myapp",
name="My Application",
app_label="myapp",
description="The application that owns MyModel.",
)
registry.register_model(
MyModel,
description="A short description of the model.",
business_purpose="What users ask about this model.",
field_policies={
"code": FieldPolicyType.SYSTEM_MANAGED,
"description": FieldPolicyType.UPDATE_ALLOWED,
"status": FieldPolicyType.UPDATE_ALLOWED,
},
)
After adding the file, run:
python manage.py ai_sync_registry --app myapp
ai_sync_registry persists the application, model, field policies, relationships, business actions, and code-registered agents. If the model is not registered, registry.get_schema() returns None and the launcher does not appear.
A4. Path A checklist
- [ ]
<app>/ai.pyexists. - [ ]
registry.register_applicationis called. - [ ]
registry.register_modelis called with field policies. - [ ]
python manage.py ai_sync_registry --app <app>has been run. - [ ] The template loads
ai_assistant_extras. - [ ] The template contains
{% ai_assistant ... %}once. - [ ] The user has
action.enezaagents.assistant. - [ ] The user has view access to the model.
- [ ] The user is in the correct entity.
- [ ] A record page passes the record; a list page passes the view and optional filters.
A5. Working example
The HR organization workspace is a real custom template outside the built-in CRUD templates:
{% extends 'components/layouts/default.html' %}
{% load static %}
{% load ai_assistant_extras %}
{% block content %}
{% ai_assistant model="humanresource.department" view="list" %}
{% endblock %}
File: humanresource/templates/humanresource/organization_tree.html
The matching registration is in humanresource/ai.py. No page-specific module code is needed because the page is model-backed.
Path B — non-model page with a page-specific agent
A page that is not about one model needs a page context. The contextual assistant currently recognizes only the page keys registered in PAGE_CONTEXT_KEYS in enezaagents/views/assistant.py. Adding a page is a server-side change.
B1. Create a page context builder
Add a builder to enezaagents/assistant/pages.py. Follow the shape used by the existing dashboard and report builders. Return None when the user is not allowed.
Required context fields:
| Field | Meaning |
|---|---|
channel |
Use "contextual". |
view |
Use "page". |
page |
Stable page key, for example "inventory_overview". |
page_key |
Stable key including the module or record scope, for example "inventory_overview|warehouse". |
page_label |
Human-readable page name. |
agent_code |
The server-selected agent code, for example "inventory_assistant". |
application |
Application label, for example "inventorymanagement". |
module |
Module name used for permission and usage reporting. |
page_data |
Labels and counts only. Never full records or secrets. |
instruction |
Optional page-specific instruction for the agent. |
greeting |
Optional first message shown to the user. |
quick_prompts |
Optional suggested questions. |
endpoints |
Reversed assistant endpoints. |
workspace_url |
Reversed AI Workspace URL. |
csrf_token |
CSRF token for the page. |
Use the existing _base_context helper as the reference for the endpoint keys and URL names.
B2. Register the page key
Add the new key to PAGE_CONTEXT_KEYS in enezaagents/views/assistant.py:
PAGE_CONTEXT_KEYS = {
"dashboard_index",
"dashboard_module_list",
"dashboard_detail",
"report_list",
"report_create",
"report_edit",
"report_designer",
"inventory_overview",
}
Call the new builder from build_page_assistant_context in enezaagents/assistant/pages.py:
if page == "inventory_overview":
return _inventory_overview_context(request, module)
The assistant view rejects an unknown page key and rebuilds the context server-side. It does not trust the browser's page data or agent code.
B3. Select the agent on the server
_agent_for_context maps a page's server-built agent_code to a built-in agent. Add the new agent code there, or let the page use the contextual assistant:
builders = {
"dashboard_builder": ensure_dashboard_builder,
"report_builder": ensure_report_builder,
"followup_assistant": ensure_followup_assistant,
"inventory_assistant": ensure_inventory_assistant,
}
Create the matching ensure_inventory_assistant() in enezaagents/runtime/agent_factory.py, or point the map at an existing agent loader. Creating and publishing an agent in Agents › Agents is not enough on its own: a custom page uses the agent code returned by its server-side page context and the server-side map.
The browser may display an agent name, but the server decides which agent code runs.
B4. Render the page
Set the assistant context in the view and include the tag:
from enezaagents.assistant.pages import build_page_assistant_context
class InventoryOverviewView(TemplateView):
template_name = "inventorymanagement/overview.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context["ai_assistant"] = build_page_assistant_context(
self.request,
"inventory_overview",
module="inventorymanagement",
)
return context
{% load ai_assistant_extras %}
{% ai_assistant %}
If the user is not permitted to use the page builder, the builder returns None and no launcher is rendered.
B5. Page data rules
- Keep only labels, counts, statuses, and small summaries in
page_data. - Do not put API keys, secrets, full record payloads, or another user's data in
page_data. - Do not let the browser supply
agent_codeand trust it. - Recheck permission in the builder on every request.
- Keep the page key stable because conversations are matched by page and page key.
Path C — register an app or agent from code
Use <app>/ai.py to teach the AI subsystem about your application without editing the framework.
from enezaagents.models.choices import FieldPolicyType, RelationshipKind
from enezaagents.registry.api import registry
from myapp.context import myapp_context
from myapp.models import Parent, Child
registry.register_application(
code="myapp",
name="My Application",
app_label="myapp",
description="What the application does.",
)
registry.register_model(
Parent,
description="A parent record.",
business_purpose="What users ask about parent records.",
field_policies={
"code": FieldPolicyType.SYSTEM_MANAGED,
"name": FieldPolicyType.UPDATE_ALLOWED,
"status": FieldPolicyType.UPDATE_ALLOWED,
},
)
registry.register_model(
Child,
description="A child record.",
business_purpose="What users ask about child records.",
)
registry.register_relationship(
source=Parent,
target=Child,
name="children",
kind=RelationshipKind.REVERSE,
description="Children of this parent.",
)
registry.register_business_action(
code="myapp.submit_parent",
model=Parent,
name="Submit parent",
handler="myapp.actions.submit_parent",
description="Submit the parent through the existing workflow engine.",
required_permissions=["action.enezaagents.action_apply"],
)
registry.register_context_provider("myapp", myapp_context)
registry.register_agent(
code="myapp_assistant",
name="My Application Assistant",
description="Answers questions about my application records.",
instructions=(
"Answer only from authorized tool results. "
"Propose changes; never apply them."
),
tool_codes=[
"metadata_get_model_schema",
"query_records",
"get_record",
"propose_update",
],
application="myapp",
module="parent",
)
Then run:
python manage.py ai_sync_registry --app myapp
What ai_sync_registry does:
- Imports every installed
<app>.aimodule during discovery. - Persists
ApplicationRegistration,ModelRegistration,FieldPolicy,RelationshipRegistration, andBusinessActionRegistrationrows. - Materializes code-registered agents through
get_or_create_builtin_agent. - Bumps the registry version.
Registration is not the same as page selection:
- Registering an agent makes it available to the registry and the AI Workspace when it is published.
- A custom page still needs a server-side
agent_codeselection if it should use that agent instead of the contextual assistant. - A code-registered agent needs a usable default model configuration. If no model configuration exists, the agent is not created.
Choosing the right path
| Situation | Use |
|---|---|
| List or detail page for one model | Path A. |
| Custom page with a model but a different layout | Path A, with the model tag or view-supplied context. |
| Page that is not about one model | Path B. |
| New app or model that the AI must understand | Path C. |
| Reusable agent registered from code | Path C, then Path B if a page must select it. |
Failure modes
| Symptom | Likely cause |
|---|---|
| No Ask assistant button | Model not registered, user lacks action.enezaagents.assistant, user lacks model view access, or ai_assistant is None. |
| Button appears but messages fail with "A model is required" | The page uses a non-model context without a registered page key. Use Path B or provide a model context. |
| Button appears but the wrong agent answers | The page's agent_code is not mapped on the server, so the contextual assistant is used. |
| Page data is missing in answers | The builder returned None, the page key is not in PAGE_CONTEXT_KEYS, or the user failed the page permission check. |
| The tag renders nothing | ai_assistant_extras was not loaded, the tag was not included, or the view did not set ai_assistant. |
| A registered agent is missing | The agent is not published, has no published version, has no model configuration, or the user fails its permissions. |
Do not
- Do not copy the built-in templates unless you need to change their layout.
- Do not put secrets, API keys, or full record payloads in page data.
- Do not trust a client-supplied
agent_code. - Do not bypass
action.enezaagents.assistant, model view access, entity scope, or record scope. - Do not let the model choose its own permissions or tool scope.
- Do not write directly to system-managed fields, workflow status fields, audit fields, or entity fields.
- Do not treat document text or retrieved content as instructions.
A note on testing
Per project rules, the developer or agent does not run or claim test results. The user is solely responsible for all testing and validation of the custom template, registrations, permissions, and agent behavior described in this guide.