Qiuqiu
A multimodal desktop pet with long-term memory
Finish a conversation with an AI, close it, reopen it, and it knows nothing about you. That isn’t a model failure — the context window simply ends. Most products that advertise “memory” paste the transcript back into the prompt until it no longer fits.
Qiuqiu is a desktop pet that lives on macOS, sitting on top of a long-term memory layer: every turn is broken into individual facts and stored, and the next question queries them first. The whole path is exposed — what got written, what got dropped, and which memories this particular answer leaned on.
The half it refuses to store matters more
“I’m in Shanghai next Friday for work” becomes a stored fact. “Uh… well… let me think” does not.
When the compression step extracts nothing, the whole utterance goes to the discard list and the turn writes nothing. The UI shows that too: the pipeline lights up as far as the novelty filter and stops there, with “redundant, dropped” in the right rail.
A memory system that only accumulates drowns in filler within a week. Watching it refuse is what earns trust — more than watching it accept.
Three retrieval paths in parallel, two storage tiers
Recall runs three ways at once: by meaning (vectors), by surface form (BM25), and by tag (entities). The three are unioned, deduplicated, then truncated to a token budget — summaries first, expanded only if there’s room.
Storage is split hot and cold: hot stays resident, cold is archived and rehydrated on demand. Vectors live in LanceDB (Rust-native, no server to run); facts and sessions live in SQLite.
Deleting a memory only deactivates it. History rows are never removed — the worst failure mode for a memory system is not being able to reconstruct why it answered the way it did.
Every lit segment comes from a backend event
The pipeline across the top isn’t an animation script. The frontend subscribes to the backend’s event stream; a segment lights only when that step actually happened. Every step in a turn hangs off one trace id, expandable row by row in the right rail.
This is a deliberate constraint (contract AD-14): events are the only source of visibility. Otherwise the demo looks great and the UI stays serene while things are broken.
Expressions never touch the model
Thirty-two expressions, disconnected from the model in both directions: the outgoing prompt carries no expression state, and the model has no channel for emitting expression markers — parenthetical stage directions it writes are stripped server-side.
Once the full reply is back, two local rule tables (keywords plus memory events) decide which face to play. Swap the model or bump its version and the face scheduling doesn’t drift.
Also in there
End-to-end realtime voice calls with barge-in, images routed through Vision into descriptions before entering memory, a persona with four sliders plus habits it settles into over time, and a click-through pet window whose eyes track the cursor.
The build is contract-first: the agreements in docs/CONTRACTS.md are parsed and checked by the test suite, so changing the contract without changing the implementation turns the tests red.
The live demo runs on a Render free instance, which sleeps after 15 idle minutes and takes about 50 seconds to cold start — a GitHub Actions cron pings its health check every 5 minutes, so it is normally warm when you open it.



- Launched
- 2026
- My role
- Solo developer
- Built with
- ElectronReactTypeScriptFastAPILanceDBSQLiteDeepSeek豆包实时语音