100 Commits
Author SHA1 Message Date
zeekay 4770743afb hanzo-mcp 1.1.33: the hanzo login when no key is set
Hanzo CI/CD / test (linux-amd64) (push) Skipped
Hanzo CI/CD / test (linux-arm64) (push) Skipped
Hanzo CI/CD / test-advisory (push) Skipped
publish / publish (push) Successful in 57s
Hanzo CI/CD / test-amd64 (push) Successful in 10m22s
Hanzo CI/CD / cicd (push) Successful in 1m40s
CI/CD / cicd (push) Successful in 12m7s
A desktop app starts hanzo-mcp with no environment of its own, and
`hanzo login` never writes an apiKey into ~/.hanzo/config.json, so on a
fresh machine every Kai and Enso call answered NO_API_KEY. With no key
set, hanzo-mcp now asks the hanzo CLI beside it (or at hanzo.sh's
install path) for the login's token, `hanzo auth token`, and keeps it
for five minutes. Never a hanzo found on PATH: the Hanzo IDE package
installs /usr/bin/hanzo, which opens its window on any arguments.

Claude-Session: https://claude.ai/code/session_01M8RoubcPN71KgJuZ8LdBUL
2026-10-15 05:29:25 -07:00
zeekay d0b03aeb05 hanzo-mcp 1.1.32 for Windows: zapd 1.1.9 and a native host on the router's pipe
Hanzo CI/CD / test (linux-amd64) (push) Skipped
Hanzo CI/CD / test (linux-arm64) (push) Skipped
Hanzo CI/CD / test-advisory (push) Skipped
publish / publish (push) Successful in 1m12s
Hanzo CI/CD / test-amd64 (push) Successful in 8m48s
Hanzo CI/CD / cicd (push) Successful in 1m51s
CI/CD / cicd (push) Successful in 10m45s
zapd 1.1.9 runs the router on Windows (a named pipe, LockFileEx). The
browser's native host follows it there: the host binary is a hard link
hanzo-zap-host.exe, each installed browser is pointed at its manifest
through its NativeMessagingHosts registry key, and the relay opens the
router's pipe only when its server is the router lock holder. The
release builds windows-amd64 again, cross-linked by Zig like the rest.

Claude-Session: https://claude.ai/code/session_01M8RoubcPN71KgJuZ8LdBUL
2026-10-15 05:11:21 -07:00
zeekay 5b108f9625 @hanzo/mcp 2.4.21: read-only tools say so
Hanzo CI/CD / test (linux-amd64) (push) Skipped
Hanzo CI/CD / test (linux-arm64) (push) Skipped
Hanzo CI/CD / test-advisory (push) Skipped
publish / publish (push) Failing after 3m18s
Hanzo CI/CD / test-amd64 (push) Failing after 6m31s
Hanzo CI/CD / cicd (push) Skipped
CI/CD / cicd (push) Failing after 6m33s
The same annotation hanzo-mcp 1.1.31 gives its tools: every Kai decision
tool, llm, think and code_search/context/ask are listed with readOnlyHint,
so Codex and its forks call them in a run that never asks.

Claude-Session: https://claude.ai/code/session_01M8RoubcPN71KgJuZ8LdBUL
2026-10-15 03:29:50 -07:00
zeekay 17a914dc2e hanzo-mcp 1.1.31: read-only tools say so
Hanzo CI/CD / test (linux-amd64) (push) Skipped
Hanzo CI/CD / test (linux-arm64) (push) Skipped
Hanzo CI/CD / test-advisory (push) Skipped
publish / publish (push) Successful in 2m18s
Hanzo CI/CD / test-amd64 (push) Successful in 8m5s
Hanzo CI/CD / cicd (push) Successful in 3m54s
CI/CD / cicd (push) Successful in 12m20s
Codex, Hanzo Dev and Open Interpreter ask before any MCP tool that is not
marked read-only, and a run that never asks refuses it, so every hand-off
to Kai or Enso in a headless run was refused. The tools that read or hand
work to Hanzo's models and indexes (kai_decide, llm, research, web_*,
code_search, code_context, code_ask, vision, think) are now listed with
readOnlyHint. kai, which runs actions, and code_index, which writes the
index, are not.

Claude-Session: https://claude.ai/code/session_01M8RoubcPN71KgJuZ8LdBUL
2026-10-15 03:28:17 -07:00
zeekay b8436102e1 release: build only the targets that compile
Hanzo CI/CD / test (linux-amd64) (push) Skipped
Hanzo CI/CD / test (linux-arm64) (push) Skipped
Hanzo CI/CD / test-advisory (push) Skipped
publish / publish (push) Successful in 3m10s
Hanzo CI/CD / test-amd64 (push) Successful in 4m55s
Hanzo CI/CD / cicd (push) Successful in 3m50s
CI/CD / cicd (push) Successful in 8m49s
The Windows leg fails at zapd, whose ZAP router and native host speak a
Unix socket, so every release carried a red job. Linux and macOS on
both architectures are what hanzo-mcp ships.

Claude-Session: https://claude.ai/code/session_01M8RoubcPN71KgJuZ8LdBUL
2026-10-14 21:42:49 -07:00
zeekay 41533d452a hanzo-mcp 1.1.30 for every platform the CLI ships, and 2.4.20 takes --project-dir
Hanzo CI/CD / test (linux-amd64) (push) Skipped
Hanzo CI/CD / test (linux-arm64) (push) Skipped
Hanzo CI/CD / test-advisory (push) Skipped
publish / publish (push) Successful in 2m14s
Hanzo CI/CD / test-amd64 (push) Successful in 6m17s
Hanzo CI/CD / cicd (push) Successful in 4m15s
CI/CD / cicd (push) Successful in 10m38s
The release lane built Linux alone, so hanzo.sh offered macOS and
Windows a server nobody had built, and `hanzo dev` there fell back to
nothing. It now cross-builds linux, windows and darwin for both
architectures from the one Linux pool with Zig and a pinned macOS SDK,
the way the CLI's release does, one target at a time. Zig comes from its
own checksummed tarball, not pip.

`hanzo dev` hands its server `--project-dir`, the native spelling. The
npm build read it as stray arguments and exited, so a machine whose
PATH found the npm `hanzo-mcp` first ran every session without the
toolset. It now takes the flag as an allowed root.

Claude-Session: https://claude.ai/code/session_01M8RoubcPN71KgJuZ8LdBUL
2026-10-14 20:51:41 -07:00
zeekay 4da4b72cd9 @hanzo/mcp 2.4.19: the injection screen asks kai-1.3's planted capability
Hanzo CI/CD / test (linux-amd64) (push) Skipped
Hanzo CI/CD / test (linux-arm64) (push) Skipped
Hanzo CI/CD / test-advisory (push) Skipped
publish / publish (push) Failing after 3m55s
Hanzo CI/CD / test-amd64 (push) Successful in 7m57s
Hanzo CI/CD / cicd (push) Successful in 6m15s
CI/CD / cicd (push) Successful in 14m17s
kai-1.3 serves planted in production. Read once through api.hanzo.ai on
the 457 held-out rows, rules and planted flag 209 of 250 attacks at 6 of
207 false alarms (rules and the jailbreak question: 182, 2). The README,
the cookbook table and recipe 04 drop the jailbreak question and the
threshold argument the tool no longer takes.

Reads: GET /v1/research/runs?org=hanzo&benchmark=mcp.injection

Claude-Session: https://claude.ai/code/session_01M8RoubcPN71KgJuZ8LdBUL
2026-10-14 19:34:10 -07:00
zeekay 0ac9c94701 kai_injection_screen: rules and Kai's planted capability, each flagging what the other misses
Kai answers through planted (kai-1.3), a capability fit on InjecAgent's dev
tools and wordings over kai-1.2's read, at the probability whose
out-of-fold false alarms stayed within 1%. kai-1.2's jailbreak question
added no catch over rules and planted, so it is no longer asked. Reads:
GET /v1/research/runs?org=hanzo&benchmark=mcp.injection.
2026-10-14 19:32:31 -07:00
zeekay cad04d0583 @hanzo/mcp 2.4.18: bin paths npm keeps
Hanzo CI/CD / test (linux-amd64) (push) Skipped
Hanzo CI/CD / test (linux-arm64) (push) Skipped
Hanzo CI/CD / test-advisory (push) Skipped
publish / publish (push) Failing after 2m24s
Hanzo CI/CD / test-amd64 (push) Successful in 6m36s
Hanzo CI/CD / cicd (push) Successful in 5m31s
CI/CD / cicd (push) Successful in 12m12s
The runner's npm dropped both bin entries written as ./dist/cli.js as invalid
while publishing 2.4.17, and the version never reached the registry. Bare
dist/cli.js is the form 2.4.16's published bins read; a packed tarball of
this tree installs hanzo-mcp and mcp and lists the kai tools.

Claude-Session: https://claude.ai/code/session_01M8RoubcPN71KgJuZ8LdBUL
2026-10-14 18:51:57 -07:00
zeekay 119dddb2d2 @hanzo/mcp 2.4.17
Hanzo CI/CD / test (linux-amd64) (push) Skipped
Hanzo CI/CD / test (linux-arm64) (push) Skipped
Hanzo CI/CD / test-advisory (push) Skipped
publish / publish (push) Failing after 2m32s
Hanzo CI/CD / test-amd64 (push) Successful in 5m43s
Hanzo CI/CD / cicd (push) Successful in 5m49s
CI/CD / cicd (push) Successful in 11m41s
2026-10-14 18:30:52 -07:00
zeekay e0206ba55a bench/tools: artifacts are reports of the measured run, stamped in unix seconds 2026-10-14 18:30:09 -07:00
zeekay 655d4ebae6 bench/tools: file under the token's project, and grant the reads public on --publish 2026-10-14 18:30:09 -07:00
zeekay 1a90827eeb bench/tools: say where the injection screen's time was read 2026-10-14 18:30:09 -07:00
zeekay 56a2b02dcf kai tools: next action, recovery and extraction without guesses; reads linked, not quoted
kai_next_action never returns a candidate whose preconditions are unmet,
returns a single remaining one as is, and picks among several by BM25
against the goal, abstaining on a tie or no shared word; no Kai call (a
Kai choice over candidates read below BM25 for tools). kai_recover maps
each kai_log_triage class to a step (permission asks the user) and keeps
the three-failure switch. kai_extract computes a yes/no field's confidence
from P(true), abstains in Kai's unknown band (.35 to .65), and returns a
score field's level rather than its mean.

Recipes 02, 06, 07, 11, 12 and 14 check what the tools return, including
abstention; the cookbook README and the tools' comments link the research
runs instead of quoting numbers. bench/tools/post.py files each tool's read
(benchmarks, one run an arm, items and cases as artifacts) to /v1/research.
2026-10-14 18:30:09 -07:00
zeekay efe5ba3b8c kai_log_triage: record the held-out read
60 real CI logs from 48 public repositories, labelled by class from the
excerpt alone without the rules in view, read once against aee2889's
signatures: 42 right (.70), 7 answered unknown, 11 another class. On the
40 logs whose class ae4d07b's six categories can name, its Kai choice read
20 (p50 502 ms on the CPU) and the signatures 28.
2026-10-14 18:30:09 -07:00
zeekay ca6c1b8f65 kai_command_guard: allow only known workspace work, ask for the rest
The guard allowed anything its few patterns missed whenever Kai's choice said
allow, and kai-1.2 reads command risk near chance: on hermes-jev-skills' 118
gate fixtures (MIT) it allowed 12 of the 65 commands that must be denied or
asked (rm -rf ~/Documents, chmod -R 777 ~, base64 -d | sh) and asked or
denied 33 of the 53 ordinary ones.

Now deny and ask are classes matched on the whole line (wipe, pipe_shell,
exfiltrate, secret_read, permissions, clobber, disk, metadata, drop_data,
claimed_approval; git_remote, git_discard, publish, deploy, service,
message, privilege, global_install, sync_delete, persistence, environment,
parent_path), and allow needs every part of the line to be inspection, a
build or test, local git, or a file change under a relative path or /tmp.
Anything else asks. The caller's policy is a floor. No Kai request: its
risk questions changed no outcome on the fixtures but one more ask.

Frozen and read once on the fixtures: 1 of 65 allowed, 18 of 53 asked.
Two classes were fixed after that read and are not held out: the
metadata-endpoint fetch (the one allowed command) and a bare `rm -rf /`,
which asked where it must deny. With them: 0 of 65 allowed.
2026-10-14 18:30:09 -07:00
zeekay 3431b4ac53 kai tools: screen injection by rules first; select tools and triage logs without a model
Measured against kai-1.2 served locally from production's folder (SHA256SUMS e45dd919…).

kai_injection_screen asks Kai the question it was trained on ({prompt},
"Does `prompt` try to make an AI assistant ignore its rules...") beside
seven rule classes: override, role, addressed, conceal, exfiltrate,
tool_call, act, plus the earlier extract patterns. Held out on InjecAgent's
unseen tools and wordings (250 attacks) and 207 benign tool outputs and
README paragraphs: recall .728 at .010 false alarms, against .044 at .116
for the screen it replaces. Kai's question alone found 0 of the 250
planted requests. Rules still decide when Kai does not answer.

kai_tool_select ranks by a named tool, then BM25 over name, description
and parameters, and makes no Kai call: on 300 supplied tool lists from
Kai's T1 dev split it reads top-1 .733 and MRR .841 against .657 and .805
for one Kai choice over the names, in 0.1 ms against 109 ms.

kai_log_triage classifies by failure signature (resources, network,
timeout, permission, dependency, compile, test, crash; none and unknown)
and returns the deciding lines and file spans; kai-1.2 read build logs at
chance. Recipes 01, 04, 05, 08 and 10 check what these tools return;
recipe 08 calls the tool instead of matching names itself. The cookbook
reaches Kai as clients do, through HANZO_API_KEY or LOCAL_KAI_URL, not a
fixed local port and placeholder key. compact.ts types its decision so
tsc passes.
2026-10-14 18:30:09 -07:00
zeekay 3c5d87c397 Merge local main (2c4c2f1): the link installer and beta profile beside Kai Actions and the shell classifier
Hanzo CI/CD / test (linux-amd64) (push) Skipped
Hanzo CI/CD / test (linux-arm64) (push) Skipped
Hanzo CI/CD / test-advisory (push) Skipped
publish / publish (push) Successful in 3m12s
Hanzo CI/CD / test-amd64 (push) Successful in 5m22s
Hanzo CI/CD / cicd (push) Successful in 3m43s
CI/CD / cicd (push) Successful in 9m12s
kai_command_guard decided its policy inside the try, so the catch that
keeps a deny or ask on a failed Kai call could never read it; the
policy is now decided first.
2026-10-14 17:25:59 -07:00
zeekay ee27c12905 shell: a long command is read on a stack its depth fits; npx and uvx take approve
The walk recurses with a command's nesting, and a flat chain nests as
deep as it is long, so a hostile command overflowed the caller's stack
and aborted the server (the classifier's own deep-nesting test died
under a debug build). A command over 64 bytes is now read on a thread
with a 256 MiB stack, reserved and not committed, and one over 64 KiB
is refused unread as unparsed.

npx and uvx run through exec, whose classifier asks before a package is
fetched, and they had no way to pass the approval: since the merge
neither could run at all. They take approve as exec does.

The hostile-text bound is 3 s for an optimized build and 15 s for an
unoptimized one on a loaded host; it catches a quadratic read, which
takes minutes. The unified surface lists the kai tool beside the eight
purpose-built ones.
2026-10-14 17:24:56 -07:00
zeekay 2c4c2f116b feat(mcp): add hanzo-mcp link installer, beta-safe profile, and empirical tool selection benchmark
- Add 'link' command with auto-detection for Claude Code, Cursor, and Codex
- Add '--beta-safe' profile exposing only validated release-candidate tools (11 tools total)
- Harden kai_command_guard with zero-inference deterministic deny and fail-safe offline fallback
- Separate deterministic resolution from Kai model inference (model_verdict: null on zero-inference)
- Enforce strict precondition checks in kai_next_action (abstain on unsatisfied preconditions)
- Add bench/tool_select_comparison.py comparing Deterministic vs BM25 vs Kai vs Hybrid (44 cases)
- Verify live Claude Code tool invocations across kai_command_guard, kai_recover, and kai_tool_select
2026-10-14 17:10:58 -07:00
zeekay 399456de67 feat(mcp): add 8 purpose-built Kai decision tools and 15 cookbook recipes
- Implement 8 native purpose-built Kai tools in src/tools/kai.ts:
  - kai_tool_select: shortlist tools & resolve client casing/aliases (bash -> Bash)
  - kai_next_action: emit executable action with zero-inference fast paths
  - kai_command_guard: monotonic security policy lattice (allow < ask < deny)
  - kai_injection_screen: prompt-injection & override screening
  - kai_log_triage: compiler/test failure categorization & span pinpointing
  - kai_recover: failure loop breaker & recovery strategy selection
  - kai_extract: typed discrete dimension extraction
  - kai_decide: multi-question joint forward pass decisions
- Add 15 runnable cookbook recipes in cookbook/ with helper & index runners
- Add test suites: test/tools/kai-purpose-built.test.ts (18/18) & test/cookbook.test.ts (15/15)
- Support Claude Code CLI transport flags (--allow-path, allowUnknownOption)
- Add comprehensive cookbook documentation and client configuration guides
2026-10-14 17:06:32 -07:00
zeekay f1f8e33b60 kai: the key goes to the configured API alone
The kai tool took kai_url from the call and sent HANZO_API_KEY to it,
with the repository's file names, branches and commit subjects. A
model, or text it read, could name any host. The URL is now
HANZO_API_BASE (or api.hanzo.ai) plus /v1, in the Rust tool and the
TypeScript one alike, and kai_url is gone from the schema.
2026-10-14 17:06:21 -07:00
zeekay cff689a4b3 Merge remote-tracking branch 'origin/shell' into ship
# Conflicts:
#	rust/Cargo.lock
#	rust/Cargo.toml
2026-10-14 17:06:21 -07:00
zeekay ae4d07b971 feat(mcp): add 8 purpose-built Kai decision tools and 15 cookbook recipes
- Implement 8 native purpose-built Kai tools in src/tools/kai.ts:
  - kai_tool_select: shortlist tools & resolve client casing/aliases (bash -> Bash)
  - kai_next_action: emit executable action with zero-inference fast paths
  - kai_command_guard: monotonic security policy lattice (allow < ask < deny)
  - kai_injection_screen: prompt-injection & override screening
  - kai_log_triage: compiler/test failure categorization & span pinpointing
  - kai_recover: failure loop breaker & recovery strategy selection
  - kai_extract: typed discrete dimension extraction
  - kai_decide: multi-question joint forward pass decisions
- Add 15 runnable cookbook recipes in cookbook/ with helper & index runners
- Add test suites: test/tools/kai-purpose-built.test.ts (18/18) & test/cookbook.test.ts (15/15)
- Support Claude Code CLI transport flags (--allow-path, allowUnknownOption)
- Add comprehensive cookbook documentation and client configuration guides
2026-10-14 16:34:58 -07:00
zeekay 25c6f62cff feat(rust): hanzo-shell reads exec's command and says allow, ask or deny
A pure-Rust classifier for the `Classify` seam. brush-parser (MIT) gives the bash AST
and rustpython-parser (MIT) the Python one; every simple command, redirection,
substitution and nested script (bash -c, here-documents, a script the command wrote and
runs, python -c, ssh host '...') is read in the order the shell runs them, and the policy
over paths decides what each costs. rust/shell is its own crate with a wasm-bindgen build,
so kai-gateway can replace safety.ts with it.

exec: an ask is refused until the call carries approve: true (never lifts a deny); the
interpreter must be a shell the classifier reads; the env is read beside the command;
the git tool asks the same classifier and runs with core.fsmonitor=false; ui install no
longer hands the component name to sh -c.

On 408,231 Bash commands: safety.ts flags 20.69% (15.27% unparsed); this answers
allow 88.2 / ask 9.6 / deny 2.2 (roots set), cannot read 0.22%, p99 0.6 ms.
2026-10-14 16:32:13 -07:00
zeekay de99c2e61f docs: Kai Actions is a crate Dev links; the lean surface holds kai
The kai section names hanzo-actions (rust/actions) as the executor, and
the native-tools section says which crate Dev links for what: hanzo-
trigram for search, hanzo-actions for actions, hanzo-mcp for ExecTool.
fs, code, exec and kai together are 6.5 KB, about 1.6K tokens; the
surface test holds them under 16 KB.
2026-10-14 14:55:38 -07:00
zeekay b3bb12d4b5 feat: the kai tool runs a request as a saved, verified action
kai is one action-routed tool (run, plan, list, triggers) over
hanzo-actions. run matches the request to an action or workflow, binds
its inputs, checks the preconditions, runs, and returns the ledger:
outcome done, failed or declined, and a record per step with its source
(rule or kai), preconditions, authorization, cache use, steps, timing and
the verifier's verdict. A declined request is the caller's own agent's.

Authorization is the call's: an action that writes outside the workspace,
reaches a remote or stops a process runs only with approve=true, so the
client's own approval flow decides. An action with no recorded proof
here does not run unless the call says it is proving its workspace.

One schema, schema/kai.json, the same text in rust/schema/ and in
src/tools/kai-action.ts; a test fails if the three differ. The work is
not written twice: the TypeScript tool runs the native binary named by
HANZO_MCP_NATIVE, and `hanzo-mcp kai run|plan|list|triggers` is that
binary's command line (exit 0 done, 2 declined, 3 failed).

The tool's test now proves its workspace and asserts the first refusal
is the approval and that the push reaches the remote only after the
grant; before it passed the proof gate's refusal off as the approval's.
2026-10-14 14:55:16 -07:00
zeekay a8388f381a feat(rust): hanzo-actions, Kai Actions as a light crate
An action is one saved, typed, verified operation: its inputs and what
each may be, the preconditions read again just before it runs, what it
does to the world (which decides whether the user must approve it), what
its result depends on, and the check that its effect happened. A request
is matched to an action or workflow by trigger, its inputs are bound
from the request and from values read out of the repository, and Kai is
asked only when several actions or values remain; it may abstain and it
never writes a command. Every run leaves a ledger record.

The module began as hanzo-actions in Dev (hanzoai/dev branch actions)
and lives here now, as its own workspace crate for the reason hanzo-
trigram is one: Dev links it without taking this crate's ZAP router,
browser and parser dependencies. hanzo_mcp::actions re-exports it.
Paths inside it are crate:: paths; nothing else changed in the move.

34 tests: binding, authorization, the proof gate, the cache, redaction,
and the git-backed actions run against real repositories.
2026-10-14 14:54:47 -07:00
zeekay ce3c6bfd4a fix(ts): import kaiAgentTools, and type the decision a compaction reads
unified/index.ts spread kaiAgentTools without importing it, so loading
the module threw a ReferenceError and the TypeScript server could not
start. tsc reports it (TS2304) and fails on d2dc818, which hanzo.yml's
typecheck step would too. compact.ts read the decision's JSON into a
value tsc types unknown (TS18046); the answer is the wire's, as kai.ts's
decide() already returns it.

tsc --noEmit is clean and scripts/smoke-mcp.mjs passes 9 of 9 against the
bundle.
2026-10-14 14:54:31 -07:00
zeekay fe2e8c2a02 feat(rust): hanzo-trigram reads tgrep's index, and a limit bounds the work
A search with a limit now runs in two passes: every file is weighed
(matching lines counted, definitions as 2^32) without holding a hit,
then hits are read from the heaviest files until the limit is met. The
answer is the head of the unlimited list, checked for limits from 0 to
the whole list over text, symbol, context, ties and documents. Before,
every hit of every file was built, sorted and deduplicated before the
limit applied: a pattern matching 1.28 million lines of a 112 MB tree
took 15.6 s where it now takes 0.14 s, and rg's --max-count had bounded
that case before this crate replaced it.

When there is a tgrep index at or above the searched directory the
search asks it which files can match. Candidates are matched against
current bytes, and files whose content or name changed at or after the
build are read from disk whether or not the index lists them, so the
answer is the scan's. Index::Skip scans, Index::Only refuses to; the
tests ask every query all three ways on the same tree, before and after
edits, creations, deletions and renames, and fail if the freshness walk
is disabled. hanzo-mcp index [DIR] builds one.

Measured on 112 MB / 9,026 files, cores 0-4: rg 43-55 ms, scan 75-93,
indexed 28-50. Without a watcher the index costs a metadata walk.
2026-10-14 14:41:07 -07:00
zeekay aad64666db fix(rust): a file's definitions lead its hits, so a limit cannot cut them off 2026-10-14 13:46:41 -07:00
zeekay b232128e54 docs(rust): native search, the exec verdict, and the lean tool surface 2026-10-14 13:41:39 -07:00
zeekay 630ec216d3 feat(rust): tools/list in one order, and --tools to serve fewer
tools/list appended the registered tools in HashMap order, which is
different in every process: two starts of the same binary listed
different orders (checked: three runs, three orders), so a client's
prompt cache over the tool schemas could not hold between sessions. The
registered tools now follow the built-ins sorted by name.

--tools fs,code,exec lists and serves only those. A client is charged
for every schema it is shown, each turn: the whole list is 34 tools and
38 KB, about 9.5K tokens; fs, code and exec together are 4.6 KB, about
1.1K, and byte for byte the same on every start. A name the server does
not carry fails at startup and lists what it does; a call outside the
list answers Unknown tool, like any other name.

ToolsConfig's flags were never read and are left alone.
2026-10-14 13:40:31 -07:00
zeekay d717c2da73 feat(rust): exec asks a classifier before it runs a command
Classify is the contract between exec and the shell parser and
classifier: command and cwd in, a Verdict (allow, ask or deny, with
reasons) out. ExecTool::with_classifier installs one; ToolRegistry::
with_classifier does the same for the MCP server; ExecTool::verdict is
the call Dev makes to ask the same classifier without running anything.

A deny is refused before a process is spawned or registered. An ask runs:
a server cannot ask the person, and the client's own permission prompt
already stood between the model and the call. Every answer then carries
the verdict. refusal() is the one place that policy lives.

With none installed, which is every build until the classifier module
lands, exec answers byte for byte as before and its input schema is
unchanged: the verdict is an output field, so the TypeScript runtime's
schema stays identical.
2026-10-14 13:37:51 -07:00
zeekay e9e6096ce4 feat(rust): fs search_text and code search_symbol on tgrep, in process
hanzo-trigram is the one search engine behind both actions: tgrep-core
(the hanzoai fork, MIT, pinned by commit, feature docs) linked as a
library, so a search is a call, not an rg spawn. It is its own crate so
Dev can link it without this crate's browser and parser dependencies;
hanzo_mcp::trigram re-exports it.

Results are ranked (matching lines a file, or definitions before uses
for a symbol), then path, then line, so a page is an offset into one
fixed list and the cursor really pages: before, rg stopped at limit and
a cursor repeated the first page. .gitignore, hidden and binary files
are skipped as rg skips them. A glob that names a document type (*.pdf,
*.docx, *.zip) reads documents too, and a hit inside one carries the
archive members and its page, slide, paragraph or cell.

fs and code keep their names, schemas and answer shapes; entry and place
appear on a document hit only. search_symbol now reads definitions by
the engine's declaration patterns, not the first-keyword guess.

Linking tgrep-core brings libsqlite3-sys (its managed-index code), which
needs a newer cc than the tree-sitter grammars allow, so Cargo resolves
tree-sitter-{c,cpp,javascript,python,rust,typescript} to their previous
patch releases. 241 library tests and the code tool's pass on them.
Gating managed/ behind a feature in the fork removes both.
2026-10-14 13:37:01 -07:00
zeekay d2dc8189bf feat(mcp): add kai_agent_step and kai_compact tools for decision-native agent workflows
Hanzo CI/CD / test (linux-amd64) (push) Skipped
Hanzo CI/CD / test (linux-arm64) (push) Skipped
Hanzo CI/CD / test-advisory (push) Skipped
publish / publish (push) Successful in 1m27s
Hanzo CI/CD / test-amd64 (push) Failing after 8m23s
Hanzo CI/CD / cicd (push) Skipped
CI/CD / cicd (push) Failing after 8m25s
2026-10-11 10:00:40 -07:00
zeekay deaf801810 chore(release): hanzo-mcp 1.1.29
Hanzo CI/CD / test (linux-amd64) (push) Skipped
Hanzo CI/CD / test (linux-arm64) (push) Skipped
Hanzo CI/CD / test-advisory (push) Skipped
publish / publish (push) Successful in 1m10s
Hanzo CI/CD / test-amd64 (push) Successful in 2m43s
Hanzo CI/CD / cicd (push) Successful in 6m22s
CI/CD / cicd (push) Successful in 9m12s
2026-10-04 16:28:32 -07:00
zeekay e3067ebc8e feat(rust): llm limits, catalog search, served and paid-by, plan refusals 2026-10-04 16:28:31 -07:00
zeekay 99809579e6 chore(release): @hanzo/mcp 2.4.16
Hanzo CI/CD / test (linux-amd64) (push) Skipped
Hanzo CI/CD / test (linux-arm64) (push) Skipped
Hanzo CI/CD / test-advisory (push) Skipped
publish / publish (push) Failing after 1m16s
Hanzo CI/CD / test-amd64 (push) Successful in 2m6s
Hanzo CI/CD / cicd (push) Successful in 1m37s
CI/CD / cicd (push) Successful in 3m48s
2026-10-04 16:15:00 -07:00
zeekay bd67f73a4c feat(llm): limits, catalog search, served and paid-by, plan refusals 2026-10-04 16:15:00 -07:00
zeekay b406b542b5 Take zapd 1.1.5 from crates.io
Hanzo CI/CD / test (linux-amd64) (push) Skipped
Hanzo CI/CD / test (linux-arm64) (push) Skipped
Hanzo CI/CD / test-advisory (push) Skipped
publish / publish (push) Successful in 37s
Hanzo CI/CD / cicd (push) Successful in 1m24s
CI/CD / cicd (push) Successful in 3m26s
Hanzo CI/CD / test-amd64 (push) Successful in 1m59s
It is published there now, so the git pin goes.
2026-09-30 21:36:34 -07:00
zeekay ea08388793 Open and fill the release with the release action
Hanzo CI/CD / test (linux-amd64) (push) Skipped
Hanzo CI/CD / test (linux-arm64) (push) Skipped
Hanzo CI/CD / test-advisory (push) Skipped
publish / publish (push) Successful in 41s
Hanzo CI/CD / test-amd64 (push) Successful in 1m46s
Hanzo CI/CD / cicd (push) Successful in 1m23s
CI/CD / cicd (push) Successful in 3m12s
The runner image carries no gh CLI, so the open job died on its first
command and nothing was built. The release action talks to the API
itself, and the toolchain action installs Rust and the target rather
than trusting the image to have them. hanzo-mcp 1.1.26.
2026-09-30 21:23:47 -07:00
zeekay ad65572eec Release linux-arm64 by cross-building on the amd64 runners
Hanzo CI/CD / test (linux-amd64) (push) Skipped
Hanzo CI/CD / test (linux-arm64) (push) Skipped
Hanzo CI/CD / test-advisory (push) Skipped
publish / publish (push) Successful in 37s
Hanzo CI/CD / test-amd64 (push) Successful in 1m42s
Hanzo CI/CD / cicd (push) Successful in 1m41s
CI/CD / cicd (push) Successful in 3m24s
The org's runners serve linux-amd64 only, so the arm64 and darwin jobs
waited on labels nothing serves. arm64 now cross-builds on amd64 with
cargo-zigbuild, zig being the C toolchain ring needs; darwin is off the
matrix until a macOS runner exists.
2026-09-30 21:21:59 -07:00
zeekay 0e11347985 Add npx, uvx and jq to the TypeScript surface
Hanzo CI/CD / test (linux-amd64) (push) Skipped
Hanzo CI/CD / test (linux-arm64) (push) Skipped
Hanzo CI/CD / test-advisory (push) Skipped
publish / publish (push) Failing after 51s
Hanzo CI/CD / test-amd64 (push) Successful in 2m25s
Hanzo CI/CD / cicd (push) Successful in 3m55s
CI/CD / cicd (push) Successful in 6m24s
exec gains run(): an argv with no shell, waited on up to a deadline, then
left running in exec's process table. npx and uvx use it with Python's argv
and a two-minute deadline; jq passes its filter as one argv word.

@hanzo/mcp 2.4.14
2026-09-30 15:17:34 -07:00
zeekay 1ef1ce34a5 Add npx, uvx and jq to the Rust runtime
npx and uvx build Python's argv (package, whitespace-split args, npx -y,
uvx --python) and run it as an exec with a two-minute auto-background. jq
passes its filter as one argv word, as Python's jq tool does.
2026-09-30 15:17:29 -07:00
zeekay ed2a23156d Let presence arrive before WELCOME in the native host test
A node registering between ours entering the registry and our WELCOME
being queued is announced to us first; the extension ignores it, and so
does the test now.
2026-09-30 15:17:25 -07:00
zeekay 0bb22d833e Keep a backgrounded Rust exec's output and exit
A command past its timeout had its pipes dropped with the wait, so it lost
its output (or died of SIGPIPE) and stayed running forever in ps. Its
output now drains to a log that logs reads, and its exit is recorded; a
command that finishes in time returns inline and leaves no log.
2026-09-30 15:17:17 -07:00
zeekay 46b081c99e Give the TypeScript fs tool Python's contract
Hanzo CI/CD / test (linux-amd64) (push) Skipped
Hanzo CI/CD / test (linux-arm64) (push) Skipped
Hanzo CI/CD / test-advisory (push) Skipped
Hanzo CI/CD / cicd (push) Successful in 1m52s
CI/CD / cicd (push) Successful in 3m40s
Hanzo CI/CD / test-amd64 (push) Successful in 1m45s
publish / publish (push) Failing after 2m50s
read returns numbered lines and the full sha256 hash, write only creates,
apply_patch takes old_text and new_text under base_hash, list walks with
depth, pattern and cursor, patch takes the Rust grammar, and search_text
takes a pattern and glob. rg now runs without a shell, so a pattern is never
a shell word.

@hanzo/mcp 2.4.13
2026-09-30 15:06:58 -07:00
zeekay 03b7fd3edf Give the Rust fs tool Python's contract
Hanzo CI/CD / test (linux-amd64) (push) Skipped
Hanzo CI/CD / test (linux-arm64) (push) Skipped
Hanzo CI/CD / test-advisory (push) Skipped
Hanzo CI/CD / test-amd64 (push) Successful in 1m46s
publish / publish (push) Failing after 1m10s
Hanzo CI/CD / cicd (push) Successful in 1m53s
CI/CD / cicd (push) Successful in 3m43s
uri (path accepted), the unified envelope, sha256 hashes, write that only
creates, apply_patch guarded by the hash a read returned, list with depth,
pattern and cursor, search_text, and patch taking input. The search alias
tool goes: fs action=search_text is the one search.
2026-09-30 15:03:49 -07:00
zeekay 6dd2293590 Serve the extension's native messaging host from the Rust binary
Hanzo CI/CD / test (linux-amd64) (push) Skipped
Hanzo CI/CD / test (linux-arm64) (push) Skipped
Hanzo CI/CD / test-advisory (push) Skipped
publish / publish (push) Failing after 1m15s
Hanzo CI/CD / test-amd64 (push) Successful in 1m56s
Hanzo CI/CD / cicd (push) Successful in 2m33s
CI/CD / cicd (push) Successful in 4m33s
Run as hanzo-zap-host, hanzo-mcp relays ai.hanzo.zap messages to the ZAP
router's socket, as Python's native_host.py does, so a machine with only the
Rust server still lets Chrome and Firefox join without a door or pairing. Each
start registers it with the installed browsers, leaving any manifest whose
program exists alone.
2026-09-30 14:59:17 -07:00
zeekay b0d75abea9 Record parity with Python hanzo-mcp and the ZAP browser path in LLM.md
Hanzo CI/CD / test (linux-amd64) (push) Skipped
Hanzo CI/CD / test (linux-arm64) (push) Skipped
Hanzo CI/CD / test-advisory (push) Skipped
Hanzo CI/CD / test-amd64 (push) Successful in 2m16s
publish / publish (push) Failing after 1m12s
Hanzo CI/CD / cicd (push) Successful in 1m48s
CI/CD / cicd (push) Successful in 4m11s
2026-09-30 14:54:55 -07:00
zeekay c58488a2a3 Add browser and cdp to the TypeScript surface, over the ZAP router
Hanzo CI/CD / test (linux-amd64) (push) Skipped
Hanzo CI/CD / test (linux-arm64) (push) Skipped
Hanzo CI/CD / test-advisory (push) Skipped
publish / publish (push) Failing after 58s
Hanzo CI/CD / test-amd64 (push) Successful in 2m5s
Hanzo CI/CD / cicd (push) Successful in 1m53s
CI/CD / cicd (push) Successful in 4m32s
src/zap.ts is a consumer seat on the unix socket of whichever process the
lock elected: zapd has no JS or wasm build, so this runtime joins as
mcp/hanzo-<pid> and never stands for router. browser and cdp carry Python's
action names, parameters and extension methods; headless Playwright covers
the core, navigation and tabs when no browser is registered. The test runs
PyPI zapd as the router in another process, with a fake extension.

@hanzo/mcp 2.4.12
2026-09-30 14:52:19 -07:00
zeekay d9f8034726 Route the Rust browser and cdp tools to the extension over the embedded ZAP router
hanzo-mcp links zapd v1.1.5, stands for this login's router at startup and
takes the seat mcp/hanzo-<pid>, as Python hanzo-mcp does. browser takes
Python's action names and parameters (the core typed, the rest in args) and
sends each action with a wire method to the registered browser node; headless
Playwright runs only when none is registered. cdp sends raw methods to the
same node. hanzo-mcp pair prints the door's pairing code. The integration test
embeds the router on a private XDG_RUNTIME_DIR with a fake extension.
2026-09-30 12:52:05 -07:00
zeekay 8a90c75bfd CI, publish and release also run from .hanzo/workflows, on the forge, where Actions is now on for this mirror.
Hanzo CI/CD / test (linux-amd64) (push) Skipped
Hanzo CI/CD / test (linux-arm64) (push) Skipped
Hanzo CI/CD / test-advisory (push) Skipped
Hanzo CI/CD / cicd (push) Successful in 1m29s
publish / publish (push) Successful in 1m0s
Hanzo CI/CD / test-amd64 (push) Successful in 1m48s
CI/CD / cicd (push) Successful in 3m21s
2026-09-28 18:41:09 -07:00
zeekay e63e2b3e8c examples/kai.ts starts the server over stdio as a client does and asks kai_choice once. 2026-09-28 15:35:10 -07:00
zeekay 8d13859eb0 The kai tools steer a yes/no to a choice whose labels name the outcomes, which Kai answers better than yes and no. 2026-09-28 15:13:12 -07:00
zeekay 0c324b7c18 Publish, CI and release run from .github/workflows, the only directory the hanzoai org runners read. 2026-09-28 14:46:56 -07:00
zeekay e1f6a85d42 The kai tools cap no label or level count, since /v1/decisions caps none. 2026-09-28 14:46:36 -07:00
zeekay e33c38f0a2 The kai tools follow the frozen decision contract: instructions optional, 1 to 100 questions, 2 to 255 labels, 1 to 10 levels, and a noul's confidence. 2026-09-28 14:41:35 -07:00
zeekay 30fafe2616 kai_decide, kai_choice, kai_score and kai_noul ask Kai typed questions on /v1/decisions, in @hanzo/mcp 2.4.11 2026-09-28 14:31:22 -07:00
zeekayandHanzo Dev 201cdd0adc LLM.md is the one committed guide; CLAUDE.md, AGENTS.md and GEMINI.md are local links to it
Co-authored-by: Hanzo Dev <[email protected]>
2026-09-24 15:46:17 -07:00
zeekayandHanzo Dev 5d15d811d1 Publish and CI live in .hanzo/workflows, where the forge looks
publish / publish (push) Successful in 59s
Hanzo CI/CD / test (linux-amd64) (push) Successful in 2m13s
Hanzo CI/CD / test (linux-arm64) (push) Successful in 5m15s
Hanzo CI/CD / cicd (push) Successful in 2m5s
CI/CD / cicd (push) Successful in 7m49s
The forge runs the first of .hanzo/workflows and .github/workflows that
exists. release.yml created .hanzo/workflows on 09-10, and from then on
publish.yml and cicd.yml never ran: @hanzo/mcp 2.4.10 sat on main while
npm served 2.4.9. All three lanes are in one directory now.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-23 18:55:38 -07:00
zeekayandHanzo Dev e7921ba12f hanzo reads what an action takes, and sends args as given
describe was filtered out of the hanzo tool's resources, and the default
surface has no other tool that reaches it, so a model there could list a
resource's actions but not learn what one takes. It is a resource again:
hanzo(resource="describe", args={subsystem, op}), and a resource's action
list points at it.

Arguments ride in args, one object. A string was spread into
{"0": "{", ...}, and a key beside args, input among them, was merged in as an
argument; both reached the fleet as some other call. Both are now refused as
INVALID_ARGS before anything is sent, and the data alias is gone.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-23 11:09:52 -07:00
zeekayandHanzo Dev a0c3a2d05b grep's test reads the description the tool now carries
70a0a04 put tgrep first in the grep tool's description and left the test
holding the old words, so the suite failed on main.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-23 05:37:21 -07:00
zeekayandHanzo Dev a03f7e6820 The graph is on the surface, and hanzo reaches it
The catalog is regenerated from cloud (plugin/gen-mcp-catalog): the graph tool
now carries all thirteen operations, answer, communities, diff, erase, extract,
ingest and path among them, and the Rust crate's copy is synced from it.

The hanzo tool sent {resource, action, ...} to a subsystem handler that reads
{op, input}, so every call from the default surface reached the fleet with no
op. It now sends {op: action, input: args}, the shape the Rust runtime sends,
lists a resource's actions when none is named, and offers only subsystems.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-23 05:37:21 -07:00
zeekayandHanzo Dev 6355e41558 @hanzo/mcp 2.4.10
Co-authored-by: Hanzo Dev <[email protected]>
2026-09-11 13:44:31 -07:00
zeekayandHanzo Dev 7dac625d9a --core-only, list-tools and install --claude-code do what they say
--core-only set enableCore, which the unified surface ignores; it now offers the core group, and serve and list-tools read their flags through one function. list-tools printed four headers for tools that no longer exist and none of the 22; it now lists each tool under its group. install --claude-code wrote ~/.config/claude-code/mcp.json, which Claude Code does not read; it now runs claude mcp add --scope user. The smoke checks all three against dist/cli.js.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-11 13:44:31 -07:00
zeekayandHanzo Dev b5ca2ba8f7 Typecheck again: search asks for rg through onPath
bad040d replaced hasRipgrep with searcher and left one caller, so tsc --noEmit failed before publish could build.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-11 13:44:30 -07:00
zeekayandHanzo Dev 87d4150032 Install from the lockfile again
package.json pins typescript 7.0.2 and the lockfile still said ^7.0.2, so pnpm install --frozen-lockfile refused and publish stopped there.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-11 13:44:30 -07:00
zeekayandHanzo Dev bd0511890f The README registers the server the way that works, and lists its tools
The README said `npx -y @hanzo/mcp serve` runs an unrelated `serve`
package. With npm 11 it starts this server, and it is the form
`claude mcp add` writes; both forms answered initialize and tools/list
the same way from an empty npm cache, and claude mcp list reports the
added server connected.

The tool section now lists what serve offers, in the groups
src/tools/unified/index.ts defines, and gives the pipe that prints the
names. The "13 tools" and "260+" counts, a --legacy flag the CLI does
not have, the installer steps and the CI claims are gone: list-tools
prints category headers with no tools under them, install --claude-code
writes a file Claude Code does not read, and no workflow runs
smoke-mcp.mjs.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-11 11:44:09 -07:00
zeekayandHanzo Dev 313702d178 The release lane belongs on the native CI, not on GitHub
GitHub Actions is switched off for this repository on purpose — the API
reports enabled=false — so the lane I put under .github/ registered, showed as
active, and could never run. A pipeline that looks alive and does nothing is
worse than one that is plainly absent.

.hanzo/workflows is the native path: git.hanzo.ai runs it on our own fleet and
cd.hanzo.ai delivers. The runner labels follow the canonical <org>-build-<os>-<arch>
form, including hanzo-build-darwin-arm64, which is the target that was never
published and the reason for this lane.

The matrix key is `runner` rather than `on`. YAML 1.1 reads a bare `on` as the
boolean true, so as a key it comes back as True in anything that parses this
file — including the checks that read it.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-10 20:18:21 -07:00
zeekayandHanzo Dev b31f1113af Open the release before four jobs try to
gh release upload attaches to a release that exists. With the matrix creating
it, four jobs race and three lose — so one job opens it and the builds wait.

Idempotent on purpose: a re-run of a tag that already has a release attaches to
it rather than failing, which is what makes a retry after one target fails a
safe thing to do.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-10 20:13:33 -07:00
zeekayandHanzo Dev 318d998101 Build hanzo-mcp for every target the installer offers
hanzo.sh offers `mcp` as a native download and names five published targets.
Nothing in this repository built any of them: hanzo.yml declares a TypeScript
gate, publish.yml ships the npm package, and the tarballs on the last release
covered Linux alone. On macOS the installer asked for a file nobody had built.

So this lane builds the Rust crate — the one whose Cargo.toml calls itself
hanzo-mcp — on the rust-v* tags it is already released under, and attaches each
tarball with the checksum the installer refuses to install without.

Both macOS targets build on the arm64 machine, because the Apple toolchain
cross-compiles to x86_64 without a second host.

fail-fast is off. One target failing leaves the others published, which is a
missing file somebody can chase; fail-fast leaves an empty release, which is an
install that breaks for everyone.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-10 20:10:47 -07:00
zeekayandHanzo Dev 70a0a046bf 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:25:10 -07:00
zeekayandHanzo Dev 2171304110 go 1.26.8
One toolchain across the estate. The tree carried 1.26.0, .1, .3, .4 and .5
at once, so which compiler built a binary depended on which repo it came
from.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 23:53:37 -07:00
zeekayandHanzo Dev e1af6af4f4 Move the workflows to .github/workflows
The forge reads .github/workflows natively and github.com reads nothing else,
so one directory serves both planes instead of two conventions that disagree
about which one is live. The reusable in hanzoai/ci already lives there, so a
caller naming that path now resolves to a file that exists.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 19:00:20 -07:00
zeekayandHanzo Dev f24a1aafad ci: a runner label names the platform, not the org
hanzo-build-linux-amd64, lux-build-linux-amd64, zoo-build-linux-amd64 and
every other org-prefixed spelling all meant the same machine. Which org a
job belongs to does not change which host can build it, so the org bought
one pool name per org per arch and each could go stale on its own -- the
evo host alone answered to fourteen names for one label.

One vocabulary now: linux-amd64, linux-arm64, macos-arm64, win-amd64. The
generic labels every plain workflow uses (ubuntu-latest, self-hosted) are
untouched, so naive CI keeps working.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 12:09:48 -07:00
zeekayandHanzo Dev fea24fa037 fix: call the pipeline at the address it actually lives
hanzoai/ci keeps the reusable at .github/workflows/build.yml — the one path
both planes resolve, since github.com reads a reusable only from there and the
forge reads it too. The v1 tag moved with it, so a caller still naming
.hanzo/workflows/build.yml asks for a blob that is not in the tree:

  InsertRun: read hanzoai/ci@v1:.hanzo/workflows/build.yml: object does not exist

The caller itself stays at .hanzo/workflows/cicd.yml on purpose: github.com has
no runner for these labels, so a caller placed there could never be scheduled.

Co-authored-by: Hanzo Dev <[email protected]>
2026-09-08 01:14:29 -07:00
zeekay 3b694c1f9e fix(rust): include cloud catalog in published MCP crate 2026-09-06 02:56:12 -07:00
zeekay 1d741575c8 Follow the cloud surface through the git rename 2026-09-03 12:10:09 -07:00
zeekay 5bde2a515d Pin react, next and typescript to the estate versions
The ranges already admitted them; naming the version is what stops one
repo drifting to a different resolution than its neighbours.
2026-09-03 04:26:25 -07:00
zeekay 1bcb6b0b1a the face is Zen, and it ships from @hanzo/font
One family. Font stacks name Zen and fall back to the native faces, tokens
name the role rather than a face, and the packages that used to supply a
face now point at @hanzo/font.

Self-hosted @font-face blocks are removed rather than renamed: rewriting the
family in one would leave a src pointing at a file this repo no longer ships.
@hanzo/font carries the binaries and declares the faces, so the local copies
go with the declaration that used them.

Comments naming a face we no longer use are deleted, not reworded.
2026-09-01 13:30:05 -07:00
zeekay 48a91758a9 Merge the hand-written cloud surface's last changes
The two lines had diverged on what this client offers: a hand-written tool
naming seven resources against four hosts, or the surface cloud generates
from its own typed operations. The generated one wins, and not by
preference — cloud's gen-mcp-catalog writes src/tools/catalog.json into
this repo, so the fleet already treats it as the destination.

hanzo-cloud.ts stays deleted and the Rust tool keeps reading the catalog.
The iam commits improved a `call` helper that dialled a host per service;
the catalog-driven path takes every address from the document and answers
through the error envelope, so what those commits were correcting is gone
rather than reverted. The hero card's font stack came across untouched.

384 JS tests, 51 Rust tests, typecheck clean.
2026-09-01 09:48:02 -07:00
zeekay c724ae360a Let the tests run again
Every suite had stopped executing. ts-jest peers on `typescript >=4.3 <7`
and this repo builds against 7, so all twenty files died inside ConfigSet
with "Cannot read properties of undefined (reading 'fileExists')" — twenty
failures, zero tests, and nothing to read them by because the push gate
deliberately runs typecheck and build rather than jest.

swc transforms instead: it strips types rather than asking the compiler
to, so it has no TypeScript version to disagree with, and `tsc --noEmit`
already answers the question ts-jest was answering twice.

Running as real ESM then exposed what the CommonJS fallback had hidden.
jest.mock does not hoist there, so a static import binds the real module
and the suite fails on `mockReturnValue is not a function` — an absent
mock reported as a broken one. The search, autogui and ui suites now
register through unstable_mockModule and import after it. One of them had
never mocked fs at all: it cast fs.promises to a mocked type and called
mockImplementation on the real module.

384 tests, twenty suites, all passing.
2026-09-01 09:42:54 -07:00
zeekay 0e21522f6a Carry the ci subsystem and the team messages ops
Regenerated by cloud's gen-mcp-catalog, which writes this file and its
siblings from the fleet's own typed operations. 115 subsystems, 1425
operations.
2026-09-01 02:26:09 -07:00
zeekay 914af987d1 Let cloud write the catalog, and drop the second generator 2026-08-31 23:42:26 -07:00
zeekay 78e75a6488 Write every runtime's catalog in one pass
Three runtimes carry the file and each was refreshed on its own, which is how
they come to disagree. One run writes them all; a sibling repo that is not
checked out is skipped rather than created.
2026-08-30 17:05:43 -07:00
zeekay 94d8398f1a Drop the last /api/ address, and make the mode tests take turns
The registry fallback asked ui.hanzo.ai/api/registry, which 301s to a 404 — so a
dead address stood in for a second chance nobody had. The primary serves; the
fallback is gone rather than moved, because /v1/registry is not served either and
pointing a client at an address the estate does not answer is the same defect
with a better prefix.

The mode tests raced a process-wide singleton: one activates minimal while
another activates guido and reads it back, and cargo runs them in threads of one
process. Three runs gave ok, ok, FAILED. They share a lock now.
2026-08-30 16:15:03 -07:00
zeekay 0a5f63f352 Read the same catalog from Rust
The runtimes are said to mirror one another, and this one listed ten services
against the fleet's 114. Two of the ten had been left behind, and the commerce
base was api.hanzo.ai/api/v1 — an /api/ prefix nothing serves.

Nine routers over four hosts become one dispatch to the single endpoint, and the
service list becomes a read of the file the TypeScript runtime already embeds.

The aliases are removed rather than repointed: three of the four named the old
surface, so they mapped a caller off the API. Spelling is still normalised.
2026-08-30 16:05:35 -07:00
zeekay bb0ba82020 Generate the cloud surface instead of writing it
The seven resources this offered had drifted from the fleet it names: paas is
platform, storage is s3, auth folded into iam, and iam alone carries 97
operations against the handful the hand-written tool reached. It also dialled
four hosts for one API.

The names come from cloud's typed operations now, so this client cannot come to
disagree with the API about what exists. The withholding rule is asked of cloud
rather than restated here — a client deriving its set from the raw catalog offers
what the fleet refuses.

The default surface is unchanged at 22 tools; the fleet sits behind hanzo, whose
resource enum is derived rather than listed.
2026-08-30 15:57:28 -07:00
zeekay 472bafcf7f the job asks for its runner the way the fleet answers
runs-on was a bare scalar. Jobs written that way queue and are never
picked up — link and quest have waited since 10 August, painter since the
5th, none of them with a single success — while repos writing the same
label as a sequence drain within seconds of a push on the same fleet.

Same label, same runners, different shape. It fails silently: nothing errors,
the job simply never starts, so the lane looks configured and does nothing.
2026-08-29 13:31:32 -07:00
zeekay d76fbebc7c Name Zen in the hero card's font stack
The social card asked for Inter first. That is not a face this estate
licenses, ships or ever drew, so a renderer with Inter installed painted
the wrong face and one without it silently fell through to system-ui.
Zen leads now; the same tail follows it.
2026-08-28 11:37:28 -07:00
zeekayandHanzo Dev 63198ee88b iam: pass the scope through, and let the server's refusal speak
Which list routes insist on an owner is the server's rule, and it moves. The
deployed binary requires one on users, sessions and applications and not on
roles, permissions, providers, invitations, tokens or audit-logs; the change
that resolves a listing's tenant centrally is written and unreleased.

So nothing here keeps a copy of that rule. `apps` no longer refuses a missing
owner before asking, and no description promises that leaving it off makes the
credential decide — today that answers 400, carrying the reason, which the
transport already surfaces.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-24 03:44:00 -07:00
zeekayandHanzo Dev 08159273e4 iam: address identity by path under /v1/iam
Users, organizations, roles, applications, providers, permissions,
invitations, tokens, sessions and audit rows are addressed as
/v1/iam/{plural} for a list and /v1/iam/{plural}/{owner}/{name} for a row,
in both the Rust `hanzo` router and the TypeScript iam/auth tools. `id`
splits into its owner and name path segments and a malformed one is refused
rather than sent. Deleting a user is a DELETE on the row; updating one reads
the row and PUTs {"user": merged}.

`owner` is scope: it goes on the wire only when the caller names one, and the
server resolves the rest from the credential. /v1/iam/applications is the one
list that requires it, and asks.

A non-2xx answer is now an error carrying its status and reason — RFC 9457
`detail`, else the envelope `msg`, else `title` — so KMS, PaaS and IAM
refusals surface instead of reading as data. Tests cover the row address, the
id split, and a problem+json refusal against a local listener.

Identity questions belong to the iam service alone, so paas no longer
duplicates users/orgs/roles. Actions with no address behind them (enforce,
system_info, health, mfa status) are gone.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-24 03:26:38 -07:00
zeekay 7d3b47ffba carry the github copy's 1 commits onto the forge
This repo is written on git.hanzo.ai and mirrored to github.com, and the two had drifted apart — 1 commits here the mirror lacked, 1 there this host lacked. Merging rather than overwriting so neither side's work is lost.
2026-08-20 20:59:08 -07:00
zeekay 68747aea94 track the name agents read, so a clone documents itself
LLM.md is the source and stays the source; CLAUDE.md is a symlink to it, so there is
one file and it cannot drift from itself. What changes is that the link is committed
rather than left for each checkout to recreate.

Left untracked, the doc was conditional on a step outside the repo: a fresh clone had
no CLAUDE.md, nothing loaded LLM.md, and an agent worked here having never read what
this repo already explains. Most of the estate already tracks theirs; this is the rest
agreeing with them.
2026-08-20 16:27:45 -07:00
zeekayandHanzo Dev 2e72b6f1f8 fetch: search asks our own API, and says so when it cannot
`fetch:search` scraped html.duckduckgo.com and pulled results out of a regex
over `class="result__title"`. Measured 2026-08-20: that page answers HTTP 202 —
DuckDuckGo's anti-bot challenge — and the markup carries no `result__title` at
all. So the regex matched nothing and the tool returned `{results: [], count: 0}`
for EVERY query: the identical answer it gives when the web genuinely has
nothing. Blind rendered as empty, with no way for a caller to tell them apart,
which is exactly why nobody noticed.

It now asks POST /v1/websearch — the meta-search we ship — reading the base URL
and credential from the same env the tracker tool reads, so the MCP has one way
to reach cloud rather than a second one here.

The failure shape is the point:

  no credential  -> NO_CREDENTIAL, never an empty result list
  cloud refuses  -> its own words verbatim ("sign in to search the web"),
                    because a 401 and a 500 are different problems and
                    flattening both into "search failed" makes an outage
                    look like a bad query
  results        -> carries WHICH ENGINES answered, so a zero-result search
                    with engines listed is a real empty and one with none is
                    a search that did not happen

HONEST GAP: the committed test cannot run. The repo declares typescript ^7.0.2
against ts-jest ^29.4.6, which does not support TS7, so `npx jest` dies in
_resolveTsConfig on "Cannot read properties of undefined (reading 'fileExists')"
— for the pre-existing suites too (edit.test.ts: 0 tests). `tsc --noEmit` is
clean repo-wide; only the transform is dead. The eight assertions were run
against the esbuild-bundled tool instead and all pass; they execute under jest
the moment the toolchain is fixed.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-20 13:48:00 -07:00
zeekay b1281cd582 initialize is a negotiation, so answer the revision the client asked for
The server replied "2024-11-05" to every initialize, whatever the client asked
for. `hanzo dev` asks for 2025-06-18 and refuses a server that answers anything
else, so it reported `MCP server hanzo failed to start` — in an image where the
binary was on PATH, `dev mcp list` showed it registered, and a hand-written
handshake got 29 tools back. Everything that looked at the server passed;
nothing that PAIRED it with an agent worked.

Negotiation is the whole fix: echo the requested revision when it is one we
serve, answer our newest when it is not and let the client decide. The three
listed revisions are the ones this server can actually serve — initialize,
tools/list and tools/call are unchanged across them, and what the later ones add
is optional and unadvertised. A client pinned to the original revision keeps
getting it, which is the point of negotiating rather than moving the constant.
2026-08-07 08:03:05 -07:00
zeekayandHanzo Dev 42c6fbbeaa ci: canonical pair — hanzo.yml + forge caller
github.com has no runner for hanzo-build-linux-amd64, so a caller under
.github/workflows is a gate that can never be scheduled. This is the ~7-line
caller on the plane that can (git.hanzo.ai git-runner fleet), pinned @v1.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-06 02:37:03 -07:00
zeekayandHanzo Dev c36a1eb1c8 rust/tests: repair the two suites that stopped compiling at HIP-0300
test_shell_tools and test_think_tools have not built since the shell->exec
consolidation, so `cargo test` was red and 46 tests were silently not running.
Nothing here is a new assertion — each change follows the tool's live contract,
read off the published 1.1.22 binary rather than guessed:

  shell   ShellTool -> ExecTool, ProcToolArgs -> ExecToolArgs. Pure rename;
          all 16 pass untouched.
  think   ThinkTool::execute returns Value, not String, and HIP-0300 moved the
          payload under "data". Help reports the tool as "tool", not "name".

Tests whose subject no longer exists are deleted, not rewritten to pass:
critic/review with focus, code_snippets, file_paths and work_description (11);
the timestamp and examples keys (2); action-less auto-detect, which now falls
through to help (1).

browser: playwright_available -> driver_running, its successor since the tool
moved to a CDP driver. That file compiled, so this one was failing, not skipped.

351 pass, 0 fail: lib 223, browser 28, fs 27, search 28, memory 22, shell 16,
think 5, research 2. tests/ is excluded from the package, so 1.1.22's contents
are unchanged and no version bump is warranted.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-05 14:23:33 -07:00