Skip to content

fix(cli): preserve run_id, validate JSON shapes, clamp quality_score - #1135

Open
lightzt99 wants to merge 1 commit into
oceanbase:mainfrom
lightzt99:fix/cli-pr1092-followup
Open

fix(cli): preserve run_id, validate JSON shapes, clamp quality_score#1135
lightzt99 wants to merge 1 commit into
oceanbase:mainfrom
lightzt99:fix/cli-pr1092-followup

Conversation

@lightzt99

Copy link
Copy Markdown
Collaborator

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_id dropped on import round trip

Memory.import_memories() forwarded messages/user_id/agent_id/metadata but not run_id. Session-scoped memories lost their run scope after a JSON/CSV export+import.

  • src/powermem/core/memory.py: pass run_id=memory.get('run_id') to self.add()
  • Regression tests: JSON + CSV round-trip asserts kwargs["run_id"] is preserved

2. JSON import accepts invalid top-level shapes

{"content": "one"} (a dict, not the supported array) decoded successfully and imported 0 succeeded, 0 failed with exit code 0.

  • src/powermem/utils/io.py: import_from_json() now raises ValueError on non-list top-level shapes
  • CLI import already catches Exception and exits non-zero
  • Tests: unit test_import_json_dict_top_level_rejected, CLI test_import_dict_top_level_fails

3. Interactive shell dropped --run-id on add/search/list/export

_cmd_export/_cmd_add/_cmd_search/_cmd_list all silently lost run_id.

  • src/powermem/cli/commands/interactive.py: all four now forward run_id (long --run-id and short -r) to the underlying SDK call. run_id=True (flag with no value) collapses to None.
  • Help text updated to advertise --run-id
  • Tests: TestInteractiveExportRunId (3) + TestInteractiveAddSearchListRunId (6) covering with/without run_id for all four commands

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.

  • src/powermem/cli/commands/memory.py: short_count now requires 0 < len(...) < 10 so empty rows aren't double-penalized. Score also clamped to [0, 1] as a backstop.
  • Test: test_quality_score_non_negative_for_empty_only asserts 0.0 ≤ quality_score ≤ 1.0 for a single empty memory

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 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)
  • Tests: TestMetadataFiltersShapeValidation — dict accepted, array/string rejected for add --metadata, search --filters, list --filters

Trailing whitespace

git diff --check upstream/main...HEAD is 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.py75 passed (12 new)
  • pytest tests/unit -q -k "io or import or export or quality or interactive" → 277 passed
  • git diff --check upstream/main...HEAD exit 0

Files

  • src/powermem/core/memory.pyimport_memories forwards run_id
  • src/powermem/utils/io.pyimport_from_json validates list top-level
  • src/powermem/cli/commands/interactive.pyrun_id forwarded in add/search/list/export; help text
  • src/powermem/cli/commands/memory.py_parse_json_dict for metadata/filters; quality_score clamped + non-double-penalized short count
  • tests/unit/test_cli_export_import.py — 12 new regression tests

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 wayyoungboy left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Member

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:

  • add --with-profile --run-id ... should pass run_id into UserMemory.add().
  • search --add-profile --run-id ... should pass run_id into UserMemory.search().

Please also add regression coverage for both profile-mode paths. Once those are fixed, this should be ready for re-review.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants