Build an AI Agent
An AI agent is an application that talks to a language model. It receives messages, reasons about them, calls tools (like reading or writing data), and responds. Under the hood, it is a standard RootCX application with one extra file: agent.json.
It inherits everything a regular internal tool has (authentication, RBAC, audit logs, encrypted secrets) plus its own AI governance layer: supervision policies, rate limits, approval flows, and session memory.
Structure of an agent
support_bot/
├── manifest.json # Data model + permissions (same as any app)
├── agent.json # Agent configuration (what makes it an agent)
├── agent/
│ └── system.md # System prompt in Markdown
└── backend/
└── index.ts # LLM invocation logic
Your AI coding agent generates all of this. You review and deploy with rootcx deploy.
agent.json
This file turns a regular application into an AI agent:
{
"name": "Support Agent",
"description": "Handles customer queries about orders and shipping",
"systemPrompt": "./agent/system.md",
"memory": { "enabled": true },
"limits": {
"maxTurns": 20,
"maxContextTokens": 100000,
"keepRecentMessages": 10
},
"supervision": {
"mode": "supervised",
"policies": [
{ "action": "mutate", "entity": "orders", "requires": "approval" },
{ "action": "mutate", "rateLimit": { "max": 20, "window": "1h" } },
{ "action": "*", "rateLimit": { "max": 500, "window": "1d" } }
]
}
}
Fields
| Field | Required | Description |
|---|---|---|
name |
Yes | Agent display name. |
description |
No | What the agent does. Shown in fleet listings. |
systemPrompt |
No | Path to the Markdown system prompt file, or inline text. |
memory.enabled |
No | Retain conversation history across messages in a session. Default: false. |
limits.maxTurns |
No | Maximum reasoning turns per invocation. Default: 50. |
limits.maxContextTokens |
No | Context window budget for history. |
limits.keepRecentMessages |
No | Messages preserved when context is compacted. |
supervision.mode |
No | "autonomous", "supervised", or "strict". |
supervision.policies |
No | Array of granular policies (see Supervision below). |
Tools
Agents use built-in tools provided by the Core. Every tool is gated by RBAC. If the agent lacks a permission, the Core blocks the call instantly.
| Tool | Permission | What it does |
|---|---|---|
query_data |
tool:query_data + app:{app}:{entity}.read |
Read records from any entity. Supports cross-app reads with the app parameter. Full where-clause DSL. |
mutate_data |
tool:mutate_data + app:{app}:{entity}.create/update/delete |
Create, update, delete, or bulk-create records. Supports cross-app writes. |
list_apps |
tool:list_apps |
Discover all installed applications and their entities. |
describe_app |
tool:describe_app |
Inspect the full data model of an app. |
list_actions |
tool:list_actions |
Discover callable actions exposed by apps. |
call_action |
tool:call_action + app:{app}:action:{actionId} |
Execute an app's declared action. |
invoke_agent |
tool:invoke_agent |
Delegate a task to another agent in the fleet. |
list_integrations |
tool:list_integrations |
Browse installed integrations and their actions. |
call_integration |
tool:call_integration |
Execute an integration action (Slack, email, etc.). Credentials resolved per user. |
Cross-app access
The query_data and mutate_data tools accept an optional app parameter to read/write data in other applications:
{ "entity": "contacts", "app": "crm", "where": { "stage": "lead" } }
The permission check uses app:{target_app}:{entity}.{action}. Grant the agent's role access to the target app's entities.
invoke_agent (delegation)
Agents can delegate tasks to other agents:
{ "app_id": "finance_agent", "message": "Summarize Q4 revenue" }
Returns the sub-agent's full response. Constraints:
- Sub-agents cannot spawn further sub-agents (single-level delegation).
- An agent cannot invoke itself.
- Approval requests from sub-agents propagate to the parent session.
Supervision
Supervision controls how much freedom the agent has. This is the AI governance layer.
Modes
| Mode | Behavior |
|---|---|
autonomous |
All tools execute immediately. Rate limits still apply. |
supervised |
Specific actions require human approval before execution. |
strict |
Every tool call requires human approval. |
Policies
In supervised mode, define granular policies per action or entity:
{
"supervision": {
"mode": "supervised",
"policies": [
{ "action": "mutate", "entity": "orders", "requires": "approval" },
{ "action": "mutate", "rateLimit": { "max": 20, "window": "1h" } },
{ "action": "query", "rateLimit": { "max": 200, "window": "1h" } },
{ "action": "*", "rateLimit": { "max": 500, "window": "1d" } }
]
}
}
The action field maps tool names to policy actions: query_data maps to "query", mutate_data maps to "mutate", other tools use their name directly. Use "*" to match all tools.
Rate limits
Rate limits apply to all supervision modes (including autonomous). Window values: "60s", "30m", "1h", "1d". If the limit is exceeded, the tool call fails with a retry-after duration.
Approval flow
When approval is required:
- The agent pauses execution and broadcasts an
approval_requiredSSE event. - The approval request appears in the dashboard and via
GET /api/v1/apps/{appId}/agent/approvals. - A human approves or rejects via
POST /api/v1/apps/{appId}/agent/approvals/{approvalId}:{ "action": "approve" } // or { "action": "reject", "reason": "Too risky" } - The agent resumes or adapts based on the decision.
Session memory
When memory.enabled is true, the agent retains conversation history across messages within a session. A user can ask follow-up questions without repeating context.
Sessions, messages, and tool calls are persisted in the database. When the context window fills up, older messages are compacted into a summary while preserving the most recent messages (controlled by keepRecentMessages).
Session endpoints
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/apps/{appId}/agent/sessions |
List sessions (paginated) |
| GET | /api/v1/apps/{appId}/agent/sessions/{sessionId} |
Get session with full history |
| GET | /api/v1/apps/{appId}/agent/sessions/{sessionId}/events |
Get session as event timeline |
Entity triggers
Agents can be automatically invoked when data changes in other apps. Define a trigger in the manifest:
{
"trigger": {
"appId": "crm",
"entity": "contacts",
"on": ["INSERT", "UPDATE"]
}
}
When a matching database operation occurs, the Core automatically invokes the agent with the change details: entity name, operation type, record data, and old record (for updates/deletes).
Cron-triggered agents
Agents can be invoked on a schedule. Declare crons in the manifest:
{
"crons": [
{
"name": "hourly-sync",
"schedule": "0 * * * *",
"payload": { "message": "Check for new support tickets and summarize" }
}
]
}
The Core detects the app is an agent and invokes it with the payload message instead of dispatching to onJob.
Agent identity and RBAC
When deployed, each agent gets:
- A system user with email
agent+{appId}@localhostand a deterministic UUID. - The admin role assigned automatically.
To restrict an agent's access, update its role to include only the permissions it needs (e.g., read-only access to specific apps).
LLM configuration
The Core supports multiple LLM providers. Configure models via the API:
| Method | Path | Description |
|---|---|---|
| GET | /api/v1/llm-models |
List configured models |
| POST | /api/v1/llm-models |
Add a model |
| PUT | /api/v1/llm-models/{id} |
Update a model |
| DELETE | /api/v1/llm-models/{id} |
Delete a model |
| PUT | /api/v1/llm-models/{id}/default |
Set as default model |
Supported providers: Anthropic, OpenAI, AWS Bedrock. Default if none configured: anthropic/claude-sonnet-4-6.
File attachments
Agents support multimodal input. Pass file IDs when invoking:
{
"message": "Analyze this invoice",
"session_id": "optional-uuid",
"file_ids": ["file-uuid-1", "file-uuid-2"]
}
The Core resolves files from storage and provides them to the agent as downloadable attachments with name and content type. Files must exist in the app's storage.
Invoke and monitor
Send a message to the agent:
POST /api/v1/apps/{appId}/agent/invoke
{ "message": "What are the open orders?", "session_id": "optional-uuid" }
The response is streamed via SSE with events:
| Event | Description |
|---|---|
chunk |
Streaming text delta from the LLM |
tool_call_started |
Tool execution began (includes tool name and input) |
tool_call_completed |
Tool finished (includes output, error, duration) |
approval_required |
Supervision policy requires human approval |
session_compacted |
History was compacted into a summary |
done |
Agent completed (full response + token count) |
error |
Agent invocation failed |
Fleet monitoring
Monitor all agents in real-time:
GET /api/v1/agents/stream
Returns events from all agents including text chunks, tool calls, approvals, and completions.
Deploy
rootcx deploy
Same workflow as any application. The Core installs the manifest, registers the agent, syncs the schema, creates the system user and role, and starts the backend.
Next steps
- Build an Application to understand the foundation every agent inherits.
- RBAC for how to configure agent permissions.
- REST API Reference for all agent endpoints.