# Triage with Kai, reply with Zen

URL: https://docs.hanzo.ai/docs/guides/cookbook/triage

> Route a support ticket with three typed decisions and no generated tokens, act only on the answers the model stands behind, then let Zen write the reply.

**Three decisions, one generation.** Which team, whether it is a bug and how
urgent are bounded questions: Kai answers them as distributions. The reply is
open work: Zen reasons over the ticket and drafts it.

## Decide

```bash
cat > ticket.json <<'EOF'
{
  "model": "kai",
  "state": {"message": "I was charged twice for my March invoice and the second charge is still pending."},
  "questions": {
    "is_bug":  {"type": "noul", "instructions": "Is this a product defect?",
                "criteria": {"true": "a defect", "false": "working as intended"}},
    "team":    {"type": "choice", "instructions": "Which team should handle it?",
                "criteria": {"account": "logins and profiles",
                             "payments": "charges, invoices and refunds",
                             "product": "features and bugs"}},
    "urgency": {"type": "score", "instructions": "How urgent is it?",
                "criteria": ["later", "this week", "today", "now"]}
  }
}
EOF
d=$(curl -s "$DECISIONS/v1/decisions" -H "Authorization: Bearer $HANZO_API_KEY" \
  -H "Content-Type: application/json" -d @ticket.json)
```

kai-1 answered `team: payments` at 0.9999, `is_bug` P(true) 0.40, and
`urgency` with `confidence` 0.08: no level above 0.32.
`usage.output_tokens` is 0. The response shape is on
[Decisions](https://docs.hanzo.ai/docs/decisions#response).

## Act on what is settled

```bash
team=$(jq -r '.answers.team | if .confidence >= 0.5 then .choice else "review" end' <<<"$d")
urgency=$(jq -r '.answers.urgency
  | if .confidence >= 0.5 then .legend[(.probabilities | to_entries | max_by(.value).key)]
    else "review" end' <<<"$d")
echo "$team $urgency"    # payments review
```

`confidence` is 0 when the options are equally likely and 1 when one takes all
the mass, so one threshold serves every question. Here `team` clears it and is
routed; `urgency` does not, so it goes to a person instead of being guessed.
Tune the threshold per question against your own outcomes, not by feel.

## Reply

```bash
msg=$(jq -r .state.message ticket.json)
curl -s https://api.hanzo.ai/v1/chat/completions \
  -H "Authorization: Bearer $HANZO_API_KEY" \
  -H "Content-Type: application/json" \
  -d "$(jq -n --arg team "$team" --arg msg "$msg" '{
        model: "zen6",
        messages: [
          {role: "system", content: "You write for the \($team) team. Two sentences. Promise nothing you cannot see."},
          {role: "user", content: $msg}
        ]}')" \
  | jq -r '.choices[0].message.content'
```

**What this buys.** The routing questions cost one forward pass of a small
encoder, return the same answer for the same ticket, and carry a hash of the
weights and of the state they read. Generation is spent once, on the reply,
where writing is the work.
