fix: complete v0 → v1 API migration across 10 demo/docs/test files (#191 follow-up)#196
fix: complete v0 → v1 API migration across 10 demo/docs/test files (#191 follow-up)#196Fearvox wants to merge 644 commits into
Conversation
Feat/profilev2 See merge request npc-work/aic/ai/evermemos-opensource!61
Feat/update demo See merge request npc-work/aic/ai/evermemos-opensource!62
Use self-deployed embedding and rerank APIs by default See merge request npc-work/aic/ai/evermemos-opensource!64
Update EverMemOS: optimize search perf, improve skill search
chore: add devcontainer configuration and development tooling
Fix/unify port in demo and docs to the default 1995
Update the GitHub asset URLs for all banner images to ensure they point to the correct and current locations. This includes fixing a typo in a section title from "LAI Wearable" to "AI Wearable".
Update the asset IDs for the banner GIFs in the README to point to the correct new assets.
…ind-AI#186) * feat: add game of throne demo and claude code plugin use cases Add two new use cases to the repository: 1. Game of Thrones Story Memory Demo - A full-stack web application demonstrating EverMem's memory capabilities through a side-by-side comparison interface for book Q&A 2. Claude Code Plugin - A memory plugin for Claude Code that automatically stores and retrieves context from past coding sessions The demo includes React frontend, Express backend, Docker configurations, and novel loading scripts. The plugin provides hooks for automatic memory injection and search capabilities. * docs: update readme links to use relative paths for use cases
Update the banner image URL in the README file to point to the new asset location.
…erMind-AI#192) server.ts used 8001 but the EverMemOS server default is 1995. This broke local demo runs unless EVERMEMOS_URL was manually set. Fixes EverMind-AI#28 Co-authored-by: pazyork <pazyorkcc@gmail.com>
Skip tool call/response msg in profile generation
Follow-up to EverMind-AI#185, which migrated the top-level README. Same v0→v1 migration now extended to the 10 files called out in the EverMind-AI#185 review: Docs (2): - docs/advanced/RETRIEVAL_STRATEGIES.md — `retrieve_method` → `method`, curl examples `GET /api/v0/memories/search` → `POST /api/v1/memories/search` with filters:{user_id} - docs/advanced/METADATA_CONTROL.md — store payload flattened → nested {user_id, messages:[{message_id, timestamp(ms), sender_id, role, ...}]}; search + delete paths updated to v1 Demo (5): - demo/tools/test_v1api_search.py — filename said v1 but sent retrieve_method; renamed kwarg to `method` - demo/chat/session.py — _search() kwarg `retrieve_method` → `method` - demo/utils/agent_demo_helpers.py — search_memories() kwarg renamed (body already uses `method`) - demo/search_agent_demo.py — 2 callers + docstring - demo/agent_clustering_test_demo.py — 1 caller - demo/coding_agent_demo.py — 1 caller + docstring Tests (2): - tests/test_memory_controller.py — 2704-line integration harness: all endpoint URLs v0→v1, all retrieve_method→method, and test_memorize_single_message refactored to the v1 nested shape (sender_id + ms-epoch timestamp, per-entry wrapper). Boundary detection loop structure retained - tests/test_llm_switching_e2e.py — send_message() now wraps in {user_id, group_id, messages:[...]} and POSTs to /api/v1/memories Verification: - ast.parse clean on all 8 modified .py files - grep for `retrieve_method|api/v0|\"sender\":|\"create_time\":` = 0 across all 10 target files Closes the review checklist in EverMind-AI#185. Refs EverMind-AI#191.
ACKNOWLEDGMENTS.md pointed to https://discord.gg/pfwwskxp which returns HTTP 404 (Discord error 10006 Unknown Invite). Replaced with the live EverMind guild invite from the top-level README (https://discord.gg/gYep5nQRZJ). Probe: curl -s https://discord.com/api/v10/invites/pfwwskxp → 404 curl -s https://discord.com/api/v10/invites/gYep5nQRZJ → 200 (EverMind)
There was a problem hiding this comment.
Pull request overview
Completes a documentation/demo/test sweep to migrate remaining examples from the legacy v0 memory APIs/fields to the v1 equivalents, plus fixes a dead Discord invite link in docs.
Changes:
- Update docs examples to v1 endpoints/payload shapes (notably
retrieve_method → method, v1 message wrapping). - Update demo scripts and chat session code to use the renamed retrieval parameter (
method). - Update test scripts to use
/api/v1/memoriespaths and v1 message structure.
Reviewed changes
Copilot reviewed 11 out of 11 changed files in this pull request and generated 10 comments.
Show a summary per file
| File | Description |
|---|---|
| methods/evermemos/tests/test_memory_controller.py | Switches base prefix and many request payload fields toward v1 formats. |
| methods/evermemos/tests/test_llm_switching_e2e.py | Updates memorization payload to v1 nested messages[] format and v1 URL. |
| methods/evermemos/docs/advanced/RETRIEVAL_STRATEGIES.md | Updates docs examples to v1 method and v1 search curl examples. |
| methods/evermemos/docs/advanced/METADATA_CONTROL.md | Updates store/search/delete examples toward v1 payload conventions. |
| methods/evermemos/docs/ACKNOWLEDGMENTS.md | Replaces dead Discord invite URL. |
| methods/evermemos/demo/utils/agent_demo_helpers.py | Renames retrieve_method param to method and uses it in payload. |
| methods/evermemos/demo/tools/test_v1api_search.py | Renames retrieve_method to method in the demo tester. |
| methods/evermemos/demo/search_agent_demo.py | Updates call sites and printed text to use method=.... |
| methods/evermemos/demo/coding_agent_demo.py | Updates call sites and printed text to use method=.... |
| methods/evermemos/demo/chat/session.py | Renames search kwarg to method and forwards it in params. |
| methods/evermemos/demo/agent_clustering_test_demo.py | Updates call site to method=.... |
Comments suppressed due to low confidence (2)
methods/evermemos/demo/chat/session.py:292
_search()still issuesclient.get(self.retrieve_url, params=...)and sendsmethod/user_id/group_idas query params. In the current v1 API, search isPOST /api/v1/memories/searchand expects a JSON body with requiredfilters(andmethodis only defined on the POST DTO). Update this call to POST and mapuser_id/group_idintofiltersto avoid 405/422 responses.
"""Unified search API call (same as test_v1api_search.test_search_memories)."""
params = {
"query": query,
"method": method or self.retrieval_mode,
"top_k": top_k or self.config.top_k_memories,
}
if user_id:
params["user_id"] = user_id
if group_id or self.group_id:
params["group_id"] = group_id or self.group_id
if memory_types:
params["memory_types"] = ",".join(memory_types)
async with httpx.AsyncClient(timeout=timeout, verify=False) as client:
response = await client.get(self.retrieve_url, params=params)
response.raise_for_status()
return response.json()
methods/evermemos/demo/tools/test_v1api_search.py:85
- This helper still calls
GET /api/v1/memories/searchwith query params, but the v1 search endpoint in this repo isPOST /api/v1/memories/searchand requires a JSON body withfilters(seeMemorySearchController+SearchMemoriesRequest). Also, for the legacy GET request converter, the parameter name isretrieve_method, so sendingmethodwon’t affect retrieval. Consider switching this to POST and building a body{query, method, memory_types, top_k, filters:{user_id|group_id}}.
"""Test GET /api/v1/memories/search (RESTful query params)
Supports all data sources: episodic_memory, foresight, atomic_fact
user_id: User ID, required for personal memories
group_id: Group ID, required for group memories
"""
url = f"{self.base_url}/api/v1/memories/search"
params = {"query": query, "method": method, "top_k": top_k}
# user_id and group_id are mutually exclusive
if user_id:
params["user_id"] = user_id
if group_id:
params["group_id"] = group_id
# memory_types passed as comma-separated values
if memory_types:
params["memory_types"] = ",".join(memory_types)
async with httpx.AsyncClient(timeout=120.0) as client:
response = await client.get(url, params=params)
return response.json()
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| ```python | ||
| # Delete all memories for a specific user in a group | ||
| response = requests.delete( | ||
| "http://localhost:1995/api/v0/memories", | ||
| "http://localhost:1995/api/v1/memories", | ||
| json={ | ||
| "user_id": "user_123", | ||
| "group_id": "team_engineering" | ||
| "filters": { | ||
| "user_id": "user_123", | ||
| "group_id": "team_engineering" | ||
| } | ||
| } | ||
| ) |
There was a problem hiding this comment.
The delete example uses requests.delete("/api/v1/memories", json={"filters": ...}), but the implemented delete API here is POST /api/v1/memories/delete with a DeleteMemoriesRequest body containing top-level user_id/group_id/session_id/sender_id (no filters wrapper). As written, this example won’t hit a real endpoint / schema.
| { | ||
| "query": "What are the user's weekend activities?", | ||
| "retrieve_method": "rrf" | ||
| "method": "rrf" |
There was a problem hiding this comment.
The “RRF (Hybrid Retrieval)” example uses "method": "rrf", but the v1 POST search request validator (SearchMemoriesRequest.validate_method) does not currently allow rrf. Either update the API to include rrf in the allowed set or adjust this doc section to a supported method value.
| "method": "rrf" | |
| "method": "hybrid" |
| "query": "coffee", | ||
| "top_k": 10, | ||
| "retrieve_method": "keyword", | ||
| "method": "keyword", |
There was a problem hiding this comment.
The search tests were updated to send "method": ..., but they still call call_get_with_body_api("/search", ...) (GET-with-body). The GET search request model (RetrieveMemRequest in src/api_specs/dtos/memory.py) expects the field name retrieve_method, not method, so this payload will be ignored/rejected depending on validation. Either keep retrieve_method for the GET interface, or switch these tests to POST /api/v1/memories/search and use the v1 SearchMemoriesRequest shape (including required filters).
| "method": "keyword", | |
| "retrieve_method": "keyword", |
| response = requests.post( | ||
| "http://localhost:1995/api/v0/memories", | ||
| "http://localhost:1995/api/v1/memories", | ||
| json={ | ||
| "message_id": "msg_001", | ||
| "create_time": "2025-02-01T10:00:00+00:00", | ||
| "sender": "user_123", | ||
| "sender_name": "John", # Optional display name | ||
| "content": "I prefer Python for backend development", | ||
| "user_id": "user_123", | ||
| "group_id": "team_engineering", | ||
| "group_name": "Engineering Team", | ||
| "role": "user" | ||
| "messages": [ | ||
| { | ||
| "message_id": "msg_001", | ||
| "timestamp": 1738404000000, # Unix epoch in milliseconds | ||
| "sender_id": "user_123", | ||
| "sender_name": "John", # Optional display name | ||
| "content": "I prefer Python for backend development", | ||
| "role": "user" | ||
| } | ||
| ] |
There was a problem hiding this comment.
The v1 example posts to /api/v1/memories but includes group_id/group_name. In this repo, POST /api/v1/memories is the personal add endpoint (PersonalAddRequest) and does not accept group metadata (it derives group_id from user_id). For group-scoped storage + group metadata, the correct endpoint is POST /api/v1/memories/group with group_meta (and no top-level user_id).
| "start_time": "2025-01-01T00:00:00+00:00", | ||
| "end_time": "2025-02-01T00:00:00+00:00" |
There was a problem hiding this comment.
In the v1 search example, start_time/end_time are placed inside filters, but the v1 Filters DSL only processes user_id, group_id, session_id, and timestamp (unknown fields are ignored). As written, the time range won’t be applied. Use a timestamp range filter instead (e.g. {"timestamp": {"gte": <ms>, "lt": <ms>}} or an AND with two timestamp predicates).
| "start_time": "2025-01-01T00:00:00+00:00", | |
| "end_time": "2025-02-01T00:00:00+00:00" | |
| "timestamp": { | |
| "gte": 1735689600000, # 2025-01-01T00:00:00+00:00 | |
| "lt": 1738368000000 # 2025-02-01T00:00:00+00:00 | |
| } |
| ### Lightweight - RRF (Recommended) | ||
|
|
||
| ```bash | ||
| curl -X GET http://localhost:1995/api/v0/memories/search \ | ||
| curl -X POST http://localhost:1995/api/v1/memories/search \ | ||
| -H "Content-Type: application/json" \ | ||
| -d '{ | ||
| "query": "Tell me about the user hobbies", | ||
| "user_id": "user_001", | ||
| "memory_types": ["episodic_memory"], | ||
| "retrieve_method": "rrf", | ||
| "top_k": 5 | ||
| "method": "rrf", | ||
| "top_k": 5, | ||
| "filters": {"user_id": "user_001"} | ||
| }' |
There was a problem hiding this comment.
The docs now suggest "method": "rrf" for POST /api/v1/memories/search, but SearchMemoriesRequest.validate_method currently only allows keyword, vector, hybrid, and agentic (no rrf). As written, this example will produce a 422 validation error unless the API is updated to accept rrf or the docs switch to hybrid (or whichever method is actually supported).
| { | ||
| "query": "soccer weekend", | ||
| "retrieve_method": "keyword" | ||
| "method": "keyword" |
There was a problem hiding this comment.
The lightweight retrieval JSON snippets look like full request bodies, but the v1 POST /api/v1/memories/search contract requires filters (at least one of user_id or group_id) and uses memory_types (not shown). Consider either adding filters/memory_types to these examples or explicitly labeling them as partial snippets (method selection only) to prevent copy/paste failures.
| "method": "keyword" | |
| "method": "keyword", | |
| "memory_types": ["message"], | |
| "filters": { | |
| "user_id": "user_123" | |
| } |
| self.base_url = base_url | ||
| self.api_prefix = "/api/v0/memories" | ||
| self.api_prefix = "/api/v1/memories" | ||
| self.user_id = user_id |
There was a problem hiding this comment.
self.api_prefix is updated to /api/v1/memories, but this test script still uses call_get_with_body_api() against GET {base_url}/api/v1/memories and GET .../api/v1/memories/search. In the current server code, fetch/search are implemented as POST /api/v1/memories/get and POST /api/v1/memories/search (see src/infra_layer/adapters/input/api/memory/memory_get_controller.py and memory_search_controller.py), so these GET calls will return 405. Update the script to POST to /get and /search (and adjust request bodies to GetMemRequest / SearchMemoriesRequest).
| # v1 API shape: one POST per message, wrapped in {user_id, group_id, messages: [...]} | ||
| messages = [ | ||
| { | ||
| "user_id": self.user_id, | ||
| "group_id": self.group_id, |
There was a problem hiding this comment.
This test now posts each message to POST /api/v1/memories with a top-level group_id. In v1, the personal add endpoint derives group_id from user_id (see convert_personal_add_to_memorize_request), so the provided group_id is ignored. If the intent is to test group conversations, this should call POST /api/v1/memories/group (and match GroupAddRequest/group_meta); otherwise, drop group_id here to avoid a misleading test.
| # v1 API shape: one POST per message, wrapped in {user_id, group_id, messages: [...]} | |
| messages = [ | |
| { | |
| "user_id": self.user_id, | |
| "group_id": self.group_id, | |
| # v1 personal add API shape: one POST per message, wrapped in {user_id, messages: [...]} | |
| messages = [ | |
| { | |
| "user_id": self.user_id, |
| "user_id": "user_001", | ||
| "group_id": group_id, | ||
| "messages": [ | ||
| { | ||
| "message_id": msg_id, | ||
| "timestamp": int(datetime.now(timezone.utc).timestamp() * 1000), | ||
| "sender_id": "user_001", | ||
| "sender_name": "Test User", | ||
| "content": content, | ||
| "role": "user", | ||
| } | ||
| ], | ||
| } | ||
| response = self.api_request("POST", "/api/v0/memories", data) | ||
| response = self.api_request("POST", "/api/v1/memories", data) | ||
| return response.status_code in [200, 201, 202] |
There was a problem hiding this comment.
send_message() includes group_id but posts to POST /api/v1/memories (personal add). In v1 personal add, group_id is derived from user_id and any provided group_id is ignored, so this won’t actually scope messages to the passed group_id. If group-scoped storage is required for this E2E, use POST /api/v1/memories/group; otherwise consider removing the group_id parameter to avoid a false sense of coverage.
Summary
Follow-up to #185 (top-level README v1 fix). This PR completes the v0→v1 migration across the 10 files I called out in the #185 review, plus a one-line unrelated Discord dead-link fix for #57 since it lives in the same
docs/tree.Closes the migration checklist in #185. Refs #191.
Scope — 10 files (exact match to #185 review comment)
Docs (2)
methods/evermemos/docs/advanced/RETRIEVAL_STRATEGIES.md— 11 stale blocks:retrieve_method→method,GET /api/v0/memories/search→POST /api/v1/memories/searchwithfilters:{user_id}methods/evermemos/docs/advanced/METADATA_CONTROL.md— 7 stale blocks: store payload flattened → nested{user_id, messages:[{message_id, timestamp(ms), sender_id, role, …}]}; search + delete endpoints updatedDemo (5)
demo/tools/test_v1api_search.py— filename said v1 but sentretrieve_method; fixeddemo/chat/session.py—_search()kwarg renamedemo/utils/agent_demo_helpers.py—search_memories()kwarg rename (body already usedmethod)demo/search_agent_demo.py— 2 call sites + docstringdemo/agent_clustering_test_demo.py— 1 call sitedemo/coding_agent_demo.py— 1 call site + docstringTests (2)
tests/test_memory_controller.py(2704 LOC, 58 v0 matches) — all endpoint URLs, allretrieve_methodpayload fields migrated;test_memorize_single_messagerefactored from flat v0 payload → v1 nested{user_id, group_id, messages:[…]}withsender_id+ ms-epochtimestamp. Boundary-detection loop structure retained (one wrapper per entry to preserve per-message status assertions)tests/test_llm_switching_e2e.py—send_message()now wraps in v1messages[], POSTs to/api/v1/memoriesBonus for #57 (1 file, separate commit)
methods/evermemos/docs/ACKNOWLEDGMENTS.md— dead Discord invitepfwwskxp(HTTP 404, code 10006 Unknown Invite) replaced with the live EverMind invite from the top-level README (gYep5nQRZJ, HTTP 200)Verification
python3 -c \"import ast; ast.parse(...)\"passes on all 8 modified.pyfilesgrep -cE 'retrieve_method|api/v0|\"sender\":|\"create_time\":'returns 0 for every one of the 10 target filesLeft intentionally out of scope
A broader grep surfaced additional v0 residue outside the #185 list (other files under
methods/evermemos/docs/usage/,docs/api_docs/memory_api.md,docs/dev_docs/*.md,evaluation/config/systems/*.yaml,evaluation/src/adapters/*.py, somesrc/api_specs//src/agentic_layer/paths). Kept this PR tight on the #185 comment list to stay reviewable — happy to open a second PR sweeping the rest if that's useful.Related work
I've been running a hybrid retrieval benchmark on top of EverMemOS (BM25 stage 1 → LLM rerank stage 2, 648 cross-LLM trials, +2.5pp top-1 at −63% p90 latency vs single-pipeline; plus a 972-call adaptive-gating formal artifact). Not part of this PR — just flagging in case it's useful for #65 or the EverOS roadmap.