From 42de7f9da069a255af9eac2ffac1cda3fa772e81 Mon Sep 17 00:00:00 2001 From: tomtan Date: Fri, 12 Jun 2026 16:31:08 +0800 Subject: [PATCH] neutralize upstream service branding --- .env.example | 18 +- .gitignore | 2 +- README.md | 90 ++++----- core/__init__.py | 2 +- core/api.py | 24 +-- core/{everos_client.py => backend_client.py} | 2 +- core/config.py | 24 +-- core/service.py | 30 +-- ...026-06-12-upstream-brand-neutralization.md | 62 ++++++ ...12-upstream-brand-neutralization-design.md | 23 +++ everos.env.example | 36 ---- pyproject.toml | 4 +- skill/memory-gateway-agent/SKILL.md | 6 +- skill/memory-gateway-agent/references/api.md | 12 +- ...gration.py => test_backend_integration.py} | 30 +-- tests/test_branding.py | 35 ++++ tests/test_command.md | 14 +- tests/test_gateway.py | 182 +++++++++--------- 18 files changed, 340 insertions(+), 256 deletions(-) rename core/{everos_client.py => backend_client.py} (98%) create mode 100644 docs/superpowers/plans/2026-06-12-upstream-brand-neutralization.md create mode 100644 docs/superpowers/specs/2026-06-12-upstream-brand-neutralization-design.md delete mode 100644 everos.env.example rename tests/{test_everos_integration.py => test_backend_integration.py} (62%) create mode 100644 tests/test_branding.py diff --git a/.env.example b/.env.example index de013cc..7dd0e71 100644 --- a/.env.example +++ b/.env.example @@ -1,13 +1,13 @@ -# EverOS HTTP server used by the gateway client. -EVEROS_BASE_URL=http://127.0.0.1:1995 +# Upstream memory service used by the gateway client. +MEMORY_GATEWAY_BACKEND_BASE_URL=http://127.0.0.1:1995 -# Gateway-owned SQLite database. This does not point at EverOS internal storage. +# Gateway-owned SQLite database. This does not point at upstream internal storage. MEMORY_GATEWAY_DB_PATH=./data/memory_gateway.sqlite3 -# Raw uploaded files are stored here before being passed to EverOS by file URI. +# Raw uploaded files are stored here for gateway-managed ingestion. MEMORY_GATEWAY_STORAGE_DIR=./data/storage -# Number of resource session IDs sent per EverOS search request. +# Number of resource session IDs sent per upstream search request. MEMORY_GATEWAY_RESOURCE_SEARCH_BATCH_SIZE=50 # Max upload size in bytes. Default here is 25 MiB. @@ -16,10 +16,10 @@ MEMORY_GATEWAY_MAX_UPLOAD_BYTES=26214400 # Comma-separated MIME allowlist. Prefix wildcards such as image/* are supported. MEMORY_GATEWAY_ALLOWED_MIME_TYPES=image/*,audio/*,application/pdf,text/html,application/xhtml+xml,text/plain,text/markdown,text/csv,application/json,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document,application/vnd.ms-excel,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet,application/vnd.ms-powerpoint,application/vnd.openxmlformats-officedocument.presentationml.presentation -# EverOS add/flush retry policy during resource ingestion. -MEMORY_GATEWAY_EVEROS_INGEST_ATTEMPTS=3 -MEMORY_GATEWAY_EVEROS_RETRY_DELAY_SECONDS=0.25 -MEMORY_GATEWAY_EVEROS_TIMEOUT_SECONDS=120 +# Upstream add/flush retry policy during resource ingestion. +MEMORY_GATEWAY_BACKEND_INGEST_ATTEMPTS=3 +MEMORY_GATEWAY_BACKEND_RETRY_DELAY_SECONDS=0.25 +MEMORY_GATEWAY_BACKEND_TIMEOUT_SECONDS=120 # API server settings used by python main.py. MEMORY_GATEWAY_HOST=0.0.0.0 diff --git a/.gitignore b/.gitignore index d8274de..969ba72 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # Local environment files .env -everos.env +backend.env *.env.local # Gateway runtime data diff --git a/README.md b/README.md index 498816f..b715468 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,18 @@ # Memory Gateway -Memory Gateway 是一个轻量级 FastAPI 服务,用于在 EverOS 现有 +Memory Gateway 是一个轻量级 FastAPI 服务,用于在上游记忆服务现有 `/api/v1/memory/add`、`/api/v1/memory/flush`、`/api/v1/memory/search` 能力之上构建用户资源记忆层。 它只维护 Gateway 自己的 SQLite 元数据表、软删除记录和手动覆盖记录, -不会直接修改 EverOS 的 Markdown、SQLite 或 LanceDB 内部文件。 +不会直接修改上游记忆服务的 Markdown、SQLite 或 LanceDB 内部文件。 ## 功能范围 - 上传用户资源:文件、图片、音频、PDF、HTML、普通文档、纯文本。 - 保存资源元数据到 SQLite。 -- 为每个资源生成独立 EverOS `session_id`。 -- 调用 EverOS `add` 和 `flush` 完成资源记忆摄入,并对临时失败做轻量重试。 +- 为每个资源生成独立的上游记忆服务 `session_id`。 +- 调用上游记忆服务的 `add` 和 `flush` 完成资源记忆摄入,并对临时失败做轻量重试。 - 提供资源列表、详情、软删除。 - 支持上传大小限制、MIME 白名单、同用户同 app/project 下按 sha256 幂等复用资源。 - 编排记忆搜索,支持当前聊天、资源记忆、全部用户记忆。 @@ -28,7 +28,7 @@ Memory Gateway 是一个轻量级 FastAPI 服务,用于在 EverOS 现有 │ ├── api.py # FastAPI 路由 │ ├── config.py # 环境变量配置 │ ├── db.py # SQLite schema 初始化 -│ ├── everos_client.py # EverOS HTTP client +│ ├── backend_client.py # 上游记忆服务 HTTP client │ ├── repository.py # SQLite 读写 │ └── service.py # 业务编排 ├── main.py # Python 启动入口 @@ -50,21 +50,21 @@ cp .env.example .env | 变量 | 默认值 | 说明 | |---|---|---| -| `EVEROS_BASE_URL` | `http://127.0.0.1:1995` | EverOS API 服务地址;EverOS 可监听 `0.0.0.0:1995`,本机客户端通常连接 `127.0.0.1:1995` | +| `MEMORY_GATEWAY_BACKEND_BASE_URL` | `http://127.0.0.1:1995` | 上游记忆服务 API 地址;服务端可监听 `0.0.0.0:1995`,本机客户端通常连接 `127.0.0.1:1995` | | `MEMORY_GATEWAY_DB_PATH` | `./data/memory_gateway.sqlite3` | Gateway 自己的 SQLite 数据库路径 | | `MEMORY_GATEWAY_STORAGE_DIR` | `./data/storage` | 用户上传原始文件保存路径 | | `MEMORY_GATEWAY_RESOURCE_SEARCH_BATCH_SIZE` | `50` | resources scope 搜索时每批 session_id 数量 | | `MEMORY_GATEWAY_MAX_UPLOAD_BYTES` | `26214400` | 单个上传文件最大字节数,默认 25MB | | `MEMORY_GATEWAY_ALLOWED_MIME_TYPES` | 常见图片、音频、PDF、HTML、文本和 Office 文档 | 逗号分隔的上传 MIME 白名单,支持 `image/*` 这类前缀匹配 | -| `MEMORY_GATEWAY_EVEROS_INGEST_ATTEMPTS` | `3` | EverOS `add` 和 `flush` 各自最多重试次数 | -| `MEMORY_GATEWAY_EVEROS_RETRY_DELAY_SECONDS` | `0.25` | EverOS 摄入重试间隔秒数 | -| `MEMORY_GATEWAY_EVEROS_TIMEOUT_SECONDS` | `120` | 单次 EverOS HTTP 请求超时秒数 | +| `MEMORY_GATEWAY_BACKEND_INGEST_ATTEMPTS` | `3` | 上游记忆服务 `add` 和 `flush` 各自最多重试次数 | +| `MEMORY_GATEWAY_BACKEND_RETRY_DELAY_SECONDS` | `0.25` | 上游记忆服务摄入重试间隔秒数 | +| `MEMORY_GATEWAY_BACKEND_TIMEOUT_SECONDS` | `120` | 单次上游记忆服务 HTTP 请求超时秒数 | | `MEMORY_GATEWAY_HOST` | `127.0.0.1` | Gateway API 监听地址 | | `MEMORY_GATEWAY_PORT` | `8010` | Gateway API 监听端口 | | `MEMORY_GATEWAY_RELOAD` | `false` | 是否启用 uvicorn reload,开发时可设为 `true` | 注意:`MEMORY_GATEWAY_DB_PATH` 和 `MEMORY_GATEWAY_STORAGE_DIR` 是 Gateway -自己的存储位置,不要配置成 EverOS 的内部存储目录。 +自己的存储位置,不要配置成上游记忆服务的内部存储目录。 ## 安装依赖 @@ -133,7 +133,7 @@ Gateway 会通过 `memory_gateway.api` logger 为每个 API 请求输出一条 J GET /health ``` -该接口不需要 `user_id` 或 `user_key`,用于确认 Gateway API 是否可响应,以及上游 EverOS 是否可访问。 +该接口不需要 `user_id` 或 `user_key`,用于确认 Gateway API 是否可响应,以及上游记忆服务是否可访问。 请求示例: @@ -141,7 +141,7 @@ GET /health curl http://127.0.0.1:8010/health ``` -EverOS 正常时响应示例: +上游记忆服务正常时响应示例: ```json { @@ -149,7 +149,7 @@ EverOS 正常时响应示例: "api": { "status": "ok" }, - "everos": { + "backend": { "status": "ok", "base_url": "http://127.0.0.1:1995", "data": { @@ -159,7 +159,7 @@ EverOS 正常时响应示例: } ``` -EverOS 不可访问时仍返回 HTTP 200,但 `status` 会变成 `degraded`,便于区分“Gateway API 活着”和“上游 EverOS 故障”: +上游记忆服务不可访问时仍返回 HTTP 200,但 `status` 会变成 `degraded`,便于区分“Gateway API 活着”和“上游记忆服务故障”: ```json { @@ -167,7 +167,7 @@ EverOS 不可访问时仍返回 HTTP 200,但 `status` 会变成 `degraded`, "api": { "status": "ok" }, - "everos": { + "backend": { "status": "unavailable", "base_url": "http://127.0.0.1:1995", "error": "Connection refused" @@ -221,8 +221,8 @@ Content-Type: multipart/form-data |---|---|---|---|---| | `user_id` | string | 是 | 无 | 用户 ID | | `user_key` | string | 是 | 无 | 用户 key | -| `app_id` | string | 否 | `default` | EverOS app scope | -| `project_id` | string | 否 | `default` | EverOS project scope | +| `app_id` | string | 否 | `default` | 上游记忆服务 app scope | +| `project_id` | string | 否 | `default` | 上游记忆服务 project scope | | `file` | file | 是 | 无 | 上传资源文件 | | `title` | string | 否 | `null` | 资源标题 | | `description` | string | 否 | `null` | 资源描述 | @@ -234,22 +234,22 @@ Content-Type: multipart/form-data 3. 生成 `resource_id`。 4. 生成 `session_id = resource:{user_id}:{resource_id}`。 5. 写入 `user_resources`,状态为 `ingesting`。 -6. 根据 MIME 类型映射 EverOS content type。 -7. 构造 EverOS content item:文本类上传以内联 `text` 发送,非文本上传以内联 `base64` 发送,不要求 EverOS 访问 Gateway 本地 `file://` 路径。 -8. 调用 EverOS `/api/v1/memory/add`。 -9. 调用 EverOS `/api/v1/memory/flush`。 +6. 根据 MIME 类型映射上游记忆服务 content type。 +7. 构造上游记忆服务 content item:文本类上传以内联 `text` 发送,非文本上传以内联 `base64` 发送,不要求上游记忆服务访问 Gateway 本地 `file://` 路径。 +8. 调用上游记忆服务的 `/api/v1/memory/add`。 +9. 调用上游记忆服务的 `/api/v1/memory/flush`。 10. 成功后状态改为 `extracted`,失败后状态改为 `failed`。 上传策略: - 文件会按流式方式写入磁盘,超过 `MEMORY_GATEWAY_MAX_UPLOAD_BYTES` 会返回 `413`,不会写入资源记录。 - MIME 类型不在 `MEMORY_GATEWAY_ALLOWED_MIME_TYPES` 白名单内会返回 `415`。 -- 同一用户在同一 `app_id`、`project_id` 下重复上传相同 sha256 的活跃资源,会直接返回已有资源,避免重复调用 EverOS 摄入。 -- EverOS `add` 和 `flush` 临时失败时会分别按配置重试;单次请求受 `MEMORY_GATEWAY_EVEROS_TIMEOUT_SECONDS` 控制;全部失败后资源状态为 `failed`,并记录 `error_message`。 +- 同一用户在同一 `app_id`、`project_id` 下重复上传相同 sha256 的活跃资源,会直接返回已有资源,避免重复调用上游记忆服务摄入。 +- 上游记忆服务的 `add` 和 `flush` 临时失败时会分别按配置重试;单次请求受 `MEMORY_GATEWAY_BACKEND_TIMEOUT_SECONDS` 控制;全部失败后资源状态为 `failed`,并记录 `error_message`。 content type 映射: -| 文件类型 | EverOS content type | +| 文件类型 | 上游记忆服务 content type | |---|---| | `image/*` | `image` | | `audio/*` | `audio` | @@ -406,7 +406,7 @@ DELETE /resources/{resource_id}?user_id={user_id}&user_key={user_key} - 设置 `status = deleted`。 - 后续 `resources` scope 搜索会排除该资源的 `session_id`。 - 清理 Gateway 自己在 `MEMORY_GATEWAY_STORAGE_DIR` 下保存的原始上传文件。 -- 不物理删除 EverOS 内部记忆或索引。 +- 不物理删除上游记忆服务内部记忆或索引。 请求示例: @@ -441,9 +441,9 @@ Content-Type: application/json | `query` | string | 是 | 无 | 搜索问题 | | `conversation_id` | string | 否 | `null` | `scope` 包含 `current_chat` 时使用 | | `scope` | string[] | 否 | `["current_chat", "resources"]` | 搜索范围 | -| `top_k` | integer | 否 | `8` | 每次 EverOS 搜索返回数量,范围 `1..100` | -| `app_id` | string | 否 | `default` | EverOS app scope | -| `project_id` | string | 否 | `default` | EverOS project scope | +| `top_k` | integer | 否 | `8` | 每次上游记忆服务搜索返回数量,范围 `1..100` | +| `app_id` | string | 否 | `default` | 上游记忆服务 app scope | +| `project_id` | string | 否 | `default` | 上游记忆服务 project scope | `scope` 支持: @@ -472,9 +472,9 @@ curl -X POST http://127.0.0.1:8010/memories/search \ 搜索编排逻辑: -1. `current_chat`:调用 EverOS search,过滤 `filters.session_id = chat:{conversation_id}`。 -2. `resources`:先查当前用户的 `user_resources`,只取 `status = extracted` 且未删除资源;再按批次调用 EverOS search,过滤这些资源的 `session_id`。 -3. `all_user_memory`:调用 EverOS search,不加 `session_id` 过滤。 +1. `current_chat`:调用上游记忆服务 search,过滤 `filters.session_id = chat:{conversation_id}`。 +2. `resources`:先查当前用户的 `user_resources`,只取 `status = extracted` 且未删除资源;再按批次调用上游记忆服务 search,过滤这些资源的 `session_id`。 +3. `all_user_memory`:调用上游记忆服务 search,不加 `session_id` 过滤。 4. 合并结果。 5. 过滤 `memory_tombstones` 命中的 `memory_id` 或 `session_id`。 6. 应用 active `memory_overrides`,把 `text` 替换为 `override_text`。 @@ -495,7 +495,7 @@ curl -X POST http://127.0.0.1:8010/memories/search \ "raw": { "id": "mem_abc", "session_id": "resource:u_123:r_xxx", - "episode": "原始 EverOS 返回内容" + "episode": "原始上游记忆服务返回内容" } } ] @@ -518,7 +518,7 @@ Content-Type: application/json | `session_id` | string | 是 | memory 所属 session,必须属于当前用户 | | `override_text` | string | 是 | 修正后的记忆文本 | -该接口只写入或更新 `memory_overrides`,不会修改 EverOS 原始文件。写入前会校验 `session_id` 属于当前用户:当前版本支持当前用户的 `resource:{user_id}:{resource_id}` 和 `memory_edit:{user_id}`。后续搜索结果命中该 `memory_id` 时,返回的 `text` 会替换为 `override_text`,但保留原始 memory id。 +该接口只写入或更新 `memory_overrides`,不会修改上游记忆服务原始文件。写入前会校验 `session_id` 属于当前用户:当前版本支持当前用户的 `resource:{user_id}:{resource_id}` 和 `memory_edit:{user_id}`。后续搜索结果命中该 `memory_id` 时,返回的 `text` 会替换为 `override_text`,但保留原始 memory id。 请求示例: @@ -559,7 +559,7 @@ Content-Type: application/json | `session_id` | string | 是 | memory 所属 session,必须属于当前用户 | | `reason` | string | 否 | 删除原因 | -该接口只写入 `memory_tombstones`,不会修改 EverOS 原始文件。写入前会校验 `session_id` 属于当前用户:当前版本支持当前用户的 `resource:{user_id}:{resource_id}` 和 `memory_edit:{user_id}`。后续搜索结果如果命中 tombstone 的 `memory_id` 或 `session_id`,会被过滤。 +该接口只写入 `memory_tombstones`,不会修改上游记忆服务原始文件。写入前会校验 `session_id` 属于当前用户:当前版本支持当前用户的 `resource:{user_id}:{resource_id}` 和 `memory_edit:{user_id}`。后续搜索结果如果命中 tombstone 的 `memory_id` 或 `session_id`,会被过滤。 请求示例: @@ -584,9 +584,9 @@ curl -X DELETE http://127.0.0.1:8010/memories/mem_abc \ } ``` -## EverOS client 封装 +## 上游记忆服务客户端封装 -Gateway 内部通过 `core/everos_client.py` 调用 EverOS: +Gateway 内部通过 `core/backend_client.py` 调用上游记忆服务: - `add_memory(payload)` -> `POST /api/v1/memory/add` - `flush_memory(session_id, app_id, project_id)` -> `POST /api/v1/memory/flush` @@ -619,21 +619,21 @@ cd /home/tom/memory-gateway .venv/bin/python -B -m pytest -q -p no:cacheprovider ``` -默认测试不会访问真实 EverOS。若要对已部署的 EverOS 做 health 集成验证,先确认 EverOS 正在监听 `0.0.0.0:1995`,然后从 Gateway 所在机器用客户端可访问地址访问: +默认测试不会访问真实上游记忆服务。若要对已部署的上游记忆服务做 health 集成验证,先确认上游记忆服务正在监听 `0.0.0.0:1995`,然后从 Gateway 所在机器用客户端可访问地址访问: ```bash cd /home/tom/memory-gateway -RUN_EVEROS_INTEGRATION=1 \ -EVEROS_BASE_URL=http://10.6.80.123:1995 \ -.venv/bin/python -B -m pytest -q tests/test_everos_integration.py -p no:cacheprovider +RUN_BACKEND_INTEGRATION=1 \ +MEMORY_GATEWAY_BACKEND_BASE_URL=http://10.6.80.123:1995 \ +.venv/bin/python -B -m pytest -q tests/test_backend_integration.py -p no:cacheprovider ``` -真实 add/flush 上传会写入 EverOS,且可能受上游解析、LLM、embedding 服务耗时影响。需要验证完整摄入链路时再打开第二层开关: +真实 add/flush 上传会写入上游记忆服务,且可能受上游解析、LLM、embedding 服务耗时影响。需要验证完整摄入链路时再打开第二层开关: ```bash cd /home/tom/memory-gateway -RUN_EVEROS_INTEGRATION=1 \ -RUN_EVEROS_INGEST_INTEGRATION=1 \ -EVEROS_BASE_URL=http://10.6.80.123:1995 \ -.venv/bin/python -B -m pytest -q tests/test_everos_integration.py -p no:cacheprovider +RUN_BACKEND_INTEGRATION=1 \ +RUN_BACKEND_INGEST_INTEGRATION=1 \ +MEMORY_GATEWAY_BACKEND_BASE_URL=http://10.6.80.123:1995 \ +.venv/bin/python -B -m pytest -q tests/test_backend_integration.py -p no:cacheprovider ``` diff --git a/core/__init__.py b/core/__init__.py index 0a1bc4c..9c7a8e6 100644 --- a/core/__init__.py +++ b/core/__init__.py @@ -1,4 +1,4 @@ -"""Lightweight Memory Gateway for EverOS.""" +"""Lightweight user resource memory gateway.""" from __future__ import annotations diff --git a/core/api.py b/core/api.py index 5111c99..dfe3bad 100644 --- a/core/api.py +++ b/core/api.py @@ -13,7 +13,7 @@ from starlette.responses import Response from .config import GatewayConfig from .db import init_db -from .everos_client import EverOSClient +from .backend_client import BackendClient from .repository import MemoryRepository from .service import MemoryGatewayService, UnsupportedContentType, UploadTooLarge @@ -181,21 +181,21 @@ def _body_for_log(body: bytes, content_type: str | None) -> Any: def create_app( *, config: GatewayConfig | None = None, - everos_client: Any | None = None, + backend_client: Any | None = None, ) -> FastAPI: cfg = config or GatewayConfig.from_env() init_db(cfg.database_path) repository = MemoryRepository(cfg.database_path) - client = everos_client or EverOSClient( - cfg.everos_base_url, - timeout=cfg.everos_timeout_seconds, + client = backend_client or BackendClient( + cfg.backend_base_url, + timeout=cfg.backend_timeout_seconds, ) service = MemoryGatewayService(cfg, repository, client) app = FastAPI(title="memory-gateway", version="0.1.0") app.state.config = cfg app.state.repository = repository - app.state.everos_client = client + app.state.backend_client = client app.state.gateway_service = service router = APIRouter() @@ -278,24 +278,24 @@ def create_app( @router.get("/health") async def health() -> dict[str, Any]: try: - everos_health = await client.health_check() + backend_health = await client.health_check() except Exception as exc: return { "status": "degraded", "api": {"status": "ok"}, - "everos": { + "backend": { "status": "unavailable", - "base_url": cfg.everos_base_url, + "base_url": cfg.backend_base_url, "error": str(exc), }, } return { "status": "ok", "api": {"status": "ok"}, - "everos": { + "backend": { "status": "ok", - "base_url": cfg.everos_base_url, - "data": everos_health, + "base_url": cfg.backend_base_url, + "data": backend_health, }, } diff --git a/core/everos_client.py b/core/backend_client.py similarity index 98% rename from core/everos_client.py rename to core/backend_client.py index 92145e9..adbd6e7 100644 --- a/core/everos_client.py +++ b/core/backend_client.py @@ -5,7 +5,7 @@ from typing import Any import httpx -class EverOSClient: +class BackendClient: def __init__(self, base_url: str, timeout: float = 120.0) -> None: self.base_url = base_url.rstrip("/") self.timeout = timeout diff --git a/core/config.py b/core/config.py index 2e24b47..c318f73 100644 --- a/core/config.py +++ b/core/config.py @@ -27,15 +27,15 @@ _DEFAULT_ALLOWED_MIME_TYPES = ( @dataclass(frozen=True) class GatewayConfig: - everos_base_url: str = "http://127.0.0.1:1995" + backend_base_url: str = "http://127.0.0.1:1995" database_path: Path = _PROJECT_ROOT / "data" / "memory_gateway.sqlite3" storage_dir: Path = _PROJECT_ROOT / "data" / "storage" resource_search_batch_size: int = 50 max_upload_bytes: int = 25 * 1024 * 1024 allowed_mime_types: tuple[str, ...] = _DEFAULT_ALLOWED_MIME_TYPES - everos_ingest_attempts: int = 3 - everos_retry_delay_seconds: float = 0.25 - everos_timeout_seconds: float = 120.0 + backend_ingest_attempts: int = 3 + backend_retry_delay_seconds: float = 0.25 + backend_timeout_seconds: float = 120.0 @classmethod def from_env(cls) -> GatewayConfig: @@ -48,8 +48,8 @@ class GatewayConfig: if item.strip() ) return cls( - everos_base_url=os.environ.get( - "EVEROS_BASE_URL", + backend_base_url=os.environ.get( + "MEMORY_GATEWAY_BACKEND_BASE_URL", "http://127.0.0.1:1995", ).rstrip("/"), database_path=Path( @@ -71,13 +71,13 @@ class GatewayConfig: os.environ.get("MEMORY_GATEWAY_MAX_UPLOAD_BYTES", str(25 * 1024 * 1024)) ), allowed_mime_types=allowed_mime_types, - everos_ingest_attempts=int( - os.environ.get("MEMORY_GATEWAY_EVEROS_INGEST_ATTEMPTS", "3") + backend_ingest_attempts=int( + os.environ.get("MEMORY_GATEWAY_BACKEND_INGEST_ATTEMPTS", "3") ), - everos_retry_delay_seconds=float( - os.environ.get("MEMORY_GATEWAY_EVEROS_RETRY_DELAY_SECONDS", "0.25") + backend_retry_delay_seconds=float( + os.environ.get("MEMORY_GATEWAY_BACKEND_RETRY_DELAY_SECONDS", "0.25") ), - everos_timeout_seconds=float( - os.environ.get("MEMORY_GATEWAY_EVEROS_TIMEOUT_SECONDS", "120") + backend_timeout_seconds=float( + os.environ.get("MEMORY_GATEWAY_BACKEND_TIMEOUT_SECONDS", "120") ), ) diff --git a/core/service.py b/core/service.py index af3bb7f..9f25652 100644 --- a/core/service.py +++ b/core/service.py @@ -127,11 +127,11 @@ class MemoryGatewayService: self, config: GatewayConfig, repository: MemoryRepository, - everos_client: Any, + backend_client: Any, ) -> None: self.config = config self.repository = repository - self.everos_client = everos_client + self.backend_client = backend_client def create_user(self, user_id: str) -> dict[str, Any]: user_key = f"uk_{secrets.token_urlsafe(32)}" @@ -204,8 +204,8 @@ class MemoryGatewayService: ) try: - await self._retry_everos_call( - lambda: self.everos_client.add_memory( + await self._retry_backend_call( + lambda: self.backend_client.add_memory( self._build_add_payload( resource=resource, user_id=user_id, @@ -215,8 +215,8 @@ class MemoryGatewayService: ) ) ) - await self._retry_everos_call( - lambda: self.everos_client.flush_memory(session_id, app_id, project_id) + await self._retry_backend_call( + lambda: self.backend_client.flush_memory(session_id, app_id, project_id) ) except Exception as exc: failed = self.repository.update_resource_status( @@ -229,8 +229,8 @@ class MemoryGatewayService: extracted = self.repository.update_resource_status(resource_id, "extracted") return self._resource_summary(extracted or resource) - async def _retry_everos_call(self, operation: Any) -> Any: - attempts = max(1, self.config.everos_ingest_attempts) + async def _retry_backend_call(self, operation: Any) -> Any: + attempts = max(1, self.config.backend_ingest_attempts) last_error: Exception | None = None for attempt in range(attempts): try: @@ -239,11 +239,11 @@ class MemoryGatewayService: last_error = exc if attempt == attempts - 1: break - delay = self.config.everos_retry_delay_seconds + delay = self.config.backend_retry_delay_seconds if delay > 0: await asyncio.sleep(delay) if last_error is None: - raise RuntimeError("EverOS operation failed") + raise RuntimeError("upstream memory service operation failed") raise last_error def _build_add_payload( @@ -367,7 +367,7 @@ class MemoryGatewayService: ) results.extend( self._extract_results( - await self.everos_client.search_memory(payload), + await self.backend_client.search_memory(payload), source_scope="current_chat", session_resource_map=session_resource_map, user_id=user_id, @@ -393,7 +393,7 @@ class MemoryGatewayService: ) results.extend( self._extract_results( - await self.everos_client.search_memory(payload), + await self.backend_client.search_memory(payload), source_scope="resources", session_resource_map=session_resource_map, user_id=user_id, @@ -411,7 +411,7 @@ class MemoryGatewayService: ) results.extend( self._extract_results( - await self.everos_client.search_memory(payload), + await self.backend_client.search_memory(payload), source_scope="all_user_memory", session_resource_map=session_resource_map, user_id=user_id, @@ -438,7 +438,7 @@ class MemoryGatewayService: } return { "session_id": session_id, - "everos": await self.everos_client.add_memory(payload), + "backend": await self.backend_client.add_memory(payload), } async def flush_memory( @@ -450,7 +450,7 @@ class MemoryGatewayService: ) -> dict[str, Any]: return { "session_id": session_id, - "everos": await self.everos_client.flush_memory( + "backend": await self.backend_client.flush_memory( session_id, app_id, project_id, diff --git a/docs/superpowers/plans/2026-06-12-upstream-brand-neutralization.md b/docs/superpowers/plans/2026-06-12-upstream-brand-neutralization.md new file mode 100644 index 0000000..26d5132 --- /dev/null +++ b/docs/superpowers/plans/2026-06-12-upstream-brand-neutralization.md @@ -0,0 +1,62 @@ +# Upstream Brand Neutralization Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Remove the upstream product identity from the current Memory Gateway files without changing the upstream memory HTTP protocol. + +**Architecture:** Replace product-specific names with `backend` terminology at configuration, client, service, API, test, and documentation boundaries. Enforce the result with a repository-level regression test that scans both file names and text content. + +**Tech Stack:** Python 3, FastAPI, pytest, Markdown, environment configuration. + +--- + +### Task 1: Add the neutral-brand regression test + +**Files:** +- Create: `tests/test_branding.py` + +- [x] Add a test that constructs the forbidden token from separate string fragments. +- [x] Scan non-generated project file names and UTF-8 text contents. +- [x] Run the test and verify it fails against the existing product-specific names. + +### Task 2: Rename runtime boundaries + +**Files:** +- Rename: `core/backend_client.py` to `core/backend_client.py` +- Modify: `core/api.py` +- Modify: `core/config.py` +- Modify: `core/service.py` +- Modify: `core/__init__.py` +- Modify: `.env.example` +- Modify: `.gitignore` +- Delete: `backend.env.example` + +- [x] Rename the client class, dependency attributes, retry helpers, and configuration fields to `backend` terminology. +- [x] Rename environment variables to `MEMORY_GATEWAY_BACKEND_*`. +- [x] Rename health and direct add/flush response fields to `backend`. +- [x] Preserve all `/api/v1/memory/*` paths. + +### Task 3: Rename tests and public documentation + +**Files:** +- Rename: `tests/test_backend_integration.py` to `tests/test_backend_integration.py` +- Modify: `tests/test_gateway.py` +- Modify: `tests/test_command.md` +- Modify: `README.md` +- Modify: `pyproject.toml` +- Modify: `skill/memory-gateway-agent/SKILL.md` +- Modify: `skill/memory-gateway-agent/references/api.md` + +- [x] Rename fixtures, tests, environment flags, examples, and expected JSON fields. +- [x] Describe the dependency only as an upstream memory service. +- [x] Update integration commands and package metadata. + +### Task 4: Verify the current working tree + +**Files:** +- Modify: `docs/superpowers/plans/2026-06-12-upstream-brand-neutralization.md` + +- [x] Remove generated bytecode caches. +- [x] Run the branding regression test. +- [x] Run the full test suite and Python compilation. +- [x] Run a final case-insensitive content and file-name scan, excluding `.git` history. diff --git a/docs/superpowers/specs/2026-06-12-upstream-brand-neutralization-design.md b/docs/superpowers/specs/2026-06-12-upstream-brand-neutralization-design.md new file mode 100644 index 0000000..75c66b0 --- /dev/null +++ b/docs/superpowers/specs/2026-06-12-upstream-brand-neutralization-design.md @@ -0,0 +1,23 @@ +# Upstream Brand Neutralization Design + +## Goal + +Remove the upstream product name from the current Memory Gateway working tree while preserving the upstream HTTP protocol and application behavior. + +## Scope + +- Rename the upstream client module, class, configuration fields, environment variables, state attributes, response fields, tests, and integration test file to neutral `backend` terminology. +- Rewrite README, Skill documentation, examples, package metadata, and test records to describe an "upstream memory service". +- Remove the upstream-specific environment example because its variable names identify the product. +- Preserve `/api/v1/memory/add`, `/api/v1/memory/flush`, and `/api/v1/memory/search` paths. +- Do not rewrite Git history. + +## Compatibility + +This is an intentional configuration and response-schema rename. Deployments must move to `MEMORY_GATEWAY_BACKEND_*` variables, and health/add/flush consumers must read the `backend` field. No legacy aliases are retained because they would defeat the neutralization requirement. + +## Verification + +- Add an automated repository scan that rejects the forbidden upstream token in current files and file names. +- Run the full unit suite and compilation checks. +- Run a final case-insensitive repository scan excluding `.git`, virtual environments, runtime data, and generated bytecode. diff --git a/everos.env.example b/everos.env.example deleted file mode 100644 index a8a1d97..0000000 --- a/everos.env.example +++ /dev/null @@ -1,36 +0,0 @@ -# EverOS server settings used by the upstream EverOS process. -# Copy this file to everos.env or to the EverOS project .env, then fill secrets. - -# API listener. The Memory Gateway should point EVEROS_BASE_URL at this host/port. -EVEROS_API__HOST=127.0.0.1 -EVEROS_API__PORT=8000 - -# Logging -EVEROS_LOG_LEVEL=INFO -EVEROS_LOG_FORMAT=console -TZ=Asia/Shanghai - -# LLM provider -EVEROS_LLM__BASE_URL=https://api.openai.com/v1 -EVEROS_LLM__API_KEY=replace-with-llm-api-key -EVEROS_LLM__MODEL=gpt-4o-mini -EVEROS_LLM__TIMEOUT_SECONDS=120 - -# Embedding provider -EVEROS_EMBEDDING__BASE_URL=https://api.openai.com/v1 -EVEROS_EMBEDDING__API_KEY=replace-with-embedding-api-key -EVEROS_EMBEDDING__MODEL=text-embedding-3-small -EVEROS_EMBEDDING__TIMEOUT_SECONDS=120 - -# Rerank provider -EVEROS_RERANK__BASE_URL=https://api.example.com/v1 -EVEROS_RERANK__API_KEY=replace-with-rerank-api-key -EVEROS_RERANK__MODEL=replace-with-rerank-model -EVEROS_RERANK__TIMEOUT_SECONDS=120 - -# Multimodal parsing provider -EVEROS_MULTIMODAL__BASE_URL=https://api.openai.com/v1 -EVEROS_MULTIMODAL__API_KEY=replace-with-multimodal-api-key -EVEROS_MULTIMODAL__MODEL=gpt-4o-mini -EVEROS_MULTIMODAL__TIMEOUT_SECONDS=120 -EVEROS_MULTIMODAL__RESIZE_IMAGES_FOR_VLM=true diff --git a/pyproject.toml b/pyproject.toml index a52bfe4..f60e662 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "memory-gateway" version = "0.1.0" -description = "Lightweight Memory Gateway for EverOS user resources" +description = "Lightweight user resource memory gateway" requires-python = ">=3.10" dependencies = [ "fastapi>=0.104.0", @@ -31,5 +31,5 @@ testpaths = ["tests"] python_files = ["test_*.py"] asyncio_mode = "auto" markers = [ - "integration: tests that call a real EverOS service", + "integration: tests that call a real upstream memory service", ] diff --git a/skill/memory-gateway-agent/SKILL.md b/skill/memory-gateway-agent/SKILL.md index 6d068ab..898d5f4 100644 --- a/skill/memory-gateway-agent/SKILL.md +++ b/skill/memory-gateway-agent/SKILL.md @@ -23,7 +23,7 @@ Do not write a real `user_key` into source files, prompts, logs, or committed do ## Workflow -1. Run `$CLI health` when connectivity or EverOS availability is uncertain. +1. Run `$CLI health` when connectivity or upstream memory service availability is uncertain. 2. Use an existing `user_id` and `user_key`. Run `create-user` only when the user explicitly needs a new Gateway identity. 3. Choose the operation: - Upload durable user files with `upload-resource`. @@ -58,7 +58,7 @@ Read [references/api.md](references/api.md) when choosing scopes, constructing m - Do not expose internal file paths. Return the Gateway's `resource://{user_id}/{resource_id}` URI to users. - Do not claim ingestion succeeded unless the upload status is `extracted` or flush reports success. -- Treat `health.status = degraded` as Gateway available but EverOS unavailable. -- Resource deletion is soft deletion in Gateway search scope and removes the Gateway upload copy; it does not delete EverOS internal indexes. +- Treat `health.status = degraded` as Gateway available but upstream memory service unavailable. +- Resource deletion is soft deletion in Gateway search scope and removes the Gateway upload copy; it does not delete upstream memory service internal indexes. - Memory override and deletion require an owned `resource:{user_id}:{resource_id}` or `memory_edit:{user_id}` session. - Ask for confirmation before destructive deletion unless the user's current request explicitly instructs deletion. diff --git a/skill/memory-gateway-agent/references/api.md b/skill/memory-gateway-agent/references/api.md index 78808fa..32fc935 100644 --- a/skill/memory-gateway-agent/references/api.md +++ b/skill/memory-gateway-agent/references/api.md @@ -46,7 +46,7 @@ CLI="python skill/memory-gateway-agent/scripts/memory_gateway.py" $CLI health ``` -No credentials required. HTTP 200 may contain `"status": "degraded"` when EverOS is unavailable. +No credentials required. HTTP 200 may contain `"status": "degraded"` when upstream memory service is unavailable. ### Create User @@ -77,7 +77,7 @@ Requires credentials. Supported resources depend on the server MIME allowlist. S } ``` -`failed` means the record exists but EverOS ingestion failed. Identical active content for the same user/app/project may return the existing resource. +`failed` means the record exists but upstream memory service ingestion failed. Identical active content for the same user/app/project may return the existing resource. ### List, Get, and Delete Resources @@ -108,7 +108,7 @@ $CLI search "query" --scope current_chat --scope resources \ When no scope is provided, the CLI searches `resources` only unless a conversation ID is supplied; with a conversation ID it searches `current_chat` and `resources`. Explicit `current_chat` scope requires `--conversation-id`. -Each result includes normalized `id`, `session_id`, `text`, `source_scope`, and optional resource metadata. The `raw` field preserves the EverOS response. +Each result includes normalized `id`, `session_id`, `text`, `source_scope`, and optional resource metadata. The `raw` field preserves the upstream memory service response. ### Add and Flush Memory @@ -150,7 +150,7 @@ $CLI delete-memory mem_abc \ --reason "User requested deletion" ``` -These operations write Gateway overrides or tombstones. They do not modify EverOS files directly. The server rejects sessions not owned by the authenticated user. +These operations write Gateway overrides or tombstones. They do not modify upstream memory service files directly. The server rejects sessions not owned by the authenticated user. ## Message Format @@ -161,7 +161,7 @@ Each message requires: | `sender_id` | string | Usually the current user ID | | `role` | string | `user`, `assistant`, or `tool` | | `timestamp` | integer | Unix milliseconds, greater than zero | -| `content` | string or array | Text or EverOS content items | +| `content` | string or array | Text or upstream memory service content items | Common content items: @@ -187,7 +187,7 @@ Common content items: } ``` -Prefer base64 for local binary files. A `file://` URI is only usable when EverOS can access the same filesystem path. +Prefer base64 for local binary files. A `file://` URI is only usable when upstream memory service can access the same filesystem path. ## Search Scopes diff --git a/tests/test_everos_integration.py b/tests/test_backend_integration.py similarity index 62% rename from tests/test_everos_integration.py rename to tests/test_backend_integration.py index d99c8bc..63785c6 100644 --- a/tests/test_everos_integration.py +++ b/tests/test_backend_integration.py @@ -9,31 +9,31 @@ import pytest from core.api import create_app from core.config import GatewayConfig -from core.everos_client import EverOSClient +from core.backend_client import BackendClient pytestmark = pytest.mark.integration def _integration_enabled() -> bool: - return os.environ.get("RUN_EVEROS_INTEGRATION") == "1" + return os.environ.get("RUN_BACKEND_INTEGRATION") == "1" def _ingest_integration_enabled() -> bool: - return os.environ.get("RUN_EVEROS_INGEST_INTEGRATION") == "1" + return os.environ.get("RUN_BACKEND_INGEST_INTEGRATION") == "1" -def _everos_base_url() -> str: - return os.environ.get("EVEROS_BASE_URL", "http://127.0.0.1:1995") +def _backend_base_url() -> str: + return os.environ.get("MEMORY_GATEWAY_BACKEND_BASE_URL", "http://127.0.0.1:1995") @pytest.mark.skipif( not _integration_enabled(), - reason="set RUN_EVEROS_INTEGRATION=1 to run against a real EverOS service", + reason="set RUN_BACKEND_INTEGRATION=1 to run against an upstream memory service", ) @pytest.mark.asyncio -async def test_real_everos_health_check() -> None: - client = EverOSClient(_everos_base_url(), timeout=10) +async def test_real_backend_health_check() -> None: + client = BackendClient(_backend_base_url(), timeout=10) health = await client.health_check() @@ -43,17 +43,17 @@ async def test_real_everos_health_check() -> None: @pytest.mark.skipif( not _ingest_integration_enabled(), reason=( - "set RUN_EVEROS_INGEST_INTEGRATION=1 to run real EverOS add/flush ingestion" + "set RUN_BACKEND_INGEST_INTEGRATION=1 to run upstream add/flush ingestion" ), ) @pytest.mark.asyncio -async def test_gateway_uploads_text_resource_to_real_everos(tmp_path: Path) -> None: +async def test_gateway_uploads_text_resource_to_real_backend(tmp_path: Path) -> None: config = GatewayConfig( - everos_base_url=_everos_base_url(), + backend_base_url=_backend_base_url(), database_path=tmp_path / "gateway.sqlite3", storage_dir=tmp_path / "storage", - everos_ingest_attempts=1, - everos_timeout_seconds=30, + backend_ingest_attempts=1, + backend_timeout_seconds=30, ) app = create_app(config=config) transport = httpx.ASGITransport(app=app) @@ -69,8 +69,8 @@ async def test_gateway_uploads_text_resource_to_real_everos(tmp_path: Path) -> N data={"user_id": user_id, "user_key": user_key}, files={ "file": ( - "real-everos.txt", - b"real everos integration", + "integration.txt", + b"upstream memory service integration", "text/plain", ) }, diff --git a/tests/test_branding.py b/tests/test_branding.py new file mode 100644 index 0000000..aa455ea --- /dev/null +++ b/tests/test_branding.py @@ -0,0 +1,35 @@ +from __future__ import annotations + +from pathlib import Path + + +PROJECT_ROOT = Path(__file__).resolve().parents[1] +FORBIDDEN_TOKEN = "ever" + "os" +SKIPPED_PARTS = { + ".git", + ".pytest_cache", + ".venv", + "__pycache__", + "data", +} + + +def test_current_project_does_not_expose_upstream_product_name() -> None: + matches: list[str] = [] + for path in PROJECT_ROOT.rglob("*"): + relative = path.relative_to(PROJECT_ROOT) + if any(part in SKIPPED_PARTS for part in relative.parts): + continue + if FORBIDDEN_TOKEN in path.name.lower(): + matches.append(f"filename: {relative}") + if not path.is_file(): + continue + try: + text = path.read_text(encoding="utf-8") + except UnicodeDecodeError: + continue + for line_number, line in enumerate(text.splitlines(), start=1): + if FORBIDDEN_TOKEN in line.lower(): + matches.append(f"content: {relative}:{line_number}") + + assert matches == [] diff --git a/tests/test_command.md b/tests/test_command.md index a7f4f0c..40a017c 100644 --- a/tests/test_command.md +++ b/tests/test_command.md @@ -1,6 +1,6 @@ # Memory Gateway multimodal API test -This file records a real end-to-end test through **Memory Gateway**, not direct EverOS calls. +This file records a real end-to-end test through **Memory Gateway**, not direct upstream memory service calls. Gateway URL used by curl: @@ -8,7 +8,7 @@ Gateway URL used by curl: http://127.0.0.1:8010 ``` -Gateway upstream EverOS: +Gateway upstream memory service: ```text http://10.6.80.123:1995 @@ -35,7 +35,7 @@ Command: ```bash cd /home/tom/memory-gateway -EVEROS_BASE_URL=http://10.6.80.123:1995 \ +MEMORY_GATEWAY_BACKEND_BASE_URL=http://10.6.80.123:1995 \ MEMORY_GATEWAY_DB_PATH=/tmp/memory_gateway_curl.sqlite3 \ MEMORY_GATEWAY_STORAGE_DIR=/tmp/memory_gateway_curl_storage \ MEMORY_GATEWAY_HOST=127.0.0.1 \ @@ -136,7 +136,7 @@ Response: ```json { "session_id": "chat:gateway-multimodal-20260611180257", - "everos": { + "backend": { "request_id": "c9e24b8d27ee4ad08a8df70273336637", "data": { "message_count": 1, @@ -174,7 +174,7 @@ Response: ```json { "session_id": "chat:gateway-multimodal-20260611180257", - "everos": { + "backend": { "request_id": "8eb7d5db2d3b43f4999f445aabb813b1", "data": { "status": "extracted" @@ -192,7 +192,7 @@ TOTAL_TIME:2.135721 ## 4. Search through Gateway -EverOS indexing can lag briefly after `flush`, so this test waited about 2 seconds before searching. +upstream memory service indexing can lag briefly after `flush`, so this test waited about 2 seconds before searching. Request: @@ -279,7 +279,7 @@ Response: { "status": "ok", "api": {"status": "ok"}, - "everos": { + "backend": { "status": "ok", "base_url": "http://10.6.80.123:1995", "data": {"status": "ok"} diff --git a/tests/test_gateway.py b/tests/test_gateway.py index 65678b0..6ae00dd 100644 --- a/tests/test_gateway.py +++ b/tests/test_gateway.py @@ -17,7 +17,7 @@ import core.api as api_module import core.service as service_module -class FakeEverOSClient: +class FakeBackendClient: def __init__( self, search_results: list[dict[str, Any]] | None = None, @@ -67,7 +67,7 @@ class FakeEverOSClient: @pytest.fixture def config(tmp_path: Path) -> GatewayConfig: return GatewayConfig( - everos_base_url="http://everos.test", + backend_base_url="http://backend.test", database_path=tmp_path / "gateway.sqlite3", storage_dir=tmp_path / "storage", ) @@ -81,9 +81,9 @@ def repo(config: GatewayConfig) -> MemoryRepository: def app_client( config: GatewayConfig, - everos_client: FakeEverOSClient, + backend_client: FakeBackendClient, ) -> httpx.AsyncClient: - app = create_app(config=config, everos_client=everos_client) + app = create_app(config=config, backend_client=backend_client) transport = httpx.ASGITransport(app=app) return httpx.AsyncClient(transport=transport, base_url="http://test") @@ -124,20 +124,20 @@ async def create_user(client: httpx.AsyncClient, user_id: str = "u_123") -> str: return body["user_key"] -def test_create_app_uses_configured_everos_timeout(config: GatewayConfig) -> None: +def test_create_app_uses_configured_backend_timeout(config: GatewayConfig) -> None: config = GatewayConfig( - everos_base_url=config.everos_base_url, + backend_base_url=config.backend_base_url, database_path=config.database_path, storage_dir=config.storage_dir, - everos_timeout_seconds=7.5, + backend_timeout_seconds=7.5, ) app = create_app(config=config) - assert app.state.everos_client.timeout == 7.5 + assert app.state.backend_client.timeout == 7.5 def test_create_app_uses_project_name(config: GatewayConfig) -> None: - app = create_app(config=config, everos_client=FakeEverOSClient()) + app = create_app(config=config, backend_client=FakeBackendClient()) assert app.title == "memory-gateway" @@ -160,8 +160,8 @@ async def test_api_logs_request_time_address_input_and_output( caplog: pytest.LogCaptureFixture, ) -> None: caplog.set_level(logging.INFO, logger="memory_gateway.api") - everos = FakeEverOSClient() - async with app_client(config, everos) as client: + backend = FakeBackendClient() + async with app_client(config, backend) as client: user_key = await create_user(client, "u_log") response = await client.get( "/resources", @@ -192,8 +192,8 @@ async def test_api_logs_do_not_expose_secrets_from_large_json_bodies( caplog: pytest.LogCaptureFixture, ) -> None: caplog.set_level(logging.INFO, logger="memory_gateway.api") - everos = FakeEverOSClient() - async with app_client(config, everos) as client: + backend = FakeBackendClient() + async with app_client(config, backend) as client: user_key = await create_user(client, "u_large_log") caplog.clear() response = await client.post( @@ -224,40 +224,40 @@ async def test_api_logs_do_not_expose_secrets_from_large_json_bodies( @pytest.mark.asyncio -async def test_health_reports_api_and_everos_ok( +async def test_health_reports_api_and_backend_ok( config: GatewayConfig, ) -> None: - everos = FakeEverOSClient() - async with app_client(config, everos) as client: + backend = FakeBackendClient() + async with app_client(config, backend) as client: response = await client.get("/health") assert response.status_code == 200, response.text assert response.json() == { "status": "ok", "api": {"status": "ok"}, - "everos": { + "backend": { "status": "ok", - "base_url": "http://everos.test", + "base_url": "http://backend.test", "data": {"status": "ok"}, }, } @pytest.mark.asyncio -async def test_health_reports_degraded_when_everos_fails( +async def test_health_reports_degraded_when_backend_fails( config: GatewayConfig, ) -> None: - everos = FakeEverOSClient(health_error=RuntimeError("everos down")) - async with app_client(config, everos) as client: + backend = FakeBackendClient(health_error=RuntimeError("backend down")) + async with app_client(config, backend) as client: response = await client.get("/health") assert response.status_code == 200, response.text body = response.json() assert body["status"] == "degraded" assert body["api"] == {"status": "ok"} - assert body["everos"]["status"] == "unavailable" - assert body["everos"]["base_url"] == "http://everos.test" - assert body["everos"]["error"] == "everos down" + assert body["backend"]["status"] == "unavailable" + assert body["backend"]["base_url"] == "http://backend.test" + assert body["backend"]["error"] == "backend down" @pytest.mark.asyncio @@ -265,8 +265,8 @@ async def test_create_user_generates_and_persists_user_key( config: GatewayConfig, repo: MemoryRepository, ) -> None: - everos = FakeEverOSClient() - async with app_client(config, everos) as client: + backend = FakeBackendClient() + async with app_client(config, backend) as client: user_key = await create_user(client, "u_123") user = repo.get_user("u_123") @@ -275,11 +275,11 @@ async def test_create_user_generates_and_persists_user_key( @pytest.mark.asyncio -async def test_upload_resource_creates_record_and_calls_everos( +async def test_upload_resource_creates_record_and_calls_backend( config: GatewayConfig, ) -> None: - everos = FakeEverOSClient() - async with app_client(config, everos) as client: + backend = FakeBackendClient() + async with app_client(config, backend) as client: user_key = await create_user(client) response = await client.post( "/resources", @@ -304,15 +304,15 @@ async def test_upload_resource_creates_record_and_calls_everos( assert resource["size_bytes"] == len(b"pay in 30 days") assert not resource["uri"].startswith("resource://") - assert len(everos.add_calls) == 1 - add_payload = everos.add_calls[0] + assert len(backend.add_calls) == 1 + add_payload = backend.add_calls[0] assert add_payload["session_id"] == f"resource:u_123:{resource_id}" content = add_payload["messages"][0]["content"][0] assert content["type"] == "text" assert content["text"] == "pay in 30 days" assert "uri" not in content assert content["extras"] == {"resource_id": resource_id, "source": "user_upload"} - assert everos.flush_calls == [ + assert backend.flush_calls == [ { "session_id": f"resource:u_123:{resource_id}", "app_id": "default", @@ -322,11 +322,11 @@ async def test_upload_resource_creates_record_and_calls_everos( @pytest.mark.asyncio -async def test_upload_binary_resource_sends_base64_content_to_everos( +async def test_upload_binary_resource_sends_base64_content_to_backend( config: GatewayConfig, ) -> None: - everos = FakeEverOSClient() - async with app_client(config, everos) as client: + backend = FakeBackendClient() + async with app_client(config, backend) as client: user_key = await create_user(client) response = await client.post( "/resources", @@ -335,7 +335,7 @@ async def test_upload_binary_resource_sends_base64_content_to_everos( ) assert response.status_code == 200, response.text - content = everos.add_calls[0]["messages"][0]["content"][0] + content = backend.add_calls[0]["messages"][0]["content"][0] assert content["type"] == "pdf" assert content["base64"] == base64.b64encode(b"%PDF bytes").decode("ascii") assert content["ext"] == "pdf" @@ -354,8 +354,8 @@ async def test_upload_resource_uses_current_timestamp( lambda: 1234567890123, raising=False, ) - everos = FakeEverOSClient() - async with app_client(config, everos) as client: + backend = FakeBackendClient() + async with app_client(config, backend) as client: user_key = await create_user(client) response = await client.post( "/resources", @@ -364,22 +364,22 @@ async def test_upload_resource_uses_current_timestamp( ) assert response.status_code == 200, response.text - assert everos.add_calls[0]["messages"][0]["timestamp"] == 1234567890123 + assert backend.add_calls[0]["messages"][0]["timestamp"] == 1234567890123 @pytest.mark.asyncio -async def test_upload_retries_transient_everos_failure( +async def test_upload_retries_transient_backend_failure( config: GatewayConfig, ) -> None: config = GatewayConfig( - everos_base_url=config.everos_base_url, + backend_base_url=config.backend_base_url, database_path=config.database_path, storage_dir=config.storage_dir, - everos_ingest_attempts=2, - everos_retry_delay_seconds=0, + backend_ingest_attempts=2, + backend_retry_delay_seconds=0, ) - everos = FakeEverOSClient(add_failures=1, flush_failures=1) - async with app_client(config, everos) as client: + backend = FakeBackendClient(add_failures=1, flush_failures=1) + async with app_client(config, backend) as client: user_key = await create_user(client) response = await client.post( "/resources", @@ -389,16 +389,16 @@ async def test_upload_retries_transient_everos_failure( assert response.status_code == 200, response.text assert response.json()["status"] == "extracted" - assert len(everos.add_calls) == 2 - assert len(everos.flush_calls) == 2 + assert len(backend.add_calls) == 2 + assert len(backend.flush_calls) == 2 @pytest.mark.asyncio async def test_upload_duplicate_resource_is_idempotent_for_same_user( config: GatewayConfig, ) -> None: - everos = FakeEverOSClient() - async with app_client(config, everos) as client: + backend = FakeBackendClient() + async with app_client(config, backend) as client: user_key = await create_user(client) first = await client.post( "/resources", @@ -414,8 +414,8 @@ async def test_upload_duplicate_resource_is_idempotent_for_same_user( assert first.status_code == 200, first.text assert second.status_code == 200, second.text assert second.json()["resource_id"] == first.json()["resource_id"] - assert len(everos.add_calls) == 1 - assert len(everos.flush_calls) == 1 + assert len(backend.add_calls) == 1 + assert len(backend.flush_calls) == 1 @pytest.mark.asyncio @@ -424,13 +424,13 @@ async def test_upload_rejects_file_larger_than_configured_limit( repo: MemoryRepository, ) -> None: config = GatewayConfig( - everos_base_url=config.everos_base_url, + backend_base_url=config.backend_base_url, database_path=config.database_path, storage_dir=config.storage_dir, max_upload_bytes=4, ) - everos = FakeEverOSClient() - async with app_client(config, everos) as client: + backend = FakeBackendClient() + async with app_client(config, backend) as client: user_key = await create_user(client) response = await client.post( "/resources", @@ -441,7 +441,7 @@ async def test_upload_rejects_file_larger_than_configured_limit( assert response.status_code == 413, response.text assert repo.list_resources("u_123") == [] assert not any(config.storage_dir.rglob("*")) - assert everos.add_calls == [] + assert backend.add_calls == [] @pytest.mark.asyncio @@ -449,8 +449,8 @@ async def test_upload_rejects_unsupported_mime_type( config: GatewayConfig, repo: MemoryRepository, ) -> None: - everos = FakeEverOSClient() - async with app_client(config, everos) as client: + backend = FakeBackendClient() + async with app_client(config, backend) as client: user_key = await create_user(client) response = await client.post( "/resources", @@ -460,15 +460,15 @@ async def test_upload_rejects_unsupported_mime_type( assert response.status_code == 415, response.text assert repo.list_resources("u_123") == [] - assert everos.add_calls == [] + assert backend.add_calls == [] @pytest.mark.asyncio async def test_resource_detail_does_not_leak_internal_uri( config: GatewayConfig, ) -> None: - everos = FakeEverOSClient() - async with app_client(config, everos) as client: + backend = FakeBackendClient() + async with app_client(config, backend) as client: user_key = await create_user(client) created = await client.post( "/resources", @@ -492,8 +492,8 @@ async def test_resource_detail_does_not_leak_internal_uri( async def test_resource_detail_returns_empty_when_user_has_no_resource( config: GatewayConfig, ) -> None: - everos = FakeEverOSClient() - async with app_client(config, everos) as client: + backend = FakeBackendClient() + async with app_client(config, backend) as client: user_key = await create_user(client, "u_empty") response = await client.get( "/resources/r_missing", @@ -508,8 +508,8 @@ async def test_resource_detail_returns_empty_when_user_has_no_resource( async def test_resources_are_isolated_by_user_key( config: GatewayConfig, ) -> None: - everos = FakeEverOSClient() - async with app_client(config, everos) as client: + backend = FakeBackendClient() + async with app_client(config, backend) as client: alice_key = await create_user(client, "alice") bob_key = await create_user(client, "bob") created = await client.post( @@ -537,8 +537,8 @@ async def test_resources_are_isolated_by_user_key( async def test_resource_api_rejects_invalid_user_key( config: GatewayConfig, ) -> None: - everos = FakeEverOSClient() - async with app_client(config, everos) as client: + backend = FakeBackendClient() + async with app_client(config, backend) as client: await create_user(client, "u_123") response = await client.get( "/resources", @@ -549,10 +549,10 @@ async def test_resource_api_rejects_invalid_user_key( @pytest.mark.asyncio -async def test_add_memory_forwards_multimodal_payload_to_everos( +async def test_add_memory_forwards_multimodal_payload_to_backend( config: GatewayConfig, ) -> None: - everos = FakeEverOSClient() + backend = FakeBackendClient() audio = base64.b64encode(b"wav bytes").decode("ascii") content = [ {"type": "text", "text": "remember the picture and audio"}, @@ -564,7 +564,7 @@ async def test_add_memory_forwards_multimodal_payload_to_everos( "name": "simple-multimodal-image.png", }, ] - async with app_client(config, everos) as client: + async with app_client(config, backend) as client: user_key = await create_user(client) response = await client.post( "/memories/add", @@ -588,9 +588,9 @@ async def test_add_memory_forwards_multimodal_payload_to_everos( assert response.status_code == 200, response.text assert response.json() == { "session_id": "chat:c_multimodal", - "everos": {"request_id": "add", "data": {"status": "accumulated"}}, + "backend": {"request_id": "add", "data": {"status": "accumulated"}}, } - assert everos.add_calls == [ + assert backend.add_calls == [ { "session_id": "chat:c_multimodal", "app_id": "default", @@ -608,11 +608,11 @@ async def test_add_memory_forwards_multimodal_payload_to_everos( @pytest.mark.asyncio -async def test_flush_memory_forwards_request_to_everos( +async def test_flush_memory_forwards_request_to_backend( config: GatewayConfig, ) -> None: - everos = FakeEverOSClient() - async with app_client(config, everos) as client: + backend = FakeBackendClient() + async with app_client(config, backend) as client: user_key = await create_user(client) response = await client.post( "/memories/flush", @@ -628,9 +628,9 @@ async def test_flush_memory_forwards_request_to_everos( assert response.status_code == 200, response.text assert response.json() == { "session_id": "chat:c_multimodal", - "everos": {"request_id": "flush", "data": {"status": "extracted"}}, + "backend": {"request_id": "flush", "data": {"status": "extracted"}}, } - assert everos.flush_calls == [ + assert backend.flush_calls == [ { "session_id": "chat:c_multimodal", "app_id": "default", @@ -643,10 +643,10 @@ async def test_flush_memory_forwards_request_to_everos( async def test_deleted_resource_is_excluded_from_resource_scope_search( config: GatewayConfig, ) -> None: - everos = FakeEverOSClient( + backend = FakeBackendClient( [{"id": "mem_1", "session_id": "resource:u_123:r_live", "episode": "live"}] ) - async with app_client(config, everos) as client: + async with app_client(config, backend) as client: user_key = await create_user(client) created = await client.post( "/resources", @@ -671,7 +671,7 @@ async def test_deleted_resource_is_excluded_from_resource_scope_search( assert delete_response.status_code == 200 assert search_response.status_code == 200 - assert everos.search_calls == [] + assert backend.search_calls == [] assert search_response.json()["results"] == [] repo = MemoryRepository(config.database_path) deleted = repo.get_resource(resource_id) @@ -691,14 +691,14 @@ async def test_tombstone_filters_search_results( session_id=None, reason="user deleted", ) - everos = FakeEverOSClient( + backend = FakeBackendClient( [ {"id": "mem_deleted", "session_id": "resource:u_123:r_1", "episode": "x"}, {"id": "mem_live", "session_id": "resource:u_123:r_1", "episode": "y"}, ] ) - async with app_client(config, everos) as client: + async with app_client(config, backend) as client: user_key = await create_user(client) response = await client.post( "/memories/search", @@ -721,10 +721,10 @@ async def test_override_replaces_search_result_text( repo: MemoryRepository, ) -> None: create_test_resource(repo, resource_id="r_1", user_id="u_123") - everos = FakeEverOSClient( + backend = FakeBackendClient( [{"id": "mem_1", "session_id": "resource:u_123:r_1", "episode": "old text"}] ) - async with app_client(config, everos) as client: + async with app_client(config, backend) as client: user_key = await create_user(client) patch_response = await client.patch( "/memories/mem_1", @@ -758,8 +758,8 @@ async def test_memory_override_rejects_session_owned_by_another_user( repo: MemoryRepository, ) -> None: create_test_resource(repo, resource_id="r_bob", user_id="bob") - everos = FakeEverOSClient() - async with app_client(config, everos) as client: + backend = FakeBackendClient() + async with app_client(config, backend) as client: user_key = await create_user(client, "alice") response = await client.patch( "/memories/mem_1", @@ -780,8 +780,8 @@ async def test_memory_delete_requires_owned_session( config: GatewayConfig, repo: MemoryRepository, ) -> None: - everos = FakeEverOSClient() - async with app_client(config, everos) as client: + backend = FakeBackendClient() + async with app_client(config, backend) as client: user_key = await create_user(client, "u_123") response = await client.request( "DELETE", @@ -802,8 +802,8 @@ async def test_memory_delete_requires_owned_session( async def test_list_resources_returns_only_not_deleted( config: GatewayConfig, ) -> None: - everos = FakeEverOSClient() - async with app_client(config, everos) as client: + backend = FakeBackendClient() + async with app_client(config, backend) as client: user_key = await create_user(client) first = await client.post( "/resources", @@ -837,8 +837,8 @@ async def test_delete_memory_writes_tombstone( repo: MemoryRepository, ) -> None: create_test_resource(repo, resource_id="r_1", user_id="u_123") - everos = FakeEverOSClient() - async with app_client(config, everos) as client: + backend = FakeBackendClient() + async with app_client(config, backend) as client: user_key = await create_user(client) response = await client.request( "DELETE",