def reflection_handler(capability: Any) -> Callable[[Any], Any]:
"""Persist insights after ordinary memory publication, never before egress.
A reflection outage leaves the completed response intact and records a
payload-free stage reason. This is a post-consolidation memory action, not
a pre-egress metacognitive verdict.
"""
async def handle(context: Any) -> StageResult[Any]:
request = getattr(context, "request", None)
turn = getattr(context, "turn", None)
if request is None or turn is None:
return no_change("reflection received no completed turn")
if getattr(turn, "tool_requests", ()):
return no_change("reflection waits for the final model round")
scope = getattr(request, "scope", None) or capability.scope
try:
result = await capability.reflection.reflect(
ReflectionRequest(
scope=scope,
user_message=request.prompt,
assistant_message=turn.text,
)
)
except Exception as exc: # noqa: BLE001 - reflection cannot revoke a response
return no_change(
f"reflection degraded: {type(exc).__name__}",
counts={"extracted": 0, "published": 0},
)
if getattr(result, "degraded", False):
return no_change(
"reflection degraded: collaborator unavailable",
counts={"extracted": 0, "published": 0},
)
records = tuple(getattr(result, "records", ()) or ())
if not records:
return no_change(
getattr(result, "reason", "") or "reflection extracted no durable insights",
counts={
"extracted": 0,
"published": 0,
"discarded": len(getattr(result, "dropped", ()) or ()),
},
)
try:
receipt = await capability.writer.write(WriteRequest(scope=scope, records=records))
if getattr(receipt, "degraded", False):
return no_change(
"reflection degraded: memory store unavailable",
counts={"extracted": len(records), "published": 0},
)
published = await capability.lifecycle.flush(scope)
except Exception as exc: # noqa: BLE001 - same side-effect boundary
return no_change(
f"reflection degraded: {type(exc).__name__}",
counts={"extracted": len(records), "published": 0},
)
if getattr(published, "degraded", False):
return no_change(
"reflection degraded: memory store unavailable",
counts={"extracted": len(records), "published": 0},
)
committed = tuple(getattr(published, "committed", ()) or ())
return applied(
ResolvedInput(capability=HMS_CAPABILITY, value=committed, provenance=scope.path),
counts={
"extracted": len(records),
"published": len(committed),
"discarded": len(getattr(result, "dropped", ()) or ()),
},
)
return handle