For agents, on top of README.md, which they read first.
Notes for agents
Speakablehas a private constructor on purpose. It is the only type a shell may hand to speech, and onlyConversation::respondcan make one. Never add a public constructor or aFrom<String>: that reopens "speak unchecked text", the one thing this type removes.Outcome::Fallback(Fallback), not one flat enum.Answeredis not a fallback and a fallback is not an answer; the nesting keepsfallback()total with nounreachable!.- Who the words are for is an
Audience, from theSharedProfile. Prompts and the guard's age come from it, read once per turn;Guard::checktakes anAgeand never a name. With no profile the audience is the youngest age (Age::YOUNGEST) with neutral wording. Do not add a prompt, greeting or fixture with a name or an age written in it. - The guard failing is a refusal.
ErrfromGuard::checkends the turn. Never map it toAllowto keep a conversation going. - The log is written before each step is acted on. A broken log, or pictures that cannot be kept, stop the turn before the model or guard is called (
LogUnavailable). The single unlogged utterance is that fallback line and, best effort, the closingSaid. Do not reorder. - Pictures are judged through a description. Jev judges words, not pictures, so a separate model call with its own prompt (
describe_pictures_prompt, no persona) describes them first, the description is logged (PictureSeen) and the guard judges it like speech; only then does the cat see them. Any failure keeps the picture from the cat. That call is itself shown the picture and could be talked at by text inside it, which is why its output is judged rather than trusted. - Only turns allowed both ways enter the history, as words. A refused question or answer never reaches the model again, and a picture is shown once.
- A remembered fact is fed back into the prompt, so it is guarded like speech and logged (
Remembered/NotRemembered). A fact that cannot be logged is forgotten again. Memory ids are never reused (next_idis saved with the facts). - A damaged memory file is an error, not an empty memory. Starting empty would silently drop everything she told it and then overwrite the evidence.
- The voice allowance spends whole lines.
VoiceBudgetnever splits a line across voices; a damaged record starts a fresh day (worst case: one day's allowance twice). - Ports are synchronous. The shell calls
respondfrom a background thread. Do not add an async runtime to this crate; it would follow the core into the Android binding. - Reflection and compression run off the conversation lock.
Reflectortakes its own shared handles; never makerespondwait on it, or the next turn stalls behind memory work (the "listens but does not reply" bug). - Jev is rate limited machine-wide (30 questions a minute). Guard facts in one batched check per reflection, and recall's rerank is one Choice for the whole shortlist; do not add a call per fact.
- Recall degrades, never fails a turn. Embedder or ranker errors fall back (recency, then embedding order) and are logged as
MemoryFailed. - Keep it buildable for wasm. Code that touches
std::fs,SystemTime,Instantor stderr belongs behind#[cfg(not(target_family = "wasm"))], with the pure rule beside it (a merge, a comparison) callable without storage. Check withcargo check --target wasm32-unknown-unknown -p whiskers-coreinsidenix develop ~/whiskers. The hubs on the service run the pure merges inwhiskers-ports' adapters; do not re-implement a merge rule in an adapter. TimeKeeper::merge_durablyis for hubs. A device'smergetolerates a failed save (a little lost time); the service's master copy of the grown-ups' choices does not, because a PIN or a limit that vanished on restart is worse than a refused sync.- Hide is not forget, and a prompt never sees a hidden fact.
Visibilityis a typed last-writer-wins register on theFact;forgottenstill beats everything. Anything built into a prompt or a search readsSharedMemory::usable();facts()is every fact, for the parents' view and the memory's own work (a hidden fact still counts as known, so Whiskers does not file it again). A new reader of facts picks one on purpose. A hide or restore is stamped later than the write it follows (hidden_after,restored_after), so a slow clock cannot lose a parent's restore. An untouched fact has novisibilityin its stored or wire form: keepskip_serializing_if, the wire is a contract. - A fact's
iconis anIconId, never a string. It can only name an entry ofcrates/whiskers-icons/allowlist.txt. The memory file reads an unknown name as no icon (lenient_icon) instead of failing: a picture lost must never read as a damaged file. Two copies with different icons meet at the smaller name; the first icon given locally is kept (give_icon). The merge never takes a picture away. - A fact's picture is chosen off the conversation, and failing to choose is never an error she sees.
Reflector::give_iconsasks theIconChooser(the service: shortlist by meaning, then a Jev Choice) for a few iconless facts per pass and per sync; anErrstops the pass and is retried at the next, a "none fits" is remembered for the process. A fact with no picture shows a generic star. Do not add a call per fact on the conversation path (Jev answers thirty questions a minute).