# Tool

A **Tool** is an external capability that agents can invoke during execution. Orloj provides a standardized tool contract, multiple isolation backends, and runtime controls for timeout, retry, and risk classification.

## Defining a Tool

Tools are declared as resources that describe the tool's endpoint, auth requirements, risk level, and runtime configuration.

```yaml
apiVersion: orloj.dev/v1
kind: Tool
metadata:
  name: web_search
spec:
  type: http
  endpoint: https://api.search.com
  auth:
    secretRef: search-api-key
```

For tools that require isolation and runtime controls:

```yaml
apiVersion: orloj.dev/v1
kind: Tool
metadata:
  name: wasm-echo
spec:
  type: wasm
  wasm:
    module: path/to/echo.wasm
    entrypoint: run
    max_memory_bytes: 16777216
    fuel: 100000
    enable_wasi: true
  capabilities:
    - wasm.echo.invoke
  risk_level: low
  runtime:
    isolation_mode: wasm
    timeout: 5s
```

### Key Fields

| Field | Description |
|---|---|
| `type` | Tool type. Determines the transport and execution model. See below. |
| `endpoint` | The tool's network endpoint. |
| `capabilities` | Declared operations this tool provides. Used for permission matching. |
| `risk_level` | `low`, `medium`, `high`, or `critical`. Affects default isolation mode. |
| `runtime.isolation_mode` | Execution isolation backend (see below). |
| `runtime.timeout` | Maximum execution time. Defaults to `30s`. |
| `runtime.retry` | Retry policy for failed invocations. |
| `auth.secretRef` | Reference to a [Secret](./secret.md) resource for tool authentication. |

## Tool Types

`Tool.spec.type` determines how the runtime communicates with the tool. Seven types are supported:

| Type | Transport | Contract | Use case |
|---|---|---|---|
| `http` | HTTP POST to `endpoint` | Raw body or `ToolExecutionResponse` | Simple API integrations. Default when omitted. |
| `external` | HTTP POST to `endpoint` | Strict `ToolExecutionRequest` / `ToolExecutionResponse` | Tools running as standalone microservices that need the full execution context. |
| `grpc` | Unary gRPC call to `endpoint` | `ToolExecutionRequest` / `ToolExecutionResponse` as JSON over `orloj.tool.v1.ToolService/Execute` | Teams that prefer gRPC for tool communication. |
| `webhook-callback` | HTTP POST to `endpoint`, then poll `{endpoint}/{request_id}` | `ToolExecutionRequest` / `ToolExecutionResponse` | Long-running tools, batch jobs, or tools that require human-in-the-loop steps. |
| `mcp` | JSON-RPC 2.0 via stdio or HTTP | MCP `tools/call` / `tools/list` | Tools exposed by MCP servers. Auto-generated by the [McpServer](./mcp-server.md) controller. |
| `cli` | execve (container or direct) | stdout / stderr captured as result | Local binaries (`kubectl`, `gh`, `aws`, etc.) run under Orloj's isolation model. See [CLI Tools](./cli-tool.md). |
| `wasm` | Embedded wazero runtime | stdin/stdout JSON contract | Tools compiled to WebAssembly, executed in-process with host-enforced resource limits. |

All types flow through the same governed runtime pipeline -- policy enforcement, retry, timeout, auth injection, and error taxonomy behave identically regardless of tool type. Unknown type values are rejected at apply time.

### MCP

The `mcp` type represents tools provided by an MCP (Model Context Protocol) server. These tools are auto-generated by the [McpServer](./mcp-server.md) controller -- you do not create them manually. Each `type=mcp` tool carries `mcp_server_ref` (the McpServer that owns it) and `mcp_tool_name` (the tool name on the MCP server).

At invocation time, the `MCPToolRuntime` resolves the server reference, obtains a session from the `McpSessionManager`, and sends a `tools/call` JSON-RPC 2.0 request through the appropriate transport (stdio or Streamable HTTP).

MCP tools also carry `description` and `input_schema` from the MCP server's `tools/list` response. These are propagated to the model gateway so the LLM receives rich, structured tool definitions instead of generic parameter schemas.

See the [Connect an MCP Server](../../guides/connect-mcp-server.md) guide for a complete walkthrough.

### CLI

The `cli` type invokes a local binary (`kubectl`, `gh`, `aws`, `jq`, etc.) using an execve-style call — no shell involved. Each entry in `cli.args` is a Go `text/template` evaluated against the agent's JSON input; each template produces exactly one argv element.

CLI tools default to `container` isolation regardless of `risk_level`, running the binary inside a Docker container specified by `cli.image`. Credentials are injected as environment variables via `cli.env_from` (referencing Secret resources); `spec.auth` is not supported for this type.

See [Define a CLI Tool](./cli-tool.md) for a complete reference and examples.

### HTTP (default)

The `http` type sends the agent's tool input as an HTTP POST body to `spec.endpoint`. The runtime accepts both raw text responses and structured `ToolExecutionResponse` JSON envelopes. Auth is injected as an `Authorization: Bearer` header when `auth.secretRef` is configured.

### External

The `external` type sends the full `ToolExecutionRequest` contract envelope as JSON to `spec.endpoint` and expects a `ToolExecutionResponse` back. This gives the external service access to the full execution context (task ID, agent, namespace, trace IDs, attempt number). Use this when your tool needs to be aware of the Orloj execution context.

### gRPC

The `grpc` type calls `orloj.tool.v1.ToolService/Execute` as a unary gRPC method on `spec.endpoint`, using a JSON codec. The request and response payloads are the same `ToolExecutionRequest` / `ToolExecutionResponse` envelopes as `external`. Use this when your tool infrastructure is gRPC-native.

### Webhook-Callback

The `webhook-callback` type supports asynchronous tool execution:

1. The runtime POSTs a `ToolExecutionRequest` to `spec.endpoint`.
2. The tool returns `202 Accepted` (or `200 OK` with an immediate result).
3. If `202`: the runtime polls `{endpoint}/{request_id}` at regular intervals until a `ToolExecutionResponse` with a terminal status arrives, or the configured timeout expires.

This is useful for tools that take minutes to complete (e.g., batch processing, code review, CI pipeline triggers) or that require external approval before returning a result.

## Isolation Modes

Isolation modes control the execution boundary of a tool, independent of tool type.

| Mode | Description | Default for |
|---|---|---|
| `none` | Direct execution in the worker process. The `http` type makes real HTTP calls; other types use their respective transports. | `low` and `medium` risk tools |
| `sandboxed` | Restricted container execution with secure defaults: read-only filesystem, no capabilities, no privilege escalation, no network, non-root user, memory/CPU/pids limits. | `high` and `critical` risk tools |
| `container` | Each tool invocation runs in an isolated container. Full filesystem and network isolation. | Explicitly configured |
| `kubernetes` | Executes the tool as an ephemeral Kubernetes Job. Requires `--tool-k8s-enabled=true`. The tool's `spec.cli.image`, `command`, `args`, and `resources` are mapped to the Job's Pod spec. | Explicitly configured |
| `wasm` | Tool runs as a WebAssembly module with a host-guest stdin/stdout contract. Memory-safe and deterministic. | Explicitly configured |

The isolation mode defaults are based on `risk_level`:
- `low` or `medium` risk: defaults to `none`
- `high` or `critical` risk: defaults to `sandboxed`

You can always override the default by setting `runtime.isolation_mode` explicitly.

### Sandboxed Defaults

When `isolation_mode` is `sandboxed`, the container backend enforces these secure defaults:

- `--read-only` filesystem
- `--cap-drop=ALL` (no Linux capabilities)
- `--security-opt no-new-privileges`
- `--network none` (no network access)
- `--user 65532:65532` (non-root)
- `--memory 128m`
- `--cpus 0.50`
- `--pids-limit 64`

These defaults can be overridden with `--tool-container-*` flags on `orlojd` and `orlojworker`, but the default posture is restrictive.

## Tool Contract v1

Every tool interaction follows a standardized request/response envelope. This contract ensures tools are portable, testable, and observable regardless of the isolation backend.

**Request envelope** (sent to the tool):
```json
{
  "request_id": "req-abc-123",
  "tool": "web_search",
  "action": "invoke",
  "parameters": {
    "query": "enterprise AI adoption trends"
  },
  "auth": {
    "type": "bearer",
    "token": "sk-..."
  },
  "context": {
    "task": "weekly-report",
    "agent": "research-agent",
    "attempt": 1
  }
}
```

**Response envelope** (returned from the tool):
```json
{
  "request_id": "req-abc-123",
  "status": "success",
  "result": {
    "data": "..."
  }
}
```

**Error response:**
```json
{
  "request_id": "req-abc-123",
  "status": "error",
  "error": {
    "tool_code": "rate_limited",
    "tool_reason": "API rate limit exceeded",
    "retryable": true
  }
}
```

The tool contract defines a canonical error taxonomy with `tool_code`, `tool_reason`, and `retryable` fields, enabling the runtime to make intelligent retry decisions.

## WASM Tools

WASM tools run as WebAssembly modules inside an embedded [wazero](https://wazero.io) runtime. No external binary is required -- the runtime is pure Go and always available regardless of `--tool-isolation-backend`.

WASM tools communicate over stdin/stdout using a JSON contract (v1). The host writes the request to the module's stdin and reads a JSON response from stdout. This provides memory-safe, sandboxed execution with no filesystem or network access unless WASI is explicitly enabled.

### Per-Tool Configuration

Each WASM tool declares its module and resource limits in `spec.wasm`. The `module` field accepts a local path, HTTPS URL, or OCI artifact reference:

```yaml
apiVersion: orloj.dev/v1
kind: Tool
metadata:
  name: wasm-echo
spec:
  type: wasm
  wasm:
    module: path/to/echo.wasm        # Local path, HTTPS URL, or oci://... reference
    entrypoint: run                    # Exported function (default: run)
    max_memory_bytes: 67108864         # 64 MB (default)
    fuel: 1000000                      # Execution fuel limit (default: 1M)
    enable_wasi: true                  # Enable stdin/stdout/stderr via WASI
    image_pull_secret: ghcr-creds      # Optional: Secret for private OCI registries
  capabilities:
    - wasm.echo.invoke
  risk_level: low
  runtime:
    isolation_mode: wasm
    timeout: 5s
```

### Writing a WASM Tool

Any language that compiles to WebAssembly can produce a WASM tool. The simplest path is Go with `GOOS=wasip1 GOARCH=wasm`:

```go
package main

import (
    "encoding/json"
    "io"
    "os"
)

func main() {
    data, _ := io.ReadAll(os.Stdin)
    var req struct {
        Input string `json:"input"`
    }
    _ = json.Unmarshal(data, &req)
    resp := map[string]string{
        "contract_version": "v1",
        "status":           "ok",
        "output":           req.Input,
    }
    _ = json.NewEncoder(os.Stdout).Encode(resp)
}
```

Build with: `GOOS=wasip1 GOARCH=wasm go build -o tool.wasm tool.go`

### Resource Limits

Memory and fuel limits are enforced by the host runtime, not by the guest module:

- **Memory**: `max_memory_bytes` limits the WASM linear memory. Defaults to 64 MB.
- **Fuel**: `fuel` limits execution steps. Prevents runaway modules from consuming unbounded CPU.
- **WASI**: When `enable_wasi` is false, the module has no access to stdin/stdout/stderr. Most tools require WASI enabled.

### Coexistence with Containers

WASM tools have their own dedicated runtime slot and work independently of `--tool-isolation-backend`. You can run WASM tools and container-isolated tools in the same agent system without conflict.

### CLI Development Tools

`orlojctl` provides scaffold and test commands for WASM tool development:

- **`orlojctl tool scaffold <name> --lang go|rust`** generates a ready-to-build project with guest code, Makefile, tool manifest, and test fixtures.
- **`orlojctl tool test <module.wasm> --fixtures <dir>`** runs the module against JSON test fixtures and validates contract compliance, expected output, and resource budgets.

For a complete walkthrough including the full stdin/stdout contract specification, error handling, multi-language examples, and local testing, see the [Build a WASM Tool](../../guides/build-wasm-tool.md) guide.

## Error Taxonomy

Tool failures use a canonical error taxonomy with three fields:

| Field | Purpose |
|---|---|
| `tool_code` | Machine-readable error code (e.g. `rate_limited`, `unsupported_tool`, `secret_resolution_failed`) |
| `tool_reason` | Human-readable explanation |
| `retryable` | Whether the runtime should retry the invocation |

HTTP status codes are mapped automatically: `429` and `5xx` are retryable, `4xx` are not. HTTP `401` maps to `auth_invalid` and `403` maps to `auth_forbidden` -- both non-retryable. All tool types share the same taxonomy, so policy and observability behave consistently.

## Auth Profiles

Tools support four authentication profiles via `spec.auth.profile`:

| Profile | Secret format | Injection |
|---------|--------------|-----------|
| `bearer` (default) | Single token value | `Authorization: Bearer <token>` |
| `api_key_header` | Single key value | Custom header via `spec.auth.headerName` |
| `basic` | `username:password` | `Authorization: Basic <base64>` |
| `oauth2_client_credentials` | Multi-key secret with `client_id` and `client_secret` | Token exchange at `spec.auth.tokenURL`, then bearer injection |

When `spec.auth.secretRef` is set without an explicit profile, the default is `bearer` for backward compatibility.

### Secret Rotation

Secret resolution is performed fresh per tool invocation -- there is no caching of raw secret values. If a secret is rotated between invocations, the new value takes effect on the next call without requiring a restart.

For `oauth2_client_credentials`, access tokens are cached with a TTL derived from the token endpoint's `expires_in` response. Tokens are evicted on expiry or when the tool endpoint returns HTTP 401, triggering a fresh token exchange.

## Retry and Timeout

Each tool can configure its own retry policy independently of the task-level retry:

```yaml
runtime:
  timeout: 5s
  retry:
    max_attempts: 3
    backoff: 1s
    max_backoff: 30s
    jitter: full
```

Retry uses capped exponential backoff. The `jitter` field controls randomization: `none` (deterministic), `full` (random between 0 and backoff), or `equal` (half deterministic, half random).

## Operation Classes

Tools can declare operation classes via `spec.operation_classes` (e.g. `read`, `write`, `delete`, `admin`). When omitted, the default is `["read"]` for low/medium risk tools and `["write"]` for high/critical risk tools.

Operation classes are used by `ToolPermission.spec.operation_rules` to define per-class policy verdicts:

- **allow**: proceed with the tool call (default).
- **deny**: block the call with a `permission_denied` error.
- **approval_required**: pause the task and create a [ToolApproval](../governance/tool-approval.md) resource. An external actor (human or system) must approve or deny the request before the task can continue.

When multiple rules match, the most restrictive verdict wins: `deny` > `approval_required` > `allow`.

## Governance Integration

Tool invocations are gated by the [governance layer](../governance/). An agent must have the required permissions (via AgentRole) to invoke a tool, and the tool must not be blocked by any applicable AgentPolicy. Unauthorized calls fail closed with a `tool_permission_denied` error.

## Related

- [McpServer](./mcp-server.md) -- auto-discover tools from MCP servers
- [Secret](./secret.md) -- store credentials for tool auth
- [ModelEndpoint](./model-endpoint.md) -- model provider configuration
- [Resource Reference: Tool](../../reference/resources/tool.md)
- [Guide: Build a Custom Tool](../../guides/build-custom-tool.md)
- [Guide: Build a WASM Tool](../../guides/build-wasm-tool.md)
- [Guide: Connect an MCP Server](../../guides/connect-mcp-server.md)
