Über dieses Projekt

Deep Whisper is a companion chat application: choose a character, talk with it, hear its voice, ask for a photo, and let shared moments become memories that outlive the conversation. This is the personal, self-hosted edition — one owner per installation, with conversations, memories and media kept in your own data directory.

It is here for a different reason than the systems next to it. It is not a service we run for customers; it is a single-user product published so the two hard parts can be read. Those parts are the memory lifecycle and the letter pipeline, and both are places where a personal product has to answer questions a multi-tenant service can defer.

Recall is assembled per conversation rather than per message. A snapshot caches the last real retrieval for fifteen minutes or ten turns, and refreshes immediately when the user asks whether the companion remembers — the trigger pattern is at `src/lib/memory/recall-snapshot.ts:29`. Every turn records where its memories came from (`live`, `snapshot`, `stale-fallback`, `unavailable`), so “the companion has nothing to say” can be told apart from “retrieval failed” (`src/lib/memory/recall-snapshot.ts:48`).

Deleting a conversation has to delete the memories sourced from it. The cascade treats a candidate listing as exhaustive only when the backend can list by source, counts a memory as deleted only if a re-list shows it gone, and keeps the bookkeeping identity scanned = skipped + deleted + failed (`src/lib/memory/cascade-forget.ts:13`), exposes whether the listing was exhaustive (`:118`) and counts only what a re-list still shows (`:179`).

Letters arrive unasked. A supervised worker picks one conversation per owner, generates only inside a three-day window measured from the last genuine interaction — sleeping for a week does not reopen it — and deduplicates by local date so a timezone change cannot produce a second letter the same day (`src/lib/letters/personal-scheduler.ts:40-59`, `src/lib/letters/policy.ts:44`). Jobs are leased for 120 seconds and retried after 30 minutes (`src/lib/letters/personal-scheduler.ts:29-30`), and there is deliberately no public cron endpoint: the route answers 404 (`src/app/api/cron/companion-letters/route.ts:4`).

One coupling is worth recording, because it is the kind of bug a personal product hides: birthday letters originally rode the memory-anchor channel, so they needed a qualifying memory and an open window. Important dates now carry a synthetic anchor prefix and are independent of recent chat (`src/lib/letters/policy.ts:30`). Single-owner changes the rest: there is no tenant column, and an installation lock decides which process may run the worker and refuses a second one against the same data directory (`src/lib/personal/instance-lock.ts:62`).

Weitere Projekte

Alle Agenten →