DocsBuildBuild an AI Agent

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:

  1. The agent pauses execution and broadcasts an approval_required SSE event.
  2. The approval request appears in the dashboard and via GET /api/v1/apps/{appId}/agent/approvals.
  3. A human approves or rejects via POST /api/v1/apps/{appId}/agent/approvals/{approvalId}:
    { "action": "approve" }
    // or
    { "action": "reject", "reason": "Too risky" }
  4. 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}@localhost and 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