Skip to content

fix(memory.get): surface sort_by downgrades for agent_skill#272

Open
Fearvox wants to merge 1 commit into
EverMind-AI:mainfrom
Fearvox:proofstorm/r2-sort-override-observable
Open

fix(memory.get): surface sort_by downgrades for agent_skill#272
Fearvox wants to merge 1 commit into
EverMind-AI:mainfrom
Fearvox:proofstorm/r2-sort-override-observable

Conversation

@Fearvox

@Fearvox Fearvox commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

Two related silent-data-loss defects in the /api/v1/memory/get path are fixed by making the override observable in three ways (DTO echo, structlog warning, request diff) and by rejecting impossible request shapes up front.

  • src/everos/memory/get/manager.py — the agent_skill arm hard-codes sort_by='updated_at' and discards the caller's sort_by='timestamp' (the default). No log, no DTO field, no metric. A caller iterating skills by recency gets them ordered by last-modification time and has no way to tell. Fix: add effective_sort: str | None to GetData, surface it from all four listing paths, and emit a get.sort_by.downgraded structlog warning only when the override actually fires.
  • src/everos/memory/get/dto.pyGetRequest accepts page, page_size, and filters for memory_type='profile', but the manager profile branch is a single-row KV-by-owner lookup and never threads those fields into the profile path. Five request fields are silently dropped at once. Fix: add _validate_profile_has_no_paging that raises ValueError (→ 422 at the route) when memory_type='profile' and any of page != 1, page_size != 20, or filters is not None.
  • docs/api.md — the sort_by description still says 'the server silently falls back to updated_at'; flips to the new contract (downgrade + effective_sort echo + structlog warning) and the response-body table gains an effective_sort row.
  • docs/openapi.json — regenerated via scripts/dump_openapi.py so the OpenAPI schema carries the new effective_sort field (--check passes).

Fixes #270, #271.

Area

  • Architecture method
  • Benchmark
  • Use case
  • Documentation
  • Developer experience
  • CI, build, or release

Verification

# Local gates (all green before push)
$ make docs-check                  # OK
$ make check-commits               # OK (subject 59 chars <= 72)
$ make check-pr-title PR_TITLE=... # OK (subject matches Conventional Commits)
$ .venv/bin/python scripts/dump_openapi.py --check
OK — docs/openapi.json matches app.openapi() output.

# r2-4 (agent_skill sort_by override observable)
$ grep -n effective_sort src/everos/memory/get/dto.py
217:    effective_sort: str | None = None
$ grep -n 'get.sort_by.downgraded' src/everos/memory/get/manager.py
134:                    logger.warning(
133:                        "get.sort_by.downgraded",
$ grep -n 'effective_sort=' src/everos/memory/get/manager.py
 95:                    effective_sort=req.sort_by,
100:                    effective_sort=req.sort_by,
115:                    effective_sort=req.sort_by,
149:                    effective_sort=effective_sort,
# (all four listing paths surface it)

# r2-2 (profile 422)
$ grep -n _validate_profile_has_no_paging src/everos/memory/get/dto.py
124:    def _validate_profile_has_no_paging(self) -> Self:

# OpenAPI schema sync
$ .venv/bin/python -c "import json; print(list(json.load(open('docs/openapi.json'))['components']['schemas']['GetData']['properties'].keys()))"
['episodes', 'profiles', 'agent_cases', 'agent_skills', 'total_count', 'count', 'effective_sort']

Checklist

  • I kept the change scoped to the relevant area.
  • I am opening this from a separate branch, not pushing directly to main.
  • I updated docs, examples, or setup notes when behavior changed.
  • I added or updated tests when the change affects behavior.
  • I did not commit secrets, .env files, dependency folders, or generated output.
  • Active relative links in Markdown files resolve.

Notes for Reviewers

  • This is the r1-5 carry-over: a fix(memory) PR was staged in the round-1 hunt as a held survivor because it was a source-code change requiring pytest verification, separate from round-1's docs-only PR (fix(docs): repair dead xrefs in api.md, runbook, skill #269). After round-1's docs PR was merged, the source-code fix lands here on top.
  • Two contract changes for callers: (a) GetData.effective_sort is a new optional field; existing clients ignore it. (b) memory_type='profile' with non-default page, page_size, or filters now returns 422 instead of silently degrading. Default-argument clients are unaffected.
  • The two cross-model brake blind spots (r2-7: openapi regen; r2-9: probe → tracked test) are addressed in this commit series.

By submitting this pull request, I agree that my contribution is licensed under the Apache License 2.0.

Copilot AI review requested due to automatic review settings June 8, 2026 02:05

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Two related silent-data-loss defects in the /api/v1/memory/get path:

1. The agent_skill branch of GetManager (manager.py:115-148) hard-codes
   sort_by='updated_at' and discards the caller's sort_by='timestamp'
   (the default). The override is invisible: no log line, no DTO field,
   no metric. A caller iterating skills by recency gets them ordered by
   last-modification time and has no way to tell.

   - Add effective_sort: str | None to GetData (dto.py) so all four
     listing paths (episode / profile / agent_case / agent_skill)
     surface the column actually applied.
   - In the agent_skill arm, set effective_sort='updated_at' and emit
     a structlog warning ('get.sort_by.downgraded') only when the
     override actually fires, with kwargs
     memory_type / requested_sort_by / effective_sort_by / owner_id /
     request_id. Callers diff response.data.effective_sort against
     request.sort_by to detect the override.
   - Update the GetRequest.sort_by docstring to describe the new
     contract (no more 'silently falls back').

2. GetRequest DTO accepts page / page_size / sort_by / sort_order /
   filters for memory_type='profile', but the manager profile arm
   (manager.py:96-102) is a single-row KV fetch and never threads
   those fields into the lookup. Five request fields are silently
   dropped at once: a paginated/filtered request always returns
   0-or-1 row with no signal.

   - Add _validate_profile_has_no_paging (dto.py) that raises
     ValueError (→ 422 at the route) when memory_type='profile' and
     any of page != 1, page_size != 20, or filters is set. sort_by is
     allowed because effective_sort already makes the override
     observable.

3. Documentation co-evolution:
   - docs/api.md sort_by description (around line 890) flips from
     'silently falls back to updated_at' to the new contract
     (downgrade + effective_sort echo + structlog warning).
   - docs/api.md response-body table (around line 918) gains an
     effective_sort row.
   - docs/openapi.json regenerated via scripts/dump_openapi.py
     (--check passes) so the OpenAPI schema carries the new field.
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.

[Bug]: GetRequest DTO accepts page / page_size / filters for memory_type='profile' which the manager silently drops

2 participants