whiskers.git / crates / whiskersd / CLAUDE.md
1@README.md
2
3## Notes for agents
4
5- **This crate is an adapter; the policy is in `whiskers-service`.** A rule about what a route does (a cap, an
6  order of checks, what a refusal says, what may pass to the model) belongs in `whiskers-service` or
7  `whiskers-ports`, where every backend gets it. What belongs here is keeping state and calling out. Adding a
8  route means a test in `whiskers-service/tests/service.rs`, not code in `main.rs`.
9- **The wire must not change.** The tablet and the app depend on it. To check a change, run
10  `cargo test -p whiskersd` (the end-to-end sync test), restart the service and run `tools/preflight.sh`; a
11  refactor of this crate should also leave a recorded request script byte-identical against the old binary.
12- **Held to the same laws as every adapter.** `tests/conformance.rs` runs the `whiskers-conformance` suite on
13  these adapters and adds what only threads and files show (eight pushes at one cursor take one; eight voice
14  lines cannot pass a cap with room for one). A new adapter here gets its laws there.
15- **Concurrent on purpose, and Jev is behind one thread.** A thread per request, so a slow model call (the
16  memory work after a turn) never holds up the next turn's safety check; that exact blocking was the "listens
17  but doesn't reply" lag. The Jev client lives on its own thread (its futures are not `Send`) and request
18  threads hand it jobs. The adapters block inside their `async fn`s and never wait, so `main.rs` drives the
19  service with `run_ready` on the request's own thread. Do not make the service single-threaded again, and do
20  not give an adapter a future that really waits without giving `main.rs` an executor for it.
21- **Never log message text.** What she says is in the parents' log, written by the tablet. This service logs
22  nothing but its own start-up, counts, ids and refusals. Never log a key or the voice id.
23- **Spend goes through jev-http's ledger**, shared with every other Jev program on the machine. Do not
24  construct a client that bypasses it (`judge.rs` builds one with `Ledger::open()`).
25- **The voice's daily cap is the service's, reserve-then-settle,** separate from the tablet's `VoiceBudget`.
26  The tablet's is a courtesy to the child; this one protects the plan. It lives in `whiskers-ports`
27  (`VoiceDay`) and the service; `speak.rs` only speaks. Do not move a cap check into the adapter.
28- **No default ElevenLabs voice.** A voice id is not something to recall from memory; `/speak` refuses until
29  `ELEVENLABS_VOICE_ID` is set. The free plan's API access is unconfirmed (open item in the brain's elevenlabs page).
30- **TLS is on here** (`ureq` default features), unlike the tablet crates, which are plain http. This runs on
31  the operator's machine, where a C crypto backend is fine.
32- **`/v1/messages` exists so the gateway is never put on the private network.** Do not widen what it lets
33  through (other models, tools, streaming, larger replies) to suit a feature: the gateway is the operator's
34  subscription with no login of its own, and `ThinkRequest::vet` in `whiskers-ports` is its only guard.
35  `pkill -f whiskersd` run from a shell whose own command line contains that string kills the shell; use the pid.