Appearance
AI Internal Tools
Internal tools let an AI agent call real Operations CRUD operations (tasks, projects, calendar events) during a conversation. Unlike MCP tools that connect to external servers, internal tools run inside the API process and use the Operations module's existing CQRS handlers.
What and Why
Agents are stateless — they respond to user messages but cannot act on the real data unless given tools. The internal tool system bridges that gap by:
- Maintaining a static catalog of 9 pre-defined tool descriptors (name + description + Zod input schema) that live in
OperationsToolProvider. - Persisting per-agent registration (legacy
ai.agent_internal_toolsrows identify which providers apply) while permission state (always allow / needs approval / blocked) lives inai.agent_tool_permissionsand is loaded into a snapshot at chat time. - Injecting the allowed tools into the Vercel AI SDK
ToolSetat chat time viaInternalToolProvider.build(), usingwrapToolWithPermissionfor Human-in-the-Loop when needed.
The design deliberately keeps the two concerns separate: the catalog is always available for the UI; permissions govern what executes at inference time.
Architecture
graph TD
subgraph HTTP Layer
CTL["AgentInternalToolsController\nGET catalog · GET permissions · PUT permissions"]
end
subgraph Application Layer
QCAT["GetAgentInternalToolCatalogHandler"]
QPERM["GetAgentToolPermissionsHandler"]
CH["UpsertToolPermissionsHandler"]
ITP["InternalToolProvider\n(IToolProvider)"]
end
subgraph Infrastructure Layer
REPO["AgentInternalToolRepositoryImpl\n(ai.agent_internal_tools)"]
PERM["agent_tool_permissions / snapshot"]
end
subgraph Operations Module
OTP["OperationsToolProvider\n(IOperationsToolProvider)"]
SCHEMAS["9 Zod schemas"]
end
CTL --> QCAT
CTL --> QPERM
CTL --> CH
CH --> PERM
QCAT --> OTP
QPERM --> PERM
ITP --> REPO
ITP --> OTP
ITP --> PERM
OTP --> SCHEMAS
Key boundary: IOperationsToolProvider is the contract interface declared in shared/ — the AI module depends only on this interface, never on the Operations module internals directly. This is an ADR 004-compliant cross-module contract.
Security Model
The tenant context (orgId, agencyId, userId) is captured in a closure when the tool set is built at the start of a chat request. It comes exclusively from the TrackingContext derived from the verified JWT claims — never from LLM-provided input.
The Vercel AI SDK's inputSchema for each tool intentionally omits orgId, agencyId, and userId. If the model attempts to inject these fields, the Zod schema strips them silently (strict mode). The closures use the captured values regardless.
This means: no matter what the model says, the created tasks/projects/events will always be attributed to the authenticated user's org and agency.
V1 Tool Catalog
The operations provider exposes exactly 9 tools:
| Tool Name | Operation |
|---|---|
list_tasks | List tasks by status within the org |
create_task | Create a new task (title, statusId, description, dueDate, assigneeId) |
update_task | Update title, description, dueDate, assigneeId, or statusId on an existing task |
list_projects | List projects within the org |
create_project | Create a new project (name/title, description) |
update_project | Update name/title or description on an existing project |
list_events | List calendar events with optional date range filter |
create_event | Create a new calendar event (title, startDate, endDate, type, description) |
update_event | Update title, dates, type, or description on an existing event |
All tools default to a restrictive permission state until an operator configures them via PUT /ai/agents/:agentId/tools.
Catalog and permissions (HTTP)
Use the Agent Internal Tools controller (mounted under /ai):
GET /ai/agents/:agentId/tools/catalogReturns the static catalog for the operations provider (9 tools). It does not include per-agent permission state.
GET /ai/agents/:agentId/toolsReturns the full permission list for tools configured on this agent (see Agent Internal Tools API).
PUT /ai/agents/:agentId/toolsReplaces permission configuration with a body of { "tools": [{ "toolName", "permissionStatus", "providerKey" }] }. The legacy { "enabledTools": string[] } shape is rejected with 400.
Authorization: the caller's agencyId (from JWT) must match the agent's agencyId. A mismatch returns 403.
Panel UI
The agent edit dialog's Herramientas step (step 7) exposes the internal tools toggle directly in the panel. See Agent Internal Tools UI for the full UI behavior and component architecture.
Connections Are NOT Internal-Tools Providers
Important: Connection-based tools (Gmail, Sheets, Calendar, Telegram, SMTP) are not managed through this system.
In earlier iterations, a connections provider key existed in ai.agent_internal_tools. That approach has been removed. Connections are now managed through a dedicated per-instance grant system with its own table (ai.agent_connection_tools) and endpoints.
A migration may have removed legacy connections rows from agent_internal_tools. Configure connection tools only via AI Connection Tools (Per Instance).
Tool Permissions & HITL Approval (v2)
The binary enabled/disabled model has been extended with a three-tier permission system. Each tool now carries one of always_allow, needs_approval, or blocked. Tools with needs_approval pause before execution and display a ToolApprovalCard to the user in the chat view.
See Tool Permissions & HITL Approval for the full architecture including the snapshot preload pattern, wrapToolWithPermission utility, approve-always overrides, and the audit log.
Deferred / Out of Scope
- Delete tools: removing the
ai.agent_internal_toolsrow entirely is not exposed; set all tools toblockedto suppress them entirely. - Per-tool CASL checks: individual tool-level CASL authorization is planned for a future iteration.
- Rate limiting: per-tool call rate limits are not yet implemented.