docs(api): use /api/v2 in docs and examples, demote v1 to legacy (#370)

1.2.0 introduced /api/v2 as the canonical, cloud-aligned prefix and mounted
every business router twice, but the user-facing entry points (README,
README.zh-CN, QUICKSTART, the docs/ set, the Langfuse example) still taught
/api/v1 — so new users were pointed at the compatibility alias while
docs/api.md already declared v2 canonical.

- Switch every EverOS endpoint reference in docs, examples, and
  `everos demo --live` to /api/v2, plus the matching CLI test expectations.
- Describe /api/v1 as a legacy compatibility alias that may be removed in a
  future major release, rather than a permanent one. Nothing changes at
  runtime: both prefixes still resolve to the same handlers and the
  v1/v2 parity test is untouched.
- Add a short note in README / README.zh-CN / QUICKSTART so existing v1
  integrations know they keep working.
- Fix the five dead endpoint anchors in the docs/api.md table of contents,
  which still pointed at the pre-1.2.0 #post-apiv1... slugs.

Left on v1 deliberately: docs/migration-to-1.0.0.md (historical record),
CHANGELOG history, tests/** (v1 must stay covered), and the
use-cases/claude-code-plugin + openher READMEs, which document a different
cloud API.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dani 2026-07-28 22:52:29 -04:00 committed by GitHub
parent 6c9792ed11
commit 649046b0df
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
17 changed files with 104 additions and 73 deletions

View File

@ -22,7 +22,7 @@ A good module docstring states:
Example (abbreviated, from `memory/search/manager.py`):
```python
"""SearchManager — top-level orchestrator for POST /api/v1/memory/search.
"""SearchManager — top-level orchestrator for POST /api/v2/memory/search.
Hard partition by owner_type: user → episodes (+ profiles), agent →
agent_cases + agent_skills. The manager never writes to storage; it only

View File

@ -19,6 +19,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
`metadata = {"method": ..., "calibrated": ...}` now, a structured field that
can be split on, alongside the existing human-readable comment. Dashboards
built on `recall_top_score` for keyword search need to switch to the new name.
- **Docs and examples now use `/api/v2`** — README, QUICKSTART, the `docs/`
reference set, the Langfuse example, and `everos demo --live` all call the
canonical `/api/v2` prefix instead of `/api/v1`. `/api/v1` keeps resolving
to the same handlers, so nothing breaks; it is now described as a **legacy
compatibility alias that may be removed in a future major release** rather
than a permanent one. New integrations should target `/api/v2`.
### Fixed
- **Broken table-of-contents links in `docs/api.md`** — the endpoint anchors
still pointed at the pre-1.2.0 `#post-apiv1…` slugs after the headings moved
to `/api/v2`, so all five endpoint links in the TOC were dead.
## [1.2.0] - 2026-07-24
@ -26,8 +38,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- **`/api/v2` API prefix** — every business endpoint (`memory/*`, `ome/*`,
`knowledge/*`) is now served under `/api/v2`, aligning the open-source API
with the EverOS Cloud contract. `/api/v1` is retained as a permanent,
backward-compatible alias: both prefixes resolve to the same handlers with
with the EverOS Cloud contract. `/api/v1` is retained as a legacy
compatibility alias: both prefixes resolve to the same handlers with
identical request/response contracts, so existing integrations keep working
unchanged. Infrastructure endpoints (`/health`, `/metrics`) stay unversioned.
- **Native OpenTelemetry tracing** — memory operations (add / flush, memcell

View File

@ -123,11 +123,17 @@ Send messages to the server — one at a time or in batches. Each batch
belongs to a `session_id`, which represents one conversation thread.
Timestamps are Unix epoch in **milliseconds** (UTC).
> [!NOTE]
> Business endpoints live under `/api/v2`. The older `/api/v1` prefix still
> resolves to the same handlers so existing integrations keep working, but it
> is a legacy alias that may be removed in a future major release — write new
> code against `/api/v2`.
First, a chat about climbing:
```bash
TS=$(($(date +%s)*1000))
curl -X POST http://127.0.0.1:8000/api/v1/memory/add \
curl -X POST http://127.0.0.1:8000/api/v2/memory/add \
-H 'Content-Type: application/json' \
-d "{
\"session_id\": \"demo-001\",
@ -143,7 +149,7 @@ curl -X POST http://127.0.0.1:8000/api/v1/memory/add \
Now the topic shifts to work:
```bash
curl -X POST http://127.0.0.1:8000/api/v1/memory/add \
curl -X POST http://127.0.0.1:8000/api/v2/memory/add \
-H 'Content-Type: application/json' \
-d "{
\"session_id\": \"demo-001\",
@ -184,7 +190,7 @@ If you want to extract memory without waiting for a topic shift — for
example at the end of a session — call `/flush`:
```bash
curl -X POST http://127.0.0.1:8000/api/v1/memory/flush \
curl -X POST http://127.0.0.1:8000/api/v2/memory/flush \
-H 'Content-Type: application/json' \
-d '{"session_id":"demo-001"}'
```
@ -202,7 +208,7 @@ This forces extraction of whatever is still in the buffer.
## 6. Search
```bash
curl -X POST http://127.0.0.1:8000/api/v1/memory/search \
curl -X POST http://127.0.0.1:8000/api/v2/memory/search \
-H 'Content-Type: application/json' \
-d '{
"user_id": "alice",
@ -277,7 +283,7 @@ edit — no database driver needed.
requests to partition memory spaces inside one server (defaults to
`"default"` when omitted).
- **Knowledge base** — upload documents via
`/api/v1/knowledge/documents` and search with hybrid retrieval. See
`/api/v2/knowledge/documents` and search with hybrid retrieval. See
[docs/knowledge.md](docs/knowledge.md).
- **Reflection** — offline memory consolidation; enable in `ome.toml`.
See [docs/reflection.md](docs/reflection.md).

View File

@ -195,18 +195,24 @@ everos demo --live
```
Live demo mode connects to the running server and performs the real
`/health` -> `/api/v1/memory/add` -> `/api/v1/memory/flush` ->
`/api/v1/memory/search` flow before opening the same memory sphere UI. Use
`/health` -> `/api/v2/memory/add` -> `/api/v2/memory/flush` ->
`/api/v2/memory/search` flow before opening the same memory sphere UI. Use
`--server-url <url>` if your server is not on `http://127.0.0.1:8000`.
### 5. Try Your First Memory
> [!NOTE]
> Business endpoints live under `/api/v2`. The older `/api/v1` prefix still
> resolves to the same handlers so existing integrations keep working, but it
> is a legacy alias that may be removed in a future major release — write new
> code against `/api/v2`.
Add a tiny conversation:
```bash
TS=$(($(date +%s)*1000))
curl -X POST http://127.0.0.1:8000/api/v1/memory/add \
curl -X POST http://127.0.0.1:8000/api/v2/memory/add \
-H 'Content-Type: application/json' \
-d "{
\"session_id\": \"demo-001\",
@ -222,7 +228,7 @@ curl -X POST http://127.0.0.1:8000/api/v1/memory/add \
Force extraction for the local demo:
```bash
curl -X POST http://127.0.0.1:8000/api/v1/memory/flush \
curl -X POST http://127.0.0.1:8000/api/v2/memory/flush \
-H 'Content-Type: application/json' \
-d '{"session_id":"demo-001","app_id":"default","project_id":"default"}'
```
@ -230,7 +236,7 @@ curl -X POST http://127.0.0.1:8000/api/v1/memory/flush \
Search it back:
```bash
curl -X POST http://127.0.0.1:8000/api/v1/memory/search \
curl -X POST http://127.0.0.1:8000/api/v2/memory/search \
-H 'Content-Type: application/json' \
-d '{
"user_id": "alice",
@ -258,7 +264,7 @@ For annotated responses and the Markdown files EverOS creates, see
### Optional: Ingest Multimodal Files
To ingest non-text content (image / pdf / audio / office documents)
through `/api/v1/memory/add` `content` items, install the optional
through `/api/v2/memory/add` `content` items, install the optional
extra:
```bash

View File

@ -212,18 +212,23 @@ everos demo --live
```
Live demo mode 会连接正在运行的 server并在打开同一个 memory sphere UI
之前真实执行 `/health` -> `/api/v1/memory/add` -> `/api/v1/memory/flush` ->
`/api/v1/memory/search`。如果 server 不在 `http://127.0.0.1:8000`,可以使用
之前真实执行 `/health` -> `/api/v2/memory/add` -> `/api/v2/memory/flush` ->
`/api/v2/memory/search`。如果 server 不在 `http://127.0.0.1:8000`,可以使用
`--server-url <url>`
### 5. 试写第一条记忆
> [!NOTE]
> 业务接口位于 `/api/v2`。旧的 `/api/v1` 前缀仍然指向同一批 handler已有集成
> 不会受影响;但它只是兼容用的 legacy alias未来的大版本可能移除 —— 新代码请
> 直接使用 `/api/v2`
添加一个很小的 conversation
```bash
TS=$(($(date +%s)*1000))
curl -X POST http://127.0.0.1:8000/api/v1/memory/add \
curl -X POST http://127.0.0.1:8000/api/v2/memory/add \
-H 'Content-Type: application/json' \
-d "{
\"session_id\": \"demo-001\",
@ -239,7 +244,7 @@ curl -X POST http://127.0.0.1:8000/api/v1/memory/add \
为了本地 demo手动触发一次 extraction
```bash
curl -X POST http://127.0.0.1:8000/api/v1/memory/flush \
curl -X POST http://127.0.0.1:8000/api/v2/memory/flush \
-H 'Content-Type: application/json' \
-d '{"session_id":"demo-001","app_id":"default","project_id":"default"}'
```
@ -247,7 +252,7 @@ curl -X POST http://127.0.0.1:8000/api/v1/memory/flush \
再把这条记忆搜索回来:
```bash
curl -X POST http://127.0.0.1:8000/api/v1/memory/search \
curl -X POST http://127.0.0.1:8000/api/v2/memory/search \
-H 'Content-Type: application/json' \
-d '{
"user_id": "alice",
@ -271,7 +276,7 @@ Markdown 会同步写入,本地索引会在后台追上。
### 可选:摄取多模态文件
如果要通过 `/api/v1/memory/add` 的 `content` items 摄取非文本内容
如果要通过 `/api/v2/memory/add` 的 `content` items 摄取非文本内容
image / pdf / audio / office documents安装可选 extra
```bash

View File

@ -26,11 +26,11 @@ business semantics the raw spec does not carry.
- [SearchMethod](#searchmethod)
- [GetMemoryType](#getmemorytype)
- [Endpoints](#endpoints)
- [POST /api/v2/memory/add](#post-apiv1memoryadd)
- [POST /api/v2/memory/flush](#post-apiv1memoryflush)
- [POST /api/v2/memory/search](#post-apiv1memorysearch)
- [POST /api/v2/memory/get](#post-apiv1memoryget)
- [POST /api/v2/ome/trigger](#post-apiv1ometrigger)
- [POST /api/v2/memory/add](#post-apiv2memoryadd)
- [POST /api/v2/memory/flush](#post-apiv2memoryflush)
- [POST /api/v2/memory/search](#post-apiv2memorysearch)
- [POST /api/v2/memory/get](#post-apiv2memoryget)
- [POST /api/v2/ome/trigger](#post-apiv2ometrigger)
- [Knowledge endpoints](#knowledge-endpoints)
- [OpenAPI spec source](#openapi-spec-source)
@ -52,11 +52,13 @@ but are intentionally outside this reference — they are runtime probes
for deployment, not part of the application contract.
`/api/v2` is the canonical prefix, aligned with the EverOS Cloud API. Every
business endpoint is **also** served under `/api/v1`, which is retained as a
permanent, backward-compatible alias: the two prefixes resolve to the same
handlers with identical request/response contracts. Existing `/api/v1`
integrations keep working unchanged; new integrations should use `/api/v2`.
Swap the prefix in any example below to reach the same endpoint under v1.
business endpoint is **also** served under `/api/v1`, kept as a legacy
compatibility alias: the two prefixes resolve to the same handlers with
identical request/response contracts, so existing `/api/v1` integrations keep
working today. The alias carries no long-term guarantee — it may be removed in
a future major release, and it will be announced in the changelog with a
deprecation window before that happens. Write new integrations against
`/api/v2`, and migrate existing ones when convenient.
### Content type

View File

@ -38,9 +38,9 @@ Live mode keeps the same TUI, but the memory lifecycle is backed by real
server calls:
1. `GET /health`
2. `POST /api/v1/memory/add`
3. `POST /api/v1/memory/flush`
4. `POST /api/v1/memory/search`
2. `POST /api/v2/memory/add`
3. `POST /api/v2/memory/flush`
4. `POST /api/v2/memory/search`
If your server is not running on `http://127.0.0.1:8000`, pass
`--server-url <url>`.

View File

@ -302,7 +302,7 @@ The CLI ([cli.md](cli.md)) is intentionally small:
`rm -rf <memory-root>/.index/lancedb`, restart — the cascade
rebuilds from markdown. For an incremental catch-up, use
`everos cascade sync`.
- **Flush** is an HTTP endpoint (`POST /api/v1/memory/flush`), not a
- **Flush** is an HTTP endpoint (`POST /api/v2/memory/flush`), not a
CLI command — it forces *extraction* of the session buffer, which is
a different thing from forcing *index sync* (`cascade sync`).

View File

@ -13,14 +13,14 @@ and keeps the original file for reference.
```bash
# Upload a document
curl -s -X POST http://localhost:8000/api/v1/knowledge/documents \
curl -s -X POST http://localhost:8000/api/v2/knowledge/documents \
-F "file=@my-report.pdf" \
-F "title=Q1 Engineering Report" \
| jq .data
# → { "doc_id": "d_a1b2c3d4e5f6", "category_id": "Technology", "topic_count": 8, ... }
# Search
curl -s -X POST http://localhost:8000/api/v1/knowledge/search \
curl -s -X POST http://localhost:8000/api/v2/knowledge/search \
-H "Content-Type: application/json" \
-d '{"query": "performance bottleneck", "method": "hybrid"}' \
| jq '.data.hits[:3] | .[] | {topic_name, score}'
@ -206,7 +206,7 @@ request to bypass LLM classification.
## API reference
All endpoints are under `/api/v1/knowledge`. Responses use the envelope
All endpoints are under `/api/v2/knowledge`. Responses use the envelope
format `{"request_id": "...", "data": {...}}`. The `request_id` field is
omitted from examples below for brevity.
@ -256,7 +256,7 @@ async def upload_document(file_path: str, title: str) -> dict:
async with httpx.AsyncClient(base_url="http://localhost:8000") as client:
with open(file_path, "rb") as f:
resp = await client.post(
"/api/v1/knowledge/documents",
"/api/v2/knowledge/documents",
files={"file": (Path(file_path).name, f)},
data={"title": title},
)
@ -267,7 +267,7 @@ async def upload_document(file_path: str, title: str) -> dict:
**Example — curl**:
```bash
curl -X POST http://localhost:8000/api/v1/knowledge/documents \
curl -X POST http://localhost:8000/api/v2/knowledge/documents \
-F "file=@report.pdf" \
-F "title=Quarterly Report" \
-F "category_id=Finance"
@ -605,7 +605,7 @@ Storage paths, SQLite rows, and LanceDB indexes are all scoped by
A complete workflow from upload to search:
```bash
BASE=http://localhost:8000/api/v1/knowledge
BASE=http://localhost:8000/api/v2/knowledge
# 1. List available categories
curl -s "$BASE/categories" | jq '[.data.categories[] | .category_id]'

View File

@ -29,7 +29,7 @@ result is fully retrievable with the same `/search` stack.
## How it works
```
POST /api/v1/memory/add
POST /api/v2/memory/add
messages[].content = [ ContentItem, ContentItem, ... ]
│ text items → used verbatim
@ -157,7 +157,7 @@ way — only the parsed text is.
```bash
TS=$(($(date +%s) * 1000)) # v1 contract: timestamp in ms
curl -X POST http://127.0.0.1:8000/api/v1/memory/add \
curl -X POST http://127.0.0.1:8000/api/v2/memory/add \
-H 'Content-Type: application/json' \
-d "{
\"session_id\": \"mm-001\",
@ -244,7 +244,7 @@ HTTP library:
import httpx
httpx.post(
"http://127.0.0.1:8000/api/v1/memory/add",
"http://127.0.0.1:8000/api/v2/memory/add",
json={
"session_id": "mm-001",
"messages": [
@ -319,7 +319,7 @@ episodes and memory cells as text turns, every retrieval method works
across multimodal-derived memory unchanged:
```bash
curl -X POST http://127.0.0.1:8000/api/v1/memory/search \
curl -X POST http://127.0.0.1:8000/api/v2/memory/search \
-H 'Content-Type: application/json' \
-d '{
"user_id": "alice",

View File

@ -221,7 +221,7 @@ dedicated CLI command).
### Triggering a run
```
POST /api/v1/ome/trigger
POST /api/v2/ome/trigger
Content-Type: application/json
```
@ -249,7 +249,7 @@ import httpx
async def trigger_reflection() -> str:
async with httpx.AsyncClient(base_url="http://localhost:8000") as client:
resp = await client.post(
"/api/v1/ome/trigger",
"/api/v2/ome/trigger",
json={"name": "reflect_episodes", "timeout": 120, "force": True},
)
resp.raise_for_status()
@ -259,7 +259,7 @@ async def trigger_reflection() -> str:
**Example — curl**:
```bash
curl -X POST http://localhost:8000/api/v1/ome/trigger \
curl -X POST http://localhost:8000/api/v2/ome/trigger \
-H "Content-Type: application/json" \
-d '{"name": "reflect_episodes", "timeout": 120, "force": true}'
```
@ -327,7 +327,7 @@ real deployment, once enabled it runs automatically on schedule, so this step
isn't needed.
```bash
BASE=http://localhost:8000/api/v1
BASE=http://localhost:8000/api/v2
# 1. With Reflection enabled (set enabled = true in <root>/ome.toml),
# trigger a run by hand here (for the demo; in production it runs on schedule)

View File

@ -42,11 +42,11 @@ zero tracing overhead.
| EverOS operation | Langfuse observation |
| --- | --- |
| `POST /api/v1/memory/add` · `flush` | span `everos.memory.add` / `everos.memory.flush` |
| `POST /api/v2/memory/add` · `flush` | span `everos.memory.add` / `everos.memory.flush` |
| memcell boundary detection (LLM) | generation `everos.memcell.boundary` (model + tokens) |
| episode extraction (LLM) | generation `everos.extract` |
| markdown persistence | span `everos.persist.markdown` |
| `POST /api/v1/memory/search` | retriever `everos.memory.search``recall` / `rank` |
| `POST /api/v2/memory/search` | retriever `everos.memory.search``recall` / `rank` |
| query / recall embedding | embedding `everos.embedding` |
| OME reflection strategies | agent `everos.ome.<strategy>` (linked to the triggering request's trace) |

View File

@ -39,7 +39,7 @@ def main() -> None:
ts = int(time.time() * 1000)
add = _post(
"/api/v1/memory/add",
"/api/v2/memory/add",
{
"session_id": SESSION,
"messages": [
@ -62,7 +62,7 @@ def main() -> None:
)
print("add ->", add["data"])
flush = _post("/api/v1/memory/flush", {"session_id": SESSION, "messages": []})
flush = _post("/api/v2/memory/flush", {"session_id": SESSION, "messages": []})
print("flush ->", flush["data"])
print("waiting for async index sync ...")
@ -70,7 +70,7 @@ def main() -> None:
for method in ("keyword", "hybrid", "agentic"):
resp = _post(
"/api/v1/memory/search",
"/api/v2/memory/search",
{
"user_id": USER,
"query": "which vector database did we move to and why",

View File

@ -125,13 +125,13 @@ def create_app(
app.include_router(health.router)
app.include_router(metrics.router)
# Business API — served under both /api/v2 (cloud-aligned name) and
# /api/v1 (retained as a permanent backward-compatible alias). The same
# router object is mounted twice, so both prefixes resolve to the exact
# same handlers; FastAPI's default operationId embeds the path, so the
# two copies get distinct OpenAPI ids automatically (no collision).
# Business API — served under both /api/v2 (canonical, cloud-aligned name)
# and /api/v1 (legacy compatibility alias, removable in a future major).
# The same router object is mounted twice, so both prefixes resolve to the
# exact same handlers; FastAPI's default operationId embeds the path, so
# the two copies get distinct OpenAPI ids automatically (no collision).
# v1 and v2 stay identical by construction — see test_api_versioning.
# v1 first — retained alias, behavior identical.
# v1 first — legacy alias, behavior identical.
app.include_router(memorize.router, prefix="/api/v1")
app.include_router(search.router, prefix="/api/v1")
app.include_router(get.router, prefix="/api/v1")

View File

@ -160,7 +160,7 @@ def _run_live_demo_flow(
timestamp_ms = int(get_utc_now().timestamp() * 1000)
request(
"POST",
"/api/v1/memory/add",
"/api/v2/memory/add",
base_url=base_url,
json_body={
"session_id": LIVE_DEMO_SESSION_ID,
@ -179,7 +179,7 @@ def _run_live_demo_flow(
)
request(
"POST",
"/api/v1/memory/flush",
"/api/v2/memory/flush",
base_url=base_url,
json_body={
"session_id": LIVE_DEMO_SESSION_ID,
@ -199,7 +199,7 @@ def _run_live_demo_flow(
for attempt in range(search_attempts):
search = request(
"POST",
"/api/v1/memory/search",
"/api/v2/memory/search",
base_url=base_url,
json_body=search_payload,
timeout_seconds=timeout_seconds,

View File

@ -1,10 +1,10 @@
"""API version aliasing — every business route is served under v1 and v2.
The ``/api/v2`` prefix is the cloud-aligned name; ``/api/v1`` is retained as a
permanent backward-compatible alias pointing to the *same* endpoint. These
tests are the completeness guard: they fail if any versioned route is exposed
under one prefix but not the other, or if the two prefixes ever diverge to
different handlers. Infrastructure endpoints (``/health``, ``/metrics``) are
The ``/api/v2`` prefix is the canonical, cloud-aligned name; ``/api/v1`` is a
legacy compatibility alias pointing to the *same* endpoint. These tests are
the completeness guard: they fail if any versioned route is exposed under one
prefix but not the other, or if the two prefixes ever diverge to different
handlers. Infrastructure endpoints (``/health``, ``/metrics``) are
deliberately unversioned and must NOT be mirrored.
Assertions run against ``app.openapi()["paths"]`` the authoritative,

View File

@ -134,11 +134,11 @@ def test_live_demo_flow_calls_server_and_builds_story() -> None:
assert timeout_seconds == 3.0
if path == "/health":
return {"status": "ok"}
if path == "/api/v1/memory/add":
if path == "/api/v2/memory/add":
return {"message_count": 1, "status": "accumulated"}
if path == "/api/v1/memory/flush":
if path == "/api/v2/memory/flush":
return {"message_count": 1, "status": "extracted"}
if path == "/api/v1/memory/search":
if path == "/api/v2/memory/search":
return {
"data": {
"episodes": [
@ -172,9 +172,9 @@ def test_live_demo_flow_calls_server_and_builds_story() -> None:
assert [path for _, path, _ in calls] == [
"/health",
"/api/v1/memory/add",
"/api/v1/memory/flush",
"/api/v1/memory/search",
"/api/v2/memory/add",
"/api/v2/memory/flush",
"/api/v2/memory/search",
]
add_body = calls[1][2]
assert add_body is not None