Agents are runtime assets that describe reusable AI behavior for a module. They live under:
modules/<module>/agents/
__init__.py
*.pyThe module loader discovers and imports files in agents/ during module
loading. The @agent(...) decorator registers each agent definition.
What An Agent Defines¶
An agent definition can declare:
| Field | Purpose |
|---|---|
name |
Stable runtime name, module-qualified by the SDK. |
title |
Human-readable label. |
description |
Short explanation for discovery surfaces. |
objective |
Model objective, such as chat. |
system_prompt |
Model-facing behavior instructions. |
tools |
Tool names the agent is expected to use. |
skills |
Skill names loaded as instruction context. |
max_iterations |
Agent loop budget. |
mcp_servers |
Explicit MCP servers the agent can use. |
handler |
Whether the decorated function is the Python handler. |
Use agents for behavior that should be discoverable and reusable. If a feature needs full control over persistence, streaming UI updates, or message windowing, an action can still call a provider directly and pass registered tools, agents, and skills in the provider options.
Declarative Agent¶
A declarative agent registers metadata without a Python handler:
from __future__ import annotations
from democrai.sdk.decorators import agent
@agent(
"document-agent",
title="Document agent",
description="Answers focused questions about uploaded documents.",
objective="chat",
tools=[
"chat.list-attachments",
"chat.search-documents",
],
skills=["chat.chat_context"],
max_iterations=4,
handler=False,
system_prompt=(
"Inspect attachments and retrieved document content before answering."
),
)
def document_agent():
"""Declarative agent metadata."""Use this pattern when an action owns the orchestration but still wants the agent contract to be registered and discoverable.
Handler Agent¶
Use handler=True when the decorated function should be executed by the agent
runtime:
from __future__ import annotations
from democrai.sdk.decorators import agent
@agent(
"triage-agent",
title="Triage agent",
description="Classifies and summarizes incoming records.",
objective="chat",
tools=["reports.get-report"],
max_iterations=3,
)
async def triage_agent(input: str = "", sdk=None, context: dict | None = None):
report_id = context["report_id"]
report = await sdk.ai.run_tool(
"reports.get-report",
arguments={"report_id": report_id},
context=context,
)
return {
"status": "ok",
"content": f"Report status: {report['report']['status']}",
}Keep handler agents focused. If the implementation starts managing UI streams, database writes, document ingestion, and provider options together, move that orchestration into an action and let the agent definition describe the model contract.
Running Agents From SDK¶
Use module_sdk.ai.run_agent(...) when a module wants the runtime-managed agent
loop:
result = await module_sdk.ai.run_agent(
"reports.triage-agent",
input="Summarize this report",
context={"report_id": report.id},
extra_tools=["reports.get-report"],
extra_skills=["reports.report_context"],
max_iterations=3,
)The SDK also exposes discovery helpers:
agents = module_sdk.ai.list_agents(module_name="reports")
agent = module_sdk.ai.get_agent("reports.triage-agent")Tools, Skills, And MCP¶
Declare the tools and skills an agent needs:
@agent(
"research-agent",
tools=["reports.search-reports"],
skills=["reports.research_context"],
mcp_servers=["internal-docs"],
)
async def research_agent(...):
...MCP servers must be listed explicitly. Do not use wildcards.
Tool and skill names should be stable module-qualified names. If an agent uses a tool from another module, that dependency should be intentional and documented in the feature design.
Context And Message Windows¶
Agents should not assume the full application state is already in the prompt. For chat-style features:
- send a bounded recent message window;
- include a compact summary when available;
- pass stable identifiers in
context; - expose retrieval and lookup through tools;
- keep long documents out of the prompt unless the user explicitly needs full content.
This keeps the provider request small while still giving the agent ways to ask for the missing state.
Runtime Events¶
When an action runs an agent, it can pass listener callbacks:
async def on_message(event: dict):
...
result = await module_sdk.ai.run_agent(
"reports.triage-agent",
input="Review this report",
context={"report_id": report.id},
on_message=on_message,
)Use runtime events for user-visible progress only when they help explain what the agent is doing. Avoid appending every low-level event to the UI.
Boundary Rules¶
Agents are contracts and orchestration units. They should not:
- import core agent runtime internals;
- reach into client renderers;
- read database tables outside the SDK boundary;
- load model providers directly;
- hide business persistence that belongs in actions or tools.
Use actions for user-triggered orchestration, tools for bounded capabilities, and skills for reusable instructions.