Add memory management APIs for OpenViking: list, read, write, and delete memories

This commit is contained in:
2026-05-29 16:38:57 +08:00
parent 0ab2a35e16
commit 68b2513043
9 changed files with 578 additions and 3 deletions

View File

@ -152,6 +152,10 @@ API=http://127.0.0.1:1934/memory-system
| `POST` | `/sessions/{session_id}/extract` | 立即触发 OpenViking extract | 需要 | | `POST` | `/sessions/{session_id}/extract` | 立即触发 OpenViking extract | 需要 |
| `GET/POST` | `/sessions/{session_id}/context` | 查询 OpenViking 会话上下文,并用同一 query 搜索 EverOS 记忆 | 需要 | | `GET/POST` | `/sessions/{session_id}/context` | 查询 OpenViking 会话上下文,并用同一 query 搜索 EverOS 记忆 | 需要 |
| `GET/POST` | `/openviking/tasks/{task_id}` | 查询 OpenViking 后台任务状态 | 需要 | | `GET/POST` | `/openviking/tasks/{task_id}` | 查询 OpenViking 后台任务状态 | 需要 |
| `GET` | `/memories` | 列出 OpenViking memory URI | 需要 |
| `GET` | `/memories/content` | 读取某条 memory 内容 | 需要 |
| `POST` | `/memories` | 创建、覆盖或追加写入 memory | 需要 |
| `DELETE` | `/memories` | 删除某条 memory URI | 需要 |
| `POST` | `/resources` | 上传本地文件或远程 URL 到 OpenViking resources | 需要 | | `POST` | `/resources` | 上传本地文件或远程 URL 到 OpenViking resources | 需要 |
| `DELETE` | `/resources` | 删除 OpenViking resource URI | 需要 | | `DELETE` | `/resources` | 删除 OpenViking resource URI | 需要 |
| `POST` | `/search` | 同时搜索 OpenViking 和 EverOS 记忆 | 需要 | | `POST` | `/search` | 同时搜索 OpenViking 和 EverOS 记忆 | 需要 |
@ -357,6 +361,91 @@ curl -sS -X POST "$API/openviking/tasks/${TASK_ID}" \
}' }'
``` ```
### `GET /memories`
列出 OpenViking memory URI。网关会调用 OpenViking
```http
GET /api/v1/fs/ls?uri=viking://user/memories&recursive=true
X-API-Key: <user_key>
```
Query 参数:
| 参数 | 类型 | 必需 | 说明 |
|---|---|---:|---|
| `user_id` | string | 是 | 用户 ID |
| `user_key` | string | 是 | `/users` 返回的 user key |
| `uri` | string | 否 | 要列出的 memory 根 URI默认 `viking://user/memories` |
| `recursive` | bool | 否 | 是否递归列出,默认 `true` |
示例:
```bash
curl -sS -G "$API/memories" \
--data-urlencode "user_id=userA" \
--data-urlencode "user_key=$USER_KEY" \
--data-urlencode "uri=viking://user/memories" \
--data-urlencode "recursive=true"
```
### `GET /memories/content`
读取某条 memory 内容。先用 `/memories``/search` 找到 `viking://user/memories/...` URI再读取
```bash
curl -sS -G "$API/memories/content" \
--data-urlencode "user_id=userA" \
--data-urlencode "user_key=$USER_KEY" \
--data-urlencode "uri=viking://user/memories/preferences/python.md"
```
### `POST /memories`
创建、覆盖或追加写入 memory。网关会调用 OpenViking `/api/v1/content/write`,写入后由 OpenViking 刷新语义和向量索引。
请求体:
| 参数 | 类型 | 必需 | 说明 |
|---|---|---:|---|
| `user_id` | string | 是 | 用户 ID |
| `user_key` | string | 是 | `/users` 返回的 user key |
| `uri` | string | 是 | 目标 memory URI例如 `viking://user/memories/profile.md` |
| `content` | string | 是 | 要写入的 Markdown/text 内容 |
| `mode` | `create`/`replace`/`append` | 否 | 写入模式,默认 `create` |
| `wait` | bool | 否 | 是否等待索引刷新,默认 `true` |
覆盖修改:
```bash
curl -sS -X POST "$API/memories" \
-H "Content-Type: application/json" \
-d '{
"user_id": "userA",
"user_key": "'"$USER_KEY"'",
"uri": "viking://user/memories/preferences/python.md",
"content": "# Python 偏好\n\n用户偏好使用 Python 做数据分析,常用 pandas。",
"mode": "replace",
"wait": true
}'
```
追加补充时把 `mode` 改为 `append`;新增 memory 时可用默认的 `create`
### `DELETE /memories`
删除某条 memory。默认非递归删除如果 OpenViking 提示目标是目录或复合资源,再把 `recursive` 设为 `true`
```bash
curl -sS -X DELETE -G "$API/memories" \
--data-urlencode "user_id=userA" \
--data-urlencode "user_key=$USER_KEY" \
--data-urlencode "uri=viking://user/memories/preferences/python.md" \
--data-urlencode "recursive=false"
```
返回中的 `memory` 是 OpenViking 对应接口的原始响应。
### `POST /resources` ### `POST /resources`
上传文件资源到 OpenViking。网关只调用 OpenViking不写 EverOS。 上传文件资源到 OpenViking。网关只调用 OpenViking不写 EverOS。

View File

@ -5,6 +5,7 @@ from fastapi import APIRouter, Depends, HTTPException, Query, status
from .auth import verify_api_key from .auth import verify_api_key
from .schemas import ( from .schemas import (
MemoryWriteRequest,
MessageIngestRequest, MessageIngestRequest,
ProfileRequest, ProfileRequest,
ResourceUploadRequest, ResourceUploadRequest,
@ -80,6 +81,58 @@ async def delete_resource(
raise user_auth_error(exc) from exc raise user_auth_error(exc) from exc
@router.get("/memories")
async def list_memories(
user_id: str = Query(min_length=1),
user_key: str = Query(min_length=1),
uri: str = Query(default="viking://user/memories", min_length=1),
recursive: bool = Query(default=True),
service: MemorySystemService = Depends(get_service),
):
try:
return await service.list_memories(user_id, user_key, uri=uri, recursive=recursive)
except PermissionError as exc:
raise user_auth_error(exc) from exc
@router.get("/memories/content")
async def read_memory(
user_id: str = Query(min_length=1),
user_key: str = Query(min_length=1),
uri: str = Query(min_length=1),
service: MemorySystemService = Depends(get_service),
):
try:
return await service.read_memory(user_id, user_key, uri)
except PermissionError as exc:
raise user_auth_error(exc) from exc
@router.post("/memories")
async def write_memory(
request: MemoryWriteRequest,
service: MemorySystemService = Depends(get_service),
):
try:
return await service.write_memory(request)
except PermissionError as exc:
raise user_auth_error(exc) from exc
@router.delete("/memories")
async def delete_memory(
user_id: str = Query(min_length=1),
user_key: str = Query(min_length=1),
uri: str = Query(min_length=1),
recursive: bool = Query(default=False),
service: MemorySystemService = Depends(get_service),
):
try:
return await service.delete_memory(user_id, user_key, uri, recursive=recursive)
except PermissionError as exc:
raise user_auth_error(exc) from exc
@router.post("/sessions/{session_id}/commit") @router.post("/sessions/{session_id}/commit")
async def commit_session( async def commit_session(
session_id: str, session_id: str,

View File

@ -190,6 +190,62 @@ class OpenVikingMemorySystemClient:
response.raise_for_status() response.raise_for_status()
return response.json() return response.json()
async def list_memories(
self,
credential: OpenVikingCredential | str,
uri: str = "viking://user/memories",
recursive: bool = True,
) -> dict[str, Any]:
async with self._credential_client(credential) as client:
response = await client.get(
"/api/v1/fs/ls",
params={"uri": uri, "recursive": str(recursive).lower()},
)
response.raise_for_status()
return response.json()
async def read_memory(self, credential: OpenVikingCredential | str, uri: str) -> dict[str, Any]:
async with self._credential_client(credential) as client:
response = await client.get("/api/v1/content/read", params={"uri": uri})
response.raise_for_status()
return response.json()
async def write_memory(
self,
credential: OpenVikingCredential | str,
*,
uri: str,
content: str,
mode: str = "create",
wait: bool = True,
) -> dict[str, Any]:
async with self._credential_client(credential) as client:
response = await client.post(
"/api/v1/content/write",
json={
"uri": uri,
"content": content,
"mode": mode,
"wait": wait,
},
)
response.raise_for_status()
return response.json()
async def delete_memory(
self,
credential: OpenVikingCredential | str,
uri: str,
recursive: bool = False,
) -> dict[str, Any]:
async with self._credential_client(credential) as client:
response = await client.delete(
"/api/v1/fs",
params={"uri": uri, "recursive": str(recursive).lower()},
)
response.raise_for_status()
return response.json()
async def find(self, credential: OpenVikingCredential | str, query: str, limit: int) -> dict[str, Any]: async def find(self, credential: OpenVikingCredential | str, query: str, limit: int) -> dict[str, Any]:
user_id = credential.user_id if isinstance(credential, OpenVikingCredential) else None user_id = credential.user_id if isinstance(credential, OpenVikingCredential) else None
target_uri = f"viking://user/{user_id}/memories/" if user_id else "viking://user/memories/" target_uri = f"viking://user/{user_id}/memories/" if user_id else "viking://user/memories/"

View File

@ -7,6 +7,7 @@ from pydantic import BaseModel, Field
OperationStatus = Literal["success", "partial_success", "failed"] OperationStatus = Literal["success", "partial_success", "failed"]
MemoryWriteMode = Literal["create", "replace", "append"]
class MessageIngestRequest(BaseModel): class MessageIngestRequest(BaseModel):
@ -54,6 +55,15 @@ class ProfileRequest(BaseModel):
level: int = Field(default=2, ge=0) level: int = Field(default=2, ge=0)
class MemoryWriteRequest(BaseModel):
user_id: str = Field(min_length=1)
user_key: str = Field(min_length=1)
uri: str = Field(min_length=1)
content: str
mode: MemoryWriteMode = "create"
wait: bool = True
class ResourceUploadRequest(BaseModel): class ResourceUploadRequest(BaseModel):
user_id: str = Field(min_length=1) user_id: str = Field(min_length=1)
user_key: str = Field(min_length=1) user_key: str = Field(min_length=1)
@ -116,6 +126,12 @@ class ProfileResponse(BaseModel):
backends: dict[str, BackendStatus] backends: dict[str, BackendStatus]
class MemoryOperationResponse(BaseModel):
status: OperationStatus
memory: Any = None
backends: dict[str, BackendStatus]
class ResourceMutationResponse(BaseModel): class ResourceMutationResponse(BaseModel):
status: OperationStatus status: OperationStatus
resource: Any = None resource: Any = None

View File

@ -11,6 +11,8 @@ from .schemas import (
BackendStatus, BackendStatus,
CommitResponse, CommitResponse,
ExtractResponse, ExtractResponse,
MemoryOperationResponse,
MemoryWriteRequest,
MessageIngestRequest, MessageIngestRequest,
MessageIngestResponse, MessageIngestResponse,
ProfileResponse, ProfileResponse,
@ -76,6 +78,63 @@ class MemorySystemService:
resource = backends["openviking"].result if backends["openviking"].status == "success" else None resource = backends["openviking"].result if backends["openviking"].status == "success" else None
return ResourceMutationResponse(status=self._aggregate_status(backends), resource=resource, backends=backends) return ResourceMutationResponse(status=self._aggregate_status(backends), resource=resource, backends=backends)
async def list_memories(
self,
user_id: str,
user_key: str,
uri: str = "viking://user/memories",
recursive: bool = True,
) -> MemoryOperationResponse:
credential = self.openviking.credential_for_user(user_id, user_key)
backends = {
"openviking": await self._capture(lambda: self.openviking.list_memories(credential, uri, recursive)),
}
memory = backends["openviking"].result if backends["openviking"].status == "success" else None
return MemoryOperationResponse(status=self._aggregate_status(backends), memory=memory, backends=backends)
async def read_memory(
self,
user_id: str,
user_key: str,
uri: str,
) -> MemoryOperationResponse:
credential = self.openviking.credential_for_user(user_id, user_key)
backends = {
"openviking": await self._capture(lambda: self.openviking.read_memory(credential, uri)),
}
memory = backends["openviking"].result if backends["openviking"].status == "success" else None
return MemoryOperationResponse(status=self._aggregate_status(backends), memory=memory, backends=backends)
async def write_memory(self, request: MemoryWriteRequest) -> MemoryOperationResponse:
credential = self.openviking.credential_for_user(request.user_id, request.user_key)
backends = {
"openviking": await self._capture(
lambda: self.openviking.write_memory(
credential,
uri=request.uri,
content=request.content,
mode=request.mode,
wait=request.wait,
)
),
}
memory = backends["openviking"].result if backends["openviking"].status == "success" else None
return MemoryOperationResponse(status=self._aggregate_status(backends), memory=memory, backends=backends)
async def delete_memory(
self,
user_id: str,
user_key: str,
uri: str,
recursive: bool = False,
) -> MemoryOperationResponse:
credential = self.openviking.credential_for_user(user_id, user_key)
backends = {
"openviking": await self._capture(lambda: self.openviking.delete_memory(credential, uri, recursive)),
}
memory = backends["openviking"].result if backends["openviking"].status == "success" else None
return MemoryOperationResponse(status=self._aggregate_status(backends), memory=memory, backends=backends)
async def ingest_messages(self, request: MessageIngestRequest) -> MessageIngestResponse: async def ingest_messages(self, request: MessageIngestRequest) -> MessageIngestResponse:
messages = self._messages_from_request(request) messages = self._messages_from_request(request)
if not messages: if not messages:

View File

@ -61,6 +61,10 @@ Base path: `/memory-system`
| `POST` | `/sessions/{session_id}/extract` | Trigger OpenViking extract only | Yes | | `POST` | `/sessions/{session_id}/extract` | Trigger OpenViking extract only | Yes |
| `GET/POST` | `/sessions/{session_id}/context` | Read OpenViking session context plus EverOS recall | Yes | | `GET/POST` | `/sessions/{session_id}/context` | Read OpenViking session context plus EverOS recall | Yes |
| `GET` | `/openviking/tasks/{task_id}` | Poll OpenViking task status | Yes | | `GET` | `/openviking/tasks/{task_id}` | Poll OpenViking task status | Yes |
| `GET` | `/memories` | List OpenViking memory URIs under a memory root | Yes |
| `GET` | `/memories/content` | Read one OpenViking memory URI | Yes |
| `POST` | `/memories` | Create, replace, or append an OpenViking memory via `content/write` | Yes |
| `DELETE` | `/memories` | Delete an OpenViking memory URI via `fs` | Yes |
| `POST` | `/search` | Search OpenViking and EverOS | Yes | | `POST` | `/search` | Search OpenViking and EverOS | Yes |
| `GET` | `/users/{user_id}/profile` | Read EverOS profile | Yes | | `GET` | `/users/{user_id}/profile` | Read EverOS profile | Yes |
@ -147,6 +151,50 @@ GET with query parameters is also supported:
curl -sS "$BASE/memory-system/sessions/sessionA1/context?user_id=userA&user_key=$USER_KEY&query=我喜欢喝什么?&limit=10" curl -sS "$BASE/memory-system/sessions/sessionA1/context?user_id=userA&user_key=$USER_KEY&query=我喜欢喝什么?&limit=10"
``` ```
List memories:
```bash
curl -sS -G "$BASE/memory-system/memories" \
--data-urlencode "user_id=userA" \
--data-urlencode "user_key=$USER_KEY" \
--data-urlencode "uri=viking://user/memories" \
--data-urlencode "recursive=true"
```
Read memory:
```bash
curl -sS -G "$BASE/memory-system/memories/content" \
--data-urlencode "user_id=userA" \
--data-urlencode "user_key=$USER_KEY" \
--data-urlencode "uri=viking://user/memories/preferences/python.md"
```
Create, replace, or append memory:
```bash
curl -sS -X POST "$BASE/memory-system/memories" \
-H "Content-Type: application/json" \
-d '{
"user_id": "userA",
"user_key": "'"$USER_KEY"'",
"uri": "viking://user/memories/preferences/python.md",
"content": "# Python 偏好\n\n用户偏好使用 Python 做数据分析。",
"mode": "replace",
"wait": true
}'
```
Delete memory:
```bash
curl -sS -X DELETE -G "$BASE/memory-system/memories" \
--data-urlencode "user_id=userA" \
--data-urlencode "user_key=$USER_KEY" \
--data-urlencode "uri=viking://user/memories/preferences/python.md" \
--data-urlencode "recursive=false"
```
## Response Handling ## Response Handling
Top-level `status` is one of: Top-level `status` is one of:

View File

@ -68,8 +68,8 @@ class FakeAsyncClient:
self.calls.append(("post", self.api_key, self.headers, path, json, files)) self.calls.append(("post", self.api_key, self.headers, path, json, files))
return self.responses.pop(0) return self.responses.pop(0)
async def get(self, path: str) -> FakeResponse: async def get(self, path: str, params: dict | None = None) -> FakeResponse:
self.calls.append(("get", self.api_key, self.headers, path, None)) self.calls.append(("get", self.api_key, self.headers, path, params))
return self.responses.pop(0) return self.responses.pop(0)
async def delete(self, path: str, params: dict | None = None) -> FakeResponse: async def delete(self, path: str, params: dict | None = None) -> FakeResponse:
@ -573,6 +573,135 @@ def test_openviking_delete_resource_sends_uri_and_recursive_flag():
] ]
def test_openviking_list_memories_calls_fs_ls_with_recursive_flag():
client = OpenVikingMemorySystemClient(store=FakeStore())
calls = []
responses = [FakeResponse(200, {"status": "ok", "result": {"children": []}})]
client._client = lambda api_key, extra_headers=None, json_content_type=True: FakeAsyncClient( # type: ignore[method-assign]
calls,
responses,
api_key,
extra_headers or {},
)
credential = client.user_credential("tom-key", "tom")
result = asyncio.run(
client.list_memories(
credential,
uri="viking://user/memories",
recursive=True,
)
)
assert result == {"status": "ok", "result": {"children": []}}
assert calls == [
(
"get",
"tom-key",
{},
"/api/v1/fs/ls",
{"uri": "viking://user/memories", "recursive": "true"},
)
]
def test_openviking_read_memory_calls_content_read():
client = OpenVikingMemorySystemClient(store=FakeStore())
calls = []
responses = [FakeResponse(200, {"status": "ok", "result": {"content": "# Python"}})]
client._client = lambda api_key, extra_headers=None, json_content_type=True: FakeAsyncClient( # type: ignore[method-assign]
calls,
responses,
api_key,
extra_headers or {},
)
credential = client.user_credential("tom-key", "tom")
result = asyncio.run(client.read_memory(credential, "viking://user/memories/preferences/python.md"))
assert result == {"status": "ok", "result": {"content": "# Python"}}
assert calls == [
(
"get",
"tom-key",
{},
"/api/v1/content/read",
{"uri": "viking://user/memories/preferences/python.md"},
)
]
def test_openviking_write_memory_posts_content_write_mode_and_wait():
client = OpenVikingMemorySystemClient(store=FakeStore())
calls = []
responses = [FakeResponse(200, {"status": "ok", "result": {"uri": "viking://user/memories/profile.md"}})]
client._client = lambda api_key, extra_headers=None, json_content_type=True: FakeAsyncClient( # type: ignore[method-assign]
calls,
responses,
api_key,
extra_headers or {},
)
credential = client.user_credential("tom-key", "tom")
result = asyncio.run(
client.write_memory(
credential,
uri="viking://user/memories/profile.md",
content="# Profile\n\nLikes Python.",
mode="replace",
wait=True,
)
)
assert result == {"status": "ok", "result": {"uri": "viking://user/memories/profile.md"}}
assert calls == [
(
"post",
"tom-key",
{},
"/api/v1/content/write",
{
"uri": "viking://user/memories/profile.md",
"content": "# Profile\n\nLikes Python.",
"mode": "replace",
"wait": True,
},
None,
)
]
def test_openviking_delete_memory_defaults_non_recursive():
client = OpenVikingMemorySystemClient(store=FakeStore())
calls = []
responses = [FakeResponse(200, {"status": "ok", "result": {"estimated_deleted_count": 1}})]
client._client = lambda api_key, extra_headers=None, json_content_type=True: FakeAsyncClient( # type: ignore[method-assign]
calls,
responses,
api_key,
extra_headers or {},
)
credential = client.user_credential("tom-key", "tom")
result = asyncio.run(
client.delete_memory(
credential,
uri="viking://user/memories/preferences/python.md",
)
)
assert result == {"status": "ok", "result": {"estimated_deleted_count": 1}}
assert calls == [
(
"delete",
"tom-key",
{},
"/api/v1/fs",
{"uri": "viking://user/memories/preferences/python.md", "recursive": "false"},
)
]
def test_everos_assistant_payload_does_not_use_user_id_as_sender(): def test_everos_assistant_payload_does_not_use_user_id_as_sender():
client = EverOSMemorySystemClient() client = EverOSMemorySystemClient()

View File

@ -14,6 +14,8 @@ def test_memory_system_server_exposes_routes():
assert {"GET", "POST"} <= context_methods assert {"GET", "POST"} <= context_methods
assert "/memory-system/search" in paths assert "/memory-system/search" in paths
assert "/memory-system/resources" in paths assert "/memory-system/resources" in paths
assert "/memory-system/memories" in paths
assert "/memory-system/memories/content" in paths
assert "/memory-system/users/{user_id}/profile" in paths assert "/memory-system/users/{user_id}/profile" in paths
task_methods = { task_methods = {
method method
@ -36,6 +38,20 @@ def test_memory_system_server_exposes_routes():
for method in getattr(route, "methods", set()) for method in getattr(route, "methods", set())
} }
assert {"DELETE", "POST"} <= resource_methods assert {"DELETE", "POST"} <= resource_methods
memory_methods = {
method
for route in app.routes
if getattr(route, "path", "") == "/memory-system/memories"
for method in getattr(route, "methods", set())
}
memory_content_methods = {
method
for route in app.routes
if getattr(route, "path", "") == "/memory-system/memories/content"
for method in getattr(route, "methods", set())
}
assert {"DELETE", "GET", "POST"} <= memory_methods
assert {"GET"} <= memory_content_methods
def test_memory_system_messages_does_not_require_account_key_header(): def test_memory_system_messages_does_not_require_account_key_header():

View File

@ -1,6 +1,12 @@
import asyncio import asyncio
from memory_system_api.schemas import MessageIngestRequest, ResourceUploadRequest, SearchRequest, SessionContextRequest from memory_system_api.schemas import (
MemoryWriteRequest,
MessageIngestRequest,
ResourceUploadRequest,
SearchRequest,
SessionContextRequest,
)
from memory_system_api.service import MemorySystemService from memory_system_api.service import MemorySystemService
@ -120,6 +126,22 @@ class FakeOpenViking:
self.calls.append(("delete_resource", user_key, uri, recursive)) self.calls.append(("delete_resource", user_key, uri, recursive))
return {"status": "ok", "result": {"uri": uri, "estimated_deleted_count": 4}} return {"status": "ok", "result": {"uri": uri, "estimated_deleted_count": 4}}
async def list_memories(self, user_key: str, uri: str, recursive: bool = True) -> dict:
self.calls.append(("list_memories", user_key, uri, recursive))
return {"status": "ok", "result": {"children": [{"uri": "viking://user/memories/profile.md"}]}}
async def read_memory(self, user_key: str, uri: str) -> dict:
self.calls.append(("read_memory", user_key, uri))
return {"status": "ok", "result": {"uri": uri, "content": "# Profile"}}
async def write_memory(self, user_key: str, uri: str, content: str, mode: str, wait: bool = True) -> dict:
self.calls.append(("write_memory", user_key, uri, content, mode, wait))
return {"status": "ok", "result": {"uri": uri, "mode": mode}}
async def delete_memory(self, user_key: str, uri: str, recursive: bool = False) -> dict:
self.calls.append(("delete_memory", user_key, uri, recursive))
return {"status": "ok", "result": {"uri": uri, "estimated_deleted_count": 1}}
class FakeEverOS: class FakeEverOS:
def __init__(self, fail_on_append: bool = False): def __init__(self, fail_on_append: bool = False):
@ -334,6 +356,93 @@ def test_delete_resource_delegates_to_openviking_only():
assert everos.calls == [] assert everos.calls == []
def test_list_memories_delegates_to_openviking_only():
openviking = FakeOpenViking()
everos = FakeEverOS()
service = MemorySystemService(openviking=openviking, everos=everos)
response = asyncio.run(service.list_memories(
user_id="tom",
user_key="tom-key",
uri="viking://user/memories",
recursive=True,
))
assert response.status == "success"
assert response.memory == {"status": "ok", "result": {"children": [{"uri": "viking://user/memories/profile.md"}]}}
assert openviking.calls == [
("credential_for_user", "tom", "tom-key", None),
("list_memories", "key-tom", "viking://user/memories", True),
]
assert everos.calls == []
def test_read_memory_delegates_to_openviking_only():
openviking = FakeOpenViking()
everos = FakeEverOS()
service = MemorySystemService(openviking=openviking, everos=everos)
response = asyncio.run(service.read_memory(
user_id="tom",
user_key="tom-key",
uri="viking://user/memories/profile.md",
))
assert response.status == "success"
assert response.memory == {"status": "ok", "result": {"uri": "viking://user/memories/profile.md", "content": "# Profile"}}
assert openviking.calls == [
("credential_for_user", "tom", "tom-key", None),
("read_memory", "key-tom", "viking://user/memories/profile.md"),
]
assert everos.calls == []
def test_write_memory_delegates_to_openviking_content_write_only():
openviking = FakeOpenViking()
everos = FakeEverOS()
service = MemorySystemService(openviking=openviking, everos=everos)
response = asyncio.run(service.write_memory(MemoryWriteRequest(
user_id="tom",
user_key="tom-key",
uri="viking://user/memories/profile.md",
content="# Profile\n\nLikes Python.",
mode="replace",
wait=True,
)))
assert response.status == "success"
assert response.memory == {"status": "ok", "result": {"uri": "viking://user/memories/profile.md", "mode": "replace"}}
assert openviking.calls == [
("credential_for_user", "tom", "tom-key", None),
("write_memory", "key-tom", "viking://user/memories/profile.md", "# Profile\n\nLikes Python.", "replace", True),
]
assert everos.calls == []
def test_delete_memory_delegates_to_openviking_only_and_defaults_non_recursive():
openviking = FakeOpenViking()
everos = FakeEverOS()
service = MemorySystemService(openviking=openviking, everos=everos)
response = asyncio.run(service.delete_memory(
user_id="tom",
user_key="tom-key",
uri="viking://user/memories/preferences/python.md",
))
assert response.status == "success"
assert response.memory == {
"status": "ok",
"result": {"uri": "viking://user/memories/preferences/python.md", "estimated_deleted_count": 1},
}
assert openviking.calls == [
("credential_for_user", "tom", "tom-key", None),
("delete_memory", "key-tom", "viking://user/memories/preferences/python.md", False),
]
assert everos.calls == []
def test_search_removes_vectors_from_items_and_backend_results(): def test_search_removes_vectors_from_items_and_backend_results():
service = MemorySystemService(openviking=FakeOpenViking(), everos=FakeEverOSWithVector()) service = MemorySystemService(openviking=FakeOpenViking(), everos=FakeEverOSWithVector())