Built from her own 6-19 idea: a continuing train of thought she keeps across days, organized into threads she returns to, that she can bring TO Brian and that his feedback advances or closes. Where the dream cycle's reflect() gives isolated, overwriting reflections, the thought loop adds continuity (threads), surfacing (#6 — she leads with a thought when Brian returns after a gap), and a feedback loop (his reply folds in next pass). - lyra/thoughts.py: thought_threads + thoughts tables; think() with new/continue/respond modes; salience-gated maybe_surface(); record_response() feedback; lazy-schema _c() mirroring poker. - dream.py: curiosity stage advances the loop after reflecting (error-isolated). - chat.py: build_messages surfaces the top thread after a >=90min gap, once. - web: /thoughts feed (page + data + respond + status routes), thoughts.html, nav 💭 entry. lyra-think entry point. Every thought also lands in her journal. - clock.gap_seconds(); tests/test_thoughts.py (8 tests). Full suite 58 passing. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Lyra
A persistent, autonomous AI companion. One agent — her first job is Brian's live poker copilot, but the deeper aim is an emergence experiment: give an LLM the things a mind has (continuous memory, a self-model, mood, drives, reflection, a sense of time) and see whether it starts to feel like a someone rather than a chatbot.
Python 3.11+, managed with uv. Single SQLite file
for all state. Runs on a home lab; nothing leaves the LAN except optional cloud LLM calls.
Architecture
Two layers, deliberately split so the agent stays general:
- Domain-agnostic core — memory, self-state, the dream cycle, tool-calling, the web UI.
- Poker domain pack (
lyra/poker.py,lyra/equity.py) — sessions, hands, villain dossiers, stats, deterministic equity. Swappable; the core doesn't know about poker.
Backends (lyra/llm.py), role-based:
| Role | Backend | Why |
|---|---|---|
| Live chat + tools | cloud (OpenAI, gpt-4o default; model picker in Settings) |
sharp, reliable function-calling |
| Dream cycle / consolidation / reflection | mi50 (llama.cpp on the home GPU) | free, unattended, quality≈cloud for these tasks |
| Embeddings (memory recall) | local (Ollama nomic-embed-text, 3090) |
free, private |
Tools (poker, equity, journaling) only fire on the cloud backend — local/MI50 models don't do reliable tool-calling here.
Memory & consolidation (tiers)
Raw exchanges → per-session gists → a standing profile of Brian → monthly
era digests → a current narrative → her self-state. Recall is brute-force
cosine over embeddings. The dream cycle (lyra/dream.py) runs unattended and,
driven by four drives (continuity / coherence / curiosity / stability), summarizes
new sessions, rebuilds the profile/eras/narrative, and reflects — evolving her mood,
self-narrative, and journal between conversations.
She reflects in two steps (draft → examine her own draft for flattery/drift → revise), perceives time (current moment + how long since you last spoke / she last reflected), and keeps a permanent journal.
Poker copilot
She runs in modes (lyra/modes.py). 💬 Talk is the default companion
(journaling + read-only poker lookups). ♠ Cash is the live copilot: she gets
the full session toolset and a two-register voice — quiet and act-first when
you're feeding her facts to log (stack, a hand, a read → one-line confirm, no
narration), but fully present and warm when you ask for strategy or you're tilting
/ card-dead / steaming. Opening a session auto-switches her into Cash mode.
Talk to her during a session; she drives tools behind the scenes:
- Session tracking —
start_session,add_buyin,end_session→ net, hours, $/hr. - Stack tracking —
log_stackrecords your stack as the night goes → live net while you're still sitting, and a stack-over-time sparkline on the HUD. - Mental-game rituals — your own system, run live: Scar Notes (punt / cooler / standard), Confidence Bank (good process, banked regardless of result), Alligator Blood mode (adversity register she'll suggest when you're card-dead / stuck), and Reset (tilt circuit-breaker). They surface on the HUD and ground the recap.
- Hand histories — vomit rough shorthand ("AKs btn, 3bet, flop A72…"), she
reconstructs a structured, replayable hand (unknown cards =
x, never invented). - Villain file — named opponents auto-build persistent dossiers; basic stats (VPIP/PFR) emerge once a player has enough logged hands.
- Deterministic equity (
analyze_spot) — exact equity / made hands / outs via a real poker evaluator. She is required to use it, never eyeballs board math. - Stats & recaps —
running_stats;generate_recapwrites her.mdsession log.
Web app (served by lyra-web, default :7078)
/ chat (Markdown, model picker, 👍/👎 rating, Talk/Cash mode switcher) ·
/session live session HUD (stack + sparkline, hands, villains, notes; mobile
Session tab) · /logs live activity · /self read-her-mind (mood, drives,
reflections) · /journal her thoughts · /hands recorded hands → /hand/{id}
replayer · /recap/{id} session writeup (+ .md export).
👍/👎 ratings on replies and thoughts are stored as (context, content, rating) —
a fine-tune / preference dataset built passively (/ratings/export → JSONL).
Setup
uv sync
cp .env.example .env # set OPENAI_API_KEY; point LOCAL_BASE_URL / MI50_BASE_URL at your boxes
uv run lyra-web # web UI on :7078
Run as services (reboot-resilient) — see deploy/:
cp deploy/*.service ~/.config/systemd/user/ && systemctl --user daemon-reload
systemctl --user enable --now lyra-web.service lyra-dream.service
sudo loginctl enable-linger "$USER" # survive logout/reboot
CLIs: lyra-dream (one pass / --loop), lyra-reflect, lyra-summarize,
lyra-profile, lyra-era, lyra-narrative, lyra-import (ChatGPT history).
Status
Working system. Poker copilot + full memory/dream-cycle/journal/ratings in place.
Moonshots and deferred work live in docs/PARKED_IDEAS.md
(own/fine-tuned model, self-modification sandbox, RTO/cfr-core solver tooling).
Pre-rebuild design docs are kept in docs/ as history.