examples/claude-code holds a Stop hook, a PreToolUse hook and the kai skill. .claude-plugin/marketplace.json makes this repo the marketplace "kai" (not "hanzo", a name another Hanzo marketplace already holds), so /plugin install kai@kai installs it. Kai does not yet read agent work reliably: its zero-shot answers to "done", "verified" and "risky" are near chance. So rules in code decide first, Kai can only tighten (send a turn back, or ask before an action; never allow), both hooks fail open on any error or timeout, and the default mode, shadow, logs Kai's answers and changes nothing. The log holds probabilities and verdicts only, 0600 in a 0700 directory. A state sent to Kai is redacted and at most 3,000 characters of JSON; only https, or http on loopback, is called, and no redirect is followed. Against the first draft: plugin options are read as CLAUDE_PLUGIN_OPTION_<KEY> uppercased, as Claude Code exports them (the draft read lowercase names and never saw the plugin's key or mode). The floor is checked before the read-only shortcut, which let "env rm -rf" and awk's system() through, and now catches "git checkout -- .", "rm -Rf" and split rm flags. A background-task notice no longer starts a new turn. Stop answers with decision "block" as JSON. Only the last 8 MiB of a transcript is read: a 745 MB one made the hook throw. test/kai.test.mjs runs both hooks offline against a stand-in for Kai on 127.0.0.1, 25 tests; test.sh runs it with the rest. In a live Claude Code 2.1.296 session against the stand-in, the Stop block came back as "Stop hook feedback", the PreToolUse ask denied an allowed command under -p, and shadow showed a notice and let it run.
6.2 KiB
name, description, metadata
| name | description | metadata | ||
|---|---|---|---|---|
| kai | Ask Kai, Hanzo's decision model, for a typed answer when the next step is a decision among answers known in advance: pick one of a set of options, rate on an ordered scale, or give the probability that a statement about a state holds. Use it to route, triage or classify, and batch every question about one state into one call. Do not use it to write text or code, for arithmetic or exact lookups, for anything a fixed rule decides, as permission to act, or to judge whether agent work is done, verified or safe. |
|
Kai
Kai answers typed questions about a state with probabilities. It writes no text. You stay the planner and the writer; code you own maps Kai's answer to an action.
Setup
Kai needs one variable, set outside the conversation:
HANZO_API_KEY a Hanzo API key (never paste it here)
KAI_URL optional; default https://api.hanzo.ai/v1/decisions
KAI_MODEL optional; default kai
If HANZO_API_KEY is missing, say so and stop. Do not search files, shell
history or the environment of other programs for a key. Never print, log or
commit the key.
When to ask Kai
Ask when the answer space is known before the call and the answer changes what happens next:
- which of these options (a queue, a team, a retrieval path, a model tier);
- how much, on an ordered scale (urgency, severity);
- whether a statement about the state holds.
Do not ask when you already know the answer, when a rule decides it, or when the answer is text you must write.
What Kai does not judge reliably yet
Kai's current version does not read agent work reliably: whether a task is done, whether a change is verified, whether a command is risky. The cookbook's command-risk, progress and completion recipes (03, 04, 05) fail on it. Decide those with rules in code (did a test, build or lint pass after the last edit; does the command match a known destructive pattern) and with the user's permission prompts. If you ask Kai anyway, keep its answer as a logged signal and do not act on it.
Keep the facts next to the call
| Facts | Do |
|---|---|
| Already in your context | One call, every question about that state batched in it |
| In a file or a tool's output | A script reads them and sends them to Kai; the items never enter your context |
| Not yet known | Gather the evidence first; then one call |
Do not paste bulk data into a call by hand.
The call
curl -sS --fail-with-body "${KAI_URL:-https://api.hanzo.ai/v1/decisions}" \
-H "Authorization: Bearer $HANZO_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "kai",
"state": {"user_query": "What is the current stock price of Apple and what did they announce this morning?"},
"questions": {
"retrieval_route": {
"type": "choice",
"instructions": "Which retrieval path is necessary to answer `user_query` accurately?",
"criteria": {
"direct_llm": "timeless knowledge, math, coding logic, or general facts",
"internal_rag": "private company docs, internal APIs, or workspace code",
"live_web_search": "real-time news, current asset prices, or recent events"
}
},
"urgency": {
"type": "score",
"instructions": "How time-sensitive is `user_query`?",
"criteria": ["not time-sensitive", "this week", "today", "right now"]
}
}
}'
state is text, an object or an array. Name its fields and refer to them in
backticks in the questions. Unknown fields are refused: 400 at the top level,
422 inside a question.
Question types:
choice:criteriamaps each option to what it means, or lists the options; at least two. The answer haschoice,probabilitiesover every option,answer_confidence(the probability of the pick) andconfidence((p − 1/n) / (1 − 1/n), which reads 0 for an even spread over any n).score:criterialists the levels, lowest first. The answer hasscore(the expected level index),legend,probabilitiesper level,answer_confidenceandconfidence.noul:instructionsis a yes/no question. The answer is{"type": "noul", "noul": p}, p the probability of yes. There is no separate confidence: certainty is the distance of p from 0.5.
At most 100 questions a call. Kai reads each question with up to 1,024 tokens
of state, the length it was trained at; a longer state is answered with
routing.extrapolated: true, and those answers are not reliable. Keep the
state to the facts the questions need.
Reading answers
- Threshold on
noulor onanswer_confidence, never onconfidence: oneconfidencevalue means different things at 2 and at 200 options. - Choose thresholds from the cost of each mistake and from labelled examples of your own traffic. There is no universal threshold.
- Below your threshold the decision is yours again: decide it yourself, gather more evidence, or ask the user.
- Kai's answer is a signal. It never grants permission, never runs the action it judged, and never replaces a deterministic check or a human approval.
Asking well
- Put the deployment in the state ("a reader assistant for a news site"), not only the item.
- Write options that describe the situation ("the request needs today's prices") rather than abstract classes ("live").
- One judgment per question. No "and", no counting, no date arithmetic.
- Add an
otherorunknownoption when the listed ones may not cover the case. - For many candidates (files, tools, documents), ask one
choicewith the candidates as options, not onenoulper candidate.
Failures
The body of an error is {"error": {"code", "message"}}.
400or422: the request is malformed or too long, and the message says which. Fix or shrink it; do not retry unchanged.429or529: waitretry-after-ms(elseRetry-After), retry once.401: the key is missing or wrong; tell the user.- Anything else, or a timeout: proceed as if Kai did not exist.
Never repeat a consequential action because a decision call failed.
Audit
When a workflow needs a record, keep the question definitions, the model id
Kai answered with (model in the reply), the probabilities, and the action
taken. Never the key.