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"))