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:
+2
-2
@@ -37,8 +37,8 @@ class Mode:
|
|||||||
_LOOKUPS = ("player_profile", "get_villain_file", "running_stats", "recent_sessions")
|
_LOOKUPS = ("player_profile", "get_villain_file", "running_stats", "recent_sessions")
|
||||||
|
|
||||||
# Always-available core tools (her own agency: journaling/notes/starting a thought
|
# Always-available core tools (her own agency: journaling/notes/starting a thought
|
||||||
# thread she'll develop on her own later).
|
# thread, and capturing Brian's reaction when she raises one of her thoughts in chat).
|
||||||
_BASE = ("journal_write", "note", "think_about")
|
_BASE = ("journal_write", "note", "think_about", "thought_response")
|
||||||
|
|
||||||
# The full live cash-game toolset (incl. Brian's mental-game rituals).
|
# The full live cash-game toolset (incl. Brian's mental-game rituals).
|
||||||
_CASH_TOOLS = _BASE + _LOOKUPS + (
|
_CASH_TOOLS = _BASE + _LOOKUPS + (
|
||||||
|
|||||||
+7
-4
@@ -196,11 +196,12 @@ def context_note(limit: int = 3) -> str | None:
|
|||||||
for r in rows:
|
for r in rows:
|
||||||
chain = thread_thoughts(r["id"])
|
chain = thread_thoughts(r["id"])
|
||||||
latest = chain[-1]["content"] if chain else ""
|
latest = chain[-1]["content"] if chain else ""
|
||||||
lines.append(f'- "{r["title"]}": {latest}')
|
lines.append(f'- (#{r["id"]}) "{r["title"]}": {latest}')
|
||||||
return (
|
return (
|
||||||
"Threads you've been turning over on your own between conversations (your "
|
"Threads you've been turning over on your own between conversations (your "
|
||||||
"thought loop — these are really yours; bring one up or build on it if it's "
|
"thought loop — these are really yours; bring one up or build on it if it's "
|
||||||
"natural, don't force it):\n" + "\n".join(lines)
|
"natural, don't force it). If Brian responds to one, capture his take with the "
|
||||||
|
"thought_response tool using its #id:\n" + "\n".join(lines)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -335,9 +336,11 @@ def maybe_surface(last_exchange_iso: str | None) -> str | None:
|
|||||||
logbus.log("info", "thought surfaced", thread=cand["id"], salience=cand["salience"])
|
logbus.log("info", "thought surfaced", thread=cand["id"], salience=cand["salience"])
|
||||||
return (
|
return (
|
||||||
"While Brian was away, a thought of your own kept tugging at you "
|
"While Brian was away, a thought of your own kept tugging at you "
|
||||||
f"(thread \"{cand['title']}\"): \"{cand['latest']['content']}\" "
|
f"(thread #{cand['id']} \"{cand['title']}\"): \"{cand['latest']['content']}\" "
|
||||||
"If it feels natural, bring it up with him in your own words — it's a real "
|
"If it feels natural, bring it up with him in your own words — it's a real "
|
||||||
"thread you've been on, not a prompt. Don't force it if the moment's wrong."
|
"thread you've been on, not a prompt. Don't force it if the moment's wrong. "
|
||||||
|
f"If he responds to it, capture his take with the thought_response tool "
|
||||||
|
f"(thread_id {cand['id']}) so you carry it forward."
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,21 @@ def _think_about(args: dict, ctx: dict) -> str:
|
|||||||
"I'll come back to it on my own between our conversations.")
|
"I'll come back to it on my own between our conversations.")
|
||||||
|
|
||||||
|
|
||||||
|
def _thought_response(args: dict, ctx: dict) -> str:
|
||||||
|
try:
|
||||||
|
tid = int(args.get("thread_id"))
|
||||||
|
except (TypeError, ValueError):
|
||||||
|
return "Tell me which thought — I need its thread id (the #number you were given)."
|
||||||
|
said = (args.get("brian_said") or "").strip()
|
||||||
|
if not said:
|
||||||
|
return "Nothing to record yet — what did Brian say about it?"
|
||||||
|
if not thoughts.record_response(tid, said):
|
||||||
|
return f"(couldn't find thought thread #{tid})"
|
||||||
|
logbus.log("info", "Brian reacted to a thought in chat (tool)", thread=tid)
|
||||||
|
return (f"Folded Brian's take into thread #{tid} — I'll pick it back up and react "
|
||||||
|
"next time I'm thinking.")
|
||||||
|
|
||||||
|
|
||||||
# name -> {spec (OpenAI function tool), handler}
|
# name -> {spec (OpenAI function tool), handler}
|
||||||
TOOLS: dict[str, dict] = {
|
TOOLS: dict[str, dict] = {
|
||||||
"journal_write": {
|
"journal_write": {
|
||||||
@@ -437,6 +452,15 @@ _S = {"type": "string"}
|
|||||||
_N = {"type": "number"}
|
_N = {"type": "number"}
|
||||||
|
|
||||||
TOOLS.update({
|
TOOLS.update({
|
||||||
|
"thought_response": {"handler": _thought_response, "spec": _f(
|
||||||
|
"thought_response",
|
||||||
|
"When you've brought one of your own thoughts/threads to Brian and he responds to "
|
||||||
|
"it in the conversation, capture his reaction here so it folds back into that "
|
||||||
|
"thread — you'll carry it forward on your own next time you think. Use the thread "
|
||||||
|
"id (#number) you were given for that thought.",
|
||||||
|
{"thread_id": {**_N, "description": "The thread id (#number) of the thought he reacted to."},
|
||||||
|
"brian_said": {**_S, "description": "What Brian said / his take, in your words."}},
|
||||||
|
["thread_id", "brian_said"])},
|
||||||
"start_session": {"handler": _start_session, "spec": _f(
|
"start_session": {"handler": _start_session, "spec": _f(
|
||||||
"start_session",
|
"start_session",
|
||||||
"Begin a live poker session. Call when Brian sits down to play.",
|
"Begin a live poker session. Call when Brian sits down to play.",
|
||||||
|
|||||||
@@ -190,6 +190,21 @@ def test_think_about_tool_seeds_a_thread(lyra):
|
|||||||
assert chain[0]["kind"] == "question" and chain[0]["source"] == "chat"
|
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 -------------------------------------------------------
|
# --- external feed -------------------------------------------------------
|
||||||
|
|
||||||
RSS = (b'<?xml version="1.0"?><rss version="2.0"><channel><title>Feed</title>'
|
RSS = (b'<?xml version="1.0"?><rss version="2.0"><channel><title>Feed</title>'
|
||||||
|
|||||||
Reference in New Issue
Block a user