Agent Studio
Adr

ADR-003: Tool Action System

Declarative JSON-based tool execution

ADR-003: Tool Action System

Status

Accepted

Context

Voice agents need to execute tools (functions) based on user requests. In the original codebase, tools contained hardcoded business logic, making it impossible to add new tools without code changes.

We need a system where:

  • Tools can be created/modified via API without deployment
  • Business logic is externalized to webhooks
  • Common operations (context updates, handoffs) are built-in
  • Tools are tenant-specific and reusable across agents

Options Considered

  1. Python functions - Flexible but requires deployment
  2. Declarative JSON actions - No-code tool creation
  3. JavaScript/Lua scripting - Flexible but complex security model
  4. Workflow engine (Temporal) - Overkill for tool execution

Decision

We will use a declarative JSON-based action system:

{
  "name": "save_meal",
  "description": "Log a meal the user consumed",
  "parameters": [
    { "name": "meal_type", "type": "string", "enum": ["breakfast", "lunch", "dinner"] },
    { "name": "dishes", "type": "array", "required": true }
  ],
  "actions": [
    { "type": "validate", "rules": [...] },
    { "type": "context.set", "data": { "logged_meals[+]": "{{params}}" } },
    { "type": "api_call", "url": "https://api.example.com/meals", "body": { "user_id": "{{user.id}}", "meal": "{{params}}" } }
  ],
  "on_success": [{ "type": "respond", "message": "I've logged your {{params.meal_type}}!" }],
  "on_failure": [{ "type": "respond", "message": "Sorry, I couldn't log that meal." }]
}

Supported Action Types

ActionDescription
context.setSet values in call context
context.getRead values from context
context.deleteDelete values from context
api_callCall external API endpoint with retry support
handoffTransfer to another agent
respondGenerate speech response
flag.setSet workflow flags
flag.clearClear workflow flags
conditionalBranch based on condition
validateValidate parameters
transformTransform data (map, filter, reduce)
logLog messages for debugging

Variable Resolution

Templates use {{path}} syntax:

  • {{params.meal_type}} - Tool parameters
  • {{user.id}} - User context
  • {{workflow.logged_meals}} - Shared workflow state
  • {{agents.meal.last_dish}} - Agent-specific state

Array append uses [+]:

  • logged_meals[+] appends to array

Tool Executor Implementation

class ToolExecutor:
    def __init__(self, context: CallContext):
        self.context = context
        self.action_handlers = {
            "context.set": self._handle_context_set,
            "context.get": self._handle_context_get,
            "context.delete": self._handle_context_delete,
            "api_call": self._handle_api_call,
            "handoff": self._handle_handoff,
            "respond": self._handle_respond,
            "conditional": self._handle_conditional,
            "flag.set": self._handle_flag_set,
            "flag.clear": self._handle_flag_clear,
            "validate": self._handle_validate,
            "transform": self._handle_transform,
            "log": self._handle_log,
        }
    
    async def execute(self, tool: ToolConfig, params: dict) -> ToolResult:
        for action in tool.actions:
            handler = self.action_handlers[action["type"]]
            result = await handler(action, params)
            if not result.success:
                return await self._handle_failure(tool, result)
        return await self._handle_success(tool)

API Call Action

The api_call action makes HTTP requests to external APIs with retry support:

{
  "type": "api_call",
  "url": "https://api.example.com/v1/meals",
  "method": "POST",
  "body": {
    "userId": "{{user.id}}",
    "mealType": "{{params.meal_type}}",
    "dishes": "{{params.dishes}}"
  },
  "headers": {
    "Content-Type": "application/json",
    "Authorization": "Bearer {{user.auth_token}}"
  },
  "timeout": 15,
  "retry_count": 3,
  "retry_delay": 0.5,
  "on_error": "fail",
  "response_path": "workflow.api_response"
}

Note: API URLs are hardcoded in tool definitions. Authentication tokens are passed via user_context at call dispatch time and resolved using {{user.auth_token}}. See Backend Integration Guide for the full authentication flow.

FieldDescriptionDefault
urlFull URL or templateRequired
methodHTTP methodPOST
bodyRequest body (JSON){}
headersHTTP headers{}
timeoutRequest timeout (seconds)30
retry_countNumber of retry attempts3
retry_delayInitial delay between retries (exponential backoff)0.5
on_error"fail" or "continue""fail"
response_pathContext path to store responseOptional

Consequences

Positive

  • Tools created/modified via API without deployment
  • No hardcoded business logic
  • Easy to understand and debug
  • Consistent execution model
  • Webhook integration for external systems

Negative

  • Limited to predefined action types
  • Complex logic requires multiple webhooks
  • No loops or recursion (by design)
  • Template syntax has learning curve

Neutral

  • Actions execute sequentially (not parallel)
  • Webhook failures stop execution (configurable)

On this page