Concepts

Architecture

How a request moves through Hubzoid, from the web app and other surfaces through the edge and the bridge to the agent runtime and the model.

Hubzoid runs each Hub as one bridge process behind a single public front door, the edge. Every surface, from the web app to Slack and connected assistants, reaches the Hub through that bridge, which knows who is asking, checks their access, runs the agent and streams the answer back.

When to read this

You do not need this page to build an agent. Read it when you plan a deployment, open a port, choose a database, or want to know where a decision such as "may Priya run this tool?" is enforced.

What starts when you run a Hub

Terminal
hubzoid run my-hub
Output
✓ Hubzoid is ready: http://127.0.0.1:3080  (local mode, sign-in off)
  Connect Claude Code: claude mcp add --transport http my-hub http://127.0.0.1:3080/mcp

That one command started:

  • the edge on port 3080, the only port meant for browsers and other machines
  • the bridge for my-hub on 127.0.0.1, from port 8000 up, skipping ports already in use
  • the workflow engine inside the bridge, for scheduled work
  • SQLite files under my-hub/.hubzoid/ for conversations, access and runs

How it works

Two seams keep the parts that change most often, the surfaces and the model, replaceable without touching the Hub folder.

  1. Surfaces

    Where people and applications send messages.

    • Web app
    • Your own app
    • Slack
    • WhatsApp
    • Telegram
    • OpenAI-compatible API
  2. Bridge

    /api/conversations/api/agui/v1/chat/completions

    One process per Hub, on loopback behind the edge. It identifies the caller, runs the Hub agent, guards restricted tools and streams the answer. It also serves the web app, Settings, /mcp and workflows.

  3. Runtime

    Hub files, tools and skills load the same way on all three.

    • OpenAI Agents SDK
    • Claude Agent SDK
    • Local Codex
  4. Model

    Through LiteLLM, the claude CLI for claude-local, or the codex CLI for codex-local.

    • OpenAI
    • Anthropic
    • Azure OpenAI
    • OpenRouter
    • claude-local
    • codex-local
Assistants connected over MCP enter the bridge at /mcp and bring their own model.
Video lesson 4 · 3:22Three types of AI agents, one brainChoose what starts a task: a person, a trigger or an assistant.

A chat request, end to end

  1. Browser
  2. Edge on :3080
  3. Bridge
  4. Agent runtime
  5. Model provider
A web app turn. Other surfaces join at the bridge.
What each process does during the turn
  1. The browser reaches the edge on the public port (PORT, default 3080). The edge passes every page and API call to the bridge, which serves the web app itself.
  2. The bridge knows who is asking from the Hubzoid session, or, with sign-in off, treats the request as the local owner admin@localhost. It requires Use this agent before anything runs, stores the message, saves attached files to the conversation and builds the prompt from the active branch of the conversation, with the person's own notes first.
  3. The runtime runs the Hub's agent in a server task that outlives the page. Closing the browser does not stop a reply, and Stop does. Restricted tools pass through the access guard, which records every allow or deny decision before a call runs.
  4. The model provider answers through LiteLLM, the claude CLI or the codex CLI.
  5. The bridge streams the reply with its tool steps, stores it in the conversation and records a usage row with tokens, estimated cost and duration, never message content.

A turn that calls a tool

The model never runs a tool itself. It asks, and the bridge checks access, runs the tool and hands back the result, as often as the answer needs. Here is one web app turn on a Hub with a restricted ledger_lookup tool.

  1. 1Web app

    A person asks

    Is bill 1042 a duplicate? The web app sends the message to the bridge.

  2. 2Bridge

    Who is asking

    Reads the signed-in person from the session, checks Use this agent, and builds the prompt from the conversation.

  3. 3Runtime

    The agent takes the turn

    The Hub agent hands the prompt to the model with its instructions, menus and tools.

  4. 4Model

    Asks for a tool

    Requests ledger_lookup with the bill number. The model only asks. It never runs code.

  5. 5Bridge

    Access check

    The tool comes from restricted/ledger.py, so the guard checks the ledger permission and records the decision before anything runs.

  6. 6Bridge

    The tool runs

    The function runs in the bridge process with the credentials of the Hub, and its result goes back to the model.

    Steps 4 to 6 repeat for each tool the model asks for, such as read_knowledge for the matching rules.

  7. 7Bridge

    The answer streams back

    The model writes the answer. The bridge streams it to the web app, stores it and records usage, never message content.

Other surfaces join at step 2. By default only the web app, your own app, the API, MCP and workflows may run restricted tools.

Processes and ports

Only the edge port is meant to be reachable from other machines. The bridge always listens on loopback behind it.

Every process, its port and when it starts
ProcessListens onRole
EdgeHUBZOID_HOST:PORT, default 127.0.0.1:3080The single public front door. Routes paths to a bridge or the inbound server.
Bridge127.0.0.1, BRIDGE_PORT or the first free port from 8000Web app and sign-in, agent runtime, access guard, workflow engine, Settings and hosted MCP. Always loopback.
Slack adapterNo port. Slack Socket Mode connects outward.Relays Slack messages to the bridge. Starts when a file in the Hub's channels/ sets provider: slack_assistant.
Inbound server127.0.0.1:8100 (HUBZOID_INBOUND_PORT)Verifies and handles WhatsApp, Telegram and generic webhook deliveries. Starts when a channel file turns one on.
Shared browserPort 8931 (HUBZOID_BROWSER_PORT)One resource-limited browser shared by every agent as MCP tools, when HUBZOID_BROWSER=true.

With sign-in off, the edge stays on 127.0.0.1, and hubzoid run refuses another interface unless HUBZOID_ALLOW_UNAUTHENTICATED_NETWORK=true accepts that anyone who can reach the port acts as the Hub's owner.

What the edge routes

The edge sends the web app, sign-in, Settings, downloads and /mcp to the bridge, and webhook deliveries under /webhooks/<hub>/ to the bridge or the inbound server. HTTP endpoints lists every route.

Health checks, internal paths and identity headers

It answers /healthz/workflows itself, with 503 while a Hub's workflow engine is unhealthy, for an external uptime monitor. The bridge's internal API (/v1, /uploads, /otel) stays on loopback, and the edge answers 404 for it.

The edge refuses . and .. path segments and drops client-sent X-Hubzoid-* identity headers, so a browser cannot assert its own identity.

How each surface reaches the Hub

Every surface builds on the same Hub files. Access rules differ per surface. See identity and access, and chat inside your own app for the AG-UI surface.

Each surface, its path into the bridge and who the caller is
SurfacePath into the bridgeWho the caller is
Web appThe bridge's conversation API, in processThe signed-in account, or the local owner with sign-in off. Surface web.
Your own appAG-UI at /api/agui, with a pass your app's server signsThe person your app vouches for, limited to the agents and tools the app allows. Surface app.
SlackThe adapter calls /v1/chat/completions with a signed identity assertionWith SLACK_IDENTITY_MAPPING=true, the sender's email, which must belong to a Hubzoid account. Surfaces slack-dm and slack-channel.
WhatsApp, TelegramThe inbound server calls /v1/chat/completions with a signed identity assertionThe person whose phone number is recorded in Settings, else the Hub's identity/access.py lookup. Unknown senders are rejected first.
Generic webhookThe inbound server stores each event as a file for a schedule/ taskNo chat reply. A task picks up the events.
Named webhookThe bridge stores each event and starts the code workflow that consumes itThe workflow's account. Surface workflow.
OpenAI-compatible APIDirect calls to /v1/chat/completions on loopbackIdentity headers count only with a valid identity assertion signed with the deployment key. A call without one names no person and is refused.
MCP clientsEdge /mcp, served inside the bridgeThe account that signed in and approved the connection. Surface mcp.
Markdown tasks, code workflowsIn process, on the workflow engineThe account the run acts as. Surface workflow.

Runtime backends

The runtime is chosen from the resolved model id: MODEL in .env, then the main AGENTS.md model, then claude-local. Loaders, tools, skills and knowledge are the same on all three, so a Hub moves between them by changing MODEL.

How the three runtimes differ
OpenAI Agents SDKClaude Agent SDKLocal Codex
Selected whenMODEL is any id that does not start with claude-local or codex-localMODEL is claude-local or claude-local/<tier>MODEL is codex-local or codex-local/<model-id>
Model callsLiteLLM, to OpenAI, Anthropic, Azure OpenAI, OpenRouter or another LiteLLM providerThe claude CLI as a subprocessThe codex CLI app-server as a subprocess, Codex CLI 0.163.0 or 0.147.0
Hub toolsFunction tools on the agentOne in-process MCP server named hubzoid, tools seen as mcp__hubzoid__<name>Offered to Codex as tools that Hubzoid runs itself
The CLI's own toolsNot applicableDisabled. Claude Code settings files and the machine's own Claude connectors are not used.Disabled. Each request uses a fresh thread and an isolated configuration with only the login copied in.

Agents and models covers model ids, sign-in for each runtime, delegation and model settings.

Workflow engine

Workflows run on an embedded DBOS (opens in a new tab) engine inside the bridge process. There is no separate scheduler service, and one engine owns each Hub at a time. See workflows and schedules.

When tasks and code workflows run, and how they resume
  • Markdown tasks (schedule/*.md) and scheduled evals run whenever their files exist, one at a time per Hub, and wait while a chat request is in flight.
  • Code workflows (workflows/<name>/*.py) start on their own only with HUBZOID_SCHEDULES=1 on a single Hub, or automatically in a deployment that hubzoid run starts from hubzoid.yaml or with several Hubs.

DBOS checkpoints each step, so an interrupted code workflow resumes after a restart without repeating finished steps.

Databases

A single Hub uses SQLite files inside its folder. Setting DATABASE_URL to a postgresql+psycopg:// URL moves them into one PostgreSQL database.

Every store, what it holds and how several Hubs share them
StoreDefault locationHolds
Operational store<hub>/.hubzoid/hub.dbAccounts and sessions, web app conversations (hz_conversations, hz_messages), grants, phone numbers, the access change log, tool decisions (hz_access_decisions), usage rows (hz_usage), each person's notes, connectors and personal connection tokens, MCP OAuth grants, workflow state (hz_workflow_kv), personal schedules, report series, artifact records and email deliveries. Artifact files stay in <hub>/.hubzoid/artifacts/.
Hub databaseSame file on a single HubWhatsApp and Telegram conversation history (hz_inbound_history).
Workflow engine<hub>/.hubzoid/dbos.dbRuns, steps, checkpoints and queues. In PostgreSQL, the dbos schema.
Deployment key<hub>/.hubzoid/secret.key, or HUBZOID_SECRET_KEYEncrypts stored credentials and signs identity assertions between Hubzoid's processes. Back it up apart from the data.

Hubzoid upgrades its own hz_* tables at start and refuses a database written by a newer release. When several Hubs run together, they share one operational store in the deployment's state folder (hubzoid-operational.db), with the deployment key beside it, while each Hub keeps its own workflow engine file. HUBZOID_OPERATIONAL_DB and HUBZOID_DBOS_DB set those stores separately.

Hosted MCP server

The bridge serves the Hub over MCP Streamable HTTP at /mcp, and the edge exposes it. hubzoid run turns it on for a Hub served on this machine or behind an https HUBZOID_PUBLIC_URL, and MCP_SERVER in the Hub's .env overrides that. See connect an assistant.

Sign-in, access rules and which tools MCP clients get
  • Sign-in is OAuth only. The MCP client opens Hubzoid's sign-in and consent page, and Hubzoid issues tokens for that person. Access tokens last 10 minutes, and refresh tokens and grants up to 30 days. Bridge keys are never accepted.
  • The same rules as chat. Each call runs as that person on the mcp surface, so restricted tools follow the same grants and every decision is recorded.
  • The client brings its own model. The Hub serves its tools and sends the AGENTS.md body, or the mcp_instructions: frontmatter, as the server's instructions.
  • Some tools stay in chat. Tools tied to a chat (write_artifact, list_artifacts, read_upload), the memory and hub change tools such as remember, call_jev and delegate sub-agents are left out.

People revoke a connection at /mcp/oauth/connections.

Settings

The bridge serves Settings at /settings, part of the web app, with a JSON API under /portal/api. It opens from the account menu, and each person sees what their access allows:

  • Personal, for everyone: Account, Connected accounts, Sources, Memory and My schedules.
  • Manage, for organization administrators and people who manage an agent: Agents, Activity, Team schedules, People, Usage and Organization. With sign-in off there are no People or Team schedules pages.

Grants made in Settings take effect on the next permission check, in every bridge that shares the database. See the Settings overview.

Several Hubs in one run

hubzoid run ./finance ./operations runs one edge and one bridge per Hub, each in its own process. Plain hubzoid run in a folder with a hubzoid.yaml starts the Hubs it lists. The bridges share the operational store, so one sign-in covers every agent a person may use, and the web app's agent switcher lists only those agents.

  1. Edge
  2. Bridge per Hub
  3. Hub folder
One sign-in and one web app in front of several independent Hubs.

See running several Hubs.

Paths per Hub, folder names and restarts

Each Hub's own paths are served under /b/<hub>/, such as /b/<hub>/mcp for its MCP server. Everything else, including the web app and /settings, goes to the first Hub's bridge, with the others as fallbacks.

Each Hub folder name is its access domain, so names must be unique without regard to case. A Hub that stops is started again on its own. While it restarts its pages answer 503, and the other agents keep answering.

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.