Skip to content

Dynamic Tool Calling ​

End-to-end documentation for dynamic tool calling in Oktivo. This feature allows users to configure custom HTTP API endpoints that AI agents can invoke during live calls. Includes both per-agent inline tools and the organization-scoped shared tool library.

⚠️ UI relocated (2026-08). The standalone /tool-library page has been deleted. Shared tools are now created and managed at /tools → Add client tool (ClientToolWizard), which attaches them to agents in the same flow. Their in-call executions appear at /logs → Client Tool Logs. /tool-library redirects to /tools.

The backend both collections, the linking model and the Agent Server resolution described below is unchanged. See TOOLS-AND-LOGS.md for how this system sits alongside webhooks and MCP integrations.

Architecture Overview ​

Two Tool Systems ​

Oktivo supports two complementary tool systems:

AspectInline Tools (per-agent)Shared Tools (library)
ScopeSingle agentOrganization-wide
CollectionDynamicToolSharedDynamicTool
Unique key(agentId, toolName)(orgUID, toolName)
Created fromAgent Editor → Tools sub-tab/tools → Add client tool
Linked to agentAutomatic (belongs to agent)Via linkedToolIds[] on Agent model
Updates propagateN/A (single agent)Automatically agents resolve at call time
DeletionSoft-delete per agentSoft-delete removes from all agents

Merge rule at call time: When resolving tools for an agent, inline tools take precedence over shared tools on toolName collision.

Feature Access Control ​

Dynamic tools use a dedicated dynamic_tools feature flag:

  1. Superadmin adds dynamic_tools to Organization's plan.features[]
  2. Admin assigns dynamic_tools to Customer's features[]
  3. Frontend checks hasFeature('dynamic_tools') before rendering UI
  4. Backend routes use requireFeatureAccess(...FEATURE_MAP.manageDynamicTools) middleware

Data Models ​

DynamicTool Collection (Per-Agent) ​

Stores inline HTTP endpoints scoped to a single agent.

FieldTypeDescription
toolIdStringUnique ID, prefix dtl_
agentIdStringFK to Agent
customerUIDStringTenant isolation
orgUIDStringOrganization scope
baseUrlStringe.g. https://api.example.com
pathStringe.g. /v1/leads
fullUrlStringComputed: baseUrl + path
methodEnumGET, POST, PUT, PATCH, DELETE
headersArray[{ key, value, enabled }]
bodyTypeEnumjson, form, raw, none
bodyTemplateStringBody with {{variable}} placeholders
authObject{ type, token?, headerName?, username?, password? } encrypted at rest
toolNameStringLLM identifier (snake_case, unique per agent)
toolDescriptionStringInstructions for LLM on when to use
parameterSchemaObjectJSON Schema for LLM parameter collection
parentToolIdString/nullGrouping by baseUrl
timeoutMsNumber1000–30000, default 10000
retryOnFailureBooleanAuto-retry on failure
maxRetriesNumber0–5
isActiveBooleanSoft-active flag
deletedAtDate/nullSoft-delete timestamp

Indexes: { agentId: 1 }, { agentId: 1, toolName: 1 } (unique), { agentId: 1, deletedAt: 1 }, { parentToolId: 1 }

SharedDynamicTool Collection (shared tool library) ​

Organization-scoped reusable tool definitions. Same fields as DynamicTool but scoped differently.

FieldTypeDescription
toolIdStringUnique ID, prefix stl_
orgUIDStringOrganization scope (indexed)
userIdStringCreator user ID
baseUrlStringe.g. https://api.example.com
pathStringe.g. /v1/leads
fullUrlStringComputed: baseUrl + path
methodEnumGET, POST, PUT, PATCH, DELETE
headersArray[{ key, value, enabled }]
bodyTypeEnumjson, form, raw, none
bodyTemplateStringBody with {{variable}} placeholders
authObjectEncrypted at rest (same as DynamicTool)
toolNameStringUnique per org (snake_case)
toolDescriptionStringLLM instructions
parameterSchemaObjectJSON Schema
parentToolIdString/nullGrouping
timeoutMsNumber1000–30000, default 10000
retryOnFailureBooleanAuto-retry
maxRetriesNumber0–5
isActiveBooleanActive flag
deletedAtDate/nullSoft-delete

Indexes: { orgUID: 1 }, { orgUID: 1, toolName: 1 } (unique), { orgUID: 1, deletedAt: 1 }, { parentToolId: 1 }

Agent Model Extension ​

typescript
// Added to Agent schema
linkedToolIds: { type: [String], default: [] }

Stores toolId references to SharedDynamicTool documents. Resolved at call time.

DynamicToolLog Collection ​

Execution logs for tool invocations during calls. 30-day TTL. (Unchanged from original system.)

API Endpoints ​

Inline Tool Routes (JWT + dynamic_tools feature gate) ​

MethodPathHandlerDescription
POST/dynamic-toolscreateToolCreate inline tool for agent
GET/dynamic-tools?agentId=XlistToolsList tools grouped by baseUrl
GET/dynamic-tools/:toolIdgetToolGet single tool
PUT/dynamic-tools/:toolIdupdateToolPartial update
DELETE/dynamic-tools/:toolIddeleteToolSoft-delete (cascade if parent)
POST/dynamic-tools/import/curlimportCurlParse cURL → tool config
POST/dynamic-tools/verifyverifyEndpointServer-side endpoint test

Shared Tool Routes (JWT + dynamic_tools feature gate) ​

MethodPathHandlerDescription
POST/shared-toolscreateSharedToolCreate org-scoped shared tool
GET/shared-toolslistSharedToolsList active shared tools for org
GET/shared-tools/:toolIdgetSharedToolGet single shared tool
PUT/shared-tools/:toolIdupdateSharedToolUpdate shared tool
DELETE/shared-tools/:toolIddeleteSharedToolSoft-delete shared tool

Agent-Tool Linking Routes (JWT + dynamic_tools feature gate) ​

MethodPathHandlerDescription
GET/agents/:agentId/linked-toolsgetLinkedToolsGet full SharedDynamicTool docs for linked tools
POST/agents/:agentId/linked-toolslinkToolLink shared tool to agent. Body: { toolId }
DELETE/agents/:agentId/linked-tools/:toolIdunlinkToolUnlink shared tool from agent

Internal Routes (x-api-secret auth) ​

MethodPathHandlerDescription
GET/internal/agent-tools/:agentIdgetAgentToolsInternalReturns merged inline + shared tools with decrypted auth
POST/internal/dynamic-tool-logscreateToolLogInternalCreate execution log

Migration Routes (x-api-secret auth) ​

MethodPathHandlerDescription
POST/internal/scripts/migrate-shared-toolsmigrateSharedToolsMigrate per-agent tools to shared library
GET/internal/scripts/migration/shared-tools/statusgetMigrationStatusGet migration state and counts

Tool Resolution (Merge Logic) ​

When the Agent Server needs tools for an agent, the internal endpoint merges both systems:

Key rule: Inline tools always win on toolName collision. This allows agents to override a shared tool with a custom version.

File Structure ​

Client Backend ​

client_backend/src/
├── types/
│   ├── dynamic-tool.types.ts          # IDynamicTool, enums (shared)
│   └── shared-tool.types.ts           # ISharedDynamicTool interface
├── models/
│   ├── DynamicTool.ts                 # Per-agent tool schema
│   ├── SharedDynamicTool.ts           # Org-scoped shared tool schema
│   ├── DynamicToolLog.ts              # Execution logs (TTL)
│   └── Agent.ts                       # + linkedToolIds field
├── services/
│   └── dynamic-tool.service.ts        # Business logic + getAgentToolsInternal (merge)
├── controllers/
│   ├── dynamic-tool.controller.ts     # Inline tool CRUD + verify + logs
│   ├── shared-tool.controller.ts      # Shared tool CRUD
│   ├── agent.controller.ts            # + getLinkedTools, linkTool, unlinkTool
│   └── migration.controller.ts        # Migration script handlers
├── routes/
│   ├── dynamic-tool.routes.ts         # Inline tool routes
│   ├── dynamic-tool-internal.routes.ts # Internal routes
│   ├── shared-tool.routes.ts          # Shared tool routes
│   ├── agent.routes.ts                # + linked-tools routes
│   └── script.routes.ts               # + migration routes
└── app.ts                             # All routes mounted

Frontend ​

oktivo_app/src/
├── services/
│   ├── dynamicTool.service.ts         # Inline tool API client + execution logs
│   └── sharedTool.service.ts          # Shared tool CRUD + agent assignments
├── components/features/tools/
│   ├── ClientToolWizard.tsx           # /tools multi-step create/edit (registered in TOOL_TYPE_CONFIGS)
│   ├── steps/ClientAgentsStep.tsx     # Attach to agents as part of the same save
│   ├── tool-registry.ts               # list / get / remove / setActive adapter
│   ├── curl-parser.ts                 # Shell-aware cURL import
│   └── logs/ClientToolLogsSection.tsx # /logs → Client Tool Logs tab
├── components/features/
│   ├── DynamicToolForm.tsx            # Agent Editor form (manual + cURL, verify, variable badges)
│   ├── DynamicToolsSection.tsx        # Agent Editor: inline tools + link picker
│   └── DynamicToolGroupList.tsx       # Grouped display
├── hooks/useToolAssignableAgents.ts   # Agent list + current attachment state
├── pages/customer/
│   ├── ToolsPage.tsx                  # /tools cards + unified list
│   ├── ToolCreatorPage.tsx            # /tools/new/:type, /tools/edit/:type?id=
│   └── AgentEditorPage.tsx            # Step 3: Tools & Transfers
└── routes.tsx                         # /tools routes; /tool-library redirects

UI Integration ​

Tools Page (/tools) ​

Shared tools are one of three tool types on the unified Tools page. ToolList renders all types through TOOL_ADAPTERS, so search, delete and the active toggle are the same code for every type.

The ClientToolWizard flow: Basic Info → Endpoint → Request Body → Auth & Settings → Agents → Verify & Create. The Agents step is a required stop rather than an optional extra a tool attached to no agent is invisible to every agent, which reads as "the tool silently does nothing". Selecting none is still allowed, but only after the warning has been shown.

On save the wizard writes the tool, then replaces the whole agent set in one PUT /shared-tools/:toolId/assignments call so a change cannot half-apply. If the tool saves but assignment fails, that is reported separately a partial success is never presented as a clean one.

Editing deep-links as /tools/edit/client?id=<toolId> so the wizard can re-fetch after a refresh instead of falling back to a blank create form.

Agent Editor (Step 3: Tools & Transfers) ​

Dynamic Tools and IVR moved to their own dedicated step:

AgentEditorPage.tsx
├── Step 1: Agent Setup
├── Step 2: Voice & Settings
├── Step 3: Tools & Transfers  ← Dynamic Tools + IVR here
│   ├── DynamicToolsSection
│   │   ├── Inline link picker (no modal inline UI)
│   │   ├── Linked shared tools display
│   │   ├── Inline tools display
│   │   └── DynamicToolForm (add/edit)
│   └── IVR & Call Transfer Configuration
└── Step 4: Analysis

DynamicToolForm Enhancements ​

  • Agent selector (optional): In the Agent Editor form, select an agent to auto-link after creation. On /tools this is the dedicated Agents step instead.
  • Agent variable badges: When agent selected, shows clickable {{variable}} badges below body template for quick insertion
  • Verify required: Tool cannot be saved until "Test Endpoint" passes successfully
  • Improved validation: Body template {{...}} validation now skips JSON content (no false positives on {{ "key": "value" }})

DynamicToolsSection (Agent Editor) ​

  • Inline link picker: "Link from Library" button toggles an inline panel (no modal) with checkboxes
  • Linked tools display: Shows shared tools with method badge, name, URL, "Shared" badge, and Unlink button
  • Backward compatible: Inline tool creation still works as before

End-to-End Flow ​

Creating a Shared Tool and Linking to Agent ​

Tool Resolution at Call Time ​

Migration Script ​

Migrates existing per-agent DynamicTool documents to SharedDynamicTool:

  1. Fetches all active DynamicTool documents
  2. Groups by (orgUID, toolName, baseUrl) for deduplication
  3. Creates one SharedDynamicTool per unique group
  4. Links the shared tool to all agents that had it ($addToSet linkedToolIds)
  5. Preserves original DynamicTool documents (no deletion)
  6. Skips and logs errors per tool, continues processing

Trigger: POST /internal/scripts/migrate-shared-tools (x-api-secret auth) Status: GET /internal/scripts/migration/shared-tools/status

How to Test ​

Prerequisites ​

  • Backend running (npm run dev in client_backend/)
  • Frontend running (npm run dev in oktivo_app/)
  • User with dynamic_tools feature enabled
  • At least one agent created

Test Shared Tool CRUD ​

  1. Navigate to Tools in the sidebar (/tools)
  2. Click the Add client tool card
  3. Fill: name=test_webhook, description=Test tool, baseUrl=https://webhook.site/your-id, path=/, method=POST
  4. On Verify & Create, click Test Endpoint → verify green success response
  5. Click Save → tool appears in the unified tool list with a Client badge
  6. Click the row to reopen the wizard → modify description → Save
  7. Click the trash icon → confirm → tool removed and detached from every agent

Test Agent Attachment ​

  1. Open a client tool from /tools and go to the Agents step
  2. Select one or more agents → complete the wizard → Save
  3. Reopen the tool → verify the same agents are pre-selected
  4. Deselect all → Save → verify the warning is shown and the tool detaches
  5. Go to Agent Editor → Step 3 (Tools & Transfers)
  6. Click Link from Library → verify the inline picker shows available tools
  7. Select a tool → click Link → verify it appears under "Linked from Library"
  8. Click Unlink → verify the tool is removed from the linked section

Both surfaces write the same Agent.linkedToolIds, so a change made in one is visible in the other after a reload.

Test Tool Resolution ​

  1. Create an agent with 1 inline tool (inline_tool) and link 1 shared tool (shared_tool)
  2. Make a test call to the agent
  3. Verify in Agent Server logs: both tools registered
  4. Trigger both tools during conversation
  5. Verify execution logs in /dynamic-tool-logs

Test Name Collision ​

  1. Create inline tool with name send_data on an agent
  2. Create shared tool with same name send_data and link to same agent
  3. Make a test call → verify only the inline version is used (check URL in logs)

Test Verify Requirement ​

  1. On /tools, start a new client tool
  2. Try to save WITHOUT clicking "Test Endpoint" → verify error toast appears
  3. Click "Test Endpoint" → wait for success → save succeeds

Test Execution Logging ​

  1. Attach a client tool to an agent and place a call that triggers it
  2. Go to /logs → Client Tool Logs
  3. Verify the row shows the resolved tool name (not a raw stl_… id), the agent name, and a Shared badge for library tools
  4. Expand the row → verify URL, method, request headers/body, response status and body are present
  5. Set both From and To to today → verify today's rows still appear

Error Handling ​

ScenarioHTTP StatusResponse
Invalid tool payload (missing required fields)400{ success: false, message: "toolName is required" }
Duplicate toolName within org409{ success: false, message: "A tool with this name already exists" }
Shared tool not found or wrong org404{ success: false, message: "Tool not found" }
Link to non-existent/deleted tool404{ success: false, message: "Shared tool not found or has been deleted" }
Link to already-linked tool409{ success: false, message: "Tool is already linked to this agent" }
Agent not found for linking404{ success: false, message: "Agent not found" }
Save without verifyUI blockToast: "Please verify the endpoint before saving"

Troubleshooting ​

SymptomCauseFix
"Add client tool" card missing on /toolsdynamic_tools feature not enabledSuperadmin: add to org. Admin: assign to customer
Shared tool not appearing in agent's linked toolsTool soft-deleted or isActive=falseCheck deletedAt and isActive in DB
Linked tool not used during callName collision with inline toolInline takes precedence rename one
"Link from Library" shows no toolsNo shared tools created yetCreate one at /tools → Add client tool
Tool saved but never runsAttached to no agentReopen the tool → Agents step → select agents
No rows in Client Tool LogsAgent Server can't reach the ingest routeCheck BACKEND_URL and x-api-secret on the Agent Server
Body template {{...}} false errorJSON content detected as placeholderFixed: validator now skips JSON-like content
Migration skipping toolsDuplicate (orgUID, toolName) already existsExpected tool already migrated
Agent Editor missing "Tools & Transfers" stepFeature flags not enabledEnable dynamic_tools or ivr_call_transfer

Changelog ​

DateChange
2025-05Initial per-agent Dynamic Tool system
2025-06Added shared tool library (org-scoped), agent linking, Tool Library page, migration script
2025-06Moved Dynamic Tools + IVR to dedicated "Tools & Transfers" step in Agent Editor
2025-06Added verify-before-save requirement, agent variable badges in body template
2025-06Fixed body template validation (no false positives on JSON curly braces)
2025-06Replaced "Link from Library" modal with inline picker (z-index fix)
2026-08Shared tool UI moved to /tools (ClientToolWizard); ToolLibraryPage deleted and /tool-library now redirects
2026-08Added an Agents step to the wizard plus GET/PUT /shared-tools/assignments so attachment is part of the same save
2026-08Fixed /tools client tool CRUD was posting to /dynamic-tools with an empty agentId (400) and reading the list from /shared-tools
2026-08Added the Client Tool Logs tab at /logs, plus enrichToolLogs so rows show tool and agent names instead of raw ids
2026-08Rewrote cURL import as a shell-aware tokenizer (curl-parser.ts) quoted bodies, -u, --data-urlencode, -XPOST

Last updated: