Agents and models
How Hubzoid defines a Hub's main agent and its sub-agents, and how the model id you write picks the runtime and provider that answer.
Every Hub has one main agent, defined by AGENTS.md, and any number of sub-agents in agents/. One model id, MODEL, 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.
When to use it
Read this page when you choose or change the model a Hub uses, run a Hub on a server without an interactive login, or split work between the main agent and a sub-agent on a different model. The Hub's files stay the same whichever runtime you pick.
The smallest example
The model is one line in the Hub's .env:
MODEL=claude-localThat runs the main agent on Sonnet through the claude CLI signed in on this machine, with no API key. Change the line to MODEL=openrouter/anthropic/claude-haiku-4.5 and add OPENROUTER_API_KEY, and the same Hub runs on the OpenAI Agents SDK through OpenRouter. Restart the Hub after changing it.
Video lesson 1 · 3:35Why AI agents need a harness, and how it worksSee how a harness turns a model request into a useful sequence of steps.
How it works
Which model a run uses
The first one set wins:
- A per-run override: a markdown task's
model:frontmatter, orhubzoid schedule run --model. MODELin the Hub's.env.model:inAGENTS.md.claude-local.
Overrides on a claude-local Hub, the chat model picker and what init writes
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.
In chat, every reply uses the agent's own model. The model picker appears only when the deployment lists models under chat.models.catalog in hubzoid.yaml, and those models must run on the agent's own runtime. See web chat.
hubzoid init in a terminal checks for a signed-in claude CLI and a signed-in Codex CLI. It writes the only one it finds into the new Hub's .env, or asks once when both are usable. When it finds neither, it offers to save an OpenRouter, Anthropic or OpenAI key. hubzoid init my-hub --model codex-local sets the model without asking.
The three runtimes
All three stream text and tool activity in the same format, record token usage per turn and use the same access guard. Each shows the agent only the controlled tools the person may use and refuses any other call. Connectors added in Settings join each person's turns on all three, with tools named mcp__my_<id>__<tool>. See architecture for where the runtime sits in a request.
How the three runtimes differ in sign-in, tools, connectors and limits
| OpenAI Agents SDK | Claude Agent SDK | Local Codex | |
|---|---|---|---|
| Chosen when | The model id does not start with claude-local or codex-local | claude-local or claude-local/<tier> | 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, version 0.163.0 or 0.147.0. Each request gets its own short-lived thread and an isolated configuration folder. |
| Sign-in | 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 its settings files are not read. | Offered to Codex as tools that Hubzoid runs. Codex's own shell, file, browser and web search tools are disabled, and the login's personal 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. Only the servers Hubzoid passes are used, never the connectors 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 ten minutes per request, less when a workflow's deadline is nearer |
Providers
MODEL prefix | Provider | Required settings | Example |
|---|---|---|---|
claude-local | Your 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 | A file-backed codex login | codex-local, codex-local/<model-id> |
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> |
For Azure, the part after azure/ is the deployment name you created in Azure, not the 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 or 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. The claude CLI must be on the PATH of the user that runs the Hub. For a server without an interactive login, make a subscription token with claude setup-token on a signed-in machine and set it as CLAUDE_CODE_OAUTH_TOKEN in the Hub's .env. 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 signed-in account can use, and Codex is told not to fall back to another one. Install a supported CLI and log in as the account that runs the Hub:
npm install -g @openai/codex@0.163.0codex -c 'cli_auth_credentials_store="file"' loginSupported Codex versions, platforms and limits
Hubzoid works with Codex CLI 0.163.0 and 0.147.0, after a review of how each keeps tools isolated. Other versions are refused with the versions to install. For 0.163.0, Hubzoid turns off on every request the newer features that could reach outside the Hub, such as plugins, browser and computer use. When the supported codex is not first on the PATH, set HUBZOID_CODEX_BIN to the binary to use.
This runtime needs macOS or Linux. 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.
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.
---
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. |
The project structure page lists every frontmatter field of AGENTS.md.
Skill or delegate
Hubzoid sorts each sub-agent when it builds the agent, by comparing its model: with the Hub's model.
Sub-agent model: | Runs as | Behavior |
|---|---|---|
| Not set, or the same model as the Hub | Skill | The main agent loads its body with load_skill and follows it in the same turn. |
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. |
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. If a skill in skills/ has the same name as a skill-classified sub-agent, 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.
Delegation on each runtime, its tools and guards
| OpenAI Agents runtime | Claude Agent runtime | Codex runtime | |
|---|---|---|---|
| Mechanism | A tool named handover_<name>, built with the Agents SDK's agent-as-tool pattern | A native Claude sub-agent on its tier (opus, sonnet, haiku or a full claude-* id) | 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 |
| If its model cannot be built | The sub-agent is loaded as a skill instead, and the Hub still starts | Not applicable | Not applicable |
Restricted tools are guarded for a delegate exactly as for the main agent. Delegates are not offered over MCP, because an MCP caller brings its own model, so there every sub-agent is a skill. A delegate never receives a person's own connections to connectors added in Settings. Only the main agent does.
Backup Claude credentials
A claude-local Hub that runs on subscription tokens can keep answering when one token reaches its usage limit. Set up to two backups beside the primary token, in the Hub's .env or the bridge's environment, and restart the bridge:
CLAUDE_CODE_OAUTH_TOKEN=<primary token>
CLAUDE_CODE_OAUTH_TOKEN_SECONDARY=<first backup>
CLAUDE_CODE_OAUTH_TOKEN_TERTIARY=<second backup>- Only a confirmed usage limit or a rejected login moves to the next token. Other errors, refusals and timeouts do not switch.
- When the failure comes before any text or tool activity, the same request moves to the next token. Once a reply has started, that turn ends with the error and the next request uses a backup. Hubzoid never replays an interrupted action.
- Hubzoid refuses the setup when
ANTHROPIC_API_KEYor another provider is also configured, rather than charge another account.
Backup tokens may share one quota, and they do not help in a Claude-wide outage. Use only credentials your organization is authorized to use. The configuration reference covers cooldowns and logging.
Model settings
A few .env keys tune the model for the whole Hub:
REASONING_EFFORT(low,medium,high) sets the reasoning effort on OpenAI and Azure reasoning models, a thinking budget on Claude and the turn's effort on Codex.SHOW_THINKING(off,indicator,full) andSHOW_TOOLS(off,compact,full) decide how much of the thinking and the tool calls people see in chat.HUBZOID_VISIONpasses attached images to the model, on by default.
The configuration reference lists these with prompt caching, tracing, image limits and every other 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 any runtime. 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.

