From de68b4c04e8f2d1b2dc1a4a4f941eaa511a792f9 Mon Sep 17 00:00:00 2001 From: Fixer Date: Sat, 1 Aug 2026 03:26:07 +0200 Subject: [PATCH] pinky_memory: reflect() falls back to 'fact' on invalid type instead of crashing Dream runs occasionally call reflect() with a plausible-sounding but invalid type (observed: 'session_log'), which raised an unhandled ValueError from ReflectionType() and aborted the entire dream run rather than just failing that one memory write. Co-Authored-By: Claude Sonnet 5 --- src/pinky_memory/server.py | 16 ++++++++++++++-- tests/test_memory_server.py | 10 ++++++++++ 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/src/pinky_memory/server.py b/src/pinky_memory/server.py index e6632edb..4469cdde 100644 --- a/src/pinky_memory/server.py +++ b/src/pinky_memory/server.py @@ -32,6 +32,18 @@ def _log(msg: str) -> None: print(msg, file=sys.stderr, flush=True) +def _parse_reflection_type(type_str: str) -> ReflectionType: + """Coerce a caller-supplied type to ReflectionType, defaulting to `fact` + on an unrecognized value instead of raising. Callers (including dream + runs) occasionally pass a plausible-sounding but invalid type; failing + hard here previously aborted the entire dream run (#session_log bug).""" + try: + return ReflectionType(type_str) + except ValueError: + _log(f"reflect: invalid type={type_str!r}, defaulting to 'fact'") + return ReflectionType.fact + + # Strict agent-name slug for cross-agent memory targets (#614/#145). Mirrors # the trust-boundary slug discipline tracked in #105 — defends the # store-factory path resolution against traversal / injection even though the @@ -369,7 +381,7 @@ def reflect( """ input_data = ReflectInput( content=content, - type=ReflectionType(type), + type=_parse_reflection_type(type), context=context, project=project, salience=salience, @@ -430,7 +442,7 @@ def reflect_for( s = _resolve_target_store(target_agent) input_data = ReflectInput( content=content, - type=ReflectionType(type), + type=_parse_reflection_type(type), context=context, project=project, salience=salience, diff --git a/tests/test_memory_server.py b/tests/test_memory_server.py index 2f72f700..3c90ac25 100644 --- a/tests/test_memory_server.py +++ b/tests/test_memory_server.py @@ -189,6 +189,16 @@ def test_reflect_with_all_fields(self, srv): assert result["salience"] == 5 assert result["type"] == "project_state" + def test_reflect_invalid_type_falls_back_to_fact(self, srv): + """Regression: an unrecognized type (e.g. dream-hallucinated 'session_log') + must not raise — it previously crashed the whole dream run.""" + result = json.loads(_tools(srv)["reflect"]( + content="something the dream agent decided to call session_log", + type="session_log", + )) + assert result["stored"] is True + assert result["type"] == "fact" + def test_reflect_supersedes(self, srv, store): # First memory r1 = json.loads(_tools(srv)["reflect"](content="old fact", type="fact"))