---
title: Agent.bx
icon: phosphor-duotone:robot
summary: "The one required file - a class that extends bx-ai's own AiAgent, so it IS the agent."
description: "The one required file - a class that extends bx-ai's own AiAgent, so it IS the agent."
tags: [conventions, configuration]
---

# Agent.bx

`Agent.bx` is the only required file in a BxAgents project. It **extends bx-ai's own [`AiAgent`](https://ai.ortusbooks.com/main-components/agents/class-based-agents)**, so it *is* the agent - the build instantiates it rather than rebuilding one from a config struct, so what you write is what runs. Inherit and add whatever you need: private helpers, overridden methods, tools registered in code. Because it's a real class rather than a struct-returning descriptor, an IDE can introspect it like any other BoxLang class - jump to definition, autocomplete on inherited methods, the works.

```javascript
class extends="bxModules.bxai.models.runnables.AiAgent" {

	function init() {
		super.init(
			name        : "my-agent",
			description : "A helpful assistant",
			instructions: "You are a helpful assistant.",
			model       : aiModel( provider: "openai", params: { model: "gpt-5" } )
		)
		return this
	}

}
```

`bxAgents new` scaffolds exactly this shape. `instructions.md` is optional - set `instructions` directly in `super.init()`, or drop the file beside `Agent.bx` and let the build wire it in for you (see the table below).

## What the build layers on top of the class

> **The rule:** an explicitly declared convention wins; otherwise the class is in charge.

So an agent that sets everything in its own `init()` gets nothing imposed on it, while one that sets the bare minimum still picks up the conventions it didn't speak for. Everything below is **optional** - declare a `configure()` returning any of these keys to override what the class itself set, or the matching `instructions.md`/`tools/`/`subagents/` convention folder:

| You declare | The build emits | If you don't declare it |
|---|---|---|
| `instructions.md` | `withInstructions( fileRead( ... ) )` | the class's own instructions stand |
| `model` in `configure()` | `setModel( aiModel( ... ) )` | the class's own model stands |
| `name` / `description` in `configure()` | `setName()` / `setDescription()` | the class's own stand |
| `memory` in `configure()` | `setMemory( ... )` | the class's own stands |
| *(nothing to declare)* | `withTools( aiToolRegistry().getAll() )` | always - `withTools()` **appends** in bx-ai rather than replacing, so tools your class registered itself are kept and the discovered `tools/` are added |
| `subAgents` on the class, or `subagents/` on disk | `addSubAgent( ... )` per child | appended the same way |
| `checkpointer` in `configure()` | `withCheckpointer( ... )` | **injected anyway** if the class set none - see below |

!!! info
    The checkpointer is the one thing the build fills in unasked. An agent reachable from a gateway with no checkpointer has *silently* broken human-in-the-loop, so a class that set none still gets the `cache` default. A class that sets its own is left alone.

!!! warning
    Deliberately **not** implemented by comparing your instance against bx-ai's `DEFAULT_AGENT_*` values. "Did the author mean this, or is it just the default?" is unanswerable, and an author who genuinely wanted the default name would find it silently replaced. Presence of an external declaration is a fact; intent behind a default value is not.

The class is copied into the generated app at `agent/classes/` and instantiated there by its own absolute file path (never a relative lookup that would depend on a registered mapping), exactly as `tools/`, `skills/` and `mcp/` are copied - so a packaged `.bxa` carries it, and `chat`/`invoke`/`serve` all instantiate it the same way whether or not a real ColdBox container is booted.

## `configure()` (optional) - overriding what the class set

A `configure()` method is entirely optional. Declare it only to override specific fields from outside the class - useful for keeping deployment-specific values (a different model per environment, say) out of the class body itself:

```javascript
class extends="bxModules.bxai.models.runnables.AiAgent" {

	function init() {
		super.init(
			name  : "with-mcp-servers-agent",
			model : aiModel( provider: "openai", params: { model: "gpt-5" } )
		)
		return this
	}

	function configure() {
		return {
			mcpServers : [
				"https://example.com/mcp",
				{ url : "https://other.com/mcp", name : "other" }
			]
		};
	}

}
```

| Field | Type | Notes |
|---|---|---|
| `name` | string | Also becomes this agent's `config/WireBox.bx` binding key at build time (`getInstance( name )`) - see [schedules/](schedules.md) - so it must be unique across the whole project (root + every subagent); `build` fails if two agents share a name. |
| `model` | string | A `provider/model` slug, a bare provider name, or a name matching a [`models/`](models.md) entry. See below. |
| `description` | string | Optional. |
| `subAgents` | array of strings | Names of sibling folders under the root project's `subagents/`. See [subagents/](subagents.md). |
| `mcpServers` | array | Remote MCP servers - each entry a URL string or `{ url, name }`. See [mcp/](mcp.md). |
| `security` | struct | Forwarded verbatim into the generated app's `bxai` module settings; bx-ai's own `SecurityDirector` turns it into guardrail middleware. Passthrough only - BxAgents has no own guardrails convention. |
| `memory` | string or struct | The agent's conversation memory. A bare string is shorthand for the type (`"cache"`); a struct is `{ type, ...config }` and is passed through to `aiMemory()` verbatim - e.g. `{ type: "cache", maxMessages: 50 }`, or with `summaryProvider`/`summaryModel`/`summaryThreshold` to make the web UI's `/compact` functional. Applies per node, so a subagent can declare its own. |
| `checkpointer` | struct | `{ type: "cache"\|"file"\|"jdbc", ...config }`. Defaults to `{ type: "cache" }` if omitted. Always applied - without one, human-in-the-loop approval flows through any gateway other than `cli` fail outright. |
| `gatewaySession` | struct | `{ policy, maxQueueDepth }`, both optional (default `"queue"` / `50`). Only meaningful if the project has at least one push-style [gateway](gateways/index.md#3-push-style-gateways-type-telegram--slack--discord--email--whatsapp-cloud--teams--twilio--github--signal-and-friends) entry - controls the generated `GatewaySession`'s policy for a second inbound message arriving on a thread that already has a turn in flight. `policy` must be one of `reject`/`queue`/`steer`/`interrupt`. |
| `agentApi` | struct | `{ enabled, tokenEnvVar }`, both optional. Controls the always-on agent route at `/__bxagents`. See [The always-on agent route](#the-always-on-agent-route) below. |
| any other key | any | Merged and available in the resolved config struct, but not interpreted by BxAgents itself. |

## The model slug

`model` is BxAgents' own convention - bx-ai itself takes `provider` and `model` as two separate arguments to `aiModel()`. BxAgents splits the slug **on the first `/` only**, so a provider that itself contains a slash (like OpenRouter's `openrouter/anthropic/claude-x`) still parses correctly:

| `model` value | provider | model |
|---|---|---|
| `openai/gpt-5` | `openai` | `gpt-5` |
| `openrouter/anthropic/claude-x` | `openrouter` | `anthropic/claude-x` |
| `mock/mock-model` | `mock` | `mock-model` |

If `model` has no `/` at all, it must be either a known core provider name or match a [`models/`](models.md) entry's name - validation rejects anything else. The recognized core providers are: `bedrock`, `claude`, `cohere`, `deepseek`, `docker`, `elevenlabs`, `gemini`, `grok`, `groq`, `huggingface`, `minimax`, `mistral`, `mock`, `ollama`, `openai`, `openai-compatible`, `openrouter`, `perplexity`, `voyage` (kept in sync with bx-ai's own `CORE_PROVIDERS`). `mock` is a real provider, useful for tests and CI - it never makes a network call.

This slug-splitting convention is what a `configure()`-declared `model` string goes through - `super.init()`'s own `model` argument instead takes a real `AiModel` instance directly (`aiModel( provider: "...", params: { model: "..." } )`), since the class already speaks bx-ai's own API.

## Environment overrides

`Agent.bx` may declare a method named after an environment (e.g. `production()`, `development()`, or any custom name) returning a struct of overrides - this works whether or not the class also declares a `configure()`:

```javascript
class extends="bxModules.bxai.models.runnables.AiAgent" {

	function init() {
		super.init(
			name        : "override-agent",
			description : "An agent with an environment override",
			model       : aiModel( provider: "openai", params: { model: "gpt-5" } )
		)
		return this
	}

	function production() {
		return {
			model : "openai/gpt-5-mini"
		};
	}

}
```

The active environment is resolved with this precedence (highest wins):

1. `--environment` CLI flag (`bxAgents build --environment=production`)
2. `Agent.bx`'s own `detectEnvironment()` method, if it declares one
3. `BX_AGENTS_ENV` environment variable
4. `"development"` (default)

### `detectEnvironment()`

For anything the first and third tiers can't express, `Agent.bx` may declare a
`detectEnvironment()` method and decide for itself. Whatever it returns is taken
as the environment name verbatim:

```javascript
class extends="bxModules.bxai.models.runnables.AiAgent" {

	function init() {
		super.init( name : "my-agent", model : aiModel( provider : "openai", params : { model : "gpt-5" } ) )
		return this
	}

	function detectEnvironment() {
		return fileExists( expandPath( "/.production-marker" ) ) ? "production" : "development";
	}

}
```

This mirrors ColdBox's own tier-1 convention (a config declaring
`detectEnvironment()` has its return value taken verbatim), with two deliberate
differences, both because this runs at **build** time:

- An explicit `--environment` flag **outranks** `detectEnvironment()` here, where
  ColdBox puts its own detect method first. A flag someone typed is the most
  specific statement of intent available.
- ColdBox's *other* detection tier - an `environments` struct of regexes matched
  against `CGI.HTTP_HOST` - is **not** supported. It cannot work here: the build
  runs in a CLI process with no HTTP host, so it would only ever describe the
  machine doing the building (a CI runner would resolve `development` for a
  production build).

Returning a blank string, or anything that isn't a string, means "no opinion" and
falls through to the next tier - so a project computing its environment from
something absent on this machine degrades to the default rather than failing the
build. A `detectEnvironment()` that **throws** is a different matter: that's a real
bug in the project's own code, and `build` fails and says so.

This is a **build-time** decision only, distinct from ColdBox's own runtime environment detection (the generated app reads `getSetting("environment")` on its own, per ColdBox's `environments` convention) - this precedence only decides which `environment()` override method on `Agent.bx`, and which `boxlang-{env}.json`/`miniserver-{env}.json` files, the build pipeline applies.

If no method matching the active environment exists, no override is applied.

## Merge semantics

The full resolution order (lowest to highest precedence) is:

1. `configure()` (optional)
2. the matching environment-override method, if any
3. `boxlang.json`
4. `boxlang-{environment}.json`
5. `miniserver.json`
6. `miniserver-{environment}.json`

Struct keys are merged **recursively** - a nested struct in a higher-precedence source only overrides the keys it actually sets, leaving sibling keys from a lower-precedence source intact. Arrays and all scalar values are **replaced whole**, never appended or concatenated.

`boxlang.json`/`boxlang-{env}.json`/`miniserver.json`/`miniserver-{env}.json` are all optional project-root JSON files, useful for config that's easier to express as data than as BoxLang code (e.g. model defaults):

```json
// boxlang.json
{
	"modelDefaults": { "temperature": 0.7, "maxTokens": 1000 }
}
```

```json
// boxlang-production.json
{
	"modelDefaults": { "temperature": 0.2 }
}
```

Building with `--environment=production` here yields `modelDefaults: { temperature: 0.2, maxTokens: 1000 }` - the recursive merge kept `maxTokens` from the base file since `boxlang-production.json` never mentioned it.

!!! warning
    Secrets (API keys, tokens) are never read or merged by BxAgents at build time - they stay external (an OS environment variable, `.env`, a platform secret manager) and are resolved live by bx-ai itself at runtime. See [Deployment & Secrets](../deployment-and-secrets.md).

## The always-on agent route

Every built project exposes its root agent over HTTP at a fixed reserved path,
whether or not it declares any [`gateways/`](gateways/index.md) entries:

```
POST /__bxagents/invoke
POST /__bxagents/stream    # SSE
POST /__bxagents/batch
GET  /__bxagents/info
```

These are the four sub-routes ColdBox's own `toAi()` terminator registers. The
route exists so this module's own tooling always has an address to talk to -
it's the service entrance, not your project's public API. A project's own
public surface belongs in `gateways/`, at whatever paths you choose.

Because the path is reserved, `build` **rejects** any `gateways/` entry whose
`path` is `/__bxagents` or sits underneath it. Two routes on one mount is a
genuine conflict: one would silently shadow the other, and yours would be the
one that never answers.

### `agentApi`

`configure()` may return an `agentApi` struct to control it:

| Key | Type | Notes |
|---|---|---|
| `enabled` | boolean | Default `true`. Set `false` to omit the route entirely - for a build going somewhere it has no business existing. |
| `tokenEnvVar` | string | The **name** of an environment variable holding a shared secret. When set, every sub-route requires a matching `x-bxagents-token` request header. |

```javascript
function configure() {
	return {
		name     : "my-agent",
		model    : "openai/gpt-5",
		agentApi : {
			tokenEnvVar : "BXAGENTS_API_TOKEN"
		}
	};
}
```

Because this comes from `configure()`, it scopes per environment with an
[environment override](#environment-overrides) like any other key - so a
project can leave the route open locally and require a token in production:

```javascript
function production() {
	return {
		agentApi : { tokenEnvVar : "BXAGENTS_API_TOKEN" }
	};
}
```

The gate **fails closed**: if the named variable is unset or blank at runtime,
nothing matches - it never degenerates into comparing `""` to `""` and letting
every caller through. A request without a valid token gets a `401` with a small
JSON body, and never reaches the agent.

!!! warning
    `tokenEnvVar` names a **variable**, never the secret itself. `build` rejects
    a value that isn't a valid environment-variable name, precisely so a literal
    token can't be committed here by mistake. Only the variable's name is ever
    written into generated source.

!!! info
    The route is bound to whatever host the [miniserver](../cli-reference.md#serve)
    is listening on - `127.0.0.1` by default, so nothing is reachable off the
    machine unless you deliberately expose it. `tokenEnvVar` is what matters once
    you do, and for any built `.bxa` artifact you deploy.
