feat: make the two-step reflection observable (draft -> revised -> critique)

You couldn't see her actually correct herself — /self showed only the result.
Now:
- reflect() logs the draft, the revised/committed version, and the self-critique
  to the live log as an expandable "view details" block
- POST /self/reflect runs a reflection in the web process so it lands in /logs
  live (reflections normally run in the dream process, whose logs only go to
  journald); "↻ Reflect now" button on /self triggers it, with a logs ↗ link
- log viewers relabel the expander "view full prompt" -> "view details" (it now
  carries prompts and reflection diffs)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-17 04:53:38 +00:00
parent 3df060a1cd
commit 4c8f7202da
5 changed files with 54 additions and 6 deletions
+27 -4
View File
@@ -150,6 +150,20 @@ def _safe_json(s: str) -> dict | None:
return None
def _fmt_reflection(label: str, d: dict | None) -> str:
"""Readable block of a reflection's key fields, for the live-log inspector."""
if not d:
return f"{label}:\n (none)"
keys = ("mood", "valence", "energy", "confidence", "curiosity",
"self_narrative", "relationship", "new_reflections")
lines = [f"{label}:"]
for k in keys:
if k in d and d[k] not in (None, "", []):
v = " | ".join(d[k]) if isinstance(d[k], list) else d[k]
lines.append(f" {k}: {v}")
return "\n".join(lines)
def reflect(backend: Backend | None = None, session_id: str | None = None) -> dict:
"""Reflect on recent activity and update the self-state. Returns new state.
@@ -189,7 +203,7 @@ def reflect(backend: Backend | None = None, session_id: str | None = None) -> di
))
# Step 2 — examine her own draft and revise it into a more honest version.
update, critique = draft, None
update, critique, revised = draft, None, None
if draft:
examine_body = body + "\n\nYOUR DRAFT REFLECTION:\n" + json.dumps(draft, indent=2)
revised = _safe_json(llm.complete(
@@ -217,9 +231,18 @@ def reflect(backend: Backend | None = None, session_id: str | None = None) -> di
state["interaction_count"] = state.get("interaction_count", 0) + 1
memory.set_self_state(state)
logbus.log("info", "self-state updated", mood=state.get("mood"),
interactions=state["interaction_count"], parsed=bool(update),
critiqued=bool(critique))
# Surface the actual self-correction (draft -> revised -> critique) to the live
# log as an expandable block, so the two-step reflection is observable.
detail = (
_fmt_reflection("DRAFT (first pass)", draft) + "\n\n"
+ _fmt_reflection("REVISED (committed)",
revised if revised else None)
+ ("" if revised else "\n (examine step didn't parse — kept the draft)")
+ "\n\nSELF-CRITIQUE:\n " + (critique or "(none recorded this pass)")
)
logbus.log("info", "reflection", mood=state.get("mood"),
critiqued=bool(critique), detail=detail)
return state