Skip to content

Tool Calling ​

Extend AI capabilities with built-in and custom tools through an agentic loop.

Overview ​

Tool calling allows the AI to:

  • Execute functions and workflows
  • Search knowledge bases (RAG)
  • Browse web pages
  • Execute SQL queries
  • Interact with browser sessions
  • Send emails and notifications
  • Access external APIs

Agentic Loop ​

The chat system uses an agentic loop for tool calling:

User Message
     │
     ▼
┌─────────────────────────────┐
│  Agentic Loop               │
│  (max 8 iterations)         │
│                             │
│  For each iteration:        │
│  1. Send "thinking" event   │
│  2. Call LLM with tools     │
│  3. Parse tool calls        │
│  4. If no tools → respond   │
│  5. Execute each tool       │
│  6. Add results to context  │
│  7. Next iteration          │
└─────────────────────────────┘
     │
     ▼
Final Response

Key Features ​

  • Maximum 8 iterations: Prevents runaway loops
  • Loop detection: Stops repeated identical tool calls
  • Parallel execution: Multiple tools can run concurrently
  • Result truncation: Large results are trimmed to save context
  • Error handling: Failed tools report errors, loop continues

Built-in Tools ​

Every agent ships with 77 built-in tools across 16 categories — task/memory, goals & reflection, workflows, scheduling, contacts, gateways, web & browser, payments, the Stratos wallet, apps, and more.

The complete reference, with descriptions and approval flags, is on a dedicated page:

➡️ Built-in Tools reference

A few example calls:

json
{
  "name": "web_search",
  "arguments": { "query": "React hooks tutorial" }
}
json
{
  "name": "browser_action",
  "arguments": {
    "sessionId": "session-123",
    "action": "click",
    "ref": 5
  }
}
json
{
  "name": "execute_workflow",
  "arguments": {
    "workflowId": "Daily standup",
    "inputs": { "email": "user@example.com" },
    "goal_id": "goal-abc"
  }
}

Some tools require approval

15 tools — including send_message, send_payment, stratos_transfer, execute_sql, and create_app — pause for human confirmation before running. See the approval list.

Tool Message Format ​

Tool Call (from AI) ​

json
{
  "role": "assistant",
  "content": null,
  "toolCalls": [
    {
      "id": "tc_abc123",
      "name": "search_knowledge",
      "arguments": {"query": "React tutorials"}
    }
  ]
}

Tool Result (from system) ​

json
{
  "role": "tool",
  "toolCallId": "tc_abc123",
  "content": "{\"results\": [...]}"
}

Streaming Tool Events ​

During streaming, tool execution emits SSE events:

tool_call Event ​

Sent when a tool is being called:

event: message
data: {"type": "tool_call", "id": "tc_abc123", "name": "search_knowledge", "arguments": {"query": "React"}}

tool_result Event ​

Sent when tool execution completes:

event: message
data: {"type": "tool_result", "toolCallId": "tc_abc123", "name": "search_knowledge", "success": true, "result": [...], "resultPreview": "Found 5 results"}

For errors:

event: message
data: {"type": "tool_result", "toolCallId": "tc_abc123", "success": false, "error": "Search failed"}

Loop Detection ​

The system prevents infinite loops by:

  1. Tracking recent calls: Last 10 tool calls are remembered
  2. Argument comparison: Detects same tool with same arguments
  3. Maximum repeats: Same call allowed maximum 2 times
  4. Force exit: Loop terminates after 8 iterations regardless

Enabling Tools ​

Via API Request ​

json
{
  "content": "Search for React tutorials",
  "enableTools": true
}

Via Agent Settings ​

Configure in agent settings:

json
{
  "tools_enabled": true,
  "enabled_tools": ["search_knowledge", "browse_url", "execute_sql"]
}

Via Chat Interface ​

Toggle the "Tools" switch in the chat settings panel.

Custom Tools via MCP ​

Add custom tools using MCP (Model Context Protocol) servers:

json
{
  "type": "mcp",
  "config": {
    "server_id": "my-mcp-server",
    "tool_name": "custom_search"
  }
}

See MCP Servers for configuration details.

OpenAPI Auto-Import ​

Import all endpoints from an OpenAPI 3.x JSON spec as callable HTTP tools, grouped under a tool cluster.

Importing via UI ​

  1. Go to Tools page
  2. Click Import OpenAPI
  3. Enter the spec URL (e.g. https://api.example.com/openapi.json)
  4. Optionally set a cluster name (defaults to the spec's info.title)
  5. All paths and operations become individual HTTP tools

Importing via API ​

bash
POST /api/agents/{id}/tools/import-openapi
Content-Type: application/json

{
  "url": "https://api.example.com/openapi.json",
  "cluster": "my_api"
}

Returns the list of created tools.

Agent Self-Provisioning ​

The agent can import APIs during conversation using the built-in import_openapi_tools tool:

User: "I need to work with the Petstore API"

Agent: [calls import_openapi_tools]
       url: "https://petstore.swagger.io/v2/swagger.json"
       cluster: "petstore"

Agent: "I've imported 20 Petstore API endpoints. What would you like to do?"

User: "Look up pet #42"

Agent: [calls call_custom_tool]
       toolName: "getPetById"
       arguments: { "id": 42 }

The agent can also remove imported APIs with delete_tool_cluster.

Tool Clusters ​

Imported tools are grouped by cluster name. On the Tools page, clusters appear as collapsible sections:

▼ Petstore API (3 tools)
  GET  /pets           - List all pets
  GET  /pets/{petId}   - Get pet by ID
  POST /pets           - Create a pet

▼ Ungrouped (2 tools)
  my_webhook_tool
  my_js_function

Each cluster can be deleted as a group, removing all tools in the cluster at once.

How Imported Tools Execute ​

Imported HTTP tools use baseUrl + pathTemplate from the OpenAPI spec:

  • Path parameters are substituted: /pets/{petId} becomes /pets/42
  • Query parameters are appended for GET requests
  • GET/HEAD requests don't send a body
  • POST/PUT/PATCH requests send non-path arguments as JSON body

Trigger Keywords ​

The custom tools category (including OpenAPI import) activates when messages contain: openapi, import api, api spec, swagger, custom tool, mcp, tool call

Tool Schema ​

Define tools with JSON Schema parameters:

json
{
  "name": "get_weather",
  "description": "Get current weather for a location",
  "parameters": {
    "type": "object",
    "properties": {
      "location": {
        "type": "string",
        "description": "City name or coordinates"
      },
      "units": {
        "type": "string",
        "enum": ["celsius", "fahrenheit"],
        "default": "celsius"
      }
    },
    "required": ["location"]
  }
}

Result Handling ​

Truncation ​

Large results are automatically truncated:

  • Results over 10,000 characters are truncated
  • Base64 images are replaced with placeholders
  • Preview text is generated for UI display

Screenshot Results ​

Browser tools that return screenshots:

  • Full image stored in R2
  • Placeholder added to context: [image displayed above]
  • URL provided for display in UI

Best Practices ​

1. Clear Tool Descriptions ​

Write descriptive tool descriptions:

json
{
  "description": "Search the product catalog by name, category, or price range. Returns up to 10 matching products with name, price, and availability."
}

2. Validate Parameters ​

Define required fields and types:

json
{
  "parameters": {
    "properties": {
      "email": {
        "type": "string",
        "format": "email",
        "description": "Customer email address"
      }
    },
    "required": ["email"]
  }
}

3. Handle Errors Gracefully ​

Return clear error messages:

json
{
  "error": "Product not found",
  "code": "NOT_FOUND",
  "suggestion": "Try searching with a different query"
}

4. Limit Tool Count ​

Only enable needed tools:

  • Reduces model confusion
  • Faster response times
  • Lower token costs
  • Better focused responses

5. Monitor Tool Usage ​

Track tool metrics:

  • Call frequency
  • Success/failure rates
  • Response times
  • Token usage per tool

Code Mode ​

Code Mode allows the LLM to write JavaScript code instead of making structured tool calls. This is more efficient for complex, multi-step tasks.

Why Code Mode? ​

Traditional tool calling requires one LLM round-trip per tool call. With Code Mode:

  • Fewer API calls - Multiple tool calls in one code block
  • Better orchestration - Use JavaScript for loops, conditionals, error handling
  • More natural - LLM can express complex logic as code
  • Cost savings - Reduced token usage and latency

Code Mode Options ​

Configure in Settings → Prompts → Code Mode:

ModeDescriptionToken Impact
OffTraditional tool calling onlyBaseline
SelectiveTools as TypeScript hints (~2,400 tokens)-77% vs Full
FullComplete TypeScript API (~10,700 tokens)+8x vs Selective

How It Works ​

When Code Mode is enabled, the LLM can respond with JavaScript code blocks:

javascript
// Example: Browse a site and take action
const page = await tools.browseUrl({ url: "https://example.com" });
const analysis = await tools.analyzePage({
  sessionId: page.sessionId,
  task: "find login button"
});
await tools.browserAction({
  sessionId: page.sessionId,
  action: "click",
  selector: analysis.selector
});

Available Functions ​

The tools object exposes all agent tools as async functions:

typescript
// Memory
await tools.remember({ key: "user_preference", value: "dark mode" });
await tools.recall({ key: "user_preference" });

// Browser
await tools.browseUrl({ url: "https://...", task: "..." });
await tools.browserAction({ action: "click", selector: "#btn" });

// Knowledge
await tools.searchKnowledge({ query: "..." });

// Workflows
await tools.executeWorkflow({ workflowId: "...", inputs: {} });

// Messaging
await tools.sendMessage({ gateway: "telegram", recipient: "...", message: "..." });

// And more...

Code Execution Sandbox ​

Code runs in a secure sandbox with:

  • Limited scope (only tools and console available)
  • No access to fetch, globals, or environment
  • Async execution with timeout limits
  • Captured logs and tool call history

Selective Tool Loading ​

By default, all 30+ tools are included in every prompt, consuming ~10,000 tokens. Selective loading dramatically reduces this.

How It Works ​

Tools are grouped into categories, and categories are loaded per message:

User: "Search for React tutorials"
       → Loads: search_knowledge, browse_url

User: "Send a message to John on Telegram"
       → Loads: send_message, list_gateways, get_contact

User: "Run my daily report workflow"
       → Loads: execute_workflow, list_workflows, get_workflow_status

The decision is made by the typed decision model (Cloudflare Clef by default): for every enabled category it answers "does this message need these tools?", using the latest message, the last three turns and the recently used tool names, and the categories at or above the threshold (default 0.6) are loaded. The regex trigger table below is the fallback — used when routing is switched off, when the decision model times out, or — only when typesafe/jev is pinned — when the data-flow policy withholds the partner model for a customer conversation. Which model answers a tool turn is governed by the model policy (tool role). Agents with a connected MCP server always get the custom category, so a confirmation turn with no trigger word can still reach call_custom_tool.

Every routing decision is recorded in the decision log with the per-category probabilities.

Trigger Keywords (fallback) ​

Tool CategoryTrigger Keywords
Browserbrowse, visit, go to, navigate, click, page, website
Memoryremember, recall, forget, memory, preference
Knowledgesearch, find, knowledge, document, vector
Workflowsworkflow, run, execute, trigger, automate
Messagingsend, message, email, telegram, notify
Calendarcalendar, schedule, event, reminder, task
SQLquery, database, sql, select, table
Custom / OpenAPIcustom tool, mcp, tool call, openapi, import api, swagger

Token Savings ​

ConfigurationApproximate Tokens
All tools~10,700
Selective (typical)~2,400
Minimal context~800

Configuration ​

Enable selective loading in Settings → Prompts:

json
{
  "codeMode": "selective",
  "toolLoadingMode": "context-aware"
}

The routing model itself is controlled in Prompt Studio → Tool Categories → Tool routing:

json
{
  "toolRouting": {
    "jev": true,
    "threshold": 0.6
  }
}

jev: false falls back to regex triggers only. The Code Mode selective prompt path goes through the same router.

Example: Multi-Tool Interaction ​

User: "Find React tutorials and summarize the best one"

The AI will:

  1. Iteration 1: Call search_knowledge

    json
    {"name": "search_knowledge", "arguments": {"query": "React tutorials"}}
  2. Result: Returns list of tutorials

  3. Iteration 2: Call browse_url for top result

    json
    {"name": "browse_url", "arguments": {"url": "https://example.com/react-guide"}}
  4. Result: Returns page content

  5. Final Response: AI synthesizes information and returns summary

API Reference ​

Get Available Tools ​

bash
GET /api/agents/{id}/chat/tools

Response:

json
{
  "tools": [
    {
      "name": "search_knowledge",
      "description": "Search the knowledge base",
      "parameters": {...}
    },
    ...
  ]
}

See Tools API for complete endpoint documentation.