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

# Agents

> Define LLM agents in the BRICKS Buttress config, give them local functions as tools, and drive them from functions, HTTP, or the CLI

An agent is an LLM loop you declare in the [BRICKS Buttress](/buttress) server config. It runs **inside the server process**, uses [local functions](/buttress/functions) (and MCP servers) as its tools, and keeps its conversation history in session files on disk.

The primary consumer is automation: a local function or daemon calls `context.agents.run(...)` to add multi-step reasoning to a server-side workflow. An interactive CLI drives and inspects the same agents.

<Warning>
  Agents are experimental. The `[[agents]]` and `[agents_options]` config keys, the `/agents` endpoints, the SSE event shape, and the `context.agents` API may change between releases without a deprecation window. The server prints this notice at startup. Pin your `bricks-buttress` version if you build on it, and re-read this page after upgrading.
</Warning>

## Define an agent

Agents are off until the config declares at least one `[[agents]]` table:

```toml theme={null}
[[agents]]
name = "ops-assistant"                            # unique; also the session scope key
model = "buttress/ggml-org/gpt-oss-20b-GGUF"      # provider/model-id, split on the FIRST slash
system_prompt = "You are an ops automation agent. Use the provided tools."
# system_prompt_file = "./prompts/ops.md"         # or a file; not both
tools = ["get-server-status", "restart-service"]  # local function names
max_turns = 30
# max_tokens_per_run = 200000
# temperature = 0.2                               # unrecognized keys pass through to generation

[agents.mcp_servers.github]                       # optional MCP servers
url = "https://api.githubcopilot.com/mcp/"
# optional = true
```

| Key                           | Type      | Default   | Description                                                                                                                                                  |
| ----------------------------- | --------- | --------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `name`                        | string    | —         | Required. Unique across the config, and the scope key for the agent's sessions. Letters, digits, `-`, and `_`, up to 64 characters                           |
| `model`                       | string    | —         | Required. `provider/model-id`, split on the **first** slash. See [Models](#models)                                                                           |
| `system_prompt`               | string    | —         | Inline system prompt                                                                                                                                         |
| `system_prompt_file`          | string    | —         | Path to a system prompt file; relative paths resolve against the config file, and `~` expands to the home directory. Mutually exclusive with `system_prompt` |
| `tools`                       | string\[] | `[]`      | Local function names, listed explicitly — there is no wildcard. See [Tools](#tools)                                                                          |
| `max_turns`                   | integer   | `30`      | Assistant↔tool round-trips per run                                                                                                                           |
| `max_tokens_per_run`          | integer   | unlimited | Per-run token budget                                                                                                                                         |
| `[agents.mcp_servers.<name>]` | table     | `{}`      | MCP servers to add as tools. See [MCP servers](#mcp-servers)                                                                                                 |

Any key not listed above passes through to generation, so `temperature`, `top_p`, and friends work as written.

Agent definitions are validated at **startup**, not on first run: an unknown `buttress/` model, a missing prompt file, a malformed tool name, or a duplicate agent name stops the server with an error rather than silently dropping the agent.

### Global options

`[agents_options]` applies to every agent:

```toml theme={null}
[agents_options]
# sessions_dir = "./.buttress-agent/sessions"
# session_max_age = "30d"
# session_max_count = 500
# max_depth = 2
# allow_unauthenticated = false
```

| Key                     | Type             | Default                        | Description                                                                                                     |
| ----------------------- | ---------------- | ------------------------------ | --------------------------------------------------------------------------------------------------------------- |
| `sessions_dir`          | string           | `"./.buttress-agent/sessions"` | Where session files live. Relative paths resolve against the config file, and `~` expands to the home directory |
| `session_max_age`       | number or string | `"30d"`                        | Retention sweep by age, in ms or as a duration string. `0` disables                                             |
| `session_max_count`     | integer          | `500`                          | Retention sweep by count, per agent. `0` disables                                                               |
| `max_depth`             | integer          | `2`                            | Cap on chained agent invocations — function → agent → function → agent                                          |
| `allow_unauthenticated` | boolean          | `false`                        | Serve `/agents` on an **unbound** server. See [Security](#security)                                             |

## Models

`model` is `provider/model-id`, split on the first slash — so the model id itself may contain slashes.

**`buttress/<repo_id>`** targets a model this server already hosts. The id must match the `repo_id` of a configured `ggml-llm` or `mlx-llm` [`[[generators]]`](/buttress/configuration#generators) entry, and the server checks that at startup. Traffic reaches the generator through an in-process loopback — no socket, and no extra configuration. Enabling `[openai_compat]` is not required; that key still governs only the external HTTP route.

```toml theme={null}
[[generators]]
type = "ggml-llm"

[generators.model]
repo_id = "ggml-org/gpt-oss-20b-GGUF"

[[agents]]
name = "ops-assistant"
model = "buttress/ggml-org/gpt-oss-20b-GGUF"
```

**Any other prefix** is a hosted provider — `anthropic/…`, `openai/…`, `google/…` — authenticated by that provider's usual environment variable (`ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, …). Set it in `[env]` or in the process environment.

```toml theme={null}
[env]
ANTHROPIC_API_KEY = "sk-ant-…"

[[agents]]
name = "reviewer"
model = "anthropic/claude-sonnet-5"
```

<Note>
  OAuth-based provider logins are not supported. A hosted provider needs an API key in the environment.
</Note>

## Tools

`tools` lists local function names explicitly. Each tool call runs through the normal functions executor, so a tool gets the same lazy reload, scratch directory, spawn tracking, and `meta.timeout` deadline it would get over HTTP or MCP — and the function's `meta.parameters` JSON Schema is what the model sees.

Aborting a run aborts its in-flight tool calls and the processes those calls spawned.

A listed function that does not exist **fails the run** rather than letting a headless automation improvise around a missing capability.

### MCP servers

`[agents.mcp_servers.<name>]` adds an MCP server's tools alongside the local functions. Give each server exactly one of `url` (Streamable HTTP) or `command` (stdio):

```toml theme={null}
[agents.mcp_servers.github]
url = "https://api.githubcopilot.com/mcp/"
headers = { Authorization = "Bearer ghp_…" }

[agents.mcp_servers.local]
command = "npx"
args = ["-y", "@modelcontextprotocol/server-filesystem", "/srv/data"]
# env = { LOG_LEVEL = "debug" }
optional = true
```

MCP tools are exposed under server-qualified names — `mcp__github__create_issue` — so they cannot collide with local function names. Servers connect lazily on the first run that needs them, and a server that will not connect **fails the run** unless it is marked `optional = true`, in which case the agent runs without its tools.

## Run an agent

### From a local function

This is the primary surface. Every configured agent is reachable through `context.agents`:

```ts theme={null}
export const meta = {
  description: 'Ask the ops agent to investigate a service.',
  timeout: '10m',
}

export default async ({ service }: { service: string }, context: ButtressFunctionContext) => {
  const result = await context.agents.run('ops-assistant', {
    prompt: `Investigate the '${service}' service and fix it if needed.`,
  })
  return { conclusion: result.content, sessionId: result.sessionId }
}
```

| Member                              | What it does                                                                                                                 |
| ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------------- |
| `agents.run(name, options)`         | Run a prompt to completion → [`AgentRunResult`](#run-result). Options: `prompt`, `sessionId`, `fork`, `onEvent`, `onSession` |
| `agents.list()`                     | Configured agent names                                                                                                       |
| `agents.sessions(name, { limit? })` | Newest-first [session summaries](#sessions)                                                                                  |

A run inherits the calling function's lifetime and deadline: `context.signal` — the function's `meta.timeout` expiring, or the caller disconnecting — aborts the run and its tool calls. Agent loops are slower than ordinary function calls, so raise `meta.timeout`, or move the work into a **daemon**, which has no deadline.

Agent progress mirrors onto the function's own SSE stream as `agent` events, so an SSE caller of the *function* sees the agent working without extra wiring.

Every method throws when the server has no `[[agents]]` configured.

### Over HTTP

| Endpoint                                  | Purpose                                                                                  |
| ----------------------------------------- | ---------------------------------------------------------------------------------------- |
| `GET /agents`                             | Configured agent names → `{ "agents": [...] }`                                           |
| `POST /agents/<name>/run`                 | Run a prompt. Body is `{ prompt, sessionId?, fork? }`; the response is `{ "result": … }` |
| `POST /agents/<name>/run?stream=1`        | The same call as SSE — `agent` progress events, then the result                          |
| `GET /agents/<name>/sessions`             | Newest-first session summaries. `?limit=` caps the list                                  |
| `GET /agents/<name>/sessions/<id>`        | Full transcript → `{ "messages": [...] }`                                                |
| `POST /agents/<name>/sessions/<id>/abort` | Abort the session's active run → `{ "aborted": true \| false }`                          |

```bash theme={null}
curl -X POST http://localhost:2080/agents/ops-assistant/run \
  -H 'Authorization: Bearer <workspace-access-token>' \
  -H 'Content-Type: application/json' \
  -d '{"prompt": "Which services are unhealthy?"}'
```

A failed run returns `AGENT_RUN_FAILED` with the `sessionId` attached, so the transcript is still reachable. An aborted run answers `499`; other failures answer `500`. Unknown agents and sessions answer `404 NOT_FOUND`.

Streaming responses carry slimmed agent events: partial assistant messages and full tool results are omitted, because they would dwarf the stream. Fetch the transcript from the sessions endpoint when you need the complete record.

### From the CLI

`bricks-buttress agent` is a streaming chat client for an agent on a **running** server. Point it at the same config the server uses and it finds the port and the local token itself:

```sh theme={null}
bricks-buttress agent -c config.toml                                # list agents
bricks-buttress agent ops-assistant -c config.toml                  # chat
bricks-buttress agent ops-assistant --sessions -c config.toml       # list sessions
bricks-buttress agent ops-assistant --session <id> -c config.toml   # continue a session
bricks-buttress agent ops-assistant --fork <id> -c config.toml      # fork, then continue the fork
```

| Option                | Description                                                             |
| --------------------- | ----------------------------------------------------------------------- |
| `-c, --config <path>` | Server config file — used to find the port and the local runtime token  |
| `--url <url>`         | Server base URL. Defaults to the config's, else `http://127.0.0.1:2080` |
| `--token <token>`     | Access token. Defaults to the config's runtime token file               |
| `--session <id>`      | Continue an existing session                                            |
| `--fork <id>`         | Fork a session, then continue the fork                                  |
| `--sessions`          | List the agent's sessions and exit                                      |

The chat streams text, thinking, and tool calls as they happen. In the chat, `/exit` quits, `/new` starts a fresh session, and <kbd>Ctrl</kbd>+<kbd>C</kbd> aborts the current run — a second <kbd>Ctrl</kbd>+<kbd>C</kbd> quits.

### Run result

`context.agents.run` and `POST /agents/<name>/run` both resolve to:

| Field              | Description                                                           |
| ------------------ | --------------------------------------------------------------------- |
| `sessionId`        | The session this run wrote to — pass it back to continue              |
| `content`          | The agent's final answer                                              |
| `reasoningContent` | The thinking text, when the model produced any                        |
| `usage`            | `{ input, output, cacheRead, totalTurns }`, aggregated across the run |
| `stopReason`       | Why the loop ended                                                    |

`stopReason` is one of `end_turn` (the agent answered), `max_turns`, `token_budget`, `aborted`, or `error`. Treat anything but `end_turn` as an incomplete answer.

## Sessions

Every run belongs to a session. Omit `sessionId` and the run starts a new one; pass it back to continue the conversation; add `fork: true` alongside it to branch into a fresh session that records its parent, leaving the original untouched.

Sessions are JSONL files under `sessions_dir`, scoped by agent name, and written **as the run streams** — so a run that was aborted or timed out still leaves a continuable transcript.

Runs on the same session queue behind each other; different sessions run in parallel.

A retention sweep prunes sessions by age (`session_max_age`) and count (`session_max_count`, per agent).

<Note>
  Sessions are scoped by the agent's `name`. Renaming an agent orphans its existing sessions — they stay on disk but the renamed agent will not list or continue them.
</Note>

## Security

`/agents` authenticates like the [functions surface](/buttress/functions#security), not like the open inference endpoints:

* A **bound** server requires a workspace access token. See [workspace binding](/buttress/workspace-binding).
* An **unbound** server rejects remote calls entirely, unless `[agents_options] allow_unauthenticated = true`.
* Cross-site browser requests are always rejected.

At startup the server writes an ephemeral **runtime token** to a `runtime-token` file next to `sessions_dir`, with mode `0600`. Same-host tools — the CLI above — read it and authenticate automatically, bound or not.

<Warning>
  `allow_unauthenticated = true` lets anyone who can reach the port run every agent, and therefore every tool you gave it. Only do this on a trusted network.
</Warning>

Agent definitions are **trusted input**, exactly like the config file and function files. An agent is only as safe as the functions and MCP servers you hand it: a model is choosing when to call them, so give an agent the narrowest tool list that does its job.

A bound server cannot mint workspace tokens for itself — the loopback to `buttress/` models is authorized by an internal token instead, and never by workspace credentials.

## Samples

The server package ships `config/function-samples/run-agent.ts`, a local function that drives a configured agent and returns its conclusion, session id, turn count, and stop reason. `config/sample.toml` carries a commented `[[agents]]` block. See [local functions](/buttress/functions#samples) for the rest of the samples.

## Next steps

<CardGroup cols={2}>
  <Card title="Local functions" icon="code" href="/buttress/functions">
    Write the functions an agent uses as its tools.
  </Card>

  <Card title="Configuration" icon="gear" href="/buttress/configuration">
    The full TOML reference, including the generators agents run on.
  </Card>
</CardGroup>
