Hubzoid
Reference

Hub files

Reference for every file and folder a Hub can contain, the frontmatter keys each loader reads, and the runtime folders Hubzoid creates.

A Hub is one folder. Hubzoid reads the files below from it every time the Hub starts, and some of them on every call. Everything except the .env files and the runtime folders belongs in Git.

Layout

AGENTS.md
.env
PathRequiredRead byPurpose
AGENTS.mdyesevery surfaceThe main agent's instructions and settings.
.envnoevery command that builds or serves the HubModel, keys and settings. See configuration.
agents/noagent builderSub-agents, loaded as skills or run as delegates.
skills/nolist_skills, load_skillPlaybooks the agent loads on demand.
knowledge/nolist_knowledge, read_knowledgeReference documents, read live from disk.
tools_local/notool registryPython tools.
restricted/notool registry, access guardPython tools that need a permission, plus their secrets in restricted/.env.
connectors/.mcp.jsonnoMCP loaderExternal MCP servers the agent can call.
schedule/noschedulerMarkdown tasks run on a cron or by a webhook.
workflows/noworkflow enginePython code workflows and their settings.
evals/nohubzoid eval, schedulerBehaviour checks.
identity/noaccess, inbound surfacesRoster of people and their groups, and permission labels.
raw_data/nogrep_data, read_file, list_filesLarge source material to search rather than load.
branding/nohubzoid run, hubzoid gatewayLogo, favicon, splash and CSS for the web chat.

Folder name variants

Folder names match without regard to case, and these variants are accepted. When two variants exist in one Hub, the first alphabetically is used and a warning is logged.

CanonicalAlso accepted
agentsagent
skillsskill
tools_localtool_local, tools, local_tools
connectorsconnector
scheduleschedules, scheduled
workflowsworkflow
evalseval, evaluations
raw_dataraw-data, rawdata
outputoutputs

knowledge, restricted and identity have no variants.

AGENTS.md

The main agent. The body is the system prompt, used verbatim. Frontmatter is optional, so a plain Markdown file works.

AGENTS.md
---
name: ops-desk
description: Answers stock and order questions for the operations team.
model: claude-local/sonnet
suggestions:
  - Which items are below reorder level?
  - Summarize yesterday's orders
mcp_instructions: |
  Use read_knowledge for policy questions and stock_level for live counts.
---

You help the operations team of a home goods retailer with four stock locations.
Answer from knowledge files and tools. Never guess a stock count.
KeyTypeDefaultEffect
namestringthe Hub folder nameThe agent's name. Its slug is the model id in /v1/models unless MODEL_LABEL is set, and it is the web chat's display name unless WEBUI_NAME is set.
descriptionstringthe first non-heading line of the body, up to 200 charactersShort summary. A gateway shows it in the model picker.
modelmodel idnoneThe Hub's model when .env does not set MODEL. MODEL in .env wins.
suggestionslist of stringsnoneClick-to-send prompts on the web chat's empty new-chat screen.
mcp_instructionsstringthe bodyInstructions sent to MCP clients when they connect, in place of the body. Use it when the body holds internal guidance or is long.
auto_addendumbooleantruefalse stops Hubzoid from appending its runtime section (knowledge index, skills index, tool guidance) to the instructions.

A file with frontmatter but no body fails to load. tools: on the main agent has no effect: the main agent always has the full tool registry.

Agent definitions: agents/

Each sub-agent is either a folder, agents/<name>/AGENTS.md (or the first *.md in the folder), or a single file, agents/<name>.md. Hidden entries are skipped.

agents/researcher.md
---
name: researcher
description: When the user wants a researched brief on a supplier or product.
model: claude-local/opus
tools: [web_search, http_get, read_knowledge]
---

Research the question with web_search and http_get, then write a short brief with sources.
KeyTypeDefaultEffect
namestringthe folder or file nameIdentifier, and the skill name when loaded as a skill.
descriptionstringthe first non-heading line of the bodyWhen the main agent should use this sub-agent. Write it as a "when" sentence.
modelmodel idnoneDecides skill or delegate. See below.
toolslist of tool namesall toolsDelegates only. The tools the delegate may call. Unknown names are dropped with a warning.

Skill or delegate. A sub-agent becomes a delegate only when its model: differs from the Hub's model on the same engine, for example claude-local/opus in a claude-local Hub, or a different LiteLLM id in a LiteLLM Hub. A delegate runs on its own model in its own context and returns its answer to the main agent, which stays in control. On the OpenAI Agents backend the main agent calls it as a tool named handover_<name>. On claude-local it runs as a Claude subagent. Every other sub-agent (no model:, the same model, or a different engine) is loaded inline as a skill, and its tools: list is ignored with a warning. A delegate whose model is missing its provider key falls back to a skill so the Hub still starts.

Skills: skills/

Each skill is skills/<name>/SKILL.md (also skill.md, Skill.md or the first *.md in the folder) or a single file, skills/<name>.md.

skills/weekly-summary/SKILL.md
---
name: weekly-summary
description: Three-bullet summary of the week's orders for the operations lead.
---

1. Call list_knowledge and read the reporting policy.
2. Pull the week's numbers with the stock and order tools.
3. Reply with three bullets and one risk to watch.
KeyTypeDefaultEffect
namestringthe folder name (or file stem) when missingName passed to load_skill.
descriptionstringSkill loaded from <file>. when missingShown in the skills menu the agent sees.

The agent sees each skill's name and description and loads the body with load_skill only when it needs it. Other frontmatter keys are ignored. Supporting files in a skill folder are not loaded automatically. Name them by their path from the Hub root in the skill body, and the agent reads them with read_file.

Name collisions resolve in this order: skills/ first, then sub-agents loaded as skills, then Hubzoid's built-in dashboard skill (chat surfaces only). A skill file that cannot be parsed stops the Hub from building.

Knowledge: knowledge/

Every *.md under knowledge/, including subfolders, is one document. Hidden files and _index.md are skipped.

knowledge/policies.md
---
name: returns-policy
description: Return windows, exceptions and who approves refunds.
keywords: [returns, refunds, exchange]
---

Returns are accepted within 14 days with the original receipt.
KeyTypeDefaultEffect
namestringthe file stemName passed to read_knowledge.
descriptionstringKnowledge document: <stem>.Shown in the knowledge menu.
keywordslist of stringsnoneSearch hints. A single string is accepted.

The knowledge tools read the folder live, so an edited or added document is visible on the next call without a restart. A file with broken frontmatter is skipped with a warning and the rest still load. Documents saved with the remember tool live in knowledge/_learned/ with the name learned/<topic>. See memory and history.

Tools: tools_local/ and restricted/

Every *.py file in these folders is imported when the Hub builds, and every module-level FunctionTool (a function decorated with @function_tool from the agents package) becomes a tool. Files whose names start with _ are skipped.

  • A tools_local/ tool with the same name as a built-in tool replaces the built-in.
  • A restricted/<permission>.py tool requires the permission named by the file stem. It wins over any tool of the same name and is guarded before every call.
  • restricted/.env holds the secrets those tools read. File tools refuse every path under restricted/.

See tools and connectors and restrict tools.

Connectors: connectors/.mcp.json

External MCP servers, in the mcpServers shape. connectors/mcp.json (no leading dot) is read when .mcp.json is absent.

connectors/.mcp.json
{
  "mcpServers": {
    "github": {
      "command": "npx",
      "args": ["@modelcontextprotocol/server-github"],
      "env": { "GITHUB_PERSONAL_ACCESS_TOKEN": "${GH_TOKEN}" }
    },
    "tickets": {
      "transport": "streamable-http",
      "url": "https://mcp.example.com/mcp",
      "headers": { "Authorization": "Bearer ${TICKETS_TOKEN}" }
    }
  }
}
KeyApplies toEffect
command, args, envstdio serversProcess to start and its arguments and environment.
urlHTTP serversServer endpoint.
transportHTTP serverssse or streamable-http (also written http). A server with a url and no transport uses SSE. A server with no url uses stdio.
headersHTTP serversRequest headers, such as an authorization token.
client_session_timeout_secondsHTTP serversPer-call timeout on the OpenAI Agents backend.

${NAME} in any string is replaced with that environment variable after .env is loaded. Names use uppercase letters, digits and underscores. With HUBZOID_BROWSER=true, a playwright server is added automatically, and an entry of your own with that name replaces it.

Scheduled tasks: schedule/

One Markdown file per task. The file name (letters, digits, ., _ and -, starting with a letter or digit) is the task name. The body is the agent's instructions, or optional notes for a run: script task.

KeyTypeDefaultEffect
schedule5-field cronnoneWhen the task runs, in the machine's local time.
on_webhookstring or truenoneRun when events arrive at this webhook inbox instead of on a cron. true means webhook. Use schedule or on_webhook, not both.
runstring or listnoneRun a command instead of the agent. A string runs in a shell, a list runs as arguments without one.
modelmodel idthe Hub's modelModel for this task.
timeoutseconds1800Time limit per round.
max_roundsinteger10Fresh-context rounds per run.
max_turnsinteger40Agent turns within one round.
writepath or listnoneExtra Hub paths the task may write, not committed.
commitpath or listnoneHub paths the task may write and Hubzoid commits after the run.
pushbooleanfalsePush the commit. Needs commit.
enabledbooleantruefalse keeps the file but stops it firing.

The full behaviour, including rounds, catch-up and webhook inboxes, is in markdown tasks.

Workflows: workflows/

Each code workflow lives in workflows/<name>/. Every *.py file there whose name does not start with _ is imported, and each function decorated with @workflow is registered under its function name. hubzoid new workflow scaffolds one. The API is in the workflow API reference.

workflows/settings.yaml is optional. Workflows read any top-level key with hub.setting("<key>"), and two keys configure the engine:

workflows/settings.yaml
agent_max_attempts: 1         # tries for a failed hub.call_agent step. 1 means no retry.
max_concurrent_workflows: 2   # hub-wide cap on code workflows running at once. Unset means no cap.
stock_drift:
  threshold_pct: 5

Evals: evals/

One Markdown file per case. The file name is the case name. Files starting with _ or . are skipped. Frontmatter keys are schedule, tags, expect_tools, forbid_tools, contains, not_contains, timeout, threshold and enabled, and any other key is an error. The body holds a ## Prompt section and an optional ## Criteria section. See evals.

Identity: identity/

identity/access.csv

A roster of people. The header row names the columns. phone, email and groups have meaning to Hubzoid, and any other column is kept as context on the person's record.

identity/access.csv
phone,email,groups,center
919800000001,meera@example.com,coordinator,north
919800000002,arjun@example.com,coordinator;finance,south
  • phone identifies WhatsApp and Telegram senders. Formatting is normalized, so a prettified number matches.
  • email links the row to the person's web chat and MCP identity, and is lowercased.
  • groups are separated by ; or ,. Rows that share an email combine their groups.

The file reloads when it changes on disk, with no restart. An unreadable or half-written file resolves to no groups, never to old ones, so save it by writing a new file and renaming it into place. On WhatsApp and Telegram the roster is the allowlist: a sender whose number is not listed is refused. For web chat and MCP callers the roster only adds groups.

identity/access.py

A function-backed roster for live lookups, such as a CRM. It wins over access.csv when both exist.

identity/access.py
def resolve(surface: str, handle: str) -> dict | None:
    """Return {"email": ..., "groups": [...]} for a phone or chat handle, or None."""
    ...

def groups_for_email(email: str) -> list[str]:
    """Optional. Groups for a web chat or MCP caller, looked up by email."""
    ...

groups_for_email is optional, and without it the file never receives email lookups. Results of groups_for_email are cached for 60 seconds. An exception in either function denies rather than failing the request.

identity/permissions.yaml

Labels for the permissions the Console shows. The Console lists use_hub, manage_access and one permission per restricted/*.py file, and this file adds text to them without running any tool code.

identity/permissions.yaml
finance:
  label: Finance reports
  description: Read margins and receivables from the accounting system.
  sensitive: true
KeyTypeDefaultEffect
labelstringUse this agent for use_hub, Manage access for manage_access, otherwise the name in title caseName shown in the Console.
descriptionstringemptyExplanation shown with the permission.
sensitivebooleanfalseTags the permission as sensitive in the Console, and the review step shows a warning before it is granted.

Access concepts are covered in identity and access.

Branding: branding/

Files copied into the web chat on every start. Names match without regard to case.

FileUsed for
favicon.svg, favicon.png, favicon.icoBrand mark in the tab and sidebar.
logo.svg, logo.pngUsed as favicon.svg or favicon.png when that file is absent.
favicon-dark.png, favicon-96x96.png, apple-touch-icon.png, logo.png, splash.png, splash-dark.png, web-app-manifest-192x192.png, web-app-manifest-512x512.pngIndividual slots. Any PNG slot you leave out is filled from favicon.png.
custom.cssReplaces Hubzoid's baseline stylesheet for the web chat.

When branding/ holds any file, Open WebUI's own name suffix and branding are replaced, unless HUBZOID_KEEP_OWUI_SUFFIX=true. In a gateway, a Hub's logo or favicon raster (PNG, WebP, JPEG or GIF) becomes its avatar in the model picker.

Open WebUI branding terms

Open WebUI's license allows removing its branding only in specific cases, such as deployments with 50 or fewer users in a 30-day period or with an Open WebUI enterprise license. Read the Open WebUI license (opens in a new tab) before you add files to branding/, and set HUBZOID_KEEP_OWUI_SUFFIX=true to keep Open WebUI's branding.

Runtime folders

Hubzoid creates these while it runs. Keep them out of Git and include them in backups with hubzoid backup.

PathContents
.hubzoid/hub.dbThe Hub's own tables, when SQLite is used.
.hubzoid/dbos.dbThe workflow engine's run history, when SQLite is used.
.hubzoid/schedule-state.jsonFire times and last results of markdown tasks and scheduled evals.
.hubzoid/schedule/<task>/A markdown task's scratch folder, always writable by that task.
.hubzoid/chats/<chat>/uploads/, .hubzoid/chats/<chat>/artifacts/Files people attached, and files the agent saved with write_artifact.
.hubzoid/evals/One JSON file per eval run. The last 50 are kept.
.hubzoid/artifact_secretSecret that signs download links (mode 0600).
.hubzoid/backups/Access snapshots written by hubzoid access migrate --apply.
.openwebui-data/The web chat's database, uploads and log for a single Hub.
.inbound/webhooks/<name>/Webhook events waiting for a task. Handled events move to .processed/.
.inbound/Delivery dedup records and Telegram bindings.
output/<session>/Fallback folder for tool output when no chat is in scope, such as hubzoid test.

Next steps

Read this page as Markdown

Choose which cookies Hubzoid can use. You can change this at any time from Cookie settings in the footer. Read the Cookie Policy for details.