703 Commits
Author SHA1 Message Date
zeekay cafae37f4e client: regenerate from hanzoai/[email protected] (3.2.5)
The document types 1495 of its 2459 operations, up from 1242. 87 new model
modules carry the response shapes ai, eval, functions, domain, experiments,
security and benchmark now declare, so those calls return a typed object
instead of a dict. 2524 generated modules import clean.

.spec-lock moves from v1.801.497 to @ffbc86064, which is v1.801.498.
v3.2.5
2026-08-08 14:20:07 -07:00
zeekay fcec4c3d80 client: regenerate from hanzoai/[email protected] (3.2.4) v3.2.4 2026-08-08 20:05:59 +00:00
zeekay b4498ce53d client: regenerate from hanzoai/[email protected] (3.2.3) v3.2.3 2026-08-08 19:11:00 +00:00
zeekay f575cf5820 the cut pushes to the forge, so take the pipeline that does it 2026-08-08 12:09:07 -07:00
zeekay 601a976ada pin the pipeline at a tag nothing else moves
`@v1` is a rolling alias and the mirror moves it back. Measured within four
minutes today: v1 was pushed to the commit carrying the fix, a sync put it back
on the commit from two releases ago, and the runs in between generated against a
pipeline that no longer matched the one just released — the client lane read an
OPENAPI its own reusable had never exported and died telling the caller to set a
variable the caller does not own. An immutable patch tag cannot be moved by
anyone, which is the only property this pin needs.
2026-08-08 12:04:55 -07:00
zeekay 8f992fd9d4 one spelling for running the driver, and it is the one that has an interpreter
`python3 generate.py` finds no `yaml` module on a fleet runner, so three of the
four call sites could not run the driver they had just been handed. js-sdk
already ran it under uv and worked; this is that same line, in the other three.
2026-08-08 11:58:41 -07:00
zeekay 4dfc7cf737 an apostrophe in the message bash parses before it decides not to print it
`${OPENAPI:?…}` only prints its word when the variable is unset, but bash
PARSES that word either way, and inside it a lone `'` opens a quote that never
closes: `unexpected EOF while looking for matching '`, at line 23, with OPENAPI
set and the branch not taken. The regeneration died on a message about how to
supply a value it already had.
2026-08-08 11:54:56 -07:00
zeekay ad0e0b843a the call site stops guessing a credential, and the publisher moves where it runs
Two separate things kept this projection from ever moving.

The generator lives in hanzoai/openapi and this repo went and got it for itself,
with no credential, from a private repo — so the first dispatch that ever
reached the client lane died at `could not read Username`, exit 128. hanzoai/ci
holds the token that reads this forge, so it now fetches the driver once and
hands it over; a call site that does not clone cannot be wrong about where to.

And the trigger: `repository_dispatch` is a verb this forge does not have, and a
nightly `schedule:` decides when by a clock rather than by a release. The forge
answers a workflow_dispatch POST with 204 and a runner takes it in seconds, so
that is the one trigger left.
2026-08-08 11:51:54 -07:00
zeekay e6370c46d0 ask for the release on a clock
hanzoai/ci's client lane, given no payload, now reads hanzoai/cloud's newest tag
off git.hanzo.ai and regenerates this tree onto it. This is the trigger that
makes it happen without anybody pressing anything.

It replaces a fan-out that could not work in either direction: cloud pushed
`repository_dispatch: spec-update` at nine projection repos, but github.com has
zero self-hosted runners and these callers ask for the forge's pool, so an
accepted event queued until GitHub cancelled it a day later — and the forge,
which has the runners, has no repository_dispatch endpoint at all. Twelve such
dispatches exist across the fleet and not one reached a first step.

Asking is the better verb even where telling would have worked. It needs no
credential spanning six owners, it runs on the runners that exist, and a release
stays green whether or not this repo is well — a projection that is down is this
repo's problem, not a reason to redden a cloud release that is already live.
2026-08-08 06:36:47 -07:00
zeekay b07499c8a5 the generator is a tool, the document is an argument
They shared a name and the lane died on it. `SPEC_REF` is the ref of the
DOCUMENT — hanzoai/ci's client lane exports a hanzoai/cloud sha or v-tag — and
this script also handed it to `git clone --branch` against hanzoai/openapi, which
is a different repository and has never had a ref by that name.
`git ls-remote hanzoai/openapi 5126939` returns nothing, and neither does
v1.801.495, so every CI regeneration died at the clone before the generator ran.
By hand it appeared to work, because with SPEC_REF unset the default was `main`
and both repositories happen to have a branch called that — which is the worst
kind of coincidence, one that makes a broken path look tested.

The generator is cloned at its own default branch now, from git.hanzo.ai, and the
document reaches the driver as a value: hanzoai/ci passes it digest-checked, and
without that the driver reads this tree's .spec-lock and fetches the same bytes
from the same forge. A hand run and a CI run project the same document instead of
whatever main happens to be.
2026-08-08 06:20:02 -07:00
zeekay 69e74af7dc one generated client in the wheel: hanzoai.cloud
This distribution shipped two complete generated clients under one import root.
`hanzoai.cloud` is the current one, generated from hanzoai/cloud's openapi.yaml at
the ref .spec-lock names. `hanzoai.api` / `hanzoai.models` was another, and the
package root re-exported its 2,202 names flat — so the obvious spelling,
`from hanzoai import ApiClient`, reached the second client, while
`hanzoai.cloud.ApiClient` reached the first. Nine core names existed twice as
distinct classes bound to distinct documents, and which one a caller got depended
on which import line they happened to write.

The second lineage was not an older copy of the first. It was a projection of a
document that no longer exists: of the 1,695 routes it binds, 243 are still in the
document at v1.801.495 and 1,452 are not. Eighty-six percent of it builds a
request the server answers with a 404. It had been broken for as long as it had
been shipping, and nothing said so, because a client only fails at the call.

So it goes, along with its runtime — api_client, api_response, configuration,
exceptions, rest — which `hanzoai.cloud` carries its own copies of. The package
root now exports `cloud` and nothing else. Nothing is re-exported flat on purpose:
a name that appears in two places is a name that can disagree with itself, and the
namespace already says which document a symbol came from.

The smoke tests pinned the deleted surface — they asserted more than 300 api
modules and imported `hanzoai.api.tracker_projects_api` by name, which is how a
suite can stay green over a client that 404s. They now assert the surviving one,
plus the invariant that made this possible in the first place: exactly one
generated package under `hanzoai`, and none of the nine core names re-exported
flat beside it.
2026-08-08 06:12:00 -07:00
zeekay 8e2060cc00 retire the GitHub Actions workflows
Actions are disabled on this repository, so these files have not run in a long
time and cannot run: they are build definitions no forge executes. Builds go
through the native path -- POST /v1/runner, which schedules a BuildKit job --
and CI lives under .hanzo/workflows. A dead workflow that still looks like a
pipeline is worse than none, because it reads as coverage nobody has.
2026-08-07 09:25:32 -07:00
zooqueenandHanzo Dev b5a37d43fa sync: the forge being ahead of GitHub is not a divergence
ff-main asked only "is LOCAL an ancestor of REMOTE" and called every other
answer DIVERGED -- so the forge simply being AHEAD of GitHub took the
loud-failure path. That is the normal state after every native push, and the
push-mirror is sync_on_commit with an 8h floor, so one commit can produce up
to 48 false alarms ten minutes apart.

The cost is not the noise. A job that is always red cannot report the one
thing it exists to report, and this job exists to refuse to force-push a real
divergence. The loud failure is only worth keeping if it is rare.

Convergence, not invention: app, git, hanzo.ai, iam and now id already carry
exactly this two-direction check. This repo held the stale copy.

Exercised against real git ancestry -- equal and github-ahead unchanged,
native-ahead DIVERGED(1) -> native-ahead(0), and true divergence still
exits 1. One row moves; the alarm survives.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-06 15:57:13 -07:00
zeekayandHanzo Dev 856a955141 prune the retired GPU billing addresses so a regen cannot re-mint them
`POST /v1/billing/gpu-charge` and `GET /v1/billing/gpu-eligibility` answer 404
at api.hanzo.ai — the address and its `-zzq9` nonsense sibling alike, which is
the only way absence is ever shown — and hanzoai/cloud has deleted the
handlers. The client still offered both, so the one outcome either method had
was a 404.

They survived in `pkg/hanzoai/api` + `pkg/hanzoai/models`, the tree the retired
driver wrote. `generate.py` does not own it: sdks.yaml declares exactly one take
path, `hanzoai/cloud -> pkg/hanzoai/cloud`. Nothing regenerates these files, so
nothing would ever have removed them and no future regen can put them back.

Six operations, three models, three exports. `pkg/hanzoai/cloud` is deliberately
untouched — it carries `/v1/billing/gpu/{charge,eligibility}`, which the
deployment still answers for (401, against a 404 control), and publishing an API
smaller than the one served is the same class of lie in the other direction.

  generate.py python --check          [python] clean
  import hanzoai                      2208 exports, 0 dangling
  cloud-client / duplicate-fields     pass, 2362 modules

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-06 04:36:37 -07:00
zooqueenandHanzo Dev e1635ebf98 feat(lsp): repo answers from /v1/code/lsp, file stays local
Mirrors hanzoai/mcp's Rust `lsp` exactly: one tool, two planes, picked by data
the caller already has. `file` names the file; `repo` (a git.hanzo.ai slug,
optional `rev`) says which world it lives in. Without `repo` nothing changes —
the same language server on the same tree. With it, the question goes to the
indexed corpus behind /v1/code/lsp, which reaches across a repo's dependencies
without checking anything out.

One table maps actions onto ops: locate carries a relation (definition|
reference|type|implementation) because "where is X" is one question with four
answers, not four routes; hover, symbols, diagnostics and complete stand on
their own. The body is {repo, rev?, path, line, character, relation?} in LSP's
own frame — 0-based line, UTF-16 character — shifted from the tool's 1-based
`line` by the same expression the local plane uses.

The call goes through the shared HanzoCloud client hanzo-tools-code and
hanzo-tools-net already compose, so there is no second client and no second
lsp tool. Actions a plane cannot serve say so up front rather than spawning a
server or calling out.

test_lsp_tools.py asked for a class named `LspTool`; the class is `LSPTool`,
so those two never ran. One name, and they run.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-06 03:19:28 -07:00
zeekay edeb65d5af Revert "PROVE THE GATE: restore the exact 3.2.0 duplicate declarations"
This reverts commit f1014b6addd78d538d06890c3d88c401aea57f96.
2026-08-05 15:21:33 -07:00
zeekayandHanzo Dev 3056f4d485 PROVE THE GATE: restore the exact 3.2.0 duplicate declarations
Temporary. O11yGettableAgentCheckIn declares integration_config and removed_at
twice each, exactly as 3.2.0 shipped them. duplicate-fields must go red on the
forge; cloud-client will stay green, which is the point. Reverted immediately
after.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-05 15:17:02 -07:00
zeekayandHanzo Dev 00fe47b0cc delete the gate that could not run; start the one that can
We do not use GitHub Actions. `.github/workflows/cicd.yml` asked for
`hanzo-build-linux-amd64`, a label github.com serves with nothing, so every
caller queued 86402s — 24h exactly, GitHub's timeout — and reported
"cancelled". Not a broken gate: a gate on a platform we retired. Deleted rather
than repaired. A file that asserts coverage it cannot deliver is what let this
repo read green while nothing ran.

The forge is where CI lives, and this repo was not wired to it. Two things were
missing and the second is the interesting one.

`.hanzo/workflows/cicd.yml` — the ~7-line caller, hanzo.yml holds the config,
same shape as the sixteen other hanzoai repos already on this path.

And sync-from-github.yml could never have started it. It fast-forwards main
with the workflow token, which by design triggers no workflow, and compensates
by dispatching `deploy.yml` — a workflow this repo has never had, because these
are libraries that publish to PyPI and deploy nothing. The forge returned 404,
`|| echo "build dispatch failed (non-fatal)"` swallowed it, and the sync went
green ten minutes at a time. Every commit arrived having started nothing. That
is the third check this session that was believed and was not running, and it
is why the forge shows zero cicd.yml runs against 775 syncs.

It now names cicd.yml, and a failed dispatch fails the job.

hanzo.yml gains `duplicate-fields`: repeated AnnAssign targets in a class body,
read from the AST. 3.2.0 shipped `integration_config` and `removed_at` declared
twice each in O11yGettableAgentCheckIn — the second binding wins, the first
field silently does not exist, and the value on the wire was read and dropped.
The `cloud-client` import gate was green for it and always would be: measured
here, with the 3.2.0 model restored, it exits 0 and prints "models: 2172
modules imported". Its comment claimed it caught name collisions; that claim is
corrected rather than left to mislead.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-05 15:11:42 -07:00
zeekayandHanzo Dev 0dcf6614ba ci: confirm a push still schedules a run
Co-authored-by: Hanzo Dev <[email protected]>
2026-08-05 15:07:07 -07:00
zeekayandHanzo Dev b19dc4dd1d the gate reads the syntax tree, and now it has a runner to read it on
3.2.0 shipped silent data loss to PyPI. O11yGettableAgentCheckIn declared
`integration_config` twice and `removed_at` twice; in Python the second
annotation rebinds the name, so the model advertised fields it did not have and
the value arriving under each shadowed name was read and dropped. 3.2.1 fixed
the models. Nothing yet fixed the reason it got out.

The `cloud-client` gate imports every generated module and calls that the build
step. A class body with a duplicate field IMPORTS CLEAN — measured here: with
the exact 3.2.0 model restored into the tree, `cloud-client` exits 0 and prints
"models: 2172 modules imported". The comment above it claimed the import caught
"a name collision". It never could. That claim is corrected rather than left to
mislead the next reader.

`duplicate-fields` reads the AST instead: repeated AnnAssign targets in one
class body. Verified both directions on the real defect, not a fixture — exit 1
naming both collisions with the 3.2.0 model in place, exit 0 with 3.2.1, 2362
modules scanned in both. It refuses a zero it did not earn: scanning no modules
fails rather than passes, because "found nothing" and "looked at nothing" print
the same otherwise.

And the gate now has somewhere to run. `.github/workflows/cicd.yml` asks for
`hanzo-build-linux-amd64`, which on github.com is served by nothing since the
ARC pool retired — every caller since has queued 86402s, GitHub's 24h timeout,
and reported "cancelled". That is why this repo's suite has not run in weeks
while looking merely flaky. The git-runner fleet serving that label lives on
git.hanzo.ai, which sync-from-github.yml already mirrors onto every ten
minutes, and where publish-pypi.yml already reads its token from KMS. So the
caller goes there, beside the publish it guards, like the other sixteen repos.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-05 14:39:55 -07:00
zeekayandHanzo Dev 91e16d5735 3.2.1: the tree stopped being what PyPI serves under 3.2.0
hanzoai 3.2.0 is on PyPI as of 18:16:29Z, and its wheel is byte-identical to the
one built here before the o11y fix landed — so the published artifact still has
o11y.GettableAgentCheckIn declaring six fields where the document declares
eight, silently dropping an old AWS agent's `removed_at`.

This tree has that fix and the zap suite, and it was still calling itself 3.2.0.
One version naming two different sets of bytes is the failure this repo already
learned once from npm; the number moves so it cannot happen quietly. Nothing is
published here — 3.2.1 goes out with the operationId unification, or sooner if
the shipped data loss is judged to warrant its own release.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-05 13:27:23 -07:00
zeekayandHanzo Dev 3ff8f5eba2 o11y: the check-in model stops dropping the field it exists to carry
One regenerated file. o11y.GettableAgentCheckIn declares eight properties and
this client had six: `integration_config`/`integrationConfig` and
`removed_at`/`removedAt` each snake_case to one attribute, so the second
shadowed the first and the survivor kept the camel alias.

That is not cosmetic. The snake_case spellings exist because hanzoai/o11y
publishes them so older AWS agents keep working, and this client read such an
agent's value and then threw it away without raising: from_dict with
{"removed_at": 2020, "removedAt": 2030} returned 2030, and
to_dict()["removed_at"] was None. `to_dict` also wrote one value under both
keys, under two different declared types.

The mapping lives in hanzoai/openapi sdks.yaml, where this client's invocation
is declared; nothing here is hand-edited. Eight fields now, both wire names
carried as aliases, and each value round-trips under its own key. Gate unchanged:
182 api and 2172 model modules import, pytest 6 passed with the same
pre-existing test_zap_transport error.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-05 13:25:35 -07:00
zeekayandHanzo Dev 5105e664e5 test(zap): lock the surface that exists — the suite has been red since Stainless was retired
bcd56c69 deleted Hanzo/AsyncHanzo/Client/Stream and 14 more; this file imported
Hanzo at module scope, so pytest errored at COLLECTION and ran none of the
transport tests. 3.1.5, 3.1.6 and 3.1.7 all shipped over that error.

The lock now names the generated client's real entry point (ApiClient,
Configuration, the ApiException hierarchy), and the end-to-end drives a real
httpx.Client — which is the only seam hanzoai.zap has left, since rest.py is
urllib3 and nothing in pkg/hanzoai takes an http_client.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-05 11:15:32 -07:00
zeekayandHanzo Dev 3a09eb4319 regenerate from cloud@5126939 — the client leaves the hand-merged lineage
pkg/hanzoai/cloud goes +2186 -2126 ~175: 1700 paths / 2354 operations / 2186
schemas, 182 api modules and 2172 model modules, against the 239/1116 it carried
from hanzo.yaml. The document is hanzoai/cloud's own emission now, pinned in a
new .spec-lock by commit and sha256, and generate.py reaches it by value —
--skip-validate-spec means the 1012 missing-`responses` errors no longer write
zero files, so a client can be cut from the authority instead of a projection of
it.

Two renamings come with that, and both are the fix rather than the damage. IAM's
types are namespace-qualified (iam.Role, iam.Application, 95 of them) because a
bare `Role` was two unrelated shapes wearing one name. And the <svc>_ prefix is
gone from every operationId, so every generated method lost it.

The prefix is what broke the examples, and the gate saw only two thirds of it.
Four flows failed on their imports; `money` and `tools` PASSED while every call
in them named a method that no longer existed — an import resolves the names in
the `from … import` line and a method is looked up at call time. All five flows
now name operations that exist, checked by resolving each one as an attribute,
and hanzo.yml records that ceiling so the next reader does not trust the gate
for more than it says.

`chat` is removed, which is a measurement and not a preference: cloud declares
POST /v1/chat/completions with no requestBody and no responses at all, so the
generated method takes no body and returns None — the one call a chat example
exists to make cannot be expressed. Inventing the type, or hand-rolling the HTTP
inside a generated client, is the drift these SDKs exist to prevent; js-sdk
dropped its own chat flow at 2.0.7 for exactly this. It returns the release
cloud gives that route a body.

Two smoke assertions were already red before this regeneration and are now true
again: they pinned AIApi/APIKeysApi/MCPApi and AdminApi.plugin_admin_*, names
from the retired lineage. The surface is all still there under AiApi/KeysApi/
McpApi and AdminApi.admin_plugins.

tests/test_zap_transport.py still errors, exactly as it did before: it imports
`Hanzo` from hanzoai, which the hand-written package does not define. That is
not this document's business and is untouched.

Minor rather than patch: every generated method changed name.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-05 11:00:03 -07:00
zeekayandHanzo Dev ccc14a5dd2 kms.lux.cloud is the brand host; kms.lux.network is retired
Caught on a second pass: grep -I treats these as binary and skipped them, so the
first sweep reported clean while they still named a host that no longer resolves.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-05 08:12:55 -07:00
zeekayandHanzo Dev bf49810b8f kms.lux.cloud is the brand host; kms.lux.network is retired
The .network host was retired 2026-07-27 and no longer resolves at all (a
request to it now returns nothing, not an error), so every reference to it named
an address that cannot answer. The brand host is .cloud, matching kms.zoo.cloud
and the rest of the white-label convention.

Left alone deliberately, because rewriting them would invert what they say:
the LLM.md line that RECORDS the retirement, the e2e spec that ASSERTS
kms.lux.network must not resolve, and the recorded applies + cluster backups,
which are faithful accounts of what was applied and are not config.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-05 08:12:54 -07:00
zooqueenandHanzo Dev e7000de487 feat(tools): research action on net/fetch — cited answers over /v1/ask
Search finds pages, web_read reads one, research is the whole loop behind a
single door: the live cloud answer engine (api.hanzo.ai /v1/ask, mode=research)
plans queries, searches, reads the best pages and writes an answer that cites
them. It is an action on the same fetch tool, not a new package — search, read
and research are one concern (the web) and share one client and one auth.

The engine replies as server-sent events, which is the cloud's other reply
shape, so HanzoCloud gains stream() beside get/post: the base URL, the auth
header and the error mapping stay in the one place that already owns them.
Each `data:` frame is yielded as a typed event (status | sources | text |
follow_ups | done | error); the terminal [DONE] sentinel ends the stream and is
never an event. Frames accumulate into {answer, sources, follow_ups}; `deep`
normalizes to `research` (one pass, two names) and any other mode is refused
rather than silently downgraded.

hanzo-tools 0.3.4->0.3.5, -net 0.1.3->0.1.4, and -net's floor moves to
hanzo-tools>=0.3.5 so the action cannot install against a client with no
stream(). Tests replay a canned SSE stream through the real parser.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-04 22:48:19 -07:00
zeekayandHanzo Dev 3f14aa2970 Point every Discord invite at the current Hanzo server (discord.gg/CJCyAsm9Vr)
Co-authored-by: Hanzo Dev <[email protected]>
2026-08-04 21:48:55 -07:00
zeekayandGitHub 00e62fca2c legal: merge legal/dual-mit-apache into main (HIP-0137) 2026-08-04 14:16:55 -07:00
zeekay f6fef478ad legal: hanzo-tools-gimp / hanzo-dev follow the root LICENSE (Apache-2.0)
Both sub-packages claimed BSD-3-Clause while hanzoai/python-sdk's root
LICENSE is Apache-2.0, and neither ships its own LICENSE file. BSD-3 is out
of scope for hanzoai originals under HIP-0137. The gimp README's aside about
hanzoai/gimp-mcp's licence is dropped rather than restated — that repo states
its own.
2026-08-04 01:27:29 -07:00
zeekayandHanzo Dev 8c1c168dff ci: verify the GitHub->forge inbound leg delivers
No file changes: this exercises the ingest path only, so the forge head
must advance to this commit without a build being cut.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-03 11:40:58 -07:00
zeekayandHanzo Dev f5fc75a0d7 hanzoai 3.1.7: ship the corrected README to the PyPI page
README-only. pypi.org/project/hanzoai renders the long description, so the
install line, the zen5-coder model id and the dropped operation counts only
reach the registry page on a release.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-01 13:43:27 -07:00
zeekayandHanzo Dev bcffc56ee3 pypi: one canonical hanzo, and superseded packages that say where the real one is
Three published packages declared a console script named `hanzo`, competing with
the native CLI and with each other. Measured on a clean venv at hanzo==0.4.3:
`hanzo --help` printed hanzo-cli's program, not the one pkg/hanzo/README.md
documents. Renamed to `hanzo-py` and `hanzo-cli` — script named after its
distribution. Nothing is yanked; both packages still install and run.

- pkg/hanzo 0.4.4 — summary and README now say the CLI is a native binary
  (curl -fsSL https://hanzo.sh | sh). Dropped the invented command tour
  (`hanzo chat --model gpt-4`, `hanzo node start`, `hanzo router start` on
  localhost:4000): none of those verbs exist in the program this package
  installs. Kept the three library entry points, each import-checked.
- pkg/hanzo-cli 0.2.4 — `hanzo login` replaced with the native `hanzo auth login`
  plus its own `hanzo-cli login`. Verified `hanzo iam users list` and
  `hanzo kms secrets list` against the shipped v1.9.18 binary.
- pkg/hanzo-node 0.1.1 — states the two meanings of the name: the `hanzo-node`
  COMMAND is a symlink to the Hanzo CLI; this package fetches a different binary
  from hanzoai/node, which is private, so the download 404s for the public.
  Documentation URL moved off docs.hanzo.ai/node (404) to hanzo.sh.
- root README (published as `hanzoai`) — `pip install hanzo` no longer advertised
  as the CLI; model ids zen-coder -> zen5-coder (zen-coder is not in the catalog);
  "2452 operations, 1798 schemas" dropped for the spec URL, since the live
  surface is 1058 paths / 1465 operations / 1139 schemas.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-01 13:35:20 -07:00
zeekayandHanzo Dev 811de70215 client: this repo is a projection, and the release drives it
hanzoai/cloud's release now sends `repository_dispatch: spec-update` carrying
(version, sha, spec_sha256). hanzoai/ci's `client:` lane fetches openapi.yaml AT
THAT SHA, REFUSES if the bytes hash to anything else, regenerates, writes
.spec-lock beside the code, and — only after this repo's own `test:` block has
passed over exactly those bytes — commits and cuts a patch.

NOTHING HAS EVER SENT THAT EVENT. The assumed sender was hanzoai/openapi, which
has zero workflows; where a generate.yml existed at all it had run twice in its
whole life, both failures, or never. That is the hole this closes, and closing
it is why the document moved: from hanzoai/openapi hanzo.yaml (hand-merged, 1742
paths, fed by nothing) to hanzoai/cloud openapi.yaml — smaller at 1058 paths,
emitted from the code by each app's own router, and gated on every cloud release
by a drift check that regenerates it from source. A smaller true document beats
a larger unverified one.

The lane is defined ONCE, in hanzoai/ci. What was hand-rolled per repo is
deleted with it.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-01 11:30:05 -07:00
zeekayandHanzo Dev 2521836783 generate: a call site, so this repo can regenerate and check itself
pkg/hanzoai/cloud is generated, and until now this was the only client of the
seven with no in-repo way to produce or verify it. The driver lives in
hanzoai/openapi (generate.py + sdks.yaml), so a checkout of python-sdk alone
could not even ask whether its committed client matched the document.

scripts/generate.sh is a CALL SITE, not a second driver — the same eight lines
java-sdk and kotlin-sdk already run, ending in `generate.py python --repo $PWD`.
That distinction is the whole point: the previous scripts/generate.sh here was a
real second driver, it did `rm -rf pkg/hanzoai`, and it was deleted for that.
This one cannot, because generate.py owns exactly the one take path sdks.yaml
names.

Measured against hanzo.yaml@9781a56: +159 -110 ~135. That drift is what the gate
exists to refuse; it is reported here, not silently regenerated, because the
fleet's regeneration is one coordinated wave and not seven.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-01 11:19:11 -07:00
zeekayandHanzo Dev 3a62c22256 regen the cloud client from the resynced spec (1f9b03b) and repoint every flow
The document grew: cloud's woven spec was merged and won, taking hanzo.yaml from
1132 paths / 1519 operations / 779 schemas to 1737 / 2452 / 1798.
pkg/hanzoai/cloud/ is now 263 api + 2031 model modules, all importing.
`generate.py python --check` reports [python] clean.

The resync also RENAMED nearly every operationId to cloud_<method>_<path>, which
renames every generated method: billing_billingBalance became
cloud_get_v1_billing_balance, cloud_AgentsController.Create became
cloud_post_v1_agents. Examples written against the old names stop resolving —
exactly what the examples gate exists to catch. It went red; this is the fix.

Every flow was re-probed against api.hanzo.ai unauthenticated and with a bogus
key, because a spec says what SHOULD be served and only a request says what IS.
All eleven operations answer 401/403 — routed and identity-gated. Two moved:

  store  to the PROVISIONING plane (POST /v1/kv, GET|DELETE /v1/kv/{name}). The
         per-key data plane the spec also describes is mounted nowhere: GET
         /v1/kv/keys/{key} 404s, PUT and DELETE 405, kv.hanzo.ai 404s the whole
         prefix. A round-trip on keys could not run.
  tools  to GET /v1/tools, the catalog. A live JSON-RPC door at POST /v1/mcp
         answers tools/list with 730 tools but is absent from hanzo.yaml, so the
         generator emits no method and an example would have to bypass the SDK
         to reach it. Of the declared MCP routes /v1/automations/mcp returns 405.

hello stays on bot_authMe: /v1/ai/account answers 200 with
type="anonymous-user" to a request with NO Authorization header, so a hello
built on it certifies a key that would 401 everywhere else.

All six run and report the server's own refusal for a bogus key:
  hello  403 no validated principal      store  403 X-Org-Id required
  chat   401 API key validation failed   agent  403 X-Org-Id required
  money  401 sign in to view billing     tools  403 a validated principal required

money goes through the generated *_without_preload_content variant, because its
two operations are declared with a `default` response and no content so the
typed methods return None though the server sends JSON — a spec gap, and not a
small one: 696 of 2425 operations model no response body. That raw variant does
NOT raise on 4xx (the check lives in the typed deserialization these lack), so
the example checks status itself; without it a 401 body printed as the balance.

3.1.5 -> 3.1.6. 3.1.5 is live on PyPI, so this is a real patch above it.

Co-authored-by: Hanzo Dev <[email protected]>
v3.1.6
2026-07-31 09:08:30 -07:00
zeekayandHanzo Dev cd441d2362 regen the cloud client, add the six canonical flows, and gate them in CI
REGENERATED pkg/hanzoai/cloud/ from hanzo.yaml @ 07783f5 via the canonical
driver (hanzoai/openapi generate.py python) — 1132 paths, 1519 operations, 779
schemas, landing as 239 api + 1116 model modules. `generate.py python --check`
reports `[python] clean`: the committed tree is byte-identical to a fresh
generation, with no local strip of any kind.

The path count fell from 1885 because the spec deleted 18 products it authored
and served nowhere. Nothing here was lost to a collapse — LLM.md carried an open
defect saying 127 of 411 operations went missing to 23 case-variant tag groups
(AI/ai, Users/users). That is now fixed upstream, and this regeneration proves
it: 239 distinct tags produce 239 api modules, exactly 1:1.

Generating at all required two spec fixes. Both landed in hanzoai/openapi first,
neither is patched here:
  - fc0c17a  35 /v1/platform operations carried no `responses`. OAS 3.x requires
             it and openapi-generator aborts the whole document, so hanzo.yaml
             was producing no client in ANY language.
  - 07783f5  ChatCompletionResponse.choices was `items: {type: object}` — so
             choices[0].message.content came out List[object], unusable without
             a cast, on the most-called route in the API.

SIX EXAMPLES under examples/{hello,chat,money,store,agent,tools}, plus
examples/client.py as the single place a base URL or an env var is resolved.
Same six, same names, same order as the TypeScript set, so a reader who knows
one can navigate the other. They import from hanzoai.cloud — the generated
surface new work targets — not the frozen pkg/hanzoai/{api,models}.

Each flow's call sits behind `if __name__ == "__main__":` deliberately: that is
what lets the gate IMPORT all six to prove every `from hanzoai.cloud import X`
still resolves, with no API key and no socket. A spec change that renames or
drops an operation goes red in CI instead of in a user's app.

CI is the fleet convention and this repo had none: root hanzo.yml + a 7-line
cicd.yml importing hanzoai/ci. The gate is two blocks — import every generated
module (for generated code that IS the build step; there is no compiler to catch
a bad $ref or a model referencing a class the generator declined to emit), then
import the six flows. Both provision an interpreter with uv, because the arc
runner image promises none and a gate that silently no-ops is worse than none.

Scope is deliberate: the cloud client and its flows, not all 65 packages, so a
red gate means "the client the spec just produced is broken" rather than
"something, somewhere". Publishing is untouched — .hanzo/workflows/publish-pypi.yml
stays canonical because it reads the PyPI token from KMS. Two publish paths is
one too many.

3.1.4 -> 3.1.5. PyPI still serves 3.1.1; the tree has been ahead since the KMS
secret at hanzo/prod/python-sdk-publish went unseeded.

Co-authored-by: Hanzo Dev <[email protected]>
v3.1.5
2026-07-30 23:10:34 -07:00
zooqueenandHanzo Dev 4062a60ea3 chore: resync uv.lock — hanzoai was pinned at 3.1.3, the tree is 3.1.4
Picked up by running the test suite; the lock had drifted behind the workspace
member's own version. No dependency changed.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-29 14:09:38 -07:00
zooqueenandHanzo Dev 6009873b17 docs: the Actions 422 is account-scoped, and I said it was gone
A previous commit here claimed the "Actions has been disabled for this user" 422 no
longer reproduced, citing enabled=true from
`gh api repos/hanzoai/python-sdk/actions/permissions` plus green Dependency Graph
runs. Both are true and neither is evidence: the first is repo scope, the second is
GitHub triggering its own workflow.

Dispatching still 422s as hanzo-dev. The same dispatch as zooqueen succeeds — which
is how hanzo-iam 1.30.2 finally reached PyPI at 21:05 today. The restriction is on
the account, so "are Actions enabled" and "can we trigger a workflow" are separate
questions and the repo-level check answers the wrong one.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-29 14:06:04 -07:00
zooqueenandHanzo Dev 35b4e61f78 ci: a manual GitHub publish, because only GitHub can read the PyPI token
hanzo-iam has been stuck at 1.30.0 on PyPI while 1.30.1 and 1.30.2 sat committed.
The native job publishes from KMS (hanzo/prod/python-sdk-publish), which is the
contract the rest of the fleet uses — and that path has nothing to read, because
the tokens live as GitHub Actions secrets on this repo, where values are
write-only and cannot be copied into KMS.

GitHub is the only place that can read them. This job is workflow_dispatch ONLY,
so it never competes with the tag-driven native release; it is a re-keying tool,
not a second release path.

Everything else in the release is already proven. With a placeholder seeded in
KMS, the native job logged in, fetched the secret, built
hanzo_iam-1.30.2-py3-none-any.whl and reached upload.pypi.org, failing 403 on the
fake credential and nothing else. So the pipeline works; the credential is the
only gap.

Retire this file once the real tokens are seeded into KMS. Two publish paths is
one too many and the native one is the keeper.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-29 14:04:10 -07:00
zooqueenandHanzo Dev 2f2b28a668 fix(publish): report a missing KMS secret instead of dying at exit 22
The first run of the KMS-backed job ended at `exitcode '22': failure` with no
error line in the log. That reads like a network fault or bad credentials. It
was neither: KMS login succeeded, and the SECRET FETCH 404'd because the path
`hanzo/prod/python-sdk-publish` has never been seeded.

The step runs under `bash -e -o pipefail`, so `curl -sf` on an absent path exits
22 and kills the step before the guard below it can say anything. The guard I
added in the previous commit was therefore unreachable — it could never fire on
the one condition it exists to explain.

The fetch now captures the HTTP status instead of failing the shell, so an
unseeded key prints its status and the guard reports which path is empty.
Credentials are unchanged and still masked; only the diagnosis changes.

Proof this is a path problem and not a credential one: hanzoai/extension runs the
identical login against the identical org-level KMS_CLIENT_ID/KMS_CLIENT_SECRET
(the only copy that exists on the forge) and gets past it — its `extension-publish`
path is seeded, so its publish reaches the packaging step.

Co-authored-by: Hanzo Dev <[email protected]>
hanzo-iam-1.30.2
2026-07-29 06:23:52 -07:00
zooqueenandHanzo Dev 36b5e4f7a7 fix(publish): pull PyPI tokens from KMS, the only secret store we use
hanzo-iam 1.30.2 was tagged, the tag reached git.hanzo.ai, a runner picked the
job up, and it failed in 56s. The job read `secrets.PYPI_TOKEN` off the forge,
and no such secret is there — the hanzoai org carries GHCR_TOKEN, GHCR_USER,
GH_PAT, KMS_CLIENT_ID, KMS_CLIENT_SECRET, OCI_TOKEN, OCI_USER, REGISTRY_TOKEN
and nothing for PyPI. So its own guard fired and PyPI stayed on 1.30.0, which
still calls the legacy verb routes the server is removing.

That secret is absent because it is not supposed to be there. hanzoai/extension
states the contract: the KMS machine identity is the single bootstrap credential
on the forge, and every publish credential is pulled from KMS at run time. This
job was the one that had not been migrated, so it asked the forge for something
the contract forbids storing.

Now it follows the same shape: KMS login, then PYPI_TOKEN and
HANZO_AI_PYPI_TOKEN from org `hanzo`, env `prod`, path `python-sdk-publish`.
Both are masked. The two-token fallback is unchanged — PYPI_TOKEN owns most
hanzo-* projects, HANZO_AI_PYPI_TOKEN the rest, so a 403 scope-miss on one is
covered by the other.

Absent credentials still FAIL rather than skip. A publish that no-ops and
reports green is worse than one that breaks, which is the lesson extension paid
for with v1.9.37.

The tokens exist as GitHub repo secrets, where values are write-only and cannot
be read out, so they must be seeded into KMS by hand once.

Also corrected the header: the HTTP 422 that motivated leaving GitHub no longer
reproduces (Actions ran green there on 2026-07-28). The migration stands on its
own terms, and the note now says so rather than resting on a stale symptom.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-29 06:15:12 -07:00
zooqueenandHanzo Dev a9e1b6bb16 fix(tools-iam): the MCP IAM tool listed users over a verb being deleted
_users() called /v1/iam/get-users and then did

    users = data if isinstance(data, list) else []

The verb answered a bare JSON list. The native /v1/iam/users answers
{"users": [...]}. So swapping only the path would have left that isinstance check
False and reported ZERO users for a healthy org — an empty result, not an error,
surfaced through an MCP tool an agent then reasons over. Both halves change here.

The dict branch also accepts "data" so a server still on the legacy envelope
keeps working during rollout; it goes when the compat surface does.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-28 15:41:54 -07:00
zooqueenandHanzo Dev 7ef22afa72 fix(iam): call the native routes, not the verbs the server is deleting
23 call sites across hanzo-iam and hanzo-tools-iam reached IAM over the legacy
verb surface — get-user, get-users, get-application(s), get-organization(s),
get-providers, get-role(s), get-user-count, get-user-roles, and the
oauth/access_token spelling. Those routes are being removed: HIP-0111 forbids
them, and a capability with an RFC uses its RFC.

Two things had to change together, which is why this adds routes.py rather than
editing the literals in place. The verb surface answered {status, msg, data} at
HTTP 200 even for a miss; the native surface returns the object at the top level,
uses real status codes, and NAMES its lists — {"users": [...]}, not
{"data": [...]}.

Swapping only the path is the dangerous half-migration, and hanzo-tools-iam shows
why: it did `users = data if isinstance(data, list) else []`, so against a native
response it would have reported ZERO users for a healthy org. Not an error — an
empty list. Both halves changed there.

routes.unwrap reads either shape so a fleet mid-rollout keeps working, and still
RAISES on the legacy error envelope because callers depended on that rather than
on a falsy return. It is deliberately temporary: once every server serves native
only, the envelope branch is dead and goes with it. Only a body whose keys are
exactly the envelope's is unwrapped, so a native row with its own `data` column
survives.

These admin methods had no coverage while they called the verb surface — the
reason migrating them blind was the risk. tests/test_routes.py pins it: no route
may contain "/get-", the token endpoint must be the one discovery advertises,
every list route must declare its key (a missing one unwraps to [] and reads as
empty), and neither client may carry a path literal. Falsified — restore a verb
in the table or in a client and the suite names it.

99 pass. tests/test_fastapi.py cannot collect in this environment
(starlette/httpx version clash, unrelated to this change); hanzo-tools-iam's
suite cannot either (`from mcp.server import FastMCP` against the installed MCP
SDK) — both verified pre-existing by reproducing them with these changes stashed.

Rebased onto upstream's IAM_ROUTE_PREFIX refactor rather than merged: that
refactor centralised the prefix but kept the verbs, so the two changes compose.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-28 15:41:22 -07:00
zeekayandHanzo Dev 017ab184b0 regen: one tag per operation, and 21 files a clean generation never makes
hanzoai/openapi beb4ea2 collapses an operation to its primary tag. Seven
commerce/pricing operations were being emitted twice under one identifier —
2381 lines of duplicate methods on CheckoutApi, CloudApi and InfrastructureApi.

The 21 deletions are the more interesting half. Generating over the existing
tree rather than into an empty one left files behind that no current generation
produces, and api/__init__.py had drifted into importing three of them:

  from hanzoai.cloud.api.ai_api import AiApi     # the module defines AIApi

which is an ImportError on "import hanzoai.cloud" — the package did not load at
all. A fresh generation is self-consistent; the accumulated one was not. Same
root cause as js-sdk 2.0.2.

Tests: 6 passed. tests/test_zap_transport.py fails to collect on main too
("cannot import name 'Hanzo' from 'hanzoai'") — unrelated and pre-existing,
verified by stashing this change and re-running.

Spec: hanzoai/openapi f9dbb2b (cloud 8143fc0e).

Co-authored-by: Hanzo Dev <[email protected]>
hanzoai-v3.1.4
2026-07-28 10:50:10 -07:00
zeekayandHanzo Dev 22b00e135a sdk: regen — the KMS org left the URL, so 3.1.2's secrets routes were dead
Regenerated pkg/hanzoai/cloud from hanzo.yaml. Consumed: cloud 8143fc0e,
openapi 2861089 (was f581a0e when 3.1.2 was cut).

openapi 3300cda took {org} out of the KMS secrets contract — the org is read from
the token now — and 3.1.2 shipped from before it, so its KMS methods addressed a
path the server no longer serves:

    /v1/kms/orgs/{org}/secrets         -> /v1/kms/secrets
    /v1/kms/orgs/{org}/secrets/{rest}  -> /v1/kms/secrets/{rest}
    kms_get_v1_kms_orgs_org_secrets    -> kms_get_v1_kms_secrets
    kms_post_v1_kms_orgs_org_secrets   -> kms_post_v1_kms_secrets

Five request/response models renamed with them. Nothing else in the 1885-path
surface moved: 5 models added, 5 removed, 6 files changed.

3.1.2 never reached PyPI (the tag cannot get to git.hanzo.ai — sync-from-github
fast-forwards main only, never tags), so this is fix-forward, not a replacement.
PyPI still serves 3.1.1; 3.1.2 stays a tag.

The upstream tag-casing collision came back exactly as LLM.md said it would: the
regen rmtree'd the tree and `import hanzoai.cloud` raised ImportError again on
AiApi/ApiKeysApi/McpApi. Stripped the same 9 dead lines. It will return on every
regen until one tag spelling per service lands in the per-service specs — that
also recovers the 127 of 411 operations those 23 colliding groups still drop.

No test pins the KMS route: asserting spec content here would make this repo a
second source of truth for routes and fight the pull-only contract. test_smoke
pins structure instead — import, version, surface breadth. 6 passed.

Version 3.1.2 -> 3.1.3, patch only.

Co-authored-by: Hanzo Dev <[email protected]>
hanzoai-v3.1.3
2026-07-28 10:34:39 -07:00
zeekayandHanzo Dev 915b2701c9 sdk: ship the generated cloud client — /v1/admin/plugins is reachable
pkg/hanzoai/cloud/ (2163 files) has existed on disk untracked and never shipped.
This commits it, so the operator surface cloud already serves is finally callable:

    AdminApi.plugin_admin_plugins / _enable_plugin / _disable_plugin / _reload_plugin

Generated by hanzoai/openapi `generate.py python` from hanzo.yaml (1885 paths,
69 merged per-service specs). Consumed: cloud 8143fc0e, openapi f581a0e.

One driver, so delete the second one. scripts/generate.sh fetched the spec itself
and then did `rm -rf pkg/hanzoai` + copied generator output over it — which would
have deleted the hand-written config/mcp/protocols/session/zap/api_response
modules AND the new cloud/ tree. Nothing referenced it. hanzoai/openapi's
generate.py + sdks.yaml is the only way now; LLM.md records the pipeline.

`import hanzoai.cloud` raised ImportError as generated. hanzo.yaml has 23 tag
groups differing only by case; openapi-generator maps both spellings to one
module, and for AI/ai, API Keys/api-keys and MCP/mcp it then emits imports for
classes it never wrote (AiApi vs AIApi). Stripped those 9 dead lines. The same
collision silently drops 127 of the 411 operations in those groups — that one is
upstream and only a single tag spelling per service fixes it; noted in LLM.md.

Version: 3.1.1 -> 3.1.2, patch only. pkg/hanzoai/__init__.py said 1.0.0 (the
generator default) — now resolved from the installed distribution, same pattern
as hanzo_cli/hanzo_iam in f453dae8. uv.lock refreshed to match committed pkg
versions. test_smoke.py locks the cloud import, the four plugin ops, and that
__version__ equals the distribution: 6 passed.

Co-authored-by: Hanzo Dev <[email protected]>
hanzoai-v3.1.2
2026-07-28 10:27:07 -07:00
zeekayandHanzo Dev e89a367a6d fix(mcp): the default mode disabled the unified hanzo tool
0.15.13 shipped the 166-service surface switched off. `tools/list` against a
clean install returned 40 tools with no `hanzo`, and the server logged
"unified `hanzo` tool unavailable — keeping per-service tools enabled".

The import was fine; the mode gate was not. register_all_tools() asks
is_tool_enabled("hanzo", True), which returns tool_config["hanzo"] whenever the
key exists rather than falling back to the default. ModeLoader builds that
config by disabling every TOOL_REGISTRY key and re-enabling only the active
mode's tools. TOOL_REGISTRY has "hanzo"; the default personality's list did
not — so the "True" default was never reached and the tool was explicitly off.

Two things broke together, because the per-service cloud tools are retired only
once `hanzo` is enabled: the unified surface was absent AND the ten tools it
replaces were still mounted. The exact state 929e103e set out to end.

`hanzo` belongs in ESSENTIAL_TOOLS. It is an axis like fs or git — the single
seam to the platform — not a mode-specific extra. It stays out of
ESSENTIAL_SYSTEM_TOOLS so it remains disableable.

Verified in a clean uv venv from built wheels only (no editable, no PYTHONPATH):
tools/list now returns 31 tools including `hanzo`, and api/auth/billing/commerce/
iam/ingress/kms/mpc/paas/team are correctly gone (40 - 10 + 1). The tool answers
hanzo(service="services") with 166 services read live from
https://api.hanzo.ai/v1/openapi.json.

A test now pins the gate where it is actually decided. It fails against the
installed 0.15.13 (3 of 4) and passes against 0.15.14 — an import-level check
cannot catch this, because the import succeeds.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-27 21:08:44 -07:00
zeekayandHanzo Dev 0178264695 deps: floor hanzo-tools at the version that actually has HanzoCloud
The unified `hanzo` tool imports HanzoCloud from hanzo_tools.core, which
hanzo-tools first shipped in 0.3.3 — but nothing in the dependency graph
required it. hanzo-tools-api asked for `hanzo-tools-core>=0.1.0`, an empty
shim whose own floor is hanzo-tools>=0.3.2, and 0.3.2 has no cloud.py at all.
A resolver was therefore free to satisfy every constraint and still produce an
install where `from hanzo_tools.core import HanzoCloud` raises ImportError.
That is exactly what happened: the legacy-tool gate logged "unified hanzo tool
not importable" and fell back, and the dev venv had to install hanzo-tools
editable to get a working `hanzo`.

Name the real dependency at the real floor:
- hanzo-tools-api: hanzo-tools-core>=0.1.0 -> hanzo-tools>=0.3.4. It imports
  hanzo_tools.core directly, so it must depend on the package that owns it;
  the >=0.1.0 shim floor also still admitted the shadowing duplicate.
- hanzo-tools-vector: hanzo-tools>=0.3.0 -> >=0.3.4 (cloud_vector.py).
- hanzo-mcp: hanzo-tools>=0.3.2 -> >=0.3.4, hanzo-tools-api>=0.3.1 -> >=0.3.2.

Metadata only, on versions not yet published. No version bumps.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-27 20:44:05 -07:00
zeekayandHanzo Dev d0c125f571 tools: generate the hanzo surface from cloud's OpenAPI registry; delete the vector fake
The `hanzo` dispatcher hand-listed 10 services in SERVICE_TOOL_PATHS, so the
other ~150 cloud products were unreachable until someone cut a Python release.
Project the surface from cloud's /v1/openapi.json instead: cloud tags every
operation with its product (the first path segment after /v1/), so that document
already IS a service/action catalog. 166 services are now reachable, and a newly
mounted app is callable the moment cloud serves it.

Still exactly one MCP tool — agents degrade badly with hundreds — now shaped
hanzo(service, action, params, method), with `services` listing the catalog and
an empty action listing a service's actions.

Delete the local Infinity vector store (3,546 lines). It shipped no embedder, so
its only possible output was random vectors: it ranked "Bananas are yellow" above
an auth document for the query "authentication oauth jwt". A previous fix put the
mock behind HANZO_VECTOR_ALLOW_MOCK, but a fake that a flag can re-enable is
still a fake, and a tool that lies to an agent is worse than a missing one. The
package keeps only the cloud-backed VectorTool, and a test now fails if any
module in it imports `random`.

Make the legacy-tool gate honest: hide the per-service tools only once the
unified tool genuinely imports. It does not import against released hanzo-tools
(HanzoCloud is newer), and disabling them unconditionally would have left no
cloud tools at all.

- spec.py: catalog projection, cache keyed per spec source, stale cache beats
  failing a call. HANZO_OPENAPI_URL decouples catalog from target so a partial
  local host can be driven with the full registry.
- HanzoCloud.call: one generic seam for the verbs the spec names (PUT/PATCH/
  DELETE); auth also resolves the hanzo CLI's IAM session, memoized.
- 40 new tests covering action naming, template binding, method inference,
  param split, and cache fallback.

Co-authored-by: Hanzo Dev <[email protected]>
2026-07-27 20:25:54 -07:00