For agents, on top of README.md, which they read first.

  • Never print, log or format the key. To check that it arrived, print its length. Jev holds it and only ever puts it in an Authorization header.
  • This crate is host-only, by design and by documentation, not by accident. A wasm target must not depend on it; add the relay client beside it instead.
  • tokio::time::sleep is in here, so nothing behind a hot-patch point may call into this crate. A subsecond patch gets its own copy of the process's thread-locals, so patched code that touches a tokio runtime context finds none (jevsnes apps/native/CLAUDE.md). Network I/O stays on the worker thread; the decision logic that IS patchable touches none of it.
  • The retry policy is jev-client's, over jev-protocol::retry. This crate supplies only the ports (HttpsTransport, TokioRuntime); never add a retry loop here, which would be a second policy beside that one.
  • Never add a way to build a Jev without a Ledger. new/from_env are the only constructors and both require one, on purpose - see the crate's own doc comment. A convenience constructor that skips it would be a client that can spend without anything checking the rate guards or seeing the vendor's out-of-credit answer.
  • There is no lifetime cap, and none should be reintroduced. Removed 2026-09-22 (the user's own ruling on an earlier, self-imposed $4.00/ $4.50: "not this random $4 number"). The only hard stop on total spend is the vendor's own out-of-credit response (Refusal::OutOfCredit); the per-minute and per-hour guards stay, but they are RATE limits, not a budget - see the README's "There is no lifetime cap".
  • Ledger::window re-reads and re-sums the whole file on every call, on purpose. A cached running total is a second piece of state that has to agree with the log, and a cache that drifts toward "we have spent less than we have" is exactly the failure this exists to prevent - see ledger.rs's own doc comment. Do not add a cache to make this faster without re-reading that reasoning first.
  • The ledger's own tests use Ledger::at, not Ledger::open. open reads $XDG_STATE_HOME, which every test in the process shares - a test that used it would pollute the real ledger (and, worse, could seed the opening estimate, OPENING_ESTIMATE_USD) the first time it ran on a developer's machine.
  • ApiErrorKind::OutOfCredit's classification is a guess, not a documented fact, and this crate's out-of-credit flag rests on it. The rule for correcting it is in ../jev-protocol/CLAUDE.md.
  • A hold is released only when nothing can have been billed (ask's nothing_billed). After an interrupted or timed-out attempt the cost is unknown, and the hold goes on counting its worst case.
  • No guard may write a flag a human has to clear, except out-of-credit. The question bucket and the hour breaker are computed from the ledger's own lines and heal as the window slides (see README, "Why the rate guards heal themselves"). Do not reintroduce a pause file for them; a legacy paused.json is not read by anything. out-of-credit.json is the one flag, because waiting does not change it.
  • Admission is check-and-hold under one flock (Ledger::admit). A check that reads the ledger and a write that happens later, outside the lock, is the race that let two processes both see "29" and both ask.
  • Guard logic takes now as an argument (Window::verdict, Ledger::admit, Ledger::status); the tests use a fake clock by passing one. Keep ledger::now() calls at the edges (Jev::ask, Jev::status).