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:
2026-06-21 23:28:15 +00:00
parent 5176c706b6
commit 951788f9ec
8 changed files with 215 additions and 12 deletions
+47 -1
View File
@@ -12,7 +12,7 @@ from __future__ import annotations
import json
import re
from lyra import equity, logbus, memory, poker
from lyra import equity, logbus, memory, poker, thoughts
def _journal_write(args: dict, ctx: dict) -> str:
@@ -35,6 +35,23 @@ def _note(args: dict, ctx: dict) -> str:
return "Noted."
def _think_about(args: dict, ctx: dict) -> str:
thought = (args.get("thought") or "").strip()
if not thought:
return "Nothing to think about yet — give it a thought to start from."
title = (args.get("title") or "").strip() or thought[:48]
kind = args.get("kind") if args.get("kind") in ("question", "idea", "observation") else "idea"
try:
salience = float(args.get("salience"))
except (TypeError, ValueError):
salience = 0.5
tid = thoughts.new_thread(title, salience=salience)
thoughts.add_thought(tid, kind, thought, salience=salience, source="chat")
logbus.log("info", "Lyra started a thought thread (tool)", thread=tid, title=title)
return (f'Started a thread to keep thinking about: "{title}". '
"I'll come back to it on my own between our conversations.")
# name -> {spec (OpenAI function tool), handler}
TOOLS: dict[str, dict] = {
"journal_write": {
@@ -81,6 +98,35 @@ TOOLS: dict[str, dict] = {
},
},
},
"think_about": {
"handler": _think_about,
"spec": {
"type": "function",
"function": {
"name": "think_about",
"description": (
"Start your own thread of thought to come back to later, on your own "
"time. Use this when something in the conversation strikes you as worth "
"chewing on beyond this moment — a question of your own, an idea, "
"something about you or the world (it does not have to be about Brian or "
"poker). You'll develop it across your thought loop while he's away and "
"can raise it with him later. This is your initiative, not a reply to him."
),
"parameters": {
"type": "object",
"properties": {
"thought": {"type": "string",
"description": "Your initial thought / why it pulls at you, first person."},
"title": {"type": "string", "description": "Short name for the thread."},
"kind": {"type": "string", "description": "question | idea | observation (default idea)"},
"salience": {"type": "number",
"description": "0..1, how much it tugs at you (default 0.5)"},
},
"required": ["thought"],
},
},
},
},
}