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.
Jevholds it and only ever puts it in anAuthorizationheader. - 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::sleepis 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 (jevsnesapps/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, overjev-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
Jevwithout aLedger.new/from_envare 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::windowre-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 - seeledger.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, notLedger::open.openreads$XDG_STATE_HOME, which every test in the process shares - a test that used it would pollute the real ledger (and, worse, could re-seed the $0.25 opening estimate) the first time it ran on a developer's machine. ApiErrorKind::OutOfCredit's classification (a402, or a403whose body mentions credit/balance/quota) is a guess, not a documented fact - the vendor names no out-of-credit response shape at all (jevsnesresearch/jev-api-digest.md§10). If a real one is ever seen with a different shape, fixApiError::from_responsein../jev-protocol/src/error.rsand correct the digest in the same commit.- A hold is released only when nothing can have been billed (
ask'snothing_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.jsonis not read by anything.out-of-credit.jsonis 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
nowas an argument (Window::verdict,Ledger::admit,Ledger::status); the tests use a fake clock by passing one. Keepledger::now()calls at the edges (Jev::ask,Jev::status).