fix(cli): preserve run_id, validate JSON shapes, clamp quality_score - #1135
fix(cli): preserve run_id, validate JSON shapes, clamp quality_score#1135lightzt99 wants to merge 1 commit into
Conversation
1. run_id dropped on import round trip
Memory.import_memories() forwarded messages/user_id/agent_id/metadata
but not run_id. Pass run_id through self.add() so session-scoped
memories survive a JSON/CSV export+import cycle.
2. JSON import accepts invalid top-level shapes
{"content": "one"} (a dict, not the supported array) decoded
successfully and imported 0/0 with exit code 0. import_from_json()
now raises ValueError on non-list top-level shapes; the CLI surfaces
it as a non-zero exit.
3. Interactive shell dropped --run-id on add/search/list/export
_cmd_export/_cmd_add/_cmd_search/_cmd_list all silently lost run_id.
All four now forward run_id (long --run-id and short -r) to the
underlying SDK call. Help text updated.
4. quality_score could go negative
Empty memories were counted as both empty and short (len 0 < 10),
yielding quality_score: -1.0 for a single empty memory. short_count
now requires 0 < len < 10, and the score is clamped to [0, 1] as a
backstop.
5. CLI --metadata / --filters accepted any JSON shape
add/search/list/update parsed the value with json.loads and passed it
straight through; '[]' or '"str"' would flow downstream with confusing
behavior. New _parse_json_dict() requires a JSON object and errors out
otherwise.
Tests: 12 new cases in tests/unit/test_cli_export_import.py covering
run_id round trip (JSON + CSV), invalid JSON top-level shape (unit +
CLI), interactive shell run_id forwarding (add/search/list/export, with
and without), quality_score non-negative for empty-only, and
metadata/filters shape validation (dict accepted, array/string rejected).
75 CLI unit tests pass; git diff --check clean.
wayyoungboy
left a comment
There was a problem hiding this comment.
Requesting changes because interactive mode still drops the session scope on the profile paths.
The PR now forwards --run-id for plain interactive add, search, list, and export, but add --with-profile --run-id ... still calls ctx.user_memory.add(...) without run_id, and search --add-profile --run-id ... still calls ctx.user_memory.search(...) without run_id. Both UserMemory.add() and UserMemory.search() support run_id, and the non-interactive CLI already forwards it in these profile modes.
I verified the current targeted CLI unit tests pass, but a minimal mocked interactive session still observes run_id=None for both profile calls. Please forward run_id in those two branches and add regression coverage for --with-profile --run-id and --add-profile --run-id.
|
Thanks for the PR. I do not think this is ready to merge yet because the previous run-id blocker still needs to be addressed. Please make sure the interactive profile paths preserve the session scope too:
Please also add regression coverage for both profile-mode paths. Once those are fixed, this should be ready for re-review. |
Summary
Five correctness fixes in the CLI paths for memory import/export, the interactive shell, the quality command, and JSON-shaped CLI options.
Fixes
1.
run_iddropped on import round tripMemory.import_memories()forwardedmessages/user_id/agent_id/metadatabut notrun_id. Session-scoped memories lost their run scope after a JSON/CSV export+import.src/powermem/core/memory.py: passrun_id=memory.get('run_id')toself.add()kwargs["run_id"]is preserved2. JSON import accepts invalid top-level shapes
{"content": "one"}(a dict, not the supported array) decoded successfully and imported0 succeeded, 0 failedwith exit code 0.src/powermem/utils/io.py:import_from_json()now raisesValueErroron non-list top-level shapesimportalready catchesExceptionand exits non-zerotest_import_json_dict_top_level_rejected, CLItest_import_dict_top_level_fails3. Interactive shell dropped
--run-idon add/search/list/export_cmd_export/_cmd_add/_cmd_search/_cmd_listall silently lostrun_id.src/powermem/cli/commands/interactive.py: all four now forwardrun_id(long--run-idand short-r) to the underlying SDK call.run_id=True(flag with no value) collapses toNone.--run-idTestInteractiveExportRunId(3) +TestInteractiveAddSearchListRunId(6) covering with/withoutrun_idfor all four commands4.
quality_scorecould go negativeEmpty memories were counted as both empty and short (
len 0 < 10), yieldingquality_score: -1.0for a single empty memory.src/powermem/cli/commands/memory.py:short_countnow requires0 < len(...) < 10so empty rows aren't double-penalized. Score also clamped to[0, 1]as a backstop.test_quality_score_non_negative_for_empty_onlyasserts0.0 ≤ quality_score ≤ 1.0for a single empty memory5. CLI
--metadata/--filtersaccepted any JSON shapeadd/search/list/updateparsed the value withjson.loadsand passed it straight through.'[]'or'"str"'would flow downstream as the wrong type with confusing behavior.src/powermem/cli/commands/memory.py: new_parse_json_dict()requires a JSON object and errors out otherwise (Invalid {field}: expected JSON object, got array)TestMetadataFiltersShapeValidation— dict accepted, array/string rejected foradd --metadata,search --filters,list --filtersTrailing whitespace
git diff --check upstream/main...HEADis clean across the full PR diff.Test plan
pytest tests/unit/test_cli_export_import.py tests/unit/test_cli_profile.py tests/unit/test_cli_config_show.py→ 75 passed (12 new)pytest tests/unit -q -k "io or import or export or quality or interactive"→ 277 passedgit diff --check upstream/main...HEADexit 0Files
src/powermem/core/memory.py—import_memoriesforwardsrun_idsrc/powermem/utils/io.py—import_from_jsonvalidates list top-levelsrc/powermem/cli/commands/interactive.py—run_idforwarded in add/search/list/export; help textsrc/powermem/cli/commands/memory.py—_parse_json_dictfor metadata/filters;quality_scoreclamped + non-double-penalized short counttests/unit/test_cli_export_import.py— 12 new regression tests