The billing gate died of a peer call that was an HTTP URL. COMMERCE_URL defaulted
to the public api.hanzo.ai edge — which is THIS binary — so the /v1/billing/*
forwarder re-entered itself, and apps/commerce/transport still carries the scar
tissue: a whole app republished as an http.Handler, every edge middleware re-run
per peer read, and a goroutine-keyed depth counter (maxDepth = 8) to stop the
recursion it cannot otherwise prevent. A call by name cannot express that mistake,
because there is no address to point at the wrong thing.
An app already declares everything a caller needs:
zip.Post[plane.BalanceIn, plane.Balance](cloud.Plane(), "/finance/balance",
planeBalance, zip.WithOperationID(plane.FinanceBalance))
— the app, the op, the request type and the response type, in one expression.
Cloud already projects that registry as OpenAPI, a CLI, an MCP tool list and a
routing declaration. A typed Go client for a peer call is ONE MORE PROJECTION of
it, which is why it is generated here rather than hand-written once per caller.
commerce.FinanceBalance(ctx, &plane.BalanceIn{Currency: "usd"})
plane/gen emits one package per peer (14 apps, 28 ops) holding ONLY request and
response types and call stubs. What it buys is a check no care buys today: a
hand-written peer call is four independent facts that must agree at RUN time, and
nothing stops pairing commerce's op with iam's name, or BalanceIn with Txns. The
wrapper fixes all four to each other where they are declared.
THE CLIENT HALF MOVED TO THE LEAF, and that is what makes any of this possible.
package cloud is itself a caller — the edge rate-limiter reads finance_scope_rules
— so a client that imported cloud could never be imported BY cloud, and the one
call that most needed to stop being a URL is the one the mechanism could not have
expressed. Ask and everything under it now live in package plane; cloud keeps the
server half (Plane, ServePlane) because binding a socket reports itself to o11y.
cloud.Ask stays as a forwarder, so the 44 existing call sites do not move and
there is still exactly one implementation.
It does not drag the peer's tree: plane/commerce is 355 packages against
apps/commerce's 1231, and imports zero apps/ packages — one more than the leaf it
needs. An importable client that linked the implementation would have rebuilt the
problem with extra steps.
Generated FROM SOURCE, judged BY THE RUNNING REGISTRY. zipdoc already reads these
same call sites; reading source buys hermeticity a mount cannot (no store opened,
no boot order, no app that must come up before it can be described). zip's rule —
project from the live router, never the AST — is about a host discovering a plugin
it does not build, and it still binds: plane_registry_test.go mounts commerce and
asserts the generated surface IS the live plane registry, so the generator never
gets to be quietly wrong. Reading the AST's index expression alone had already
been quietly wrong once — treasury spells its registration with inferred type
arguments, so its only op was dropped; types.Info.Instances sees both spellings.
Proven against the real thing, not a fake. plane_client_test.go mounts commerce as
a plugin process does, binds its plane socket as Serve does, and calls the
generated function: commerce answers amount="0" currency="USD" over the socket. A
cold peer with no router answers ErrNoPeer naming the app — a named absence, never
a timeout a caller would have to guess at.
Three root-package call sites converted, including both money ops — the prepaid
gate and the meter now reach commerce as commerce.FinanceAuthorize and
commerce.FinanceRecord. Those are the imports that were structurally impossible
before, so they are the proof the direction is real.
Full suite: 133 failing test names / 26 packages, byte-identical to the same
measurement on origin/main. Regression set EMPTY. go vet ./... exit 0.
Co-authored-by: Hanzo Dev <dev@hanzo.ai>
Hanzo Cloud
The Open AI Cloud as one deployment. Identity, secrets, data, AI, gateway, observability, and the console — 116 Hanzo-native subsystems behind one origin and one /v1, each its own binary, composed by a light host router through the plugin contract in HIP-0106.
The same artifact serves api.hanzo.ai, api.lux.cloud, api.zoo.cloud, api.osage.cloud, and every white-label reseller. Brand, enabled subsystems, and org scope are deployment configuration — one binary, one origin, no sidecars.
Quick start
# Run the unified binary. `:latest` to try it; pin a v1.x.y tag for anything real
# — the tags are cut per build, so any number written here is stale by tomorrow.
docker run -p 8080:8080 ghcr.io/hanzoai/cloud:latest
Open http://localhost:8080 for the embedded console; the API is served under /v1 on the same origin.
Build this repo's own client binary with go build ./cmd/hanzo — see below for what it
serves and what it delegates. It is NOT what curl -fsSL https://hanzo.sh | sh installs;
that gets the Rust CLI (hanzoai/cli), which is the primary hanzo on a developer's
machine and whose verbs are different.
What this is
hanzoai/cloud serves the whole API from one origin. cmd/cloud is the front door: it
links zip, the app manifest and the console embed — and nothing else. It knows only
where each app lives and what path it answers, never what the app does. Each subsystem
(iam, kms, base, gateway, ai, commerce, vfs, mq, dns, amqp, mcp, o11y, tasks, …) is its
own plugin/<name> binary serving its own prefixes through the same cloud.Listen
middleware it would serve standalone.
Apps start lazily, on the first request that reaches their prefix; the four that own
a listener or a background loop (pubsub, kafka, o11y, catalogsync) say so and
start with the host. That is what makes 116 subsystems affordable — an app nobody calls
costs a route entry and a struct, not a process and a resident set.
This was one fused process once, and that binary is gone: it linked every subsystem's
graph into a ~3105-package build, and apps.Wire() went with it.
The same deployment serves api.hanzo.ai, api.osage.cloud, api.lux.cloud,
api.zoo.cloud, and every white-label reseller. Brand, enabled subsystems, and org
scope are deployment configuration.
hanzo — cloud control CLI
cmd/hanzo is the client-only control binary: a thin client over Hanzo IAM
(hanzo.id), the platform control plane (platform.hanzo.ai/v1) and the cloud
/v1 API, inventing no parallel API. It cannot serve a subsystem — that is
cmd/cloud's job.
Two different programs answer to hanzo, and this is the one almost nobody has.
A developer installs the Rust CLI (hanzoai/cli) from hanzo.sh; it becomes their
hanzo, and it writes hanzo-node as a symlink to itself. THIS binary is the Go
control CLI, built from this repo. When it is the hanzo on a machine, a verb it does
not own is handed to whatever hanzo-node resolves to (cli.Passthrough), so the
single name is a superset of both — but that delegation runs in this direction only.
Read the verbs below as cmd/hanzo's, not as "what hanzo does": on a normal
developer machine hanzo login and hanzo deploy reach the Rust CLI, which has
neither, and it reads them as a task for the coding agent.
cli.IsControlVerb draws the line off the cobra command tree itself, so the router and
the tree cannot drift apart. The complete set it owns:
hanzo login # IAM password grant against hanzo.id → token in ~/.hanzo (0600)
hanzo logout
hanzo whoami # identity from the stored token (--verify hits IAM userinfo)
hanzo auth … # token / switch / status
hanzo apps list # platform apps board: declared/running/latest tag + drift + health
hanzo apps get <org>/<app>/<env> # one app row
hanzo deploy <container> --project <p> --env <e> # rolling, zero-downtime redeploy
hanzo clusters … # dedicated DOKS cluster lifecycle
hanzo build <repo> --sha <sha> --image <img> # platform-native (arcd/Kaniko) build, no GitHub builders
hanzo run <task> # one-off task on the platform
hanzo agent … | hanzo bot … # managed agents and bot nodes
hanzo engine … | hanzo runner … # local engine, and this machine as a CI runner
hanzo link | hanzo unlink # attach this machine to the fleet (`hanzo gpu connect` rides here)
hanzo security … # rules / scan
hanzo config set <k> <v> # ~/.hanzo/config preferences
hanzo version
hanzo completion bash|zsh|fish # shell completion for every verb above
Global flags: --org, -o/--output table|json, --platform-url, --iam-issuer,
--platform-token. Tokens resolve from flag → env → ~/.hanzo (never hardcoded):
the IAM user token is the identity; the platform control plane is service-token
authed (it cannot validate user tokens), so apps/deploy/clusters use
--platform-token / HANZO_PLATFORM_TOKEN / PLATFORM_SERVICE_TOKEN, and
build uses HANZO_BUILD_TOKEN / PLATFORM_BUILD_CALLBACK_TOKEN.
Install the Rust CLI: curl -fsSL https://hanzo.sh | sh, or
brew install hanzoai/tap/hanzo. It is hanzoai/cli; this module serves /v1, ships
plugins, and builds the control half above (go build ./cmd/hanzo).
Subsystems mounted
manifest/apps.go is the source of truth: every app that ships as its own binary, in
mount order — which IS the routing order, first matching prefix wins. Three facts per
row and no more (name, the paths it answers, whether it must already be running), because
that is the whole of what the light host needs to know. What an app DOES it states once
in its own plugin/<name>/main.go.
iam— identity & access (users, orgs, roles, OIDC/JWKS per HIP-0026)base— per-org SQLite + in-process extension runtimes (HIP-0105)kms— secret custody (sealed secrets, HIP-0027)commerce— checkout, billing, pricing, invoicing (light router; NOT in PCI-DSS scope)ai— AI control plane: inference, RAG, model hub, agents, MCP managementgateway— HTTP routing + policyo11y— metrics / traces / logsvfs— virtual filesystem / object-store abstractionmq— message queuedns,amqp,mcp,auto,tasks, … — the other 107 rows are inmanifest/apps.go
Deployment modes
Same artifact; different startup configuration:
cloud --brand=hanzo --domain=hanzo.ai
cloud --brand=osage --domain=osage.cloud
cloud --brand=lux --domain=lux.cloud
cloud --brand=zoo --domain=zoo.cloud
Architecture
api.{org}.{brand}
|
cmd/cloud — the host router
(links zip + manifest + webui, nothing else)
|
+----------+----------+----------+----------+----------+
| iam | base | kms | ai | gateway | ...
| its own | its own | its own | its own | its own |
| process | process | process | process | process |
+----------+----------+----------+----------+----------+
per-org SQLite (HIP-0302) | Hanzo IAM JWKS (HIP-0026)
replicate -> S3 (HIP-0107) | ZAP inter-subsystem RPC
Every app is loaded through the same Mount seam and answers on its own prefix; the
host takes the first prefix that matches and starts the app if it is not up yet. The
console is registered LAST so no app prefix can be shadowed. Cross-subsystem calls ride
ZAP; no subsystem reaches into another's store.
The host owns three things no app can: it serves the white-labelled console at /, it
threads the deployment's operator flags to the children as CLOUD_* env, and it SCOPES
CREDENTIALS — it scrubs the KMS root key from its own environment so no child inherits
it, and hands it to the kms broker child alone.
White-label fork pattern
Customers fork hanzoai/cloud to launch their own ecosystem. Brand detection, enabled
subsystems, and ZAP endpoints (payments / vault backends) are all deployment
configuration.
Web framework
zap-proto/zip — Sinatra-style Go web framework built
on Fiber v3. The ONE Go web framework. No .Fast escape hatch. That is the module path
this repo imports (github.com/zap-proto/zip, currently v1.18.22); hanzoai/zip is the
old home and is not what go.mod resolves.
Console UI — embedded in the host
The host binary serves the console (@hanzo/gui, hanzoai/console — private) UI at the
web root AND routes /v1 — one origin, no separate console Service. The UI is compiled
in via //go:embed (see webui.go).
Pipeline (in the Dockerfile, before go build):
console stage → build console static bundle → /out
COPY --from=console /out/ → src/webui/dist/ (overlays the fallback shell)
build stage → go build → //go:embed all:webui/dist bakes it into /cloud
Serving (webui.go, registered LAST in Serve so it never shadows the API):
GET /and any client-side route (/orgs,/models, …) → the SPA shell (index.html) withCache-Control: no-cache; fingerprinted assets underassets//_next/are servedimmutablefor a year, with brotli/gzip precompressed negotiation when the build emits.br/.gzsiblings.GET /v1/*(and/zap,/healthz, …) → the API. Real subsystem routes are registered before the console catch-all, so they always win; an unmatched path under an API prefix returns a real 404 (JSON namespace), never HTML.- Same-origin: the embedded console calls
/v1on its own host, so the session cookie is first-party — no second origin, no CORS.
webui/dist/index.html is a committed fallback shell (a real same-origin
/v1 bootstrap) so go build always compiles and the binary always serves a UI
even without the Node toolchain. The image build overwrites webui/dist with the
real console bundle. See webui_test.go for the boot-and-assert tests
(/ → shell, deep link → shell 200, /v1/* → API, unmatched /v1 → 404).
The hanzoai/console build:embed script (scripts/build-embed.mjs) stashes its
Next server route handlers (BFF proxies that collapse to the cloud /v1/* the SPA
calls same-origin), wraps the client catch-all pages for output: 'export',
neutralizes the root layout's request-time headers() read, and emits a real
static export at out/ (a ~360 KB index.html + _next/ chunks). The image
build (and make webui) run it and overlay webui/dist, so //go:embed bakes
the FULL @hanzo/gui console into the host binary. The Dockerfile console stage
FAILS HARD if that bundle is missing or degenerate — the placeholder shell can
never silently ship to prod (escape hatch: --build-arg ALLOW_PLACEHOLDER=1 for a
pure-Go dev image).
Specs
Implements, by the filenames in hanzoai/HIPs:
- HIP-0014 Application Deployment
- HIP-0026 Identity & Access Management
- HIP-0027 Secrets Management
- HIP-0105 In-Process Extension Runtime
- HIP-0106 Hanzo Plugin Contract
- HIP-0107 Streaming Replication over VFS
- HIP-0129 Eval — the Judgment Plane
- HIP-0302 Encrypted SQLite Replication
Status
In production. It serves api.hanzo.ai and the white-label cloud surfaces today, with
per-org SQLite (HIP-0302) and the embedded console. manifest/apps.go is the one ordered
list of everything mounted — 116 apps, 4 of them eager. For repo-level engineering
doctrine (module graph, route-table projections, cross-subsystem seams), see
LLM.md.
Hanzo — the Open AI Cloud
Open source · every language · on-chain settlement. hanzo.ai · docs.hanzo.ai
SDKs in every language — Python (flagship) · TypeScript · Go · Rust · C++ · Swift · Kotlin · umbrella