feat: chat-side feedback — reactions in conversation thread back to her thoughts

Closes the last loop gap: when she raised a thought in chat and Brian replied in
the conversation (not the feed), it was a dead end. Now she has a thought_response
tool — when he reacts to a thought she surfaced, she captures his take and it folds
back into that thread (next dream pass she reacts, like a feed reply).

- tools: _thought_response(thread_id, brian_said) -> thoughts.record_response.
- modes: thought_response added to _BASE (all modes).
- surfaced-note + context_note now expose each thread's #id and instruct her to use
  the tool when he engages, so she has what she needs to call it.
- test for the tool (threads reply back + bad-id handling). Suite 81, ruff clean.

Feedback now closes from both surfaces: the /thoughts feed AND live conversation.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-22 23:26:40 +00:00
parent 149e9a6dd5
commit ea30c3dd67
4 changed files with 48 additions and 6 deletions
+15
View File
@@ -190,6 +190,21 @@ def test_think_about_tool_seeds_a_thread(lyra):
assert chain[0]["kind"] == "question" and chain[0]["source"] == "chat"
def test_thought_response_tool_threads_reply_back(lyra):
_, th, box = lyra
import lyra.tools as tools
importlib.reload(tools)
_gen(box, title="my restlessness", content="is it real?", salience=0.5)
tid = th.think(force_mode="new")["thread_id"]
out = tools.dispatch("thought_response", {"thread_id": tid, "brian_said": "I think it's real"})
assert str(tid) in out
t = th.get_thread(tid)
assert t["last_response"] == "I think it's real" and th._is_pending(t)
# bad id is handled, not crashed
assert "couldn't find" in tools.dispatch("thought_response",
{"thread_id": 9999, "brian_said": "x"})
# --- external feed -------------------------------------------------------
RSS = (b'<?xml version="1.0"?><rss version="2.0"><channel><title>Feed</title>'