# Tools

> Built-in tools, HTTP tools that call your API (templating rules and network limits), and client tools your app runs — plus the policy checks every call passes first.

Tools let the agent act: hand a conversation to your team, open a ticket, look something up in your systems. There are three kinds:

| Kind | Runs where | Defined in |
|---|---|---|
| **Built-in** | Inside K-Agent | Always available; switch each one on or off in `tools.builtin` |
| **HTTP tool** (`tool_…`) | K-Agent calls your HTTPS endpoint | `POST /v1/tools`, then granted to agents in `tools.http` |
| **Client tool** | Your app runs it and sends back the result | The agent's `tools.client` |

`tools.enabled` is the master switch: turning it off removes every tool but keeps your grants and rules for later.

## Built-in tools

| Tool | Effect | Default | What it does |
|---|---|---|---|
| `transfer_to_human` | escalation | on | Hands the conversation to your team. Parameters: `reason_type` (`customer_requested` · `out_of_scope` · `complaint` · `liability` · `technical`) and `summary` (the topic, not an accusation). Returns a [truthful status](/docs/en/concepts/handoff-and-safety/#truthful-tool-results). |
| `create_ticket` | action | off | Opens ticket `TKT-<n>` with `title`, `description`, `priority` (`low` · `normal` · `high` · `urgent`) and `resolution_attempted`. Refuses when the customer already has an open ticket from the agent, and gives the model the existing number instead. |
| `flag_conversation` | escalation | off | Marks the session `flagged` with a `reason` and an `urgency` (`low` · `normal` · `high`) so your team reviews it. Sessions only. |
| `search_knowledge` | read | on | Searches the agent's `searchable` [knowledge](/docs/en/concepts/knowledge/) with an Arabic-aware `query`. Offered only when the agent has searchable sources. |

The tool descriptions carry rules measured in production: announcing an action is not doing it, ask at most one clarifying question (none for angry customers or damage claims), and record the topic rather than the accusation.

### Your rules for each tool

Every tool grant has a `rules` field (up to 4,000 characters). It is added to the tool's description under "This company's rules for this action", so you can say *when* to use it in your own words:

```json
{
  "tools": {
    "builtin": {
      "transfer_to_human": {
        "enabled": true,
        "rules": "Hand over any question about wholesale or corporate gift orders. During Ramadan, hand over requests for same-day delivery."
      },
      "create_ticket": { "enabled": true, "rules": "Open a ticket only for delivery complaints after the customer has described the problem." }
    }
  }
}
```

The dashboard offers starter rules for each tool, with the reasoning behind them.

## Effects and when tools are available

Every tool has an **effect**:

| Effect | Tools | Gated? |
|---|---|---|
| `read` | `search_knowledge`, HTTP tools that only look things up | HTTP read tools follow their `requires_verified_user` flag. |
| `escalation` | `transfer_to_human`, `flag_conversation` | **Never.** They work in one-shot calls, for anonymous users and in the playground, so the liability reply works everywhere. |
| `action` | `create_ticket`, HTTP tools that change something, client tools (by default) | Removed when the call is one-shot without `allow_actions: true`, when the end user isn't verified and `tools.allow_anonymous_actions` is off, or when the agent's `actions_paused` is on. |

The tool list is decided when a [segment](/docs/en/concepts/sessions/#history-and-segments) starts and stays fixed, sorted by name, until the next one. This keeps prompt caching effective. Inside a segment, a tool that can't succeed right now is refused with guidance rather than removed.

## The policy check

Before **any** tool executes, K-Agent checks all of the following. If one fails, the model gets `{"ok": false, "error": {"code": …, "guidance": …}}` and nothing runs:

1. The tool is in the run's tool set and `tools.enabled` is on.
2. The arguments validate strictly against the tool's JSON Schema.
3. If the tool needs a verified user, the run has one. Identity is filled in by K-Agent from the credential — the model never supplies it.
4. It is available: business hours, catalog present, no open agent ticket for `create_ticket`.
5. The same tool with the same arguments hasn't already succeeded in this run.
6. The run hasn't been cancelled or superseded.
7. Per-user limits: all HTTP tools together, 20 calls an hour per end user (or per session for anonymous users), counting every attempt; `create_ticket`, 5 a day.
8. The round budget isn't used up, and at most **5 calls** run per round (extra calls get `too_many_calls`).

The agent works for up to `tools.max_tool_rounds` rounds (1–5, default 3). If it still wants a tool after the last round, one final answer is produced with tools disabled. Confirmations — a ticket number, a handoff — come only from tool results, never from the model's imagination.

## HTTP tools

An HTTP tool calls an endpoint you own. K-Agent fills the request from a template, calls it through a protected client and shows the model only the fields you choose. The [HTTP tools guide](/docs/en/guides/http-tools/) walks through one end to end.

```json
{
  "name": "order_status",
  "description": "Look up the status of the customer's order by its number.",
  "effect": "read",
  "requires_verified_user": true,
  "config": {
    "method": "GET",
    "url": "https://api.example.com/customers/{{end_user.external_id}}/orders/{{args.order_number}}",
    "headers": { "Authorization": "Bearer {{secret.ORDERS_TOKEN}}" },
    "parameters": {
      "type": "object",
      "properties": {
        "order_number": { "type": "string", "description": "The order number, e.g. 8812" }
      },
      "required": ["order_number"],
      "additionalProperties": false
    },
    "response": {
      "items_path": "data",
      "fields": [
        { "path": "status", "label": "Status" },
        { "path": "eta", "label": "Expected delivery" }
      ],
      "max_items": 1,
      "empty_message": "No order with that number."
    },
    "timeout_ms": 8000
  }
}
```

### Templating rules

- Placeholders: `{{args.x}}` (from the model), `{{end_user.external_id}}` and `{{end_user.traits.k}}` (from the credential, verified users only), `{{secret.NAME}}` (from your secrets) and `{{var.name}}` (from the agent's variables).
- **Fail-closed:** any placeholder that is unresolved or blank aborts the call. A request is never sent with a hole in it.
- Values are escaped for where they land: URL path, query string, header or JSON body.
- The URL must start with a literal `https://host[:port]/`. Placeholders are allowed only in the path and query, and after substitution the path may not contain `.` or `..` segments.
- At most 8 parameters. The names `args`, `end_user`, `secret`, `var` and `sys` are reserved.
- Any `{{end_user.*}}` placeholder requires `requires_verified_user: true`. On such tools, parameters named like identity (`phone`, `mobile`, `email`, `user_id`, `customer_id`, `account_id`, `external_id`) are rejected with `422 identity_as_parameter` — identity comes from the credential, never from the model.
- Each parameter has a format, `x-kagent-format`:
  - `token` (default, and required for anything used in the URL or headers): letters, digits, spaces and `._@:+-`, up to 64 characters;
  - `text`: only for body parameters; up to 1,000 characters, JSON-encoded, with control, bidi and zero-width characters stripped.
- Arabic-Indic digits are converted to ASCII before checks, so `٨٨١٢` and `8812` are the same order number.
- A secret can only be sent to the hosts in its `allowed_hosts`; anything else fails with `secret_host_not_allowed`.
- Headers named `Host`, `Forwarded`, `X-Forwarded-*`, `X-Real-IP`, `Connection`, `Transfer-Encoding`, `Content-Length` or `Proxy-*` are rejected.

### Network limits

Every request to a URL you configure — HTTP tools, API knowledge sources, webhooks and custom model `base_url`s — goes through one protected client:

- `https://` only, on port 443 or 8443, with no proxy and **no redirects**;
- addresses are checked when connecting, after DNS: loopback, private ranges (10/8, 172.16/12, 192.168/16), carrier-grade NAT (100.64/10), link-local and cloud metadata (169.254/16), IPv6 unique-local and link-local, multicast, `0.0.0.0/8` and K-Agent's own hosts are all refused;
- connect timeout 3 seconds; total time up to `timeout_ms` (at most 15 seconds);
- responses over 1 MiB are rejected, not truncated.

### What the model sees

- Only the `response.fields` you list (up to 12, with labels), from up to `max_items` rows (at most 20).
- Values are cleaned — control, bidi and zero-width characters removed, Arabic joiners kept — and cut to 200 characters. The whole result stays under 4 KiB by dropping whole rows.
- The result is marked as **data, not instructions**, so text in your API can't steer the agent.
- Upstream errors reach the model only as `upstream_failed`, `unreadable` or `rate_limited`, with neutral wording. Status codes and bodies go to the run steps only.

Every call is signed with [Standard Webhooks](https://www.standardwebhooks.com/) headers (`webhook-id`, `webhook-timestamp`, `webhook-signature`) using your project's tool signing secret (`GET /v1/tool_signing_secret`), and sends `User-Agent: K-Agent/1`, so your endpoint can verify the call came from K-Agent.

HTTP tool definitions are **copied into each published version**. After editing a tool, publish the agent again for the change to reach production.

## Client tools

A client tool runs in **your** app — reading a shopping cart in the browser, opening a screen in your mobile app. You declare it in the agent:

```json
{
  "tools": {
    "client": [
      {
        "name": "get_cart",
        "description": "Returns the items in the customer's cart with prices.",
        "parameters": { "type": "object", "properties": {}, "additionalProperties": false },
        "effect": "read",
        "rules": "Check the cart before answering questions about delivery fees."
      }
    ]
  }
}
```

When the model calls it, the run pauses:

```json
{
  "status": "requires_action",
  "required_action": {
    "type": "submit_tool_outputs",
    "tool_calls": [{ "call_id": "call_4kq7", "name": "get_cart", "arguments": {} }],
    "expires_at": 1791272520
  }
}
```

Your app runs the tool and resumes the run with `POST /v1/runs/{run}/submit_tool_outputs` and `{"tool_outputs": [{"call_id": "call_4kq7", "output": {…}, "is_error": false}]}`. Every open call must be answered. If no outputs arrive within 10 minutes, the run fails with `tool_outputs_expired`. The [client tools guide](/docs/en/guides/client-tools/) shows the whole loop.

Client tool names match `^[a-z][a-z0-9_]{0,47}$`; their `effect` defaults to `action`. They are not offered on `store: false` calls.

## Names

Tool names must be unique across an agent's built-in, HTTP and client tools (`422 tool_name_conflict`) and can't reuse a built-in name. HTTP tool names match `^[a-z][a-z0-9_]{0,29}$`.

## In the test panel

The dashboard's test panel is a sandbox: read tools run live, action tools return `{"ok": true, "simulated": true}` unless you switch on **Run real actions**, and handoffs and tickets are marked as tests and never reach your Desk or webhooks.
