Commit Graph
613 Commits
Author SHA1 Message Date
zeekay 4240afb736 feat(hanzoai): typed usage-limit errors and read_usage 2026-10-04 16:37:35 -07:00
zeekay 259f4d2fa4 fix(client): regenerate 8.5.623 with the current python driver 2026-10-04 16:23:43 -07:00
zooqueenandHanzo Dev 5c0cc67e39 Put llm and kai_decide on the default hanzo-mcp surface
Every mode disables a registered tool it does not list, and llm was
listed nowhere but HEAVY_TOOLS, written when it pulled a 100 MB client.
It is an HTTP call to api.hanzo.ai now, so llm and kai_decide join the
essential set every personality carries, and the llm package maps to the
two tools it ships.

hanzo-mcp 0.15.19, on hanzo-tools 0.3.8 and hanzo-tools-llm 0.2.1.

Co-authored-by: Hanzo Dev <[email protected]>
2026-10-04 13:55:32 -07:00
zooqueenandHanzo Dev 034e1a6caf Route llm through api.hanzo.ai with Enso, and add kai_decide
The llm tool called upstream providers through the litellm fork, read
provider keys, and never reached the MCP surface: its register() was a
no-op and it skipped BaseTool's constructor. Kai had no Python tool.

hanzo-tools-llm now carries two HIP-0300 tools over HanzoCloud:
- llm (default action query): POST /v1/chat/completions with model
  enso-auto unless named, "auto" for Enso's pick, max_cost and
  max_latency_ms as X-Max-Cost and X-Max-Latency-Ms, returning id and the
  model that served; models: GET /v1/models, one row per model with the
  catalog's per-million prices, narrowed by family; feedback: POST
  /v1/ai/feedback with request_id, signal and a 1-3 rating.
- kai_decide (default action decide): POST /v1/decisions with the
  TypeScript tool's schema and shape checks.

The consensus, llm_manage and provider modules wrapped the upstream
client and were never registered, so they go, with hanzo-consensus and
the [full] extra. hanzo-tools-llm 0.2.1.

Co-authored-by: Hanzo Dev <[email protected]>
2026-10-04 13:55:32 -07:00
zooqueenandHanzo Dev e0897353ce Let HanzoCloud carry request headers, and a tool name its default action
Enso reads its routing bounds from X-Max-Cost and X-Max-Latency-Ms on
/v1/chat/completions, and HanzoCloud could send no header but its own, so
no tool could bound a routed request. post() and the shared request path
now take extra headers that ride beside the bearer.

BaseTool runs `help` for a call that names no action. A tool with one
obvious act (llm's query, kai_decide's decide) now declares
DEFAULT_ACTION, which the call, the advertised schema's default and its
required list all read, so it answers the same no-action call the
TypeScript and Rust runtimes do. Every other tool keeps `help`.

hanzo-tools 0.3.8.

Co-authored-by: Hanzo Dev <[email protected]>
2026-10-04 13:55:31 -07:00
zeekayandHanzo Dev 10f2ac7769 client: regenerate from hanzo-inc/cloud@97eb976f07 (8.5.617)
Co-authored-by: Hanzo Dev <[email protected]>
2026-10-04 09:22:49 +00:00
zeekayandHanzo Dev dc0ca692a7 client: regenerate from hanzo-inc/cloud@60477ef158 (8.5.607)
Co-authored-by: Hanzo Dev <[email protected]>
2026-10-04 08:26:01 +00:00
zeekayandHanzo Dev 454e3bfcd6 client: regenerate from hanzo-inc/cloud@45a5d493c1 (8.5.604)
Co-authored-by: Hanzo Dev <[email protected]>
2026-10-04 04:17:37 +00:00
zeekayandHanzo Dev babd327276 client: regenerate from hanzo-inc/cloud@1e6d2a97de (8.5.583)
Co-authored-by: Hanzo Dev <[email protected]>
2026-10-03 23:19:03 +00:00
zeekayandHanzo Dev 64ef70f33a client: regenerate from hanzo-inc/cloud@8edb6f1601 (8.5.578)
Co-authored-by: Hanzo Dev <[email protected]>
2026-10-03 17:19:13 +00:00
zeekayandHanzo Dev be5f1dff52 client: regenerate from hanzo-inc/cloud@470b2430b9 (8.5.550)
Co-authored-by: Hanzo Dev <[email protected]>
2026-10-03 09:46:59 +00:00
zeekayandHanzo Dev 608ba2fd52 client: regenerate from hanzo-inc/cloud@1a819b53e3 (8.5.547)
Co-authored-by: Hanzo Dev <[email protected]>
2026-10-03 05:59:55 +00:00
zeekayandHanzo Dev 2dd8c6f865 client: regenerate from hanzo-inc/cloud@d62601149e (8.5.539)
Co-authored-by: Hanzo Dev <[email protected]>
2026-10-03 00:41:54 +00:00
zeekayandHanzo Dev e6d3482d1a client: regenerate from hanzo-inc/cloud@12a3344d6d (8.5.533)
Co-authored-by: Hanzo Dev <[email protected]>
2026-10-02 21:44:16 +00:00
zeekayandHanzo Dev 010e046347 client: regenerate from hanzo-inc/cloud@5075921e62 (8.5.528)
Co-authored-by: Hanzo Dev <[email protected]>
2026-10-02 18:48:48 +00:00
zeekayandHanzo Dev 4b762d3a59 client: regenerate from hanzo-inc/cloud@9b7cbb92dc (8.5.511)
Co-authored-by: Hanzo Dev <[email protected]>
2026-10-02 02:25:10 +00:00
zeekayandHanzo Dev f6cc56fbc0 client: regenerate from hanzo-inc/cloud@fb00f045f9 (8.5.509)
Co-authored-by: Hanzo Dev <[email protected]>
2026-10-02 01:44:08 +00:00
zeekayandHanzo Dev 0d006d27b4 client: regenerate from hanzo-inc/cloud@b852e740ce (8.5.506)
Co-authored-by: Hanzo Dev <[email protected]>
2026-10-01 23:54:56 +00:00
zeekayandHanzo Dev c23f20cbaf client: regenerate from hanzo-inc/cloud@9b7f663b28 (8.5.505)
Co-authored-by: Hanzo Dev <[email protected]>
2026-10-01 23:08:56 +00:00
zeekayandHanzo Dev d019bb67d4 client: regenerate from hanzo-inc/cloud@0a52a53e66 (8.5.500)
Co-authored-by: Hanzo Dev <[email protected]>
2026-10-01 19:39:01 +00:00
zeekay 32ce9a8595 Answer with the connected browser's error, never a Playwright stand-in
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.
2026-09-30 21:49:37 -07:00
zeekay 4c0e40351c hanzo-kai reads /v1/models pricing as per-token prompt and completion strings plus per-million rates under keys that name the unit. 2026-09-30 14:56:35 -07:00
zeekay cac19547ff hanzo-tools-browser 0.5.15 2026-09-30 14:54:29 -07:00
zeekay fedeec8d70 Regenerate the cloud client from v8.5.491
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.
2026-09-30 14:54:13 -07:00
zeekay 58801c6ccc Register the native host with Firefox as well
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.
2026-09-30 12:32:10 -07:00
z e629d5e539 A browser joins the ZAP router through native messaging
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.
2026-09-30 10:17:05 -07:00
zeekay 2b672025db client: regenerate from hanzo-inc/[email protected] (8.5.164)
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.
2026-09-30 07:13:42 -07:00
zeekay ad606c7611 The browser tool takes a seat on the embedded ZAP router
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.
2026-09-30 06:56:52 -07:00
zeekay 379600adc8 client: regenerate from hanzo-inc/cloud@2774987ccc (8.5.164)
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.
2026-09-29 13:53:29 -07:00
zeekay 1b4cfa4c16 devserver passes browser options through args
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
2026-09-29 13:06:50 -07:00
zeekay 450f65932d browser teaches the snapshot loop in its description and the rest on help
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
2026-09-29 13:05:21 -07:00
zeekay 82a090d0ba hanzo-tools and hanzo-mcp stay on mcp 1.x
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
2026-09-29 12:41:52 -07:00
zeekay af78255a39 devserver: running dev servers as MCP nodes
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
2026-09-29 12:37:53 -07:00
zeekay 351b1a2b1b mcp_proxy reaches running servers over HTTP and SSE
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
2026-09-29 12:37:53 -07:00
zeekayandHanzo Dev e34ab7e14c client: regenerate from hanzo-inc/cloud@efc1644bfe (8.5.163)
Co-authored-by: Hanzo Dev <[email protected]>
2026-09-29 17:17:07 +00:00
zeekay 4dac77fe06 hanzo-train 0.1.1 drives the training wire at /v1/train/clients. 2026-09-28 22:11:20 -07:00
zeekayandHanzo Dev 875469edc7 client: regenerate from hanzo-inc/cloud@20e58292d2 (8.5.162)
Co-authored-by: Hanzo Dev <[email protected]>
2026-09-29 01:37:51 +00:00
zeekayandHanzo Dev d044022152 client: regenerate from hanzo-inc/cloud@8eb2e12e26 (8.5.162)
Co-authored-by: Hanzo Dev <[email protected]>
2026-09-29 01:18:01 +00:00
zeekay fb29d854fa The versioned-id fixture asks /v1/decisions only to accept the id, and /v1/systemone to answer with it 2026-09-28 18:10:48 -07:00
zeekay 8102dad40b sdk_jev asks about a March invoice, as the other examples do. 2026-09-28 18:05:58 -07:00
zeekay 169648c43d hanzo_kai.jev lists the decision models of data in Jev's shape, and the conformance fixtures name every reach refusal and pin Kai's versioned id 2026-09-28 17:37:54 -07:00
zeekay 70cb092e67 hanzo-kai's examples are the programs behind every Python page of the Kai docs. 2026-09-28 16:35:51 -07:00
zeekay 15c3a6ea95 Bring hanzo-kai to the frozen decision contract, with the Jev-compatible hanzo_kai.jev and a conformance fixture per rule 2026-09-28 14:57:06 -07:00
zeekay 75771bfe7f Add hanzo-kai, the Python client for Kai decisions 2026-09-28 14:32:02 -07:00
zeekayandHanzo Dev 40cb597c2f client: regenerate from hanzo-inc/cloud@2314af8b90 (8.5.160)
Hanzo CI/CD / cicd (push) Failing after 2m6s
CI/CD / cicd (push) Failing after 2m7s
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]>
2026-09-18 17:28:47 -07:00
zeekayandHanzo Dev a2652a6979 Stop a rate-limited call from sleeping for hours
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]>
2026-09-11 13:44:15 -07:00
zeekayandHanzo Dev 234c2d8cf1 Prefer tgrep where the tree carries an index
Trigram index prunes files a query cannot match: 23ms against ripgrep's 78ms
for an absent symbol, which is most of what a search asks. It loses where many
files match (128ms against 78ms), and with no index it scans everything and is
slower than plain grep — so the index is the condition, not the binary.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-10 17:24:51 -07:00
zeekayandHanzo Dev 0da7a4185c fix(six): one word, one body, one shape across the three SDKs
Thirteen places where this SDK and the other two answered the same question
differently. Every one is now what the served handler reads, or what HIP-0044
says where the handler is silent.

Where this SDK was already right, the other two moved: search normalizes the
knowledge kind in both directions, `Page.total` stays the server's own count,
and `put` picks its method from the name. Where it was not:

- kb.Import carries what the handler answers. `/v1/knowledge/import` writes
  {format, imported, pages} (knowledge/import.go); this modelled `imported`
  alone, so a caller could not see which pages landed.
- Fact carries the server's three. `GET /v1/graph` serves twelve members
  (graph/typed.go, wireFact) and no other route hands back an assertion's
  content address, so projecting the nine an asserter states made a read lossy
  with nothing to recover it from. `id`, `by` and `knowable` arrive read-only
  and `write()` never sends them.
- graph.extract and graph.ingest take a Source. Both routes bind the same
  {source, subject, text, at} (graph/semantic.go, graphSourceIn), so it is one
  value — and the first positional argument is now the same thing here as it is
  in the other two.
- import_(format, data, project) — the order the contract writes and the other
  two take. It was (export, *, format).
- A body carries what somebody set. `send` dropped None from the query and left
  it in the body, so `search.find` posted {"index": null, "offset": null,
  "project": null} where the other two omit the key: three requests for one
  call, and cloud asked to read a value where there is none. Audit's pageSize
  and p go the same way — the route's own defaults are the same 100 and 1, so
  an unset term is one the route decides identically, and `all` pins the size it
  walks at because it needs to know the page length.
- A write body is field data and nothing else. `url` belongs to a source, and an
  empty title is left out so the doctype names the required field that is
  missing rather than being handed a blank one.
- kb.list takes order and kb.links takes a project. `/v1/framework/{doctype}`
  serves order_by and `/v1/knowledge/graph` serves ?project=.
- policy_denied and entitlement_required do not exist. Neither string appears
  anywhere in hanzoai/cloud, so REFUSED is the two codes it does emit. A 403
  read as denied on an invented code would hand an unauthenticated caller a cure
  for a refusal nobody made.
- One refusal reader, by named fallback: code from `code` else `error`, reason
  from `detail` else `message`, cures from `cure`. The gate's own `reason` —
  "unpaid", "unresolved" — names the admit leg that failed rather than
  explaining anything, so it is dropped, and `title` is not a refusal member.
- Money counts minor units, not cents: hundredths for USD, whole yen for JPY,
  thousandths for KWD.
- Balance.reserved, not held. The wire says `holds`; `held` is an arm.
- Vocabulary.rules, because it is a list. The wire member stays `rule`.
- The fourth outcome is Fault, not the generated client's ApiException. It is
  the one shared concept whose name a reader could not carry between languages,
  and an exception from the generated layer is not this layer's vocabulary.
- A client with no credential builds. It raised at construction, which is a
  process that cannot start where the other two are a process whose first call
  fails — and the first call is where the credential is actually needed.

HIP-0044 is amended where the code was right and the document was not.

ruff check, ruff format --check and mypy clean on every changed file; 109
passed. The two test_smoke failures are pre-existing (ToolsApi is absent from
the generated tree) and reproduce on a stashed working copy.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-10 16:26:15 -07:00
zeekayandHanzo Dev 0a8e3272a0 fix(six): send the body /v1/authz/check reads, and stop stamping instants nobody set
policy.check was built from the route's OpenAPI description, which is stale.
The handler cloud pins — hanzoai/authz v1.10.37, serve/use.go — binds
{subject, verb, path} and answers {allow, subject, verb, path}; a body of
{sub, obj, act} gets 400 "subject, verb, path required". go-sdk and js-sdk both
send the served spelling. The caller-facing surface is unchanged: check(sub,
act, obj) -> Decision{allow, sub, act, obj, reason}.

graph left an unset instant on the body as "". Cloud reads an absent `seen` as
`at` and an absent `as_of` as now, but reads "" as a timestamp it refuses:
POST /v1/graph/ingest with at:"" answers 400 timestamp "" is not RFC 3339. An
instant nobody set is now left off.

kb.Edge takes PEP 8's keyword escape, from_ and to, rather than renaming the
pair to source and target — the word is `from`, which is what the other two
spell it. Document names and connector providers are escaped into their path
segment, as they already are in Go and TypeScript.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-10 15:28:12 -07:00
zeekayandHanzo Dev 7824191215 feat: budget, policy, audit, search, kb and graph on one credential
Six capabilities hang off the client as first-class methods rather than as
generated operations a caller assembles for itself: budget (allowance, wallet,
tier, charged ledger), policy (one authorization question), audit (the org's
trail, read-only by construction), search (one ranked set over every corpus),
kb (the corpus you write and the connectors that fill it), and graph
(assertions with provenance and time). The same six words, the same method
names and the same answer type as the Go and TypeScript clients.

A refused budget and a refused policy are answers. Answer is a union of three
frozen types with a discriminant — Ok, Denied, Held — so a caller reads
"denied, and here is why" without catching, and reading .value off a refusal
raises it rather than handing back a half-answer. One rule maps HTTP to an arm:
a 202 whose body says held is a hold, 402 and the four refusal codes on 403 are
refusals, and a status that decided nothing raises. Every arm carries the
x-request-id, which is the word audit.Event.request uses too — the join that
lets a caller find the row the server wrote for the call it just made.

Auth is IAM and nothing else. The client exchanges its own clientId and
clientSecret for a short-lived access token (client_credentials,
client_secret_basic, RFC 8707 resource), holds it until shortly before expiry,
and re-mints once on a 401. It no longer accepts a bearer, so HANZO_API_KEY,
Configuration(access_token=) construction and result() are gone rather than
deprecated: two ways to hold one credential is the defect this removes. as_
still scopes to a subject and now mints from the client's own token.

hanzoai resolves its public names on first use, and the capability layer is
written over the wire rather than over the generated client, so importing the
six costs milliseconds instead of 2,700 generated modules.

ruff and ruff format are clean, mypy is clean over the thirteen new and changed
modules, and 106 tests pass. The two failures in test_smoke are the ToolsApi
name the current document no longer carries, and they fail at HEAD too. The
mint was exercised against the live hanzo.id, which answers invalid_client for
a credential it does not know; the six were exercised against a stub asserting
the exact request and decoding the shapes cloud's own schemas declare.

examples/six runs the whole thing as a caller would.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-10 15:01:41 -07:00