feat: thought loop closer to her vision — wander grist, continuity, seeding, lifecycle
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>
This commit is contained in:
@@ -3,9 +3,12 @@ from __future__ import annotations
|
||||
|
||||
import importlib
|
||||
import json
|
||||
from datetime import timedelta
|
||||
|
||||
import pytest
|
||||
|
||||
from lyra import clock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def lyra(tmp_path, monkeypatch):
|
||||
@@ -130,3 +133,49 @@ def test_thought_recorded_in_journal(lyra):
|
||||
th.think(force_mode="new")
|
||||
kinds = [e["kind"] for e in memory.list_journal(limit=50)]
|
||||
assert "thought" in kinds
|
||||
|
||||
|
||||
def test_decay_rests_stale_threads_but_spares_pending(lyra):
|
||||
_, th, box = lyra
|
||||
_gen(box, title="stale one", content="old idea", salience=0.8)
|
||||
r1 = th.think(force_mode="new")
|
||||
_gen(box, title="stale pending", content="awaiting his reply", salience=0.8)
|
||||
r2 = th.think(force_mode="new")
|
||||
|
||||
conn = th._c()
|
||||
old = (clock.now() - timedelta(hours=72)).isoformat()
|
||||
with conn:
|
||||
conn.execute("UPDATE thought_threads SET updated_at=? WHERE id=?", (old, r1["thread_id"]))
|
||||
conn.execute("UPDATE thought_threads SET updated_at=?, last_response='hm', responded_at=? WHERE id=?",
|
||||
(old, clock.now().isoformat(), r2["thread_id"]))
|
||||
|
||||
assert th.decay() == 1 # only the non-pending one
|
||||
rested = th.get_thread(r1["thread_id"])
|
||||
assert rested["status"] == "resting"
|
||||
assert rested["salience"] == pytest.approx(0.8 * th.RESTING_DECAY)
|
||||
# the pending thread is spared — she still owes a reaction
|
||||
assert th.get_thread(r2["thread_id"])["status"] == "open"
|
||||
assert th._is_pending(th.get_thread(r2["thread_id"])) is True
|
||||
|
||||
|
||||
def test_context_note_lists_active_threads(lyra):
|
||||
_, th, box = lyra
|
||||
assert th.context_note() is None # nothing yet
|
||||
_gen(box, title="my own restlessness", content="a real thread of mine", salience=0.6)
|
||||
th.think(force_mode="new")
|
||||
note = th.context_note()
|
||||
assert note and "my own restlessness" in note and "a real thread of mine" in note
|
||||
|
||||
|
||||
def test_think_about_tool_seeds_a_thread(lyra):
|
||||
_, th, _ = lyra
|
||||
import lyra.tools as tools
|
||||
importlib.reload(tools) # bind to the reloaded memory/thoughts
|
||||
out = tools.dispatch("think_about",
|
||||
{"title": "am I continuous?", "thought": "do I persist between turns?",
|
||||
"kind": "question"})
|
||||
assert "am I continuous?" in out
|
||||
threads = th.list_threads()
|
||||
assert len(threads) == 1 and threads[0]["title"] == "am I continuous?"
|
||||
chain = th.thread_thoughts(threads[0]["id"])
|
||||
assert chain[0]["kind"] == "question" and chain[0]["source"] == "chat"
|
||||
|
||||
Reference in New Issue
Block a user