whiskers.git / crates / whiskers-core

whiskers-core

The conversation pipeline and what hangs off it. Conversation::respond(heard) takes what the child said (and any pictures they showed) and returns what Whiskers may say back. Every turn runs the same path: keep any pictures, log what the child said, ask the guard, ask the model, log what it wrote, ask the guard again, log what is said.

The parents' log gets everything, including model text the guard kept from the child. If any step fails, or the guard cannot be reached, the answer is one of a few fixed lines (Fallback), so the child is never met with silence or an error message. Something that means the child is hurt or unsafe is its own outcome, NeedsAGrownUp.

The chat is persistent (chat.json): a running summary plus the recent turns, so closing the app or folding the phone loses nothing, and it greets the child only the first time or after a long quiet. Once it grows past a limit the oldest turns are folded into the summary.

Remembering happens off to the side, never inside a turn. After a reply the shell calls the engine's reflect, and the Reflector asks the model for structured facts (kind, who, place, when, pictures), passes the batch through the guard, embeds what passes and stores it, skipping near-duplicates. On the next turn Recall finds the facts that matter: all of them while few, otherwise the nearest by embedding, reranked by Jev. If the embedder or ranker is down it degrades to recency or embedding order. Parents can remove a fact with forget, which is permanent and tombstones its identity on every device.

Putting away is not forgetting. She can put a fact away (Memory::hide): it stays, the parents still see it under "removed by her" and can restore it (Memory::restore), but Whiskers stops using it (recall, prompts, search never see it). It is a last-writer-wins register on the fact (Visibility, src/visibility.rs), so merging in any order gives the same memory; the parents' forgotten beats everything.

FileWhat
src/conversation.rsConversation, Parts, Reply, Speakable, memory reflection, the fixed fallback lines.
src/ports.rsThe traits an adapter implements: Model, Guard, Log, Pictures, Memory, Clock.
src/profile.rsThe child's profile (Child, ChildName, Age), the Audience prompts are written for, and the SharedProfile the app updates.
src/persona.rsThe prompts and greeting, written for an Audience.
src/visibility.rsVisibility: whether she has put a fact away, and the register's merge.
src/types.rsTurns, pictures, verdicts, outcomes, facts and the log entry format.
src/log.rsJsonlLog (append-only, flushed per line) and DirPictures.
src/chat.rs, src/shared.rsThe persistent chat and the shared, lock-light handles to log, memory and chat.
src/reflect.rs, src/recall.rsBackground remembering and compression; finding the facts for a turn.
src/memory.rsMemoryDoc (the memory with no storage attached, and its pure merge) and JsonMemory, which keeps one in a file replaced atomically on every change.
src/wire.rsWhat the tablet and the service say on the Jev routes (CheckRequest, EmbedRequest, RankRequest and their replies). In the core because both ends need it.
src/digest.rsReading the log back and shaping a day for the parents' view, with the model-written summary.
src/voice.rsThe natural voice's daily character allowance.
tests/The pipeline against fakes; the storage adapters on a real directory.

Nothing that touches a file, the system clock or stderr is compiled for wasm32-unknown-unknown (atomic.rs, log.rs, logging.rs, JsonChat, JsonMemory, TimeKeeper, read_log, read_shared_log are cfg(not(target_family = "wasm"))), so a Worker that links the core for the documents and their merge rules (whiskers-ports does) cannot reach file code by accident. The merge rules themselves (MemoryDoc::merge, Household::merge, ChatState::merge) are pure. Note this module's ports.rs are what a device's pipeline runs through; the backend's ports are in whiskers-ports.