Skip to content

Tools & Execution Logs ​

The three tool types a customer can create, where each one is configured, when each one runs, and which Logs tab shows its executions.

Last verified: 2026-08-24

Why This Doc Exists ​

Tool configuration used to be spread across four pages (/webhooks, /mcp-integrations, /tool-library, and the Agent Editor), each with its own list UI and its own copy of the delivery-log table. The copies drifted: date filters behaved differently, status pills were styled differently, and two of the four tool types had no log view at all.

Everything is now consolidated behind /tools (configuration) and /logs (execution history). This doc is the single source of truth for which surface owns what. The per-system docs WEBHOOKS.md, DYNAMIC-TOOL-CALLING.md, MCP-INTEGRATION.md, API-ACTIONS.md remain authoritative for their own internals.

Architecture Overview ​

The Three Tool Types ​

WebhookClient toolIntegration (MCP)
Create at/tools → Add webhook/tools → Add client tool/tools → Add integration
WizardWebhookWizardClientToolWizardIntegrationToolForm
CollectionWebhookEndpointSharedDynamicToolMcpIntegration
Reaches an agent viaagentIds[] on the endpoint (empty = all agents)linkedToolIds[] on the Agentscope: global or agentId
When it runsAfter the call, on lifecycle eventsDuring the call, LLM-invokedDuring the call, LLM-invoked
Who decidesEvent subscription (deterministic)The LLMThe LLM
Feature flagwebhooksdynamic_toolsmcp_integrations
Logs tabWebhook LogsClient Tool LogsIntegration Logs

The split that matters: webhooks are push, tools are pull. A webhook fires on a call lifecycle event and delivers call data outward. A client tool or integration is called by the model mid-conversation and its response re-enters the conversation. Use a webhook for guaranteed delivery; a tool call only happens if the model decides to make it.

Which one for webhook delivery ​

Use the Webhook tool. Then pick a payload mode:

  • Standard fixed versioned payload, HMAC-SHA256 signed (X-Oktivo-Signature). Use when you own the receiver and want to verify authenticity.
  • Custom your own method, headers, body template and auth. Use when posting into a third-party CRM that expects a specific shape.

Subscribe to call.completed for end-of-call delivery; it fires only once call status, transcription, analysis and recording are all present. call.ended is earlier but has no analysis or recording yet. Full detail in WEBHOOKS.md.

Do not use client tools for this they are in-call and model-driven. Do not use API Actions deprecated, see below.

Configuration Surfaces ​

/tools canonical ​

ToolsPage renders ToolCards (create) and ToolList (manage). Both are driven by registries so adding a tool type is one entry, not three branches:

  • config.ts → TOOL_TYPE_CONFIGS: card title, icon, feature flag, wizard component.
  • tool-registry.ts → TOOL_ADAPTERS: list / get / remove / setActive per type, plus listAllTools() which loads all types in parallel and skips a failing type rather than blanking the list.

setActive is null for types without an activation toggle, so ToolList hides the switch instead of calling a mismatched endpoint. An earlier version branched on tool.type in three separate places, which is how "toggle a client tool" ended up hitting the webhook endpoint.

Editing deep-links as /tools/edit/:type?id=<toolId>. The id is in the URL as well as router state so ToolCreatorPage can re-fetch via TOOL_ADAPTERS[type].get(id) after a refresh, instead of silently falling back to a blank create form.

Agent Editor overlapping, still live ​

The Agent Editor's Tools sub-tab (DynamicToolsSection) and MCP sub-tab (McpTabContent) still work and overlap with /tools:

  • Tools lets you link a library tool to the agent, or define an agent-scoped inline tool (DynamicTool collection, unique on (agentId, toolName)).
  • MCP duplicates the integration form.

/tools now covers create-and-attach for client tools via the wizard's Agents step (sharedToolService.setAssignments), so the Agent Editor tab is redundant except for inline tools, which exist nowhere else. It is deliberately kept until those are migrated hiding it would leave existing inline tools executing but unreachable in the UI.

At call time getAgentToolsInternal merges inline and linked tools; on a name collision inline wins.

Execution Logs /logs ​

LogsPage renders LOG_TAB_CONFIGS from config.ts, filtered by feature flag, in the same order as the tool cards so a tool and its logs sit in the same position.

TabCollectionFlagWritten by
Webhook LogsWebhookDeliveryLogwebhooksBullMQ worker, every attempt
Client Tool LogsDynamicToolLogdynamic_toolsAgent Server → POST /internal/dynamic-tool-logs
Integration LogsMcpToolCallLogmcp_integrationsAgent Server → POST /internal/mcp-tool-call-logs
API Action LogsApiActionLogdynamic_toolsLegacy worker (historical only)

All four collections carry a 30-day TTL on createdAt.

One table, four configurations ​

Each tab is a declarative config passed to a single LogsExplorer component. The tab files contain only columns, filters and the expanded-row body.

app/src/components/features/tools/logs/
├── LogsExplorer.tsx            # filter bar + expandable table + pagination
├── useLogFeed.ts               # filters, paging, 30s polling, debounced refresh
├── LogPrimitives.tsx           # LogStatusBadge, LogPayload, LogDetailPanel, MonoCell
├── logFormat.ts                # formatMaybeJson / formatLatency / formatTimestamp
├── types.ts                    # LogColumnDef, LogFilterDef, STATUS_FILTER, DATE_RANGE_FILTERS
├── WebhookLogsSection.tsx
├── ClientToolLogsSection.tsx
├── IntegrationLogsSection.tsx
└── ApiActionLogsSection.tsx

Behaviour that is now identical everywhere because it lives in one place: 30-second background polling, a 2-second debounce on manual refresh, filter changes resetting to page 1, stale-response discarding via a request-id guard (a slow earlier page can't overwrite a newer one), and only the first load showing a skeleton so filtering doesn't flash an empty state.

logFormat.ts is split out from LogPrimitives.tsx because that file must export only components for React Fast Refresh to work on it.

Shared server-side query helpers ​

utils/logQuery.utils.ts is used by every log endpoint:

  • parseLogPagination(page, limit) clamps to LOG_MAX_LIMIT (100), default 20.
  • applyCreatedAtRange(filter, from, to) inclusive range. A date-only to such as 2026-08-24 parses to midnight UTC, so an $lte used to exclude the whole selected day; it is now widened to 23:59:59.999. Invalid dates are ignored rather than producing an Invalid Date that matches nothing.
  • buildLogPaginationMeta(total, pagination) emits both pages and totalPages, because existing clients read different keys.

Client tool log enrichment ​

DynamicToolLog stores only toolId, and that id may point at either DynamicTool (inline) or SharedDynamicTool (library). enrichToolLogs in dynamic-tool.controller.ts resolves display fields in two batched queries regardless of page size, adding:

  • toolName from whichever collection owns the id (null if hard-deleted)
  • agentName from Agent.AgentName
  • toolSource "shared" or "agent", rendered as the Shared badge

Without this the tab would show opaque dtl_… ids.

Why MCP logging is shaped differently ​

MCP tool calls travel straight from the Agent Server to the external MCP server over the MCP protocol, so the backend never observes the HTTP exchange. There is no responseStatus or header detail to record. Instead Agent/src/core/mcp_tool_logging.py listens for LiveKit's function_tools_executed event and reports the tool name, the arguments the LLM supplied, the result returned, and the latency.

Only MCP-owned tools are reported: toolset ids are set to the McpIntegration id in mcp_servers.create_mcp_toolsets, which is what lets the backend resolve the catalog entry for display. Client tools already log themselves with full HTTP detail, and built-in transfer/end-call tools are not integrations. Logging is fire-and-forget a logging failure must never affect the conversation.

API Endpoints ​

MethodPathPurpose
GET/webhooks/delivery-logsWebhook deliveries. Filters: webhookId, eventType, status, from, to
GET/webhooks/delivery-logs/:deliveryIdSingle delivery
GET/dynamic-tool-logsClient tool calls. Filters: agentId, toolId, status, from, to
GET/dynamic-tool-logs/:logIdSingle call
GET/api/v2/mcp/tool-call-logsMCP tool calls. Filters: integrationId, agentId, toolName, status, from, to
GET/api/v2/mcp/tool-call-logs/:logIdSingle call
GET/api-action-logsLegacy. Filters: actionId, status, triggerType, from, to
POST/internal/dynamic-tool-logsAgent ingest (x-api-secret)
POST/internal/mcp-tool-call-logsAgent ingest (x-api-secret)

All public routes require JWT plus the tab's feature flag, and are scoped to customerUID. Every list endpoint accepts page and limit and returns { success, logs, pagination }.

Tool CRUD endpoints live in the per-system docs: WEBHOOKS.md, DYNAMIC-TOOL-CALLING.md, MCP-INTEGRATION.md.

Deprecated: API Actions ​

Superseded by webhook Custom payload mode. No creation UI remains the only surviving reference is a read-only active toggle on CampaignDetailPage. The API Action Logs tab is kept so historical runs stay auditable, and the worker still runs for actions configured before the migration. Migrate with POST /migration/api-actions. See API-ACTIONS.md.

Removed Pages ​

These were lazily imported in routes.tsx but never mounted their paths already redirected to /tools so they were unreachable UI that could still drift out of sync. Deleted 2026-08:

FileReplaced by
pages/customer/WebhooksPage.tsx/tools + /logs. Held its own duplicate webhook and API-action log tables.
pages/customer/McpIntegrationsPage.tsxIntegrationToolForm on /tools
pages/customer/ToolLibraryPage.tsxClientToolWizard on /tools
components/features/tools/WebhookToolForm.tsxWebhookWizard
components/features/tools/ConfigurationPanel.tsxNothing ToolsPage navigates to /tools/new/:type

Redirects retained: /webhooks, /mcp-integrations, /tool-library → /tools.

How to Test (End-to-End) ​

Webhook delivery

  1. /tools → Add webhook. Enter an HTTPS URL, select call.completed, save. Copy the secret (shown once).
  2. Use Send Test on the review step. In Custom mode this replays the real request including auth a bearer-protected endpoint answering 401 means the token is wrong, not that the test is unauthenticated.
  3. Run a real call. /logs → Webhook Logs. Expand the row to see request headers, request body, response status and response body per attempt.

Client tool

  1. /tools → Add client tool. Complete the wizard and attach at least one agent on the Agents step an unattached tool is invisible to every agent and reads as "the tool silently does nothing".
  2. Run a call with that agent and prompt the behaviour that should trigger the tool.
  3. /logs → Client Tool Logs. Confirm the row shows the tool name (not a raw id) and a Shared badge.

Integration

  1. /tools → Add integration, pick a catalog entry, supply credentials, verify.
  2. Run a call and prompt a task the MCP server handles.
  3. /logs → Integration Logs. Expand to see the arguments the LLM supplied and the result returned.

Date filter regression check: set both From and To to today and confirm today's rows still appear. This was broken before applyCreatedAtRange widened a date-only to.

Troubleshooting ​

SymptomCauseFix
A tool type is missing from /toolsFeature flag not assignedAssign webhooks / dynamic_tools / mcp_integrations
A Logs tab is missingSame flags gate the tabsAs above
Client tool never runsNot attached to the agent handling the callOpen the tool → Agents step → select agents
Client tool log shows a raw stl_… id instead of a nameTool was hard-deleted, so enrichment can't resolve itExpected; the log is retained deliberately
Two tools with the same name, wrong one runsInline and library tool share a toolNameInline wins rename one
Filtering by To = today returns nothingBackend predates applyCreatedAtRangeEnsure the endpoint uses logQuery.utils
Webhook deliveries stop entirelyEndpoint auto-disabled after 50 consecutive failuresCheck disabledReason, fix the receiver, re-enable (resets the counter)
Custom-mode delivery rejected with 400Predates JSON-aware interpolationSee WEBHOOKS.md quotes/newlines in analysis used to corrupt the body
Only one of several endpoints receives an eventPredates per-endpoint dedupeDedupe is keyed on (eventId, webhookId); a shared key used to suppress the rest
Custom deliveries unfilterable by event typeOlder rows logged as custom_deliveryKept in the filter list; clears with the 30-day TTL
Logs never appear for any in-call toolAgent can't reach the ingest routeCheck BACKEND_URL and x-api-secret on the Agent Server

Changelog ​

DateChange
2026-08Consolidated configuration on /tools; added TOOL_ADAPTERS registry
2026-08/tools client tool CRUD fixed was posting to /dynamic-tools with an empty agentId (400) and reading the list from /shared-tools
2026-08Added MCP tool call logging end-to-end (model, ingest route, Agent listener)
2026-08Added Client Tool Logs and Integration Logs tabs; every tool type now has one
2026-08Rebuilt all four log tabs on a shared LogsExplorer; added logQuery.utils.ts and fixed the exclusive to date bound
2026-08Added enrichToolLogs so client tool logs show tool and agent names
2026-08Deleted five unreachable files (see Removed Pages)

Last updated: