In auto mode any extension error fell through to headless Playwright, so
a slow page in the user's own browser came back as Playwright's missing
executable. A connected browser's failure is now the answer; Playwright
stands in only when no browser is on the router. hanzo-tools-browser
0.5.16.
The release lane regenerates pkg/hanzoai/cloud from the served document
and refuses to publish when the committed client differs; it had drifted
since v8.5.458, so every python-sdk tag, hanzo-mcp and
hanzo-tools-browser included, stopped at that check. The lock now names
v8.5.491 and the hash api.hanzo.ai serves.
Firefox reads its host manifests from ~/.mozilla/native-messaging-hosts
(Mozilla/NativeMessagingHosts on macOS) and names the extension by its
gecko id in allowed_extensions, not an origin. With the extension now
asking for nativeMessaging, Firefox joins the router without a pairing
code, as Chromium does. hanzo-tools-browser 0.5.14.
hanzo-zap-host is the Chrome native messaging host ai.hanzo.zap: it relays
router envelopes between the extension's stdio and the router's unix socket
and stands for the router itself. hanzo-mcp registers it with every installed
Chromium-family browser on start. hanzo-tools-browser 0.5.13.
The served document moved past the one this projection was generated
from, so the client lane's check failed every release. uv.lock follows
hanzo-tools-browser onto zapd.
hanzo-mcp said HELLO as consumer:hanzo-mcp/<pid> to a separate zapd
daemon, which the router that replaced it refuses as bad_id. Every
process that speaks ZAP now embeds the router (zapd 1.1 on PyPI) and the
kernel lock elects one; hanzo-mcp stands for it at startup and takes the
seat mcp/hanzo-<pid>. A browser is a browser/<host>/<name> node, found
by engine or name and reached with Node.call, and the extension's
command codec is unchanged.
hanzo-mcp pair prints the code the extension's popup takes. The daemon
spawn and socket probing are gone, and so is the zap-proto dependency.
The client lane checks the committed client against the document
api.hanzo.ai serves now, not against the ref .spec-lock pins. Cloud
v1.833.257 (2774987c) went live after 8.5.163 was cut and rewrote the
POST /v1/decisions description (cad76e1a0: Jev by OpenRouter's vendor
ids), so the served document moved from 296b1f84 to 783f66a3 and every
push since has failed the check on ai_api.py's docstring. The drift
reproduces identically at e34ab7e1; nothing in this tree caused it.
This is the lane's own move, done by hand: .spec-lock names the served
revision and digest, the generator reruns over those bytes, uv.lock
takes what `uv run` wrote, and the version takes the next patch.
The browser tool types only its core fields; everything else rides in
`args`. navigate's state and console's level sat at the top level,
where the schema drops them, so errors opened the app without waiting
for networkidle and read every console message rather than errors.
The test's stand-in browser now has the real tool's shape.
Claude-Session: https://claude.ai/code/session_01Dc8DXc3fEQCRHUa6XWK4Ku
The tool put ~120 actions in a Literal enum and ~60 parameters in its
schema, all of it in every agent's context, and most of it Playwright
locator builders that returned a count. Three hand-kept lists (the enum,
extension_actions, _zap_method_for) disagreed: snapshot, wait and
select_option routed to the extension under names it did not answer.
ACTIONS is now the one table: each action once, its topic, its usage
line, and the extension method that serves it. The schema carries the
core loop (navigate, snapshot, click, fill, type, press, read,
screenshot, evaluate, wait, tabs, help) with an open `action` string;
help prints the rest by topic, and their parameters travel in `args`.
snapshot, click-by-ref and friends go to the extension's page engine
(hanzo.snapshot / hanzo.act / hanzo.read / hanzo.wait / hanzo.navigate,
screenshot annotate=true to hanzo.annotate). A tree or a page answers as
text; a refusal ("Stale ref @e4 … run snapshot again", "covered by
<div#consent>") is the error, not a success wrapping "ERR:", and is
never retried on Playwright, which has no refs to act on.
Aliases and no-ops are gone: select_option (select), get_inner_text and
get_value (get_text), clear (fill), content (get_html), new_page
(new_tab), press_key, mouse_wheel (scroll), list_browsers and
list_mcp_instances (browsers), use_browser, set_default_browser,
claim/release_browser, the locator/get_by_*/first/last/nth/filter/all
builders, is_hidden, frame, main_frame, on and off.
Claude-Session: https://claude.ai/code/session_01Dc8DXc3fEQCRHUa6XWK4Ku
hanzo_tools.core imports mcp.server.FastMCP, which mcp 2 removed, so a
fresh resolve that picks mcp 2.2.0 fails at import. The published
hanzo-mcp 0.15.17 already declares mcp<2; the tree now says the same,
and hanzo-tools, which owns the import, says it too.
Claude-Session: https://claude.ai/code/session_01Dc8DXc3fEQCRHUa6XWK4Ku
An agent editing a web app needs the dev server's own view of it: which
routes exist, and whether the last edit broke the build. Next 16 serves
that over MCP at /_next/mcp, and Vite-based frameworks can with
vite-plugin-mcp, but nothing here found those endpoints.
index finds Next, Vite, Nuxt, Remix, React Router, Astro and SvelteKit
dev servers from each process's argv, cwd and listening ports, and lists
the tools of the MCP each serves. call runs one. errors returns current
build and runtime errors: Next's get_errors, after opening the app in the
browser tool, since Next reports only what a connected session reported;
otherwise Vite's error overlay, console errors and page errors, read
through the same browser tool.
discover() returns the servers as plain records, the shape a devserver
node will take. hanzo-mcp depends on the package so the tool ships.
Claude-Session: https://claude.ai/code/session_01Dc8DXc3fEQCRHUa6XWK4Ku
MCPServerConnection only spawned stdio servers, so an endpoint a process
already serves - Next 16's /_next/mcp, vite-plugin-mcp's /__mcp/sse - had
no client in this repo.
MCPServerConfig now takes a url. connect lists the tools and call_tool
calls one through the SDK's streamable HTTP or SSE client, a session per
request, and returns the same JSON-RPC result the stdio path returns. A
loopback URL skips TLS verification, since a local dev certificate is
self-signed. A failed connect says why in .error.
Claude-Session: https://claude.ai/code/session_01Dc8DXc3fEQCRHUa6XWK4Ku
Hanzo CI/CD / test (linux-amd64) (push) Successful in 2m42s
Hanzo CI/CD / test (linux-arm64) (push) Successful in 5m16s
Hanzo CI/CD / cicd (push) Failing after 2m2s
CI/CD / cicd (push) Failing after 9m50s
hanzoai 8 requires Python 3.12, and PyPI serves 2.1.3 to 3.9-3.11. An
unpinned `pip install hanzoai` there installed the 2.1 client without a word,
and every example here failed on `from hanzoai.cloud import ...`. Every install
line now carries the floor, which pip 21 on Python 3.9 answers with "No
matching distribution found for hanzoai>=8", and the quickstart no longer says
Python 3.9.
Co-authored-by: Hanzo Dev <[email protected]>
PyPI has shipped Client since 8.5.161, so a new user following the README
was sent to a git install they do not need.
Co-authored-by: Hanzo Dev <[email protected]>
v8.5.160 regenerated the client and its release run passed the client check,
then stopped at declared-imports: the step called bare `python`, the runner has
none on its PATH, and exit 127 from a step that is report only ended the run
before anything was published. It now runs through uv like the three steps
beside it. Verified by running the whole test: block with no python on PATH.
Co-authored-by: Hanzo Dev <[email protected]>
The committed client had fallen behind the API it projects: 116 modules the
document now names were missing, 13 it no longer names were still here, and
30 had moved. The release lane refuses to publish a projection that does not
match the document, so v8.5.158 and v8.5.159 were tagged and never reached
the index.
Regenerated from the document production serves (sha256 aa1b79d0797704608e25a3898adb2ca69fec82a03f74c09671c372b63066cafe),
with the driver at hanzoai/openapi@88ea26b — the same inputs the lane reads,
and the same +116 -13 ~30 it reported. The repo's own gate ran over exactly
these bytes: 121 api and 2675 model modules import, no class declares a field
twice across 2804 modules, and every example flow resolves its method names.
Co-authored-by: Hanzo Dev <[email protected]>
urllib3 sleeps whatever Retry-After says, clamped at 6 h in 2.7, and retries three times. api.hanzo.ai sends ~23,000 s for an exhausted quota, so one GET held its caller for 18 h. Client sleeps through at most 60 s; past that the answer returns at once, as ApiException with the header for generated operations and as Fault.retry_after for the six.
Co-authored-by: Hanzo Dev <[email protected]>
pip install hanzoai on 3.12 gets 8.5.156, which has no Client; on 3.9 to
3.11 it gets the 2.1 line, a different client. The README said neither.
It showed KeysApi and MemoryApi, which the generated client does not
have, and mapped hanzo-agent and hanzo-agents to copies here that PyPI
does not publish.
examples/agent and examples/tools imported AgentsApi and ToolsApi, which
the document now names AgentApi and ToolApi. Both flows ran against a
local server after the change. The per-agent runs read is generated
without its ref, so the agent flow reads /v1/agent/runs and keeps its
own rows.
The status table and the Retry-After lines come from runs against a
local server: a capability call is one request, two on a 401; a
generated operation sleeps for Retry-After on 429 or 503 and retries
three times.
Co-authored-by: Hanzo Dev <[email protected]>
hanzo-agent 0.1.0 went to PyPI importing numpy and httpx and declaring
neither. In this repo the workspace supplies every sibling, so its own tests
all passed; `pip install hanzo-agent` then failed at import for everybody else.
An outside adopter hit that, gave up on three of our packages, and wrote their
own clients — the most expensive kind of bug we ship, because it is invisible
from inside the tree.
This reports every import across the 64 packages that its own manifest does not
declare. 90 of them.
It does NOT fail, and the reason is a limit of reading rather than leniency:
hanzo-zap imports `zap` and declares `zap-proto`, which provides it;
hanzo-tools-iam imports `mcp` and declares a sibling that depends on it.
Neither is broken and both look identical to a parser. Failing 90 rows on day
one is how a gate gets switched off.
It still earns its place. Relying on a transitive dependency is a break waiting
for the day that sibling drops it, and 90 rows is a map of where that sits.
The authoritative check — install each wheel alone in a clean venv and import
it — belongs in the shared publish lane rather than a copy here, which is the
point dd889c29 already made when it removed this repo's own lane.
The `zap` row was mine: the first run said 92 because the alias table did not
know zap-proto provides it.
Co-authored-by: Hanzo Dev <[email protected]>
The line under `pip install hanzoai` was `python -m examples.models`, and
examples/ is in the sdist only — hatch's wheel target is pkg/hanzoai. So
the first command in the README answered ModuleNotFoundError to everyone
who installed the package the way the line above told them to.
Co-authored-by: Hanzo Dev <[email protected]>
publish-pypi.yml was a per-repo spelling of a rule the whole estate
needs, and it was the estate's only one: a Python repo without a copy
of this file could not publish at all. `pypi:` in hanzo.yml now names
what ships and hanzoai/ci ships it, so the rule has one home.
The hardcoded package list goes with it. A glob over pkg/ cannot fail
to notice a package added beside it, and the tag is matched against
each pyproject's own name rather than against a case list, so all four
tag shapes already in this repo's history resolve to the same
distributions they did before.
Co-authored-by: Hanzo Dev <[email protected]>