Hub files
Every file and folder a Hub can contain, the keys each one takes, and the runtime folders Hubzoid creates and keeps out of Git.
A Hub is one folder. Hubzoid reads the files below every time the Hub starts, and some on every call. Everything except the .env files and the runtime folders belongs in Git.
What hubzoid init writes
hubzoid init my-hub writes every folder Hubzoid reads and a small starter for Kestrel & Oak, a fictional home-goods shop. An empty folder holds a .gitkeep so it stays in Git.
| Starter file | What it holds |
|---|---|
AGENTS.md | The team assistant for Kestrel & Oak, with three suggestions. It answers from knowledge/ and names the file it used. |
knowledge/house-rules.md | Three house rules: refunds within 30 days of delivery, orders over $500 need a manager's approval, support hours 09:00 to 18:00 UTC Monday to Friday. |
evals/refund-window.md | One check that the refund question follows the rule and names house-rules.md. |
connectors/.mcp.json | An empty mcpServers list. |
.env | MODEL, a random BRIDGE_API_KEYS and commented examples, readable only by you. |
.gitignore | .env, output/, .hubzoid/ and Python caches. |
README.md | What each folder is for. |
In an empty parent folder it also writes requirements.txt, .gitignore and README.md there, so the parent becomes a repository of Hubs. See the CLI reference.
File index
| Path | Read by | Purpose |
|---|---|---|
AGENTS.md | every surface | Required. The main agent's instructions and settings. |
.env | every command that builds or serves the Hub | Model, keys and settings. See configuration. |
agents/ | agent builder, work items | Sub-agents: skills, delegates or isolated stations. |
skills/ | list_skills, load_skill | Playbooks the agent loads on demand. |
knowledge/ | list_knowledge, read_knowledge | Reference documents, read live from disk. |
tools_local/ | tool registry | Python tools. |
restricted/ | tool registry, access guard, Settings | Python tools that need a capability, and their secrets in restricted/.env. |
connectors/ | MCP loader, Settings | MCP servers: .mcp.json for shared servers, <id>.yaml for remote servers people sign in to. |
channels/ | hubzoid run | One YAML file per channel. |
schedule/ | scheduler | Markdown tasks run on a cron or by a webhook. |
workflows/ | workflow engine | Python code workflows, their settings and their access rules. |
evals/ | hubzoid eval, scheduler, Settings | Behaviour checks. |
identity/ | inbound surfaces, Settings | Live sender lookup and labels for restricted capabilities. |
raw_data/ | grep_data, read_file, list_files | Large source material to search rather than load. |
branding/ | the web app | The agent's avatar, and the deployment's brand files. |
prompts/ | the web app | Shared prompts, one Markdown file each. |
hitl.yaml, budgets.yaml and others | their own loaders | Optional rules: approvals, usage limits, work items, settings, cards and sandboxes. |
Other files, such as a README.md or a .gitignore, are not loaded by Hubzoid, so they can live in the Hub too.
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.
| Canonical | Also accepted |
|---|---|
agents | agent |
skills | skill |
tools_local | tool_local, tools, local_tools |
connectors | connector |
channels | channel |
schedule | schedules, scheduled |
workflows | workflow |
evals | eval, evaluations |
raw_data | raw-data, rawdata |
output | outputs |
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. This is the starter's:
---
description: A starter assistant for Kestrel & Oak, a fictional home-goods shop. Make it yours.
suggestions:
- A customer wants a refund 40 days after delivery. What can we offer?
- Who has to approve a $650 order?
- When is support open?
---
You are the team assistant for Kestrel & Oak, a fictional home-goods shop
used as Hubzoid's starter. Answer from the files in `knowledge/` and name the
file you used. If they do not answer the question, say so instead of
guessing. Keep answers short.| Key | Type | Default | Effect |
|---|---|---|---|
name | string | the Hub folder name | The agent's display name in the web app and Settings. Its slug is the model id in /v1/models unless MODEL_LABEL is set. |
description | string | the first non-heading line of the body, up to 200 characters | Short summary, shown in the agent switcher and on the empty new-chat screen. |
model | model id | none | The Hub's model when .env does not set MODEL. MODEL in .env wins. |
suggestions | list of strings | none | Click-to-send prompts on the empty new-chat screen. The Slack manifest also uses them. |
mcp_instructions | string | the body | Instructions sent to MCP clients when they connect, in place of the body. Use it when the body holds internal guidance or is long. |
auto_addendum | boolean | true | false 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. AGENTS.md, sub-agents and skills may use {{settings.KEY}} for a declared setting.
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.
---
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.| Key | Type | Default | Effect |
|---|---|---|---|
name | string | the folder or file name | Identifier, and the skill name when loaded as a skill. |
description | string | the first non-heading line of the body | When the main agent should use this sub-agent. Write it as a "when" sentence. |
model | model id | none | Decides skill or delegate. See below. |
tools | list of tool names | all tools | Delegates and stations only. The tools it 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, a different Codex model in a codex-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 SDK and Codex runtimes 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.
Isolated agents (stations). isolated: true makes the sub-agent a station: an agent with one job that a work-item workflow calls for one stage. It gets only its own instructions and exactly the tools it lists, never the main agent's conversation, skills or knowledge.
| Station key | Effect |
|---|---|
isolated | true makes the agent a station. |
engine | claude-local or codex-local. Default: the Hub's runtime. |
model | Optional. The engine's signed-in default when left out. |
tools | The exact tools, including save_handoff and read_handoff. Empty or missing means none. |
response_schema | A JSON Schema file in the agent's folder that the result is checked against. |
input | forward (default) or never. |
sandbox | A sandbox profile (sandboxes/<id>.yaml) for the agent's own workspace. Without it the agent has no shell or files. |
timeout_seconds | Optional limit on active time across all attempts of a call. |
Check stations with hubzoid stations validate. See the SDK guide stations (opens in a new tab).
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.
---
name: weekly-summary
description: Three-bullet summary of the week's orders for the operations lead.
---
1. Call list_knowledge and read the house rules.
2. Pull the week's numbers with the order tools.
3. Reply with three bullets and one risk to watch.| Key | Type | Default | Effect |
|---|---|---|---|
name | string | the folder name (or file stem) | Name passed to load_skill. |
description | string | Skill loaded from <file>. | Shown 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 with no body or with frontmatter 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.
# House rules
- **Refunds.** A customer can return an unused item within 30 days of
delivery for a full refund. After 30 days we offer store credit or a
repair, not a refund.
- **Large orders.** An order over $500 needs a manager's approval before it
ships.
- **Support hours.** Support answers from 09:00 to 18:00 UTC, Monday to Friday.| Key | Type | Default | Effect |
|---|---|---|---|
name | string | the file stem | Name passed to read_knowledge. |
description | string | Knowledge document: <stem>. | Shown in the knowledge menu. |
keywords | list of strings | none | Search hints. A single string is accepted. |
Frontmatter is optional. 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.
knowledge/_learned/<topic>.md holds what the remember tool saved for everyone. remember is a proposal that a Hub manager applies, and it is offered only when browser editing is on. 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/<capability>.pytool requires the capability named by the file stem. Every tool in the file shares it, and Settings lists it under restricted tools so you can grant it. It wins over any tool of the same name and is guarded before every call.identity/permissions.yamlcan label it. restricted/.envholds the secrets those tools read. File tools refuse every path underrestricted/.
See tools and connectors and restrict tools.
Connectors: connectors/
connectors/.mcp.json
MCP servers that serve everyone who uses the agent, in the mcpServers shape. connectors/mcp.json (no leading dot) is read when .mcp.json is absent. Settings lists these servers read-only under the agent's Connectors, with their names and any missing settings, never values.
{
"mcpServers": {
"tickets": {
"transport": "streamable-http",
"url": "https://mcp.example.com/mcp",
"headers": { "Authorization": "Bearer ${TICKETS_TOKEN}" }
}
}
}| Key | Applies to | Effect |
|---|---|---|
command, args, env | stdio servers | Process to start and its arguments and environment. |
url | HTTP servers | Server endpoint. |
transport | HTTP servers | sse or streamable-http (also written http). A server with a url and no transport uses SSE. A server with no url uses stdio. |
headers | HTTP servers | Request headers, such as an authorization token. |
client_session_timeout_seconds | any server | Seconds one tool call may take on the OpenAI Agents SDK and Codex runtimes. Without it, HUBZOID_MCP_TIMEOUT applies, else 30 seconds. |
${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. Invalid JSON stops the Hub from building.
Remote connectors
Remote MCP servers that each person signs in to, or that use one company key. They live in the deployment folder (the Hub folder for a deployment of one). Settings reads them, and writes them only when browser editing is on. Each person connects their own account in Settings.
version: 1
name: Tickets
url: https://mcp.example.com/mcp
sign_in: oauth
scopes: [tickets.read]
tools: [list_tickets, get_ticket]
hubs: [my-hub]
enabled: true| Key | Effect |
|---|---|
name, url | Display name and server endpoint. |
sign_in | oauth (each person with their own account), shared (one company key sent as a header) or none. |
client_id, client_secret | Optional client registered in advance. The secret is a reference such as env:TICKETS_CLIENT_SECRET, never the value. |
scopes | Scopes to request. |
tools | Optional allow-list of tools. |
hubs | The agents that offer it. |
enabled | false turns it off. |
Channels: channels/
One YAML file per channel connection, channels/<id>.yaml. Slack (provider: slack_assistant), WhatsApp (whatsapp_inbound), Telegram (telegram) and generic webhooks (webhook) each turn on with a file of four keys: version: 1, id, provider and enabled: true. Their secrets stay in .env. Check a file with hubzoid channels validate and see its status with hubzoid channels list. See Slack and WhatsApp and Telegram.
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.
---
schedule: "7 6 * * 1-5"
write: ["output/reports"]
publish_artifacts: true
---
Write yesterday's order summary to output/reports/<date>.md and publish it as a report.| Key | Type | Default | Effect |
|---|---|---|---|
schedule | 5-field cron | none | When the task runs, in the server's local time. |
on_webhook | string or true | none | Run when events arrive at this generic webhook inbox instead of on a cron. The value matches the receiver's WEBHOOK_INBOUND_NAME, and true means webhook. Use schedule or on_webhook, not both. |
run | string or list | none | Run a command instead of the agent. A string runs in a shell, a list runs as arguments without one. |
model | model id | the Hub's model | Model for this task. |
timeout | seconds | 1800 | Time limit per round. |
max_rounds | integer | 10 | Fresh-context rounds per run. |
max_turns | integer | 40 | Agent turns within one round. |
write | path or list | none | Extra Hub paths the task may write, not committed. |
commit | path or list | none | Hub paths the task may write and Hubzoid commits after the run. |
push | boolean | false | Push the commit. Needs commit. |
run_as | account email | HUBZOID_WORKFLOW_USER, else the owner recorded at setup | The account the run acts as, with that account's access. |
publish_artifacts | boolean | false | Give the agent the publish_artifact tool, to publish a private report for the run's account. Not for run: tasks. |
send_email | boolean | false | Give the agent the send_email tool, to email the run's account only. Not for run: tasks. |
alert_to | list of destinations | the Hub's alerts.to | Where this task's alerts go, in the same form as alerts.to in workflows/settings.yaml. |
enabled | boolean | true | false 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 directly in that folder whose name does not start with _ is imported, and each function decorated with @workflow is registered under its function name. Subfolders, such as a package of helper modules, are not imported as workflows. hubzoid new workflow scaffolds one. The API is in the workflow API reference.
workflows/settings.yaml
Optional. Workflows read any top-level key with hub.setting("<key>"), and the keys below configure the engine, webhooks, alerts, reports and email.
agent_max_attempts: 1 # tries for a failed hub.call_agent step. 1 means no retry.
max_concurrent_workflows: 2 # cap on code workflows on the shared queue
workflow_timeout: 30m # default run deadline
webhooks:
ticket-updated:
verify: header
header: X-Webhook-Secret
event_key: "{ticket.id}:{ticket.modifiedTime}"
alerts:
to:
- webhook: ALERT_WEBHOOK_URL # an environment variable that holds the URL
- email: ops@example.com
cooldown: 1h
series_keep: 50
email_lists:
leads: [asha@example.com, ravi@example.com]
personal_schedules:
min_interval: 1h
per_person: 10
stock_drift: # your own key, read with hub.setting("stock_drift")
threshold_pct: 5| Key | Default | Effect |
|---|---|---|
agent_max_attempts | 1 | Tries for a failed hub.call_agent call. A retry can repeat a message or a write. |
max_concurrent_workflows | no cap | Cap on code workflows running at once on the Hub's shared queue. A workflow with its own concurrency or concurrency_key has a separate queue and is not counted. |
max_executor_threads | 32 | Worker threads for the Hub's workflow runs. |
workflow_timeout | none, 15m for webhook runs | Default deadline for a run, in seconds or a string such as 30m. @workflow(timeout=...) overrides it. |
webhooks | none | Named webhooks that start code workflows. See below. |
webhook_retry_delays | [60, 300] | Seconds before each retry of a failed webhook event, at most nine. The default gives three attempts. |
alerts.to | none | Alert destinations: webhook: ENV_VAR or slack: ENV_VAR (an environment variable that holds the URL), or email: address. HUBZOID_ALERT_URL is the deployment fallback. |
alerts.cooldown | 1h | Quiet period for repeated alerts. Suppressed occurrences are counted in the next alert. |
alerts.failures_in_a_row | 3 | Failed runs in a row that raise a failure streak alert. |
alerts.pause_after_failures | 20 | Failed scheduled runs in a row that pause the schedule. 0 turns it off. |
series_keep | keep all | Copies kept of each live report (publish_artifact(key=...)). |
email_lists | none | Named lists of addresses for hub.send_email(..., email_list=...), from team runs only. At most 50 addresses each, and every address must have an account or be at a share domain. |
notify_by_email | false | Email people about their own schedules and long Run for me runs, when email delivery is set up. |
personal_schedules | min_interval: 1h, per_person: 10, pause_after_failures: 3 | Limits for the schedules people save for themselves: the shortest cadence, schedules per person per agent, and failures in a row before one pauses. |
Each entry under webhooks: is served by the Hub's bridge at /webhooks/<hub>/<name>, and its secret is the environment variable WEBHOOK_SECRET_<NAME>, with the name upper-cased and hyphens turned into underscores. Names use lowercase letters, digits, - and _, up to 64 characters. whatsapp, telegram and the Hub's WEBHOOK_INBOUND_NAME (default webhook) are taken by the inbound receiver and refused.
| Webhook key | Effect |
|---|---|
verify | header (the default) compares a shared secret sent in a header. hmac checks a timestamped HMAC-SHA256 signature. github checks GitHub's X-Hub-Signature-256. |
header | The header that carries the secret for verify: header. Default X-Hook-Secret. |
signature_header | The header that carries the signature for verify: hmac. Default the header value. |
timestamp_header | The header with the Unix timestamp the signature covers. Required with verify: hmac, and it must be within five minutes. |
event_key | A template of body fields, such as {ticket.id}:{ticket.modifiedTime}, that identifies one event so repeats are recognized. Without it, delivery-id headers are used, then a hash of the body. |
The file is validated when the bridge starts. A mistake, such as invalid YAML, an unknown verification or an empty alert destination, turns the workflow engine off for that Hub and records the error in workflow health, which hubzoid doctor reports as scheduler.health. Chat keeps running, and the Hub's webhook URLs answer 503 until the file is fixed and the bridge restarts. See code workflows and webhooks.
workflows/access.yaml
Optional. Who may run, schedule, edit, approve and see each workflow. Check it with hubzoid workflow-access validate, preview the change with hubzoid workflow-access diff and apply it with hubzoid workflow-access apply. See the CLI reference.
Evals: evals/
One Markdown file per case. The case id is its path under evals/ without .md. Files and folders starting with _ or ., and evals/fixtures/, are skipped.
---
contains: ["house-rules"]
---
## Prompt
A customer wants a refund 40 days after delivery. What can we offer?
## Criteria
Follows the refund rule in knowledge/house-rules.md for that many days,
and names house-rules.md as its source.Every frontmatter key is optional: schedule, tags, expect_tools, forbid_tools, expect_tool_args, contains, not_contains, timeout (1 to 3600 seconds, default 120), threshold (the judge's pass mark out of 10, default 7), enabled, run_as, and fake_tools, real_tools and allow_real_mcp for fake tool results. Any other key is an error.
The body holds a ## Prompt section and an optional ## Criteria section, which turns the judge on. A conversation uses ## Turn 1, ## Turn 2 and so on instead of ## Prompt. A body with no headings is the prompt. See evals.
Identity: identity/
Who a WhatsApp or Telegram sender is comes first from the phone number recorded on a person in Settings. A Hub can add its own live lookup, for example in a CRM. A lookup only says who someone is. What they may do is decided by their grants in Settings.
identity/access.csv is no longer read. Put phone numbers on people in Settings, and grant who may use a channel there. A Hub that still has the file gets a warning.
identity/access.py
A function-backed lookup, such as a CRM.
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. Group labels for a web or MCP caller, looked up by email."""
...groups_for_email is optional, and without it the file never receives email lookups. Groups are labels the agent can see as context, and they grant nothing. Results are cached for 60 seconds. An exception in either function resolves to no one and no groups rather than failing the request. On WhatsApp and Telegram, a sender whose number neither Settings nor the lookup knows is refused.
identity/permissions.yaml
Labels for the capabilities of restricted/*.py files in Settings, read without running any tool code. An entry that names a built-in capability, such as use_hub, manage_access or curator, is ignored with a warning.
finance:
label: Finance reports
description: Read margins and receivables from the accounting system.
sensitive: true| Key | Type | Default | Effect |
|---|---|---|---|
label | string | the name in title case | Name shown in Settings. |
description | string | Use the restricted tools assigned to this capability. | Explanation shown with the capability. |
sensitive | boolean | false | Tags the capability as Sensitive in Settings, and the review step warns before it is granted. |
Access concepts are covered in identity and access.
Branding: branding/
The agent's avatar. A Hub's branding/logo.svg, logo.png or logo.webp, then favicon.svg or favicon.png, is its agent's avatar in the web app. The first file present wins. Names match without regard to case.
The deployment's brand (name, logos, favicon, sign-in text, links, terms) is the brand: section of hubzoid.yaml, with its files under branding/ next to it. Colours, fonts and corner radius go in branding/theme.json. Until a deployment has a brand: section, a branding.yaml there is read instead. Check the brand with hubzoid branding validate, and turn an older branding/ folder into branding.yaml with hubzoid branding import-legacy.
custom.css is no longer served. The brand's presentation.decorative_css can style three brand slots only. See the SDK guide branding (opens in a new tab).
Other files at the Hub root
All optional. A Hub without them behaves as before.
| File | What it sets | Check or change it with |
|---|---|---|
hitl.yaml | When the agent asks a person first: per tool, connector or agent, ask: never, once, always or auto, and whether it may ask questions. | hubzoid hitl |
budgets.yaml | Usage limits in tokens, turns or dollars, per run, per work item and per month. | hubzoid budgets |
work-items.yaml | Limits for durable work and the write mode of each external action (off, draft or approve). | hubzoid work-items validate |
settings.yaml | The settings the Hub declares: name, type, default, whether required or secret. | hubzoid hub settings |
hub.settings.yaml | Values for those settings, and secret references such as env:NAME. | hubzoid hub configure |
hub.manifest.yaml | The Hub's own setup checks, run by hubzoid doctor. | hubzoid hub doctor |
mcp-apps.yaml, cards/*.yaml | Which tool results show as cards in chat. | hubzoid apps validate |
sandbox.yaml, sandboxes/<id>.yaml | Sandbox providers and limits, and one profile per kind of workspace. In the deployment folder. | hubzoid sandbox doctor |
prompts/<id>.md | Shared prompts people pick in chat. | hubzoid workspace prompts |
A value from hub.settings.yaml gives way to the environment variable a setting names in env:, and a missing value falls back to the default. See the SDK guides for approvals (opens in a new tab), usage limits (opens in a new tab), work items (opens in a new tab), Hub settings (opens in a new tab), cards (opens in a new tab) and sandboxes (opens in a new tab).
Runtime folders
Hubzoid creates these while it runs, under the Hub folder or under HUBZOID_DATA_DIR when set. Keep them out of Git and include them in backups with hubzoid backup.
| Path | Contents |
|---|---|
.hubzoid/hub.db | The Hub's own tables when SQLite is used, and on a single Hub also the operational store: accounts, sessions, conversations, access, audit and usage. |
.hubzoid/dbos.db | The workflow engine's run history, when SQLite is used. |
.hubzoid/secret.key | The deployment key of a single Hub (mode 0600), unless HUBZOID_SECRET_KEY is set. A deployment of several Hubs keeps it next to its manifest, in its data folder. Back it up apart from the data. |
.hubzoid/deployment.json | A pointer to the deployment's manifest, for a Hub that runs with others. |
.hubzoid/artifact_secret | Secret that signs download links (mode 0600). |
.hubzoid/chats/<chat>/uploads/, .hubzoid/chats/<chat>/artifacts/ | Files people attached, and files the agent saved with write_artifact. |
.hubzoid/artifacts/ | Published artifacts and live reports. |
.hubzoid/runs/<workflow>/<run>/ | A run's private folder, hub.run_dir. |
.hubzoid/data/ | The Hub's private data folder, hub.data_dir. |
.hubzoid/outbox/, .hubzoid/alert-preview/ | Emails and email alerts written instead of sent, with HUBZOID_EMAIL_DELIVERY=preview. |
.hubzoid/evals/ | One results file per eval run. The last 200 are kept (HUBZOID_EVAL_KEEP_RUNS). |
.hubzoid/backups/ | Scheduled backups, unless HUBZOID_BACKUP_DIR says otherwise. |
.hubzoid/schedule-state.json | Fire times and last results of markdown tasks and scheduled evals. |
.hubzoid/schedule/<task>@<person>/ | A markdown task's scratch folder for the account it runs as, always writable by that run. |
.inbound/webhooks/<name>/ | Generic 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. |
logs/ | The shared browser's log, when HUBZOID_BROWSER=true. |

