> ## Documentation Index
> Fetch the complete documentation index at: https://beige.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# How Tool Calls Work

> What happens from the moment the LLM calls a tool to when the result comes back

Every action an agent takes — reading a file, running a command, querying a database — is a tool call. The gateway is the single chokepoint through which all of them flow. This page walks through exactly what happens.

***

## Two Kinds of Tool Calls

There are two distinct paths depending on which tool is called:

| Type            | Example                  | Path                                                                                                  |
| --------------- | ------------------------ | ----------------------------------------------------------------------------------------------------- |
| **Core tool**   | `exec curl https://...`  | Gateway → `docker exec` → sandbox → stdout back                                                       |
| **Plugin tool** | `exec git log --oneline` | Gateway → `docker exec` → launcher (on PATH) → socket → gateway plugin handler → socket → stdout back |

Core tools (`read`, `write`, `patch`, `exec`) execute directly via `docker exec`. Plugin tools go through an extra hop: a launcher script on `$PATH` inside the sandbox forwards the call back out through a Unix socket to the gateway, where the plugin handler runs.

***

## Core Tool Call

When the LLM calls `exec curl https://api.example.com`:

```mermaid theme={null}
sequenceDiagram
    participant LLM
    participant Gateway
    participant Docker
    participant Sandbox

    LLM->>Gateway: exec("curl https://api.example.com")
    Gateway->>Gateway: Write audit log (core_tool / exec)
    Gateway->>Docker: docker exec <container> curl https://api.example.com
    Docker->>Sandbox: Run process
    Sandbox-->>Docker: stdout / exit code
    Docker-->>Gateway: Output
    Gateway-->>LLM: Result
```

1. The LLM emits an `exec` tool call via the pi SDK
2. The gateway writes an audit log entry (`type: "core_tool"`, `tool: "exec"`)
3. The gateway runs `docker exec <container> curl https://api.example.com`
4. The process runs inside the sandbox; stdout and exit code are captured
5. The result is returned to the LLM

The sandbox has no special awareness of the gateway here — it simply runs a process.

***

## Plugin Tool Call (Launcher + Socket)

Plugin tools like `git`, `git`, or any toolkit tool take a different path. Since `/tools/bin` is on `$PATH`, the agent calls them naturally. When the LLM calls `exec git status`:

```mermaid theme={null}
sequenceDiagram
    participant LLM
    participant Gateway
    participant Docker
    participant Launcher as Launcher Script (on PATH)
    participant ToolClient as tool-client (sandbox)
    participant Socket as Socket Server (gateway)
    participant Policy as Policy Engine
    participant Handler as Plugin Handler (gateway)

    LLM->>Gateway: exec("git commit mykey value")
    Gateway->>Gateway: Write audit log #1 (core_tool / exec)
    Gateway->>Docker: docker exec <container> sh -c "git commit mykey value"
    Docker->>Launcher: Shell finds /tools/bin/git on PATH
    Launcher->>ToolClient: Invoke tool-client with tool name + args
    ToolClient->>Socket: Connect to /beige/gateway.sock, send JSON request
    Socket->>Socket: Identify agent from which socket connection
    Socket->>Policy: Check: is this agent allowed to use "git"?
    Policy-->>Socket: Allowed
    Socket->>Handler: Execute git plugin handler with args
    Handler-->>Socket: Result
    Socket-->>ToolClient: JSON response
    ToolClient-->>Launcher: Exit with result as stdout
    Launcher-->>Docker: stdout / exit code
    Docker-->>Gateway: Output
    Gateway->>Gateway: Write audit log #2 (tool / git)
    Gateway-->>LLM: Result
```

**Step by step:**

1. The LLM calls `exec git status`
2. Gateway writes **audit log entry #1** (`type: "core_tool"`, `tool: "exec"`)
3. Gateway runs the command in the sandbox — the shell finds `git` on `$PATH` at `/tools/bin/git`
4. `/tools/bin/git` is a **launcher script** generated by the gateway at startup — it's a thin shell wrapper that invokes `tool-client`
5. `tool-client` serialises the tool name and args as JSON and connects to `/beige/gateway.sock` — a Unix socket mounted read-write into the container
6. The **socket server** receives the connection; the agent's identity comes from *which socket file* was connected to, not from the payload
7. The **policy engine** checks whether this agent is permitted to invoke `git`
8. If allowed, the **plugin handler** runs on the gateway host (outside the sandbox), executes the git logic, and returns the result
9. The result travels back through the socket → `tool-client` → launcher → `docker exec` → gateway
10. Gateway writes **audit log entry #2** (`type: "tool"`, `tool: "git"`)
11. The LLM receives the result

Two audit log entries are always produced for a plugin tool call: one for the `exec` that triggered it, one for the tool invocation itself.

***

## Why the Extra Hop?

Plugin tool handlers run on the **gateway host**, not inside the sandbox. This is intentional:

* **Access to secrets** — a `git` tool needs SSH keys, which never enter the sandbox
* **Access to host resources** — a `git` tool writes to `~/.beige/data/`, which the sandbox cannot reach directly
* **Consistent identity enforcement** — the gateway always knows which agent is calling, regardless of what runs inside the container

The sandbox is an isolated execution environment. The gateway is the only entity that can reach the outside world on the agent's behalf.

***

## Policy Enforcement

The policy engine runs on every plugin tool call, before the handler executes:

* If the agent's config lists `tools: ["git", "git"]`, only those tools are permitted
* All other tool calls are rejected and the rejection is written to the audit log
* Core tools (`read`, `write`, `patch`, `exec`) are always available — they are controlled by the Docker sandbox itself

***

## Audit Log

Every tool call produces at least one audit log entry at `~/.beige/logs/gateway.log`:

```json theme={null}
{
  "ts": "2025-01-15T10:23:45.123Z",
  "agent": "assistant",
  "type": "tool",
  "tool": "git",
  "args": { "action": "set", "key": "mykey", "value": "value" },
  "decision": "allow",
  "durationMs": 12,
  "exitCode": 0
}
```

`decision` is either `"allow"` or `"deny"`. Denied calls include an `error` field explaining why.
