Skip to content

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:

  1. Maintaining a static catalog of 9 pre-defined tool descriptors (name + description + Zod input schema) that live in OperationsToolProvider.
  2. Persisting per-agent registration (legacy ai.agent_internal_tools rows identify which providers apply) while permission state (always allow / needs approval / blocked) lives in ai.agent_tool_permissions and is loaded into a snapshot at chat time.
  3. Injecting the allowed tools into the Vercel AI SDK ToolSet at chat time via InternalToolProvider.build(), using wrapToolWithPermission for 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 NameOperation
list_tasksList tasks by status within the org
create_taskCreate a new task (title, statusId, description, dueDate, assigneeId)
update_taskUpdate title, description, dueDate, assigneeId, or statusId on an existing task
list_projectsList projects within the org
create_projectCreate a new project (name/title, description)
update_projectUpdate name/title or description on an existing project
list_eventsList calendar events with optional date range filter
create_eventCreate a new calendar event (title, startDate, endDate, type, description)
update_eventUpdate 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/catalog

Returns the static catalog for the operations provider (9 tools). It does not include per-agent permission state.

GET /ai/agents/:agentId/tools

Returns the full permission list for tools configured on this agent (see Agent Internal Tools API).

PUT /ai/agents/:agentId/tools

Replaces 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_tools row entirely is not exposed; set all tools to blocked to 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.