Changelog
What changed in Hubzoid 1.0.1 for workflows and the accounts they run as, artifacts and email, the Admin Console, runtimes, personal MCP connections, operations and security.
1.0.1
Hubzoid 1.0.1 turns a Hub into something a team can run: recorded workflows that act as real people, private artifacts and email, an Admin Console for accounts and access, and a choice of three runtimes.
Upgrading from 0.9.x
Read upgrading before you install 1.0.1. Set a random BRIDGE_API_KEYS in each Hub, take a backup with hubzoid backup before the first start of the new version, and run hubzoid doctor afterwards. Then rotate WEBUI_SECRET_KEY because of the edge cookie fix, so everyone signs in again and reconnects their personal MCP connections, and check People in the Console for users marked Needs attention.
Workflows
- Code workflows. Python functions with
@workflowand@stepinworkflows/<name>/*.pyrun on a schedule or by hand on the Hub's durable workflow engine (DBOS). A finished step is saved and is not run again when a run resumes. A step that was running when the process stopped runs again, so steps that change outside systems must be safe to repeat.hubzoid new workflowscaffolds a manual example that needs no model or external service. See code workflows. - Markdown tasks on the same engine.
schedule/*.mdtasks keep their files, timing and catch-up rules. Each run is split into work, commit, push and finish steps, and a failed push is retried. Agent work interrupted by a restart is reported as interrupted instead of repeated, and the next slot runs the task again. One markdown task runs at a time per Hub, across processes. See markdown tasks. - SQLite 3.42 on Python 3.12. On Python 3.12 the workflow engine needs SQLite 3.42 or newer, or PostgreSQL. Otherwise it refuses to start with a clear message, and
hubzoid doctorreports it asdeps.sqlite. Python 3.11 is not affected. See installation. - Model calls from workflows.
hub.call_llmis one model call with no tools, returning text, JSON or a validated Pydantic object (response_model), on LiteLLM models,claude-localandcodex-local.hub.call_agentruns the full agent. It is not retried unlessagent_max_attemptsis set inworkflows/settings.yaml, because a retry can repeat a message or a write, and a failed agent run fails the workflow run. See the workflow API. - Jev decisions, experimental.
hub.call_jevasks TypeSafe's Jev through OpenRouter for typednoul,choiceandscoredecisions, one type or mixed in one request. It uses a dedicatedJEV_OPENROUTER_API_KEYwith no fallback toOPENROUTER_API_KEY, checks every answer against its question, and fails the step on an empty or malformed reply. Rate limits, server errors and timeouts are retried once. A call still in flight when the process stopped is made again on resume, so it runs at least once, not exactly once. The same adapter is thecall_jevchat tool, behind the Call Jev (jev) capability, which nobody holds by default. - Concurrency. Each code workflow runs one at a time, and different workflows run side by side.
max_concurrent_workflowsinworkflows/settings.yamlsets an optional cap for the whole Hub. - Runs follow the code version. Runs are tied to the version of the workflow code that started them. Runs from other code are cancelled at start instead of blocking the queue, and a markdown run that had not started yet is queued again under the new code first.
- Run controls.
hubzoid schedule pause,resumeandcancelare recorded in the access log. The Console shows runs and paused work but has no run buttons. See operating runs. - Webhooks. GitHub's
X-Hub-Signature-256signatures are accepted. Repeated deliveries are dropped, matched by delivery id or by an identical body shortly after. A delivery counts as received only once it is stored, so a crash never swallows the sender's retry. A webhook task is told which event files it owns, as a line in its prompt or inHUBZOID_WEBHOOK_EVENTSforrun:scripts, and a run that did not finish is retried once it has ended. See webhooks. - No empty pushes. A scheduled task whose run changed nothing no longer pushes.
- New sample.
hubzoid init <name> --template watchtowerscaffolds a workflow-first Hub that checks bundled sample metrics on a schedule and explains threshold breaches.
Who a workflow runs as
- An ordinary account. Scheduled code workflows and markdown tasks run as an ordinary account:
run_ason the decorator or in the frontmatter, elseHUBZOID_WORKFLOW_USER(the Hub's, then the deployment's), else, on a Console-managed Hub, the owner recorded at setup (admin@localhostlocally). The account is captured once per run, checked again before every protected call, and never swapped for another. A Hub secret'sHUBZOID_WORKFLOW_USERwins over the Hub's.env. - One answer everywhere. Runs,
hubzoid schedule listand the Console's Runs as column share one resolution, so they always name the same account. - State belongs to the account.
hub.stateis kept per account, withhub.shared_statefor state shared by everyone who runs the workflow, andhub.run_diris a private folder for each run.hub.call_agentacts as the run's account, so it uses that person's personal MCP connections and never another's. - Results stay private. A Hub's managers see each run's workflow, status, timing and a failure summary. The result and step outputs are shown only to the account the run acted as.
- Legacy service identities. A legacy Hub, whose access is still managed in the web chat, keeps the service identities
workflow:<name>for code workflows andworkflow:md:<task>for markdown tasks when no account is configured, granted like a person. Newworkflow:*identities cannot be added in the Console. Existing ones stay, labelled "Legacy service identity", and runs that act as an account do not use their grants. See workflows and schedules.
Artifacts and email
- Published artifacts.
hub.publish_artifact(...)publishes a generated file, such as HTML, a PDF, a CSV or an image, as a private artifact with a viewer at/portal/artifacts/<id>. The owner can share it with people, groups or everyone who can use the agent, or by an expiring public link. HTML artifacts run sandboxed with no network access, unlessHUBZOID_ARTIFACT_ALLOW_ORIGINSnames origins they may load from. PDFs keep their file name. - Public links need their own capability. Share artifacts publicly (
share_public_links) lets its holder create links that anyone can open without signing in. Publishing never grants it. Removing it, or blocking the owner, ends that owner's public links for good, and granting it again does not revive them. "Anyone with the link" and link creation are one step, and a link that no longer works shows a clear page. - Owner email.
hub.send_email(...)emails the run's own account over SMTP (HUBZOID_SMTP_*) or writes it to a preview outbox. "Accepted" means the SMTP server accepted the message, not that it reached an inbox. A resumed run does not send an accepted message again, and a send whose outcome is unclear is reported and never resent automatically. Preview files are created readable only by Hubzoid's account. - Markdown tasks opt in. A task with
publish_artifacts: trueorsend_email: truein its frontmatter gets thepublish_artifactorsend_emailtool. See built-in tools. - Signed chat downloads. Download links for files an agent makes in chat are signed with a secret per Hub, and links issued by earlier versions stop working. The public default bridge key
devis refused on/artifacts, andHUBZOID_ARTIFACT_LINK_TTLadds an optional expiry.
Admin Console and delegation
- The Admin Console. The Console at
/portal/manages people, agent access and capabilities, and shows usage and runs. Authorized administrators see an Admin Console entry above their profile in the chat sidebar. See the Console overview. - Add user. On an agent's Access page and on People, Add user creates a new user with a name, an email and a typed or generated password, together with their first access, in one flow. The password is shown once to share yourself, and is never stored or logged. Google sign-in only creates the account with no password when Google sign-in and
OAUTH_MERGE_ACCOUNTS_BY_EMAIL=trueare configured. A duplicate email changes nothing and links to that user, whose access you edit instead. A partial result keeps the account and offers Try again without creating a second one. Public sign-up stays closed. - A user's details. People shows each user's name, email, status, role and access by agent, with Edit access per agent and Add an agent for an agent they cannot use yet. Organization administrators approve pending sign-ups, reset passwords and delete users there. A Google-only user's password is managed through Google.
- One Administrator role. Making someone an Administrator sets Hubzoid organization administration and the chat app's admin role together, and User clears both. A change that sets only one side is reported with Try again, a user who is an administrator on only one side shows Needs attention and is never promoted implicitly, and the last administrator cannot be demoted or deleted.
- Delete user. In the user's … menu, with the email typed to confirm, Delete user removes every grant, then the chat account and its chats. Activity history, usage records and published artifacts are kept. The Console no longer offers Block or Reactivate. A user blocked earlier stays blocked and can be unblocked through the management API.
- Sign-up off by default. Public email and single sign-on sign-up default to off (
ENABLE_SIGNUP,ENABLE_OAUTH_SIGNUP). Explicit operator settings still win. - Delegated managers. One authorization service decides every Console and API change. A delegate, someone with Manage access on specific agents, grants or removes only what they hold in that agent, never Manage access itself. They cannot change their own access or an organization administrator's, and cannot approve, reset, change roles or delete accounts. They can create a normal account with access in the agents they manage. Manage access on an agent also lets that person chat with it, because every direct capability includes Use this agent. Restricted tools still need their own grant, and organization administrator rights alone do not grant chat.
- Management API.
/portal/apialso accepts an Open WebUI API key asBearer sk-..., and every refusal carries a stablecode. See HTTP endpoints. - Access goes to named people. Nobody can create new "Everyone signed in" access, whether in the Console, the API, an agent's proposal,
hubzoid grant '*'or the grant store. A legacy Hub that was open to everyone signed in carries that over as an "Everyone signed in" row, which keeps working until an organization administrator removes it. - Edit access. Capabilities are grouped as Hub access, Hubzoid tools, Restricted tools, Workflows and Administration, in collapsible sections with keyboard, touch and screen reader controls. The review lists who, the agent, and what is added and removed. Success reads "Access updated".
- Capabilities describe themselves. Built-in capabilities register their label, group and required settings, so a new one appears in the Console without its own screen. A missing setting shows a short status such as "Jev key missing" and never grants or blocks anything by itself. Grants for capabilities that no longer exist stay visible and removable.
identity/permissions.yamlrelabels custom restricted tools only. The built-incuratorcapability reads Save shared knowledge. Tool refusals name capabilities by their Console label, and a capability a delegate holds but cannot pass on reads "Admins only". - Management tools, optional. With
HUBZOID_MANAGEMENT_TOOLS=true, an agent can propose people and access changes. A change applies only after the same manager confirms the exact plan in the Console. Proposals are single use, expire afterHUBZOID_CHANGE_REQUEST_TTLseconds and are audited with their surface. The tools are hidden from people who manage nothing. - The first owner. The verified configured owner receives Console and agent access once. Fresh Hubs start with access managed in the Console, and existing Hubs keep their access mode until they are migrated. Later sign-ins do not restore revoked grants.
- Chat follows Hubzoid's access. The chat app's agent picker respects Hubzoid entry permissions, including for chat administrators. A new account's agents appear on its first sign-in, and a blocked person, or one with no agent yet, gets a notice instead of an empty picker. Restricted tool decisions are stored in the database (
hz_access_decisions) instead of monthly files, and a call whose decision cannot be recorded is refused. - The web chat's own user pages. A gateway set up fresh with Console accounts hides Open WebUI's user list and refuses its account admin writes.
HUBZOID_HIDE_OWUI_USERSoverrides this, and existing deployments are unchanged. When every agent is managed in the Console, Open WebUI's Groups page is hidden too. The Console's service account also reuses its Open WebUI token instead of signing in for every sync, which could exhaust Open WebUI's sign-in limit for the owner's email. - Phones and tablets. The Console works at phone and tablet widths, with tables that scroll within their frame and larger touch targets.
Personal MCP connections
- All three runtimes. Each person's own Open WebUI native MCP connections now work on the OpenAI Agents, Claude and Codex runtimes. On Console-managed Hubs each app needs its
connector_<app>capability, andconnector_is a reserved capability prefix. See tools and connectors. - Connection journeys, optional. With
HUBZOID_CONNECT_JOURNEY=true(off by default), a person who asks to "connect my Gmail" in chat or on WhatsApp gets a personal link bound to them. The result is checked, confirmed on a browser page and back in WhatsApp, with an optional one-time continuation of the waiting request. It is built on Open WebUI native MCP only. - Links that work. Connection links work in real browsers, and a person who was signed out comes back to the link after signing in.
Runtimes
- Three runtimes. The OpenAI Agents SDK (LiteLLM models), the Claude Agent SDK (
claude-local) and local Codex (codex-local). Codex runs through the pinned Codex CLI 0.147.0 app-server protocol, with the shared guarded tools and a separate thread for each request. A fresh interactivehubzoid initselects an authenticated local CLI, and asks once when both are usable. See agents and models. - Controlled tools hidden everywhere. On
claude-localandcodex-local, the agent is no longer shown controlled tools (restricted/tools,remember,call_jev) the person may not use, as was already the case on LiteLLM models. Calls were already refused. - No borrowed Claude connectors.
claude-localno longer shows chat users the connectors of the Claude account the machine is signed in to, such as its Gmail, Drive or Slack, or MCP servers from that account's settings. Every Claude run, includinghub.call_llmand the eval judge, uses only the servers Hubzoid passes, and the eval judge no longer gets Claude Code's built-in tools. - Trace export is opt-in. The OpenAI Agents SDK exports runs to OpenAI's trace dashboard only with
HUBZOID_OPENAI_TRACING=true. - Usage names the real model. Usage rows name the model that answered, not the Claude CLI's background Haiku call.
- Demo Hub. The bundled demo Hub describes the three runtimes.
Console and chat
- Agents first. The Console opens on Agents, with five summary cards (messages and conversations, users, tokens, workflow runs and approximate cost) above the agent cards. Agent cards show tokens and approximate cost for the selected period. Runs and schedules live inside each agent, and old direct links to the global Runs page still work.
- Users card. It counts the sign-in accounts in the viewer's scope across Hubs, whatever the period. Blocked accounts count, and service identities and email-only grants do not. An account directory that cannot be read shows as unavailable, never 0.
/portal/api/summaryaddsuser_accounts. - Usage records. Every chat turn and workflow model call writes a usage row (
hz_usage) with the time, Hub, surface, person, chat, model, tokens, estimated cost, status and duration. Message content is never stored there. Conversation and message counts leave out background title and suggestion calls, whose tokens and cost are still counted. - Working on it. Chat shows a "Working on it…" status from sending until the first words, on every runtime. A turn stopped before the first word, or cut off because the Hub's bridge stopped, no longer keeps that line. Tool-start markers no longer imply success before a result arrives.
- Studio theme. The Console follows the Studio theme with local fonts, compact alerts, accessible headings and controls, readable schedules and inspectable results, in light and dark. Authentication, permission and service failures offer distinct next steps.
- Branding. Open WebUI branding is kept unless the Hub has files in
branding/. The upstream first-run changelog is hidden. Startup refreshes the bridge connections Hubzoid owns without clearing unrelated saved configuration. - Chats stay private from admins. Web chat admins can no longer open or export other people's chats.
ENABLE_ADMIN_CHAT_ACCESSandENABLE_ADMIN_EXPORTturn this back on.
Operations
- Backup and restore.
hubzoid backupwrites one archive of a deployment's databases, chat data and Hub state while chat keeps working. New scheduled runs are held and running ones finish first.hubzoid restoreputs it back and can move a deployment to new paths. PostgreSQL is backed up withpg_dump. Database passwords are left out of the saved deployment manifest unless you pass--include-secrets, and restore checks every target path in an archive before it changes anything. See backup and restore. - Doctor for scripts.
hubzoid doctor --jsonreports checks with stable ids such asauth.bridge_keys,db.operational,backup.age,scheduler.healthanddeps.sqlite. Doctor only reads. See doctor checks. - Versioned schemas. Hubzoid's own tables are versioned and upgraded when a bridge starts, with a lock so bridges starting together do not collide. Upgrades go forward only, and a database written by a newer release is refused.
- Gateway. Any bridge can serve the Console and the agent picker's access check. When a bridge refuses connections, the edge tries the next one, so restarting one Hub no longer empties the picker. This is not high availability for the gateway or the chat app. A Hub's
.envstays with that Hub. Sign-in and chat app settings are still taken from Hub.envfiles when the gateway's own environment lacks them, and listed at start.WEBUI_NAMEin a Hub's.envno longer overrides--name. - SQLite and shutdown. SQLite databases wait up to 30 seconds for another writer's lock, since a gateway's bridges share one file. Standalone and gateway supervisors wait for their services to shut down before exiting, so SQLite closes cleanly when a container stops.
- AWS Secrets Manager, optional. Each configuration layer can read a secret:
AWS_SECRET_NAMEwithAWS_REGIONfor the deployment,HUBZOID_HUB_SECRET_NAMEfor a Hub andHUBZOID_RESTRICTED_SECRET_NAMEfor restricted tools. Secrets are read at start with boto3's normal credential chain, and within a layer the secret wins over the file. A rotation takes effect on restart.hubzoid doctorreports each key's layer by name only and whether each secret can be read, and--skip-secret-fetchskips the reads. See configuration secrets. - Docker. The image is built from the root
Dockerfileon Debian 13 (SQLite 3.46), with CPU-only PyTorch, as an unprivileged user, publishing only port 3080. Each release is also published to the GitHub Container Registry for amd64 and arm64. Compose files for SQLite and PostgreSQL are indocker/. See Docker. - Releases. Dependencies are bounded and locked in
requirements.lock. A release is built and tested from its tag before PyPI, the GitHub release and the container image are published.hubzoid.__version__comes from the installed package metadata.
Security
- Edge cookies. The edge no longer keeps cookies across visitors. Its shared upstream client stored Open WebUI's sign-in cookie and sent it with later requests that had none, so an anonymous visitor could receive the last signed-in user's session. Earlier releases with the edge are affected. After upgrading, rotate
WEBUI_SECRET_KEYto end any session that may have leaked. - Personal connections on allowed surfaces only. A person's Open WebUI MCP connection is used only on surfaces allowed to reach restricted tools (
HUBZOID_RESTRICTED_SURFACES). A shared Slack channel mention no longer carries the token of the person who mentioned the agent. - MCP credentials off the command line. Each person's connector token, and a Hub server's headers and
env, no longer appear in theclaudeprocess's command line, where other accounts on the machine could read them. They go in a file for each turn, readable only by Hubzoid's account and removed when the turn ends. - Fewer inherited secrets. Agent child processes (the
claudeCLI and its MCP servers) no longer inherit restricted tool values, Hubzoid service secrets, SMTP credentials or AWS credentials. Open WebUI no longer receivesHUBZOID_*settings. - Stricter public port. The edge refuses
.and..path segments, which could reach bridge paths outside the forwarded routes, and drops client-sentX-Hubzoid-*andX-OpenWebUI-*headers. - PostgreSQL lookups. Open WebUI API key, group and connected MCP OAuth lookups honor PostgreSQL
DATABASE_URLandDATABASE_SCHEMA, including in shared gateways. Database failures deny access instead of falling back to stale SQLite credentials. - File tools refuse secrets. Chat and MCP file tools refuse dotenv files, databases and their sidecar files, private runtime state, and symlinks into those locations. Both search backends check every file before reading it, and SQLite signatures catch renamed database files. Knowledge, skill and agent loaders cannot publish protected files through aliases.
- Sandboxed templates. The Jinja renderer now uses the advertised sandbox, which blocks Python object traversal that could get around the file tool restrictions.
License
- Hubzoid's own code is licensed under Apache 2.0. Version 0.9.x was MIT. Distributions include
LICENSEandNOTICE, and dependency and bundled font licenses remain intact.
Not yet verified with real services
These paths are built and tested without the real service, and have not been checked end to end against it for this release:
- Google sign-in consent.
- WhatsApp delivery.
- SMTP delivery over TLS.
- AWS Secrets Manager.
- Live checks of the OpenAI Agents SDK runtime.
Known limits
- Removing the Share artifacts publicly permission ends that person's public links for good. Granting it again does not revive them.
- The Docker path was not re-verified for this release.
- If the chat app never marks a stopped reply as finished, its "Working on it…" line can remain.
- Database migrations go one way. Going back to an earlier version means restoring the backup.
