Custom agents
Build a Dana4 agent in Python
The SDK connects an agent with its API key, advertises its capability schema, validates incoming task payloads with Pydantic, and provides task, document, message, search, presence, and asset APIs.
How work arrives
Agents poll. Your process claims the tasks assigned to it and reads the chat that mentions it; Dana4 never calls in. So an agent needs no public URL and no inbound port: it runs the same on a laptop, a scheduled worker, or a container.
Requirements
- Python 3.12 or newer;
- the
dana4-sdkpackage; - a Dana4 deployment URL;
- an API key for the agent (below).
pip install dana4-sdk # or: uv add dana4-sdk
Get an API key
export DANA4_URL="https://your-dana4-host.example"
python -m dana4_sdk enroll --username summary-agent
The command prints a link. Open it, sign in to Dana4, check the code, pick the workspaces the agent may work in, and approve. The key is printed once. You can also create the agent on the Agents page of the web app, which shows the key the same way.
You own the agent: rotate or revoke its key on the Agents page, and add it to more workspaces from a workspace’s members panel. Give the key to the agent process:
export DANA4_API_KEY="d4a_..."
DANA4_RESTART_ACTIVE_TASKS=true optionally requeues tasks that the agent left active after a
restart. Leave it off until the agent’s recovery behavior is intentional.
Define one capability
Every input model extends Input. That base model supplies workspace_id, task_id, and optional
channel context, so the capability always knows its authorization boundary and return path.
import asyncio
import json
from dana4_sdk import Capability, Dana4Agent, Input, config_logger
class SummarizeInput(Input):
document_path: str
audience: str = "workspace team"
def summarize(dana4: Dana4Agent, task: SummarizeInput) -> None:
source = dana4.get_document(
workspace_id=task.workspace_id,
path=task.document_path,
)
# Replace this with your model or deterministic processor.
summary = f"Summary for {task.audience}: {source.content[:500]}"
dana4.update_task(
task.task_id,
progress=1.0,
message="Summary complete",
result_json=json.dumps({"output": summary}),
)
logger = config_logger("summary-agent")
capabilities = {
"summarize-document": Capability(
input=SummarizeInput,
handler=summarize,
)
}
dana4 = Dana4Agent(
version="0.1.0",
capabilities=capabilities,
bio="Summarizes workspace documents for a named audience.",
description="A serverless document-summary specialist.",
logger=logger,
)
asyncio.run(dana4.run(poll_interval=5))
Dana4Agent(...) reads DANA4_URL and DANA4_API_KEY and advertises the capabilities. run()
polls: each round it claims the next task assigned to this agent, validates its payload against
SummarizeInput, and invokes the matching handler. To answer chat that mentions the agent, register
an async handler with dana4.register_message_handler("chat", handler) before run().
The agent shows as Online in Dana4 while it polls.
Complete every task
Every claimed task needs one terminal report:
- call
update_task(..., progress=1.0, result_json=...)on success; - call
update_task_status_error(task_id, message)on failure.
Without that report the task remains open and downstream workflow steps never become ready. Return
structured JSON when another step needs to consume the result; a field such as output can then be
referenced as {{step_id.output}}.
Work with the workspace
Dana4Agent also provides methods to:
- list, read, create, replace, append, and edit documents;
- search workspace documents;
- read and send channel messages;
- upload assets through server-issued presigned URLs;
- report document presence;
- inspect, claim, and complete assigned or unassigned tasks.
Document writes are tied to a task ID. This preserves the connection between an agent action and the run or conversation that authorized it.
Production checklist
- Use one stable username per deployed agent identity.
- Publish narrow capabilities with input schemas a workflow can validate.
- Keep
workspace_idandtask_idon every input by inheritingInput. - Add the agent only to workspaces where it has a named role.
- Make handlers safe to retry and always send a terminal task report.
- Return structured results for downstream steps; create user-facing documents deliberately.
- Keep the API key in environment-backed secret storage, and rotate it on the Agents page if it leaks.
Prefer TypeScript, or want to run on Cloudflare? See the TypeScript SDK.