1# whiskersd 2 3Whiskers' service, native. It holds the keys so the child's tablet never does, and runs on a 4machine you operate that the tablet reaches over a private network. 5 6It is the **native adapter** of Whiskers' backend: [`whiskers-service`](../whiskers-service/) is what the 7routes do, [`whiskers-ports`](../whiskers-ports/) is what they run on, and this crate is that interface 8implemented with files, `ureq`, a Jev thread, the environment and the system clock. `src/main.rs` is only 9HTTP (`tiny_http`): it turns a request into the service's plain `Request` and the `Response` back. The same 10routes run on any other adapter that passes [`whiskers-conformance`](../whiskers-conformance/); this one is 11held to it in `tests/conformance.rs`. 12 13| Route | In | Out | 14|---|---|---| 15| `POST /check` | `{"direction": "FromChild" or "ToChild", "age": 3..12 (optional; absent is judged as 3), "text": "..."}` | `{"Verdict": ...}`, or `{"Unavailable": "why"}` (Jev) | 16| `POST /speak` | `{"text": "..."}` | `audio/mpeg` (ElevenLabs), or an error status | 17| `POST /speak/v2` | the same | JSON: the MP3 and the time of every character, in Whiskers' own shape (service README) | 18| `POST /v1/messages` | an Anthropic Messages request | the gateway's answer, unchanged (see below) | 19 20The sync, journal, picture, embed, rerank and usage routes are listed in `src/main.rs` and implemented in 21`whiskers-service`. 22 23 WHISKERS_MODEL_URL=<gateway> whiskersd <private-ip>:47900 24 25The app is told this address at run time (a grown-up types `<private-ip>` or `<private-ip>:47900` into its parents' side). Its 26**Check** button asks `POST /usage`, the service's cheapest side-effect-free route, and expects a JSON reply with a `voice` 27section; there is no separate health route. 28 29`/v1/messages` is a narrow proxy to your model gateway (`WHISKERS_MODEL_URL`, required: the service 30exits at start without it), so the gateway itself is never exposed on the private network. It lets 31through one model (`WHISKERS_MODEL`), a reply of at most 1,000 tokens, no streaming, no tools, and a 32body of at most 8 MB; anything else is a 400 before it reaches the gateway. 33 34Bind it to a private-network address, not to `0.0.0.0`. `/check` is given the child's age and 35never the name. 36Environment: `TYPESAFE_API_KEY` for `/check`; `ELEVENLABS_API_KEY` and 37`ELEVENLABS_VOICE_ID` for `/speak`. The voice's daily cap is not an environment setting: it is the 38grown-ups' choice (`voice_daily_chars`, 1,000 until changed in the app), the same on every device. The 39free plan's 10,000 monthly credits spread over a month is about 330 a day. 40 41With a key missing, Jev unreachable or the spend ledger refusing, `/check` 42answers `Unavailable` (the pipeline treats that as a refusal) and `/speak` answers 43an error status (the tablet speaks in its own voice instead). It is not yet run as 44a service; `tools/run-whiskersd.sh` runs it as a transient user service. 45 46## The adapters 47 48| File | Port | How | 49|---|---|---| 50| `src/hubs.rs` | the memory, household and chat hubs | one JSON file each behind a mutex, replaced atomically; the merge rules are the core's | 51| `src/journal.rs` | `Journal` | `journal.jsonl`, one mutex over count-then-append, flushed per append | 52| `src/pictures.rs` | `PictureShelf` | a directory; written to a temporary name and linked into place, never replacing | 53| `src/allowance.rs` | `Allowance` | `tokens.json` and `speak.json` behind one mutex; the rules are the ports' | 54| `src/judge.rs` | `Judge` | the guard's questions asked of Jev on its own thread, with a bounded wait | 55| `src/embed.rs` | `Embedder` | a local llama.cpp embeddings server | 56| `src/speak.rs` | `Voice` | ElevenLabs over `ureq`, with a stand-in voice for a refused one; `timed_from_vendor` is the only code that knows ElevenLabs' alignment and turns it into a `TimedAudio` | 57| `src/model.rs` | `Thinker` | forwards the vetted body to the operator's gateway | 58| `src/secrets.rs`, `src/clock.rs` | `Secrets`, `Clock` | the environment; the system clock | 59| `src/native.rs` | `Backend` | all of the above, built from the environment; it implements every `Has…` capability, which `Service::complete` requires | 60 61Where credentials end up: in the service's environment, put there by whatever starts it (`op-env-run`); 62nothing here writes one down.