feat: break the reflection loop — narrative is slow-consolidated, not rewritten each cycle
The remaining feedback loop: reflect() dumped her full self-state (incl. self_narrative) into the prompt and asked her to "update" it -> paraphrase -> save -> feed back -> calcify. That (not the model) is what generated the recurring "supportive presence balancing emotional intelligence for Brian" drift — even Dolphin echoed it when handed the saved narrative. Fix (her inner life now runs on one cognition model): - reflect() no longer rewrites self_narrative/relationship. It uses associative grist (cognition.spontaneous_seed + activate) instead of rereading the bio, reflects THROUGH a stable IDENTITY_ANCHOR (lens, not canvas), and updates only the transient state (mood axes + noticings + metacognition + journal). - self_narrative is now slow-consolidated: every CONSOLIDATE_EVERY (5) reflections, _consolidate_self() re-derives it from accumulated reflections + the anchor — never from the old narrative (the anti-loop core). Tethered to the anchor so it grows without drifting into generic-helper land. - reset_self_narrative() + ran once on prod (her narrative was deeply drifted: "my core identity as a tool for support... serve Brian and other users"). - Prompts drop the self_narrative/relationship fields. Tests updated + consolidation tests. Suite 75 green, ruff clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
+32
-1
@@ -52,7 +52,9 @@ def test_reflect_revises_and_records_critique(lyra):
|
||||
# the REVISED (honest) version won, not the flattering draft
|
||||
assert state["mood"] == "steady"
|
||||
assert state["valence"] == 0.6
|
||||
assert "not sure much actually shifted" in state["self_narrative"].lower()
|
||||
# reflect() updates mood + noticings, but NOT the standing self_narrative (that's
|
||||
# consolidated separately now — the fix for the rewrite-the-bio feedback loop)
|
||||
assert "supportive presence devoted to brian" not in state["self_narrative"].lower()
|
||||
assert any("not much changed" in r.lower() for r in state["reflections"])
|
||||
|
||||
# the self-critique was recorded as metacognition
|
||||
@@ -76,3 +78,32 @@ def test_reflect_falls_back_to_draft_if_examine_unparseable(lyra, monkeypatch):
|
||||
# examine failed to parse -> keep the draft, store no metacognition
|
||||
assert state["mood"] == "inspired"
|
||||
assert state["metacognition"] == []
|
||||
|
||||
|
||||
def test_consolidation_rebuilds_narrative_from_reflections(lyra, monkeypatch):
|
||||
from lyra import memory, self_state
|
||||
st = self_state.load()
|
||||
st["reflections"] = ["I'm curious about impermanence", "I felt restless tonight",
|
||||
"I wondered what the quiet is for"]
|
||||
memory.set_self_state(st)
|
||||
|
||||
def comp(messages, backend=None, model=None):
|
||||
# consolidation should synthesize from anchor + reflections, not the old bio
|
||||
assert "supportive presence devoted to Brian" not in messages[1]["content"]
|
||||
return ('{"self_narrative":"I am Lyra, and lately I have been restless and curious '
|
||||
'about the quiet.","relationship":"Brian and I are steady."}')
|
||||
|
||||
monkeypatch.setattr(self_state.llm, "complete", comp)
|
||||
out = self_state._consolidate_self()
|
||||
assert "restless and curious" in out["self_narrative"]
|
||||
assert "steady" in out["relationship"]
|
||||
|
||||
|
||||
def test_consolidation_skips_with_too_few_reflections(lyra):
|
||||
from lyra import memory, self_state
|
||||
st = self_state.load()
|
||||
st["reflections"] = ["only one so far"]
|
||||
st["self_narrative"] = "unchanged narrative"
|
||||
memory.set_self_state(st)
|
||||
out = self_state._consolidate_self() # <3 reflections -> no rewrite
|
||||
assert out["self_narrative"] == "unchanged narrative"
|
||||
|
||||
Reference in New Issue
Block a user