For your own Claude Code setup

Two models, one budget.
Stop paying for context you didn't need.

A reference for prompting Anthropic (Sonnet 5 / Opus 5) and Z.AI (GLM-5.3 / Flash) in Claude Code — built from your own measured sessions, not generic advice.

usage-guard.py verified 2026-09-13 archive_transcripts.py: 18,730 sessions recovered single file · works offline data through 2026-09-13

1 · Mental model

Your Claude Code setup is not "a model you talk to." It is a pipeline: a router picks which model answers, a meter watches what that costs, and an archive keeps a copy of what happened because the provider's own transcript store does not. All three are hooks — small scripts Claude Code runs at fixed points (prompt submit, session start, session stop) — and all three were built in response to one measured fact: a handful of runaway sessions consume almost all of your quota, and nothing was watching.

The two-tier design exists because model capability and token cost move independently. A work-tier model (Sonnet 5 on Anthropic, GLM-5.3-Flash on Z.AI) is cheap per token and fine for anything mechanical. A deep-tier model (Opus 5, GLM-5.3) costs roughly 3x more per input token and 3x more per output token, and is worth it exactly when the task involves a judgment call with no obvious right answer. The entire prompting discipline in this guide reduces to one question, asked honestly, per task: does this need judgment, or does it need typing?

Misconception: "the expensive model gives better code, so default to it" Your own data says otherwise. 96% of tokens processed across 59,397 Z.AI turns were cached input — context being re-read, not new reasoning. The model tier barely moves that number; context size does. A deep-tier model re-reading a bloated context is not buying you quality, it's buying you a bigger per-token multiplier on the exact same waste.
You type a prompt peak-hours-guard.sh usage-guard.py log-prompt.sh (UserPromptSubmit) Model responds SessionStart → sweep + archive Session ends Stop → archive_transcripts.py
Every prompt passes through the peak guard and budget guard before reaching the model; every session's start and end pass through the archive.

2 · Your setup at a glance

Two config directories, each a complete Claude Code identity: ~/.claude talks to Anthropic directly; ~/.claude-zai talks to Z.AI's Anthropic-compatible endpoint via zclaude. Both are now wired identically for metering and archiving.

FileRoleRegistered in
~/.claude/hooks/usage-guard.pyPer-session + rolling 5h/7d budget meter. Warns or blocks.Both, UserPromptSubmit
~/.claude/hooks/peak-hours-guard.shWarns/blocks the deep tier during Z.AI's peak-rate window.Z.AI only, UserPromptSubmit
~/.claude/hooks/log-prompt.shAppends every prompt to ~/.claude/logs/prompts.jsonl.Both, UserPromptSubmit
~/.claude/archive/archive_transcripts.pySQLite archive of every session transcript, raw + parsed.Both, SessionStart + Stop
~/.claude/hooks/caveman-*.jsTerse-output mode — cuts filler tokens from every response.Both, various
~/.claude/hooks/active-plan-check.shSurfaces an in-progress plan file at session start.Anthropic only, SessionStart
~/scripts/zclaudeLauncher: picks the Z.AI tier pair, sets the compact window, applies the peak guard.
~/.claude/tier-discipline.mdThe short routing policy loaded into every session via both CLAUDE.md files.Both
~/.claude/model-routing.mdThe long version — full measured data, not loaded into sessions.Reference only

Sources: ~/.claude/settings.json, ~/.claude-zai/settings.json, ~/.claude/model-routing.md.

3 · Verify it's live

Before trusting any of the guidance below, confirm the pipeline is actually running in your current setup.

python3 ~/.claude/hooks/usage-guard.py --status
# Expect: policy, budgets, and rolling 5h/7d totals — not an error

python3 ~/.claude/archive/archive_transcripts.py stats
# Expect: session/message counts and a date range for both configs

~/.claude/hooks/peak-hours-guard.sh --status
# Expect: current SGT peak window state; exit 1 only if inside peak
If any of these error The guard's own design philosophy is "never break a session" — every unexpected failure inside the hook exits 0 silently. That means a broken guard is invisible from inside a Claude Code session. Running these three commands directly, outside a session, is the only way you'll actually notice one has stopped working.

4 · Where the money actually goes

The credit formula looks like it rewards short output. It doesn't — it rewards small context. This is the single most important number in this guide, because every prompting rule below is downstream of it.

SourceTokensShare of tokensShare of credits
Cached input4,096,294,84896%82%
Fresh input140,101,0703%11%
Output23,295,4001%7%

Measured across 59,397 Z.AI turns, 2026-07-15 to 2026-09-11. Per-token, output is 3.5x fresh input and fresh input is 4.1x cached input — but volume beats ratio: there is 175x more cached-input volume than output volume, so cached input still wins on total cost even at its lower per-token rate.

What this means in practice: the size of your context window, re-read on every single turn, is the lever. Terse replies help, but only at the margin — they shave the 7% column. A bloated context that never gets compacted taxes every turn for the rest of the session.

The concentration problem

It isn't evenly distributed usage either. One session accounted for nearly half of eight weeks of spend:

SessionsShare of credits
Top 1 of 67047.5%
Top 361.3%
Top 1076.1%
Top 5092.6%
Remaining 6207.4%

That top session ran 18,045 turns over 93 hours, median context 520,077 tokens, peaking at 921,806 against the 1M ceiling — an unattended loop nobody was watching, on the expensive tier, without a compact window. Replaying it against three interventions:

ScenarioCreditsvs actual
As it ran (1M window, GLM-5.3)170,5141.0x
250K auto-compact only75,7552.3x
Flash only56,2653.0x
Both changes24,9966.8x
Don't over-correct: the median turn is fine Across all sessions the median turn carries 33,140 tokens and only 3.7% exceed 250K. The 250K auto-compact window (now the zclaude default) never fires for ordinary work — it exists purely for the tail. Don't shrink it further; a tighter window compacts normal sessions needlessly, and compaction itself costs a summarization call and loses fidelity.

Sources: ~/.claude/model-routing.md §1–3, replay against the largest recorded session's transcript (170,514 credits).

5 · Task → model routing

The wrong question is "which model is smarter?" Both are smart enough for roughly three-quarters of what actually gets typed into this setup. The right question is "does getting this wrong cost more than the token savings from getting it fast?" Below is the actual composition of your own Anthropic-side prompt corpus, split by that question.

Work tier — /model sonnet (Flash / Sonnet 5)

ClassShareWhy it's safe
Acks and steers (1, Yes, push, Go)20%205 prompts averaging 4 characters. No reasoning at all.
Short steers and re-runs26%Averaging 73 characters. Mechanical.
Commit, push, branch, PR, worktree21%Format-bound.
Tests, lint, formatting, chasing CI greenVerification is reading output, not reasoning.
Docs, README, changelog2%Bounded scope, known format.
"Where is X" / "what calls Y"Search, not analysis.
Mechanical refactorsScope known before you start.
Log / stack-trace triagePattern matching.
Long unattended loopsThe one rule with measured consequences — see §4.

Deep tier — /model opus (GLM-5.3 / Opus 5)

ClassShareWhy
Architecture, specs, phased plans5%Wrong answer costs hours downstream.
Code review and security audit3%Cheap models miss what they don't look for.
Root-cause debugging4%After two honest attempts on the work tier.
Auth, money, migrations, data integrityBlast radius.
Deciding whether, not howJudgment calls with no obvious right answer.

Roughly 75% of Anthropic-side turns fit the work tier by this breakdown. If your own ratio looks very different from that, that's a signal worth noticing — not a rule you're breaking.

Work tier /model sonnet — start here Deep tier /model opus prints ESCALATE: prints DOWNSHIFT: Neither line switches the model by itself. You run /model opus or /model sonnet, then re-send. One escalation per session — never ping-pong. Switching invalidates the prompt cache: the new model re-reads all context as fresh input, not cached.
ESCALATE and DOWNSHIFT are signals the model prints — you decide whether to act on them.
Escalation is not free Switching /model mid-session invalidates the prompt cache — the new model re-reads the whole context as fresh input at the 4.1x-more-expensive rate instead of cached. At 250K tokens of context that's roughly 172 credits on GLM-5.3, about twenty ordinary turns. Escalate early, before context has accumulated, and never more than once per session. For work you already know is deep, start there — zclaude deep — rather than switching later.

6 · Prompt patterns per tier

Small models fail on inference, not raw capability. The gap between a work-tier model that nails a task and one that flails on it is almost always missing information the prompt should have supplied outright.

Work-tier prompts: make inference unnecessary

Every work-tier prompt should carry four things:

  • Explicit paths — not "the config file," but ~/.claude/settings.json.
  • An explicit done condition — how the model knows it's finished, not just what to start doing.
  • A scope boundary — what NOT to touch, especially in a repo with adjacent unrelated code.
  • An escape hatch for hidden difficulty — when the task might be harder than it looks, add: If you'd have to guess at anything, stop and print ESCALATE: <reason>.
Misconception: "vague prompts save typing" They save typing once and cost it back with interest: a vague prompt produces a wrong-scope answer, which produces a correction turn, which is now fresh context re-read for the rest of the session. The specific prompt is cheaper end-to-end even though it looks more expensive up front.

Deep-tier prompts: state the judgment call, not the steps

If you're paying the deep-tier multiplier, spend it on judgment, not typing speed. Good deep-tier prompts name the trade-off explicitly ("pick between eager and lazy loading here, and say why") rather than a numbered procedure — a numbered procedure is work-tier material regardless of which model executes it.

Stop pasting command output

51% of every character typed into this setup was pasted terminal output; one lsof dump alone ran 21,864 characters. Pasted text is not compactable the way a tool result is — it sits in the conversation and is re-read on every subsequent turn.

Instead ofSay
Pasting a stack trace or log dumpRun <cmd> and diagnose — let the tool call carry it, not the prompt
Re-pasting a template every timeA slash command (/postgate, /sym-phase) — same content, but now a stable cache prefix instead of fresh tokens each time
Describing what you just watched happenPoint at the file/output and ask for the specific judgment you need

Sources: ~/.claude/model-routing.md §8, ~/.claude/tier-discipline.md.

7 · The budget guard

usage-guard.py exists because routing ordinary work to a cheaper tier doesn't stop a runaway session — only a budget does. On every prompt it accounts for what the session has spent since the last check, appends the delta to a durable ledger, and checks three numbers against limits: this session's total, the rolling 5-hour total across all sessions, and the rolling 7-day total.

Z.AIAnthropic
Soft — notice in context1,000 credits$2.00
Hard — next prompt refused5,000 credits$10.00

Z.AI's numbers are real credits, directly comparable to plan caps. Anthropic's are list-price dollars — a quota-pressure proxy, not a bill, since Anthropic doesn't publish the weighting behind its own 5-hour/weekly limits. At these defaults, the 402,576-credit runaway session from §4 would have been stopped at 1.2% of its eventual spend.

A blocked prompt is not a lost prompt

Hitting the hard threshold exits 2 and Claude Code surfaces the reason, but your typed text is preserved. Downshift with /model sonnet, or start a fresh session, and re-send the same prompt.

~/.claude/hooks/usage-guard.py --status       # policy, budgets, rolling windows
~/.claude/hooks/usage-guard.py --windows      # rolling 5h / 7d vs plan caps
~/.claude/hooks/usage-guard.py --top 10       # largest sessions in the ledger
CLAUDE_BUDGET_ZAI_HARD=50000 zclaude deep  # raise the ceiling for a session you know is legitimately large
CLAUDE_BUDGET_POLICY=warn zclaude          # never block, just tell me
Known limit: this bounds a session, not a turn The hard block fires at the next prompt. One prompt that launches a long autonomous run can still overshoot before the guard gets another look. It's a seatbelt for the session, not an airbag for the turn — don't launch an unbounded autonomous loop and assume the guard caps its damage mid-flight.

Sources: ~/.claude/usage-budget.md, ~/.claude/hooks/usage-guard.py.

8 · The peak-hours guard

Z.AI's legacy prompt-count plans bill GLM-5.3 at 3x and Flash at 1.2x during Mon–Fri 14:00–18:00 Singapore time — 01:00–05:00 CDT locally. That's the middle of the night, exactly when unattended overnight loops run: 15.3% of Z.AI turns and 13.3% of credits fell inside that window, almost none of it watched work.

ValueAt launchDuring a session
warn (default)Prints a warning, starts the deep tier anywayNotice in context each turn
enforceStarts on Flash instead (--peak-ok overrides)Blocks the prompt (exit 2)
offSilentSilent
# Interactive work — warn and let me judge (the default; nothing to set)
zclaude deep

# Overnight loop — never let it hold the deep tier through peak
ZCLAUDE_PEAK_POLICY=enforce zclaude deep --danger -p "$(cat nightly-task.md)"

zclaude --peak       # window status in local time
Caveat, stated plainly Peak multipliers are documented for legacy prompt-count plans. Credit-based plans publish flat multipliers with no peak surcharge. If you're on a credit plan, the guard saves nothing directly — set ZCLAUDE_PEAK_POLICY=off — but --status is still a useful signal that an unattended session is sitting on the expensive tier at 3am.

Sources: ~/.claude/peak-hours.md, ~/.claude/hooks/peak-hours-guard.sh.

9 · The transcript archive

Claude Code prunes session transcripts. Between 2026-09-11 and 2026-09-13, the live Z.AI transcript count fell from 1,200 files to 585, and recorded GLM-5.2 turns fell from 10,925 to 910. Anything computed by re-reading transcripts — cost analysis, session history, this guide's own source data — silently loses history as that pruning happens. archive_transcripts.py exists so nothing has to trust the live transcript store again.

What it captures

Both storage granularities you asked for: the full raw JSONL of every transcript (zlib-compressed) for perfect-fidelity replay, and a parsed messages table (role, model, timestamp, token counts, content preview) for fast querying without decompressing anything. Scope is both configs — ~/.claude/projects and ~/.claude-zai/projects — including subagent transcripts nested under <session>/subagents/<name>.jsonl.

TriggerWhat runsWhy it's crash-safe
SessionStarthook-sessionstart — sweeps every live transcript file, imports anything new or more completeCatches sessions that never fired a clean Stop — a crash, a killed terminal, a force-quit
Stophook-stop — archives the just-finished session's transcript immediatelyMinimizes the pruning window for the common, clean-exit case

Import is "most-complete-version-wins": a file is only re-imported if it has more lines than what's already archived for that transcript_key. Nothing is ever deleted or overwritten with less-complete data.

python3 ~/.claude/archive/archive_transcripts.py stats
# claude: sessions=5259 messages=485364 range=2025-12-24..2026-09-13
# claude-zai: sessions=13471 messages=716060 range=2025-12-24..2026-09-12
# db size: 1608.8 MiB

python3 ~/.claude/archive/archive_transcripts.py sweep              # manual catch-up sweep of live files
python3 ~/.claude/archive/archive_transcripts.py backfill-timemachine # pull older snapshots from TM backups
python3 ~/.claude/archive/archive_transcripts.py export <transcript_key> --out session.jsonl
Recovery, for scale A one-time TimeMachine backfill against 45 historical snapshots recovered 13,471 claude-zai sessions and 5,259 claude sessions — against only 585 and 535 respectively surviving live at the time. That gap is the size of the problem this closes going forward.

Sources: ~/.claude/archive/archive_transcripts.py, ~/.claude/settings.json, ~/.claude-zai/settings.json.

10 · Caveman mode

The caveman-mode hooks (caveman-activate.js at SessionStart, caveman-mode-tracker.js at UserPromptSubmit) inject a standing instruction to drop articles, filler, and pleasantries from responses, keeping technical substance intact. It's a direct application of §4: output tokens are 3.5x the cost of input tokens, and every token emitted this turn is re-read as cached input on every turn after it. Terse-by-default is a small, compounding discount that costs nothing to keep on.

It explicitly steps aside for security warnings, irreversible-action confirmations, and anywhere fragment order could be misread — code, commits, and PRs are always written in full prose regardless of mode.

Labs

Lab 1 · Read your own routing signal

5 minutes · local only · no session risk

Run ~/.claude/hooks/usage-guard.py --top 10 and look at your own top 10 sessions by spend. For each one, ask: was that session's size a judgment call being made repeatedly (legitimate deep-tier work), or a work-tier task that ran long because the context never got trimmed?

Checkpoint: you can name at least one habit from your own top-10 list you'd change — e.g. "that was an unattended loop I forgot was on the deep tier."

Lab 2 · Practice the ESCALATE / DOWNSHIFT signal

10 minutes · a real work-tier session

Start on /model sonnet. Give it a task you already suspect is a genuine judgment call (per §5's deep-tier table). Confirm it prints ESCALATE: <reason> and stops rather than guessing. Switch with /model opus and resend once, not twice.

Checkpoint: the model stopped instead of guessing, and you switched exactly once.

Lab 3 · Query the archive for a session you thought was gone

5 minutes · read-only

Pick a session older than a week. Run archive_transcripts.py export <transcript_key> against it (find the key by grepping sqlite3 ~/.claude/archive/transcripts.db "select transcript_key from sessions where session_id like '...'").

Checkpoint: you get a real JSONL file back, even if the live projects/ copy has since been pruned.

Capstone

Start a real task on zclaude (work tier). Before typing the prompt, write down: the explicit path(s) involved, the done condition, the scope boundary, and whether you expect this to need ESCALATE. Work the task. If it escalates, switch once, finish, and afterward run usage-guard.py --session <id> to see what that escalation actually cost against the §5 estimate. Then check --windows to see where this session left your rolling 5h/7d totals.

Troubleshooting

SymptomCauseFix
Budget notice never appears even though spend feels highGuard failed silently (its design: never break a session) or CLAUDE_BUDGET_POLICY=offRun usage-guard.py --status directly, outside a session
Session totals look wrong after a transcript got prunedByte-offset accounting restarts from zero if a transcript shrinks or is replacedTrust the ledger (~/.claude/usage/ledger.jsonl), not a live recompute from transcripts
Archive stats shows fewer sessions than expectedHooks were only just registered — historical sessions predate themRun backfill-timemachine once to recover pre-hook history
Deep tier feels "stuck on" all nightAn unattended loop was launched with /model opus or zclaude deep and never downshiftedNever leave an unattended loop on the deep tier — §5's one hard rule
Escalating felt like it cost a lotContext had already grown large before the switchEscalate earlier next time — cost scales with accumulated context, per §5

Cheat sheet

Universal starting pattern

zclaude (work tier, default) zclaude deep (known-deep work) /model sonnet / /model opus (switch in-session, once)

Work-tier prompt must have

Explicit path · done condition · scope boundary · "stop and print ESCALATE if you'd guess"

Deep-tier triggers

Architecture/spec · security/auth/money/migration · root cause after 2 honest attempts · "whether" not "how"

Never

Paste terminal output — say "run X and diagnose" · leave an unattended loop on the deep tier · ping-pong tiers

Budget commands

usage-guard.py --status usage-guard.py --windows usage-guard.py --top 10

Archive commands

archive_transcripts.py stats archive_transcripts.py sweep archive_transcripts.py export <key>

Glossary

Work tier / deep tier
The cheap-per-token model (Sonnet 5 / Flash) vs the expensive one (Opus 5 / GLM-5.3). (§1)
Cached input
Context re-read from a prior turn rather than freshly processed; billed at roughly a quarter of fresh-input rate but dominates total cost by sheer volume. (§4)
ESCALATE:
A line the model prints on the work tier when a task needs judgment the cheap model shouldn't guess at. Does not switch models by itself. (§5)
DOWNSHIFT:
The reverse signal — remaining work is mechanical, offered to a deep-tier session. Does not switch models by itself. (§5)
Ledger
The append-only spend record at ~/.claude/usage/ledger.jsonl, kept because live transcripts get pruned and can't be trusted as a durable history. (§7)
transcript_key
The archive's stable identity for a transcript file — its path relative to projects/ — used because subagent transcripts have no session-UUID-shaped filename of their own. (§9)
Peak window
Z.AI's Mon–Fri 14:00–18:00 SGT surcharge window, billed only under legacy prompt-count plans. (§8)
Compact window
The context-size threshold (250K default) past which zclaude auto-summarizes to bound growth. (§4)

Index

Quiz

Flashcard style, self-scored. Aim for 12/14. Score: 0 learned · 0 review · 0/14 answered

Q1Why doesn't routing ordinary work to a cheaper model stop a runaway session?
Routing changes per-token cost, not the number of tokens a session accumulates. Nothing was counting cumulative spend, so nothing could stop it. (§7)
Q2What share of total tokens was cached input, and what share of credits did it cost?
96% of tokens, 82% of credits. (§4)
Q3Does printing ESCALATE: switch the model automatically?
No. It's a signal; you run /model opus yourself and re-send. (§5)
Q4Why is escalating early cheaper than escalating late?
Switching models invalidates the prompt cache — all existing context gets re-read as fresh input at ~4x the cached rate. Cost scales with how much context has accumulated before the switch. (§5)
Q5What's the four-part shape of a good work-tier prompt?
Explicit path, explicit done condition, scope boundary, and an escape hatch ("if you'd guess, print ESCALATE"). (§6)
Q6Why say "run X and diagnose" instead of pasting the output of X?
Pasted text isn't compactable and is re-read every subsequent turn; a tool result is. 51% of characters typed in this setup were pasted terminal output. (§6)
Q7Z.AI credits vs Anthropic dollars in the budget guard — what's the difference in what they represent?
Z.AI credits are real, comparable to plan caps. Anthropic dollars are list-price, a quota-pressure proxy, not an actual bill. (§7)
Q8Does the budget guard bound a single turn?
No — it bounds a session. The hard block fires at the next prompt; one prompt that launches a long autonomous run can overshoot before the guard looks again. (§7)
Q9Why does the usage guard keep its own ledger instead of recomputing from transcripts?
Transcripts get pruned — Z.AI's fell from 1,200 to 585 files in two days. A recompute from transcripts alone silently loses history; the ledger is append-only and durable. (§7, §9)
Q10What two storage granularities does the transcript archive keep for every session?
The full raw JSONL (compressed, for fidelity) and a parsed per-message table (for fast querying). (§9)
Q11Why does the archive key on transcript_key (a relative path) instead of session ID?
Subagent transcripts live at <session>/subagents/<name>.jsonl and have no session-UUID-shaped filename of their own; the path is a stable identity, the real session ID is still captured as a separate queryable column. (§9)
Q12Which hook catches a session that crashed without a clean exit?
SessionStart's sweep — it re-scans every live transcript, not just the one that just ended, so a crashed prior session still gets picked up on the next launch. (§9)
Q13Under what condition does the peak-hours guard actually save money?
Only on legacy prompt-count Z.AI plans, which publish a 3x peak multiplier. Credit-based plans have no peak surcharge — there ZCLAUDE_PEAK_POLICY=off is correct, though --status is still a useful signal. (§8)
Q14What is the one rule in the routing table with directly measured consequences?
Never leave an unattended loop on the deep tier — the 402,576-credit runaway session was exactly this. (§5)

Sources

All figures traced to files on this machine, checked 2026-09-13. No external network access required to read this guide.

  • ~/.claude/model-routing.md — full measured routing data, credit formula, escalation cost math. Best used for: re-deriving any number in §4–5.
  • ~/.claude/usage-budget.md — budget guard design, defaults, verified test cases. Best used for: tuning thresholds.
  • ~/.claude/peak-hours.md — peak-window guard design and policy semantics. Best used for: overnight/unattended launch config.
  • ~/.claude/tier-discipline.md — the short policy loaded into every session. Best used for: the exact wording the model sees.
  • ~/.claude/hooks/usage-guard.py — the budget guard implementation. Best used for: exact CLI flags and exit codes.
  • ~/.claude/hooks/peak-hours-guard.sh — the peak guard implementation.
  • ~/.claude/archive/archive_transcripts.py — the transcript archive implementation and schema. Best used for: querying historical sessions directly with sqlite3.
  • ~/scripts/zclaude — the launcher source, tier selectors, and environment overrides.
  • ~/.claude/settings.json, ~/.claude-zai/settings.json — live hook registration for both configs.
  • Vault note: Coding Agent usage tracking and optimization.md — the original build narrative and the transcript-pruning discovery that motivated §9.