Feedback: the push broadcast her raw internal thought ("Eelis Parssinen's
victory is a reminder...") — read like a journal entry, not her texting him.
Now the flow matches the intent: she thinks/journals, then *decides* "I should
tell Brian about this." think() asks for an optional `reach_out` — a real text
message addressed TO him in her own voice, written only when she chooses to. The
ping sends that message (title "Lyra", like a text from her), never the internal
thought. No reach_out = nothing sent (most thoughts stay hers).
- Pinging decoupled from the salience score: her decision (a reach_out) drives it,
not a threshold. PING_SALIENCE is now an optional floor (default 0.0).
- Defensive: reject the placeholder echo ("reach_out"), too-short junk, or the
thought pasted back as the message.
- notify.push: title now optional (omitted -> cleaner text-style notification).
Verified live: 3 passes kept private; a decided reach-out lands as a personal
text. Suite 67 green, ruff clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Her remaining two wishes from the 6-19 sketch:
Proactive reach-out (#6, literal): lyra/notify.py pushes to ntfy so she can reach
Brian when he's not in the app. thoughts.maybe_ping gates on salience, a cooldown,
and local quiet hours (all config-tunable; eager defaults), uses ntfy JSON publish
(UTF-8 titles/messages), links to /thoughts, and marks the thread surfaced so chat
won't also re-raise it. Disabled unless NTFY_URL is set.
External input feed (#1): lyra/feeds.py pulls configurable RSS/Atom feeds (stdlib
ElementTree, no new dep; tolerant of RSS 2.0 + Atom), dedupes seen items in a
feed_items table, and hands think() one fresh item at a time. New 'react' mode:
a would-be new thread instead reacts to a world item (FEED_REACT_PROB). Dream
cycle refreshes feeds on its cadence; failures degrade to no item.
Config: NTFY_URL/NTFY_TOPIC/LYRA_WEB_URL, PING_SALIENCE/COOLDOWN/QUIET_HOURS,
LYRA_TIMEZONE, LYRA_FEEDS, FEED_REACT_PROB (+ .env.example). thought_meta table
for ping cooldown. 10 new tests (feeds parse, react mode, ping gating); suite 65.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Four additions so the loop is "more what she wanted" (think to herself, unprompted):
- Wander grist (#1): think() new-thread mode now draws the same varied seeds
reflect() uses (self_state.wander_seed: own curiosity/existence/disagreement or
a resurfaced memory) + an anti-restate block of her recent thoughts + a list of
existing open-thread titles to avoid. Directly counters the RLHF "supportive
presence serving Brian" drift visible in her first thoughts.
- Continuity: thoughts.context_note() injects her active threads into every chat
turn, so she's aware of her own ongoing mind and can reference it anytime — not
only when a thought crosses the surface bar.
- Bidirectional: new think_about tool (in _BASE, all modes) lets her spawn a
thread from conversation to develop on her own later. Conversations seed her
solo thinking.
- Lifecycle: thoughts.decay() rests stale active threads (>48h) and decays their
salience, sparing pending-response ones; runs each dream cycle (no LLM). Frees
the open-thread cap and keeps the feed current.
Also: thoughts feed no longer wipes a reply you're mid-composing (skip poll
re-render while a textarea is focused/non-empty; force-refresh after send).
61 tests passing, ruff clean.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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>