---
title: Agents and models
description: How Hubzoid defines the main agent and sub-agents, when a sub-agent becomes a delegate on its own model, and how runtimes and providers are chosen.
canonical_url: https://hubzoid.com/docs/concepts/agents-and-models
last_updated: 2026-09-27
---

# Agents and models

How Hubzoid defines the main agent and sub-agents, when a sub-agent becomes a delegate on its own model, and how runtimes and providers are chosen.

Every Hub has one main agent, defined by `AGENTS.md`, and any number of sub-agents in `agents/`. The model id decides which of three runtimes runs them: the OpenAI Agents SDK with LiteLLM, the Claude Agent SDK with your `claude` CLI, or local Codex with your `codex` CLI. The Hub files are the same on each.

## The main agent

`AGENTS.md` at the Hub root is the main agent. Its body is the system prompt, and its frontmatter sets the name, description, model and chat suggestions. Every surface talks to the main agent. It holds the full tool registry and decides when to load a skill or call a delegate.

```markdown title="AGENTS.md"
---
name: ops-desk
description: Answers operations questions and prepares the weekly review.
model: claude-local
suggestions:
  - What changed in stock this week
---

You help the operations team. Use the knowledge documents for thresholds,
and load the weekly-review skill when someone asks for the weekly review.
```

The [project structure](https://hubzoid.com/docs/getting-started/project-structure#agentsmd) page lists every frontmatter field.

## Sub-agents

A sub-agent is a Markdown file in `agents/`, either `agents/<name>.md` or `agents/<name>/AGENTS.md` when it has supporting files. It uses the same frontmatter as the main agent, plus `tools`.

```markdown title="agents/researcher.md"
---
name: researcher
description: Writes a short, sourced brief when someone asks for research on a topic.
model: claude-local/opus
tools: [web_search, http_get, read_knowledge]
---

Research the topic you are given. Use at most five sources and cite each one.
Return a brief of no more than 300 words with the sources listed at the end.
```

| Field | Purpose |
| --- | --- |
| `name` | Identifier. Defaults to the file or folder name. |
| `description` | When to use this sub-agent. The main agent sees it in its skill or delegate menu. |
| `model` | Optional. Decides whether the sub-agent runs as a skill or as a delegate. |
| `tools` | Optional list of tool names a delegate may call. Ignored when the sub-agent runs as a skill. |

## Skill or delegate

Hubzoid classifies each sub-agent when it builds the agent. The rule depends only on the sub-agent's `model:` compared with the Hub's model.

| Sub-agent `model:` | Runs as | Behavior |
| --- | --- | --- |
| Not set | Skill | The main agent loads its body with `load_skill` and follows it in the same turn. |
| Same model as the Hub | Skill | As above. |
| A different engine (for example a `claude-local` id on a LiteLLM or `codex-local` Hub) | Skill | As above. A sub-agent never switches runtime. |
| A different model on the same engine | Delegate | Runs on its own model in its own context and returns a result to the main agent. |

For example, on a Hub running `claude-local` (Sonnet), the `researcher` above has `model: claude-local/opus`, so it becomes a delegate that runs on Opus. On a Hub running `openai/gpt-4o-mini`, a sub-agent with `model: openai/gpt-4o` is a delegate, and one with `model: claude-local/opus` is a skill. On a Hub running `codex-local`, a sub-agent with `model: codex-local/<model-id>` is a delegate on that Codex model.

A skill-classified sub-agent is added to the skill menu. If a skill in `skills/` has the same name, the one in `skills/` wins and a warning is logged.

### How delegation works

The main agent keeps control. It calls a delegate within the turn with a brief, the delegate works in isolation on its own model, and its final message comes back as the result. The runtime addendum lists each delegate with its model and tells the main agent to relay the answer faithfully.

|  | OpenAI Agents runtime | Claude Agent runtime | Codex runtime |
| --- | --- | --- | --- |
| Mechanism | The delegate is a tool named `handover_<name>`, built with the Agents SDK's agent-as-tool pattern. | The delegate is a native Claude sub-agent on its tier (`opus`, `sonnet`, `haiku` or a full `claude-*` id), dispatched through Claude's sub-agent tool. | The delegate is a tool named `handover_<name>` that runs a separate Codex request on the delegate's model. |
| Tools | The `tools:` list, intersected with the Hub's tools. Unknown names are dropped with a warning. With no list, every Hub tool. | Same rule. | Same rule. |
| Restricted tools | Guarded exactly as for the main agent. | Same. | Same. |
| If its model cannot be built | Missing provider key or another build error: the sub-agent is loaded as a skill instead, and the Hub still starts. | Not applicable. The tier is passed to the CLI. | Not applicable. The model is passed to Codex. |

Delegates are not exposed over MCP, because an MCP caller brings its own model. There, every sub-agent is a skill. A delegate never receives the person's own [personal MCP connections](https://hubzoid.com/docs/guides/tools-and-connectors#per-user-mcp-servers). Only the main agent does.

## Runtimes

|  | OpenAI Agents SDK | Claude Agent SDK | Local Codex |
| --- | --- | --- | --- |
| Chosen when | The model id does not start with `claude-local` or `codex-local` | The model id is `claude-local` or `claude-local/<pin>` | The model id is `codex-local` or `codex-local/<model-id>` |
| Model calls | Through LiteLLM | Through the `claude` CLI, started as a subprocess | Through the Codex CLI's app-server, pinned to Codex CLI 0.147.0. Each request gets its own short-lived thread and an isolated configuration folder. |
| Authentication | The provider key in `.env` | `claude login` on the machine, `CLAUDE_CODE_OAUTH_TOKEN`, or `ANTHROPIC_API_KEY` | A file-backed `codex login` by the account that runs the Hub, read from `$CODEX_HOME/auth.json` (by default `~/.codex/auth.json`). Keychain-only logins are not supported. |
| Hub tools | Function tools on the agent | An in-process MCP server named `hubzoid`. Claude Code's own tools (Bash, Read, Edit and so on) are disabled, and Claude Code settings files are not read. | Offered to Codex as dynamic tools that Hubzoid runs. Codex's own shell, file, browser and web search tools are disabled, and the login's personal Codex settings, MCP servers, skills and hooks are not used. |
| MCP connectors | Connected when the bridge starts. A server that fails to connect is dropped with a warning. | Passed to the SDK, which manages the connections. Only the servers Hubzoid passes are used, never the connectors or MCP settings of the Claude account the machine is signed in to. | Connected by Hubzoid, which adds their tools to the ones it offers Codex. |
| Turn limit | 20 agent turns per chat turn | Set by the SDK unless a scheduled run passes its own limit | 20 tool calls per request by default, and five minutes per request |

All three runtimes stream text and tool activity in the same format, record token usage per turn, and use the same access guard. Each one shows the agent only the controlled tools (`restricted/` tools, `remember`, `call_jev`) the person may use, and refuses any other call. The [architecture](https://hubzoid.com/docs/concepts/architecture#runtime-backends) page shows where the runtime sits in a request.

## Choosing the model

The Hub's model is resolved in this order. The first one set wins.

1. A per-run override: a markdown task's `model:` frontmatter, or `hubzoid schedule run --model`.
2. `MODEL` in the Hub's `.env`.
3. `model:` in `AGENTS.md`.
4. `claude-local`.

On a `claude-local` Hub, an override of a bare tier (`opus`, `sonnet`, `haiku`) or a `claude-*` id stays on the Claude runtime, so `model: opus` in a task means `claude-local/opus`. A full id for another engine, such as `openai/gpt-4o` or `codex-local`, switches that run to it.

`hubzoid init` in an interactive terminal checks for a logged-in `claude` CLI and a logged-in Codex CLI. It writes the only one it finds into the new Hub's `.env`, or asks once when both are usable. `hubzoid init my-hub --model codex-local` sets the model without asking. Detection only reads the login status and makes no model call.

### Providers

| `MODEL` prefix | Provider | Required settings | Example |
| --- | --- | --- | --- |
| `openrouter/` | OpenRouter | `OPENROUTER_API_KEY` | `openrouter/anthropic/claude-haiku-4.5` |
| `openai/` | OpenAI | `OPENAI_API_KEY` | `openai/gpt-4o-mini` |
| `anthropic/` | Anthropic | `ANTHROPIC_API_KEY` | `anthropic/claude-haiku-4-5` |
| `azure/` | Azure OpenAI | `AZURE_API_KEY`, `AZURE_API_BASE` (`https://<resource>.openai.azure.com`), optional `AZURE_API_VERSION` | `azure/<deployment-name>` |
| `claude-local` | Claude subscription through the `claude` CLI | A `claude` login, `CLAUDE_CODE_OAUTH_TOKEN` or `ANTHROPIC_API_KEY` | `claude-local`, `claude-local/opus` |
| `codex-local` | Your Codex account through the Codex CLI 0.147.0 | A file-backed `codex login` | `codex-local`, `codex-local/<model-id>` |

For Azure, the part after `azure/` is the deployment name you created in Azure, not the underlying model name. For the four key-based prefixes, a missing key stops the agent from building with an error naming the variable. Any other model id is passed to LiteLLM unchanged, and LiteLLM reads that provider's own environment variables.

### Tiers for claude-local

| `MODEL` | Runs on |
| --- | --- |
| `claude-local` | Sonnet |
| `claude-local/sonnet` | Sonnet |
| `claude-local/opus` | Opus |
| `claude-local/haiku` | Haiku, with lower latency |
| `claude-local/claude-<model-id>` | That exact model id |

An unrecognized pin falls back to Sonnet with a warning in the log, so an unattended run never goes to the CLI with a typo. For a server without an interactive login, mint a subscription token with `claude setup-token` on a logged-in machine and set it as `CLAUDE_CODE_OAUTH_TOKEN` in the Hub's `.env`. The `claude` CLI must be installed and on the `PATH` of the user that runs the Hub. The Docker image has no `claude` CLI, so use a provider key there.

### Codex models

`codex-local` uses the Codex CLI's default model. `codex-local/<model-id>` pins a model the logged-in account can use, and Codex is told not to fall back to another one. Install the pinned CLI and log in as the account that runs the Hub:

```bash
npm install -g @openai/codex@0.147.0
codex -c 'cli_auth_credentials_store="file"' login
```

Other CLI versions are refused until their tool isolation has been reviewed, so upgrade the CLI only with a Hubzoid release that supports it. `hubzoid doctor` reports the CLI and login as `runtime.codex_login`. Your Codex account's model access and usage limits still apply. Token counts are recorded, and the cost is an estimate only when the model's price is known. The Docker image has no Codex CLI.

## Model settings

These `.env` keys apply to the whole Hub.

| Key | Values | Effect |
| --- | --- | --- |
| `REASONING_EFFORT` | `low`, `medium`, `high` | Reasoning models on OpenAI and Azure receive this effort. On Claude it sets an extended thinking budget. On Codex it is sent as the turn's effort. Unset leaves the model's default. |
| `SHOW_THINKING` | `off`, `indicator` (default), `full` | Claude runtime only. `indicator` shows a thinking panel without the text, `full` streams the summarized reasoning. |
| `SHOW_TOOLS` | `off`, `compact` (default), `full` | How tool calls appear in chat. `compact` is a collapsible entry per call in the web chat and is hidden in Slack. |
| `HUBZOID_VISION` | `true` (default), `false` | Pass attached images to the model. `HUBZOID_VISION_MAX_EDGE` (default 1568 pixels) and `HUBZOID_VISION_MAX_IMAGES` (default 4) bound them. |
| `HUBZOID_DISABLE_PROMPT_CACHE` | `true` to disable | Prompt caching that Hubzoid adds for Anthropic-family models on LiteLLM. `HUBZOID_CACHE_TTL` sets its lifetime, `1h` by default. |
| `HUBZOID_OPENAI_TRACING` | `true` to enable | Lets the OpenAI Agents SDK export runs to OpenAI's trace dashboard. Off by default. |
| `MODEL_LABEL` | Any name | The model name the bridge reports on `/v1/models`. Defaults to the agent name. |

The [configuration reference](https://hubzoid.com/docs/reference/configuration) lists every key.

## Models in workflows

Code workflows call the Hub's models in two ways. `hub.call_agent(task)` runs the full agent with its tools, skills and knowledge. `hub.call_llm(prompt)` is a single model call with no tools and no Hub instructions, returning text, JSON or a validated Pydantic object, on LiteLLM models, `claude-local` and `codex-local`. Both default to the Hub's model and record a usage row. The experimental `hub.call_jev` asks TypeSafe's Jev through OpenRouter for typed decisions with its own `JEV_OPENROUTER_API_KEY`, never the Hub's model key. See the [workflow API](https://hubzoid.com/docs/reference/workflow-api).

## Next steps

- [The Hub](https://hubzoid.com/docs/concepts/hub): What the agent receives on every run.
- [Code workflows](https://hubzoid.com/docs/guides/code-workflows): Mix exact Python steps with agent judgment.
- [Configuration](https://hubzoid.com/docs/reference/configuration): Every environment variable Hubzoid reads.
