docs: make quick start prove first memory (#293)
* docs: make quick start prove first memory * docs: add env example for quick start * docs: highlight quick start success moment * docs: move use cases after quick start * docs: align quickstart response contracts
This commit is contained in:
parent
a10cdcd197
commit
9c7c9d7316
|
|
@ -0,0 +1,114 @@
|
|||
# =====================================================
|
||||
# EverOS — md-first Memory Extraction Framework
|
||||
# Configuration Template
|
||||
# =====================================================
|
||||
#
|
||||
# Setup:
|
||||
# 1. Create .env with `everos init` or `cp .env.example .env`
|
||||
# 2. Edit .env with your values
|
||||
# 3. .env is gitignored (never commit)
|
||||
#
|
||||
# Override priority (low → high):
|
||||
# src/everos/config/default.toml (shipped baseline)
|
||||
# ↓
|
||||
# ~/.everos/config.toml (user-level overrides; optional)
|
||||
# ↓
|
||||
# .env (this file; gitignored)
|
||||
# ↓
|
||||
# EVEROS_<SECTION>__<KEY> process envs
|
||||
# ↓
|
||||
# programmatic init args / CLI flags
|
||||
#
|
||||
# The user-level toml path defaults to ~/.everos/config.toml; override
|
||||
# with EVEROS_CONFIG_FILE=/path/to/your.toml. Missing file is skipped.
|
||||
# =====================================================
|
||||
|
||||
|
||||
# ─── LLM (OpenAI-protocol compatible) ────────────────
|
||||
# Any OpenAI-API-compatible endpoint plugs in via base_url. Defaults
|
||||
# below target OpenRouter (one key, broad model catalogue); switch to
|
||||
# OpenAI, vLLM, Ollama (OpenAI bridge), or any other compatible endpoint
|
||||
# by changing model + base_url + api_key.
|
||||
|
||||
EVEROS_LLM__MODEL=openai/gpt-4.1-mini
|
||||
EVEROS_LLM__API_KEY=
|
||||
EVEROS_LLM__BASE_URL=https://openrouter.ai/api/v1
|
||||
|
||||
|
||||
# ─── Multimodal LLM (independent from [llm]; vision/audio capable) ────
|
||||
# Separate model for parsing multimodal content items (image / pdf /
|
||||
# audio / ...); must support OpenAI image_url parts. Defaults target
|
||||
# Gemini via OpenRouter so the same key covers chat + multimodal.
|
||||
|
||||
EVEROS_MULTIMODAL__MODEL=google/gemini-3-flash-preview
|
||||
EVEROS_MULTIMODAL__API_KEY=
|
||||
EVEROS_MULTIMODAL__BASE_URL=https://openrouter.ai/api/v1
|
||||
# Concurrency cap for parallel multimodal calls (default 4):
|
||||
# EVEROS_MULTIMODAL__MAX_CONCURRENCY=4
|
||||
#
|
||||
# file:// content-item support (read locally by EverOS, not everalgo).
|
||||
# Size cap per file:// asset (bytes; default 50 MiB):
|
||||
# EVEROS_MULTIMODAL__FILE_URI_MAX_BYTES=52428800
|
||||
# Allowlisted base dirs for file:// uris (JSON list). Empty/unset = allow any
|
||||
# readable file (local-first default); set to confine reads when the API is
|
||||
# exposed beyond loopback:
|
||||
# EVEROS_MULTIMODAL__FILE_URI_ALLOW_DIRS=["/srv/uploads"]
|
||||
|
||||
|
||||
# ─── Embedding (OpenAI-protocol /embeddings) ─────────
|
||||
# Any OpenAI-compatible embedding endpoint plugs in via base_url.
|
||||
# model / api_key / base_url have no shipped default — set them here
|
||||
# or in ~/.everos/config.toml before the embedding capability is used.
|
||||
|
||||
EVEROS_EMBEDDING__MODEL=Qwen/Qwen3-Embedding-4B
|
||||
EVEROS_EMBEDDING__API_KEY=
|
||||
EVEROS_EMBEDDING__BASE_URL=https://api.deepinfra.com/v1/openai
|
||||
# Runtime knobs — uncomment to override defaults (30s / 3 / 10 / 5):
|
||||
# EVEROS_EMBEDDING__TIMEOUT_SECONDS=30
|
||||
# EVEROS_EMBEDDING__MAX_RETRIES=3
|
||||
# EVEROS_EMBEDDING__BATCH_SIZE=10
|
||||
# EVEROS_EMBEDDING__MAX_CONCURRENT=5
|
||||
|
||||
|
||||
# ─── Rerank (OpenAI-protocol /rerank) ────────────────
|
||||
# base_url should point at the rerank endpoint (e.g. .../v1/rerank).
|
||||
|
||||
EVEROS_RERANK__MODEL=Qwen/Qwen3-Reranker-4B
|
||||
EVEROS_RERANK__API_KEY=
|
||||
EVEROS_RERANK__BASE_URL=https://api.deepinfra.com/v1/inference
|
||||
# Runtime knobs — uncomment to override defaults (30s / 3 / 10 / 5):
|
||||
# EVEROS_RERANK__TIMEOUT_SECONDS=30
|
||||
# EVEROS_RERANK__MAX_RETRIES=3
|
||||
# EVEROS_RERANK__BATCH_SIZE=10
|
||||
# EVEROS_RERANK__MAX_CONCURRENT=5
|
||||
|
||||
|
||||
# ─── Storage paths ───────────────────────────────────
|
||||
# memory-root holds md files + .index/ (LanceDB) + .system.db (SQLite) + ...
|
||||
# Override the default with EVEROS_MEMORY__ROOT (note the double-underscore
|
||||
# for nested config keys); see config/default.toml for all tunables.
|
||||
|
||||
# EVEROS_MEMORY__ROOT=~/.everos
|
||||
|
||||
|
||||
# ─── HTTP API ────────────────────────────────────────
|
||||
# Bind for ``everos server start``. Default ``127.0.0.1`` keeps the
|
||||
# server on loopback only; EverOS ships no built-in authentication (see
|
||||
# SECURITY.md). Set HOST=0.0.0.0 only after you have your own gateway /
|
||||
# auth layer in front — the CLI logs a warning if you bind to 0.0.0.0.
|
||||
|
||||
# EVEROS_API__HOST=127.0.0.1
|
||||
# EVEROS_API__PORT=8000
|
||||
|
||||
|
||||
# ─── Observability ───────────────────────────────────
|
||||
|
||||
EVEROS_LOG_LEVEL=INFO # DEBUG | INFO | WARNING | ERROR
|
||||
EVEROS_LOG_FORMAT=json # json | text
|
||||
# EVEROS_OTEL_ENDPOINT=http://localhost:4317 # OTel exporter (optional)
|
||||
|
||||
|
||||
# ─── Runtime ─────────────────────────────────────────
|
||||
|
||||
# TZ used by component.utils.datetime when input has no timezone
|
||||
TZ=UTC
|
||||
|
|
@ -33,6 +33,9 @@ Generate a starter `.env` and drop in your two keys:
|
|||
|
||||
```bash
|
||||
everos init # writes ./.env (use --xdg for ~/.config/everos/.env)
|
||||
# or, from a source checkout:
|
||||
cp .env.example .env
|
||||
|
||||
# Edit .env and fill four API key slots (only two distinct keys needed):
|
||||
# EVEROS_LLM__API_KEY (OpenRouter — chat LLM)
|
||||
# EVEROS_MULTIMODAL__API_KEY (OpenRouter — same key works)
|
||||
|
|
@ -105,7 +108,7 @@ curl -X POST http://127.0.0.1:8000/api/v1/memory/add \
|
|||
}"
|
||||
```
|
||||
|
||||
Response:
|
||||
Typical response:
|
||||
|
||||
```json
|
||||
{
|
||||
|
|
@ -119,7 +122,9 @@ Response:
|
|||
|
||||
`status: "accumulated"` means the three messages are in the session
|
||||
buffer, but the boundary detector hasn't decided to extract a memory
|
||||
cell yet. For a quick demo we'll force it.
|
||||
cell yet. If you see `status: "extracted"` instead, EverOS already
|
||||
carved out a memory cell; you can still continue. For a deterministic
|
||||
quick demo we'll force the boundary in the next step.
|
||||
|
||||
## 5. Force boundary extraction
|
||||
|
||||
|
|
@ -141,7 +146,7 @@ Response (this takes a few seconds — one LLM call for extraction):
|
|||
```
|
||||
|
||||
`status: "extracted"` means at least one memory cell was carved out and
|
||||
written to disk + indexed.
|
||||
written to disk; the local cascade process then indexes the Markdown.
|
||||
|
||||
> `/flush` is **OSS-only**. The cloud edition decides boundary timing
|
||||
> server-side and does not expose this endpoint.
|
||||
|
|
@ -184,7 +189,8 @@ Response (trimmed):
|
|||
],
|
||||
"profiles": [],
|
||||
"agent_cases": [],
|
||||
"agent_skills": []
|
||||
"agent_skills": [],
|
||||
"unprocessed_messages": []
|
||||
}
|
||||
}
|
||||
```
|
||||
|
|
@ -192,8 +198,13 @@ Response (trimmed):
|
|||
The hybrid retrieval (BM25 + vector + scalar) returns the episode
|
||||
that contains the climbing fact, with the matching atomic fact nested
|
||||
under it. Other response arrays (`profiles` / `agent_cases` /
|
||||
`agent_skills`) are always present for client-side symmetry, populated
|
||||
only when the requested kind matches.
|
||||
`agent_skills` / `unprocessed_messages`) are always present for
|
||||
client-side symmetry, populated only when the requested kind matches
|
||||
or when `filters.session_id` asks for in-flight buffer rows.
|
||||
|
||||
If the result is empty on the first try, wait a moment and retry; the
|
||||
Markdown write is synchronous, while the local index catches up in the
|
||||
background.
|
||||
|
||||
## 7. Your memory is just Markdown
|
||||
|
||||
|
|
|
|||
335
README.md
335
README.md
|
|
@ -25,12 +25,12 @@
|
|||
- [EverOS: One Memory For All](#everos-one-memory-for-all)
|
||||
- [How EverOS Is Different](#how-everos-is-different)
|
||||
- [Quick Start](#quick-start)
|
||||
- [Use Cases](#use-cases)
|
||||
- [Architecture At A Glance](#architecture-at-a-glance)
|
||||
- [Storage Layout](#storage-layout)
|
||||
- [Features](#features)
|
||||
- [Project Structure](#project-structure)
|
||||
- [Documentation](#documentation)
|
||||
- [Use Cases](#use-cases)
|
||||
- [Watch EverOS](#watch-everos)
|
||||
- [EverMind Ecosystems](#evermind-ecosystems)
|
||||
- [Contributing](#contributing)
|
||||
|
|
@ -176,40 +176,116 @@ Search independently by <code>user_id</code>, <code>agent_id</code>,
|
|||
|
||||
## Quick Start
|
||||
|
||||
### 1. Install EverOS
|
||||
> Goal: start EverOS, write one memory, and search it back.
|
||||
|
||||
### 0. Prerequisites
|
||||
|
||||
- Python 3.12+
|
||||
- API keys for the default providers: OpenRouter for chat / multimodal, and
|
||||
DeepInfra for embedding / rerank. You can use other OpenAI-compatible
|
||||
providers by changing the matching `*__BASE_URL` fields in `.env`.
|
||||
|
||||
### 1. Install
|
||||
|
||||
```bash
|
||||
uv pip install everos
|
||||
# or: pip install everos
|
||||
```
|
||||
|
||||
### 2. Initialize Configuration
|
||||
### 2. Configure
|
||||
|
||||
Generate a starter `.env` file, then fill the API key fields shown in
|
||||
the generated comments.
|
||||
Generate a starter `.env` file, then fill the four API key slots shown in the
|
||||
generated comments. Only two distinct keys are needed with the defaults:
|
||||
OpenRouter for `LLM` / `MULTIMODAL`, and DeepInfra for `EMBEDDING` / `RERANK`.
|
||||
|
||||
```bash
|
||||
everos init
|
||||
# or, from a source checkout:
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
`everos init` writes `./.env` by default. Use `everos init --xdg` to
|
||||
write `${XDG_CONFIG_HOME:-~/.config}/everos/.env` instead.
|
||||
|
||||
### 3. Start The Server
|
||||
### 3. Start EverOS
|
||||
|
||||
```bash
|
||||
everos --help
|
||||
everos server start
|
||||
```
|
||||
|
||||
Keep the server running, then open a second terminal and check it:
|
||||
|
||||
```bash
|
||||
curl http://127.0.0.1:8000/health
|
||||
```
|
||||
|
||||
Expected response:
|
||||
|
||||
```json
|
||||
{"status":"ok"}
|
||||
```
|
||||
|
||||
`everos server start` searches for `.env` in this order: `--env-file <path>` →
|
||||
`./.env` (cwd) → `${XDG_CONFIG_HOME:-~/.config}/everos/.env` → `~/.everos/.env`.
|
||||
The endpoint stack is OpenAI-protocol compatible (OpenAI / OpenRouter / vLLM /
|
||||
Ollama / DeepInfra) - override `*__BASE_URL` in the generated `.env` to point
|
||||
at any of them.
|
||||
|
||||
For a step-by-step walkthrough (add a conversation, flush, search, then
|
||||
read the markdown), see [QUICKSTART.md](QUICKSTART.md).
|
||||
### 4. Try Your First Memory
|
||||
|
||||
Add a tiny conversation:
|
||||
|
||||
```bash
|
||||
TS=$(($(date +%s)*1000))
|
||||
|
||||
curl -X POST http://127.0.0.1:8000/api/v1/memory/add \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{
|
||||
\"session_id\": \"demo-001\",
|
||||
\"app_id\": \"default\",
|
||||
\"project_id\": \"default\",
|
||||
\"messages\": [
|
||||
{\"sender_id\": \"alice\", \"role\": \"user\", \"timestamp\": $TS, \"content\": \"I love climbing in Yosemite every spring.\"},
|
||||
{\"sender_id\": \"alice\", \"role\": \"user\", \"timestamp\": $((TS+10000)), \"content\": \"My favorite coffee shop is Blue Bottle in SOMA.\"}
|
||||
]
|
||||
}"
|
||||
```
|
||||
|
||||
Force extraction for the local demo:
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:8000/api/v1/memory/flush \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"session_id":"demo-001","app_id":"default","project_id":"default"}'
|
||||
```
|
||||
|
||||
Search it back:
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:8000/api/v1/memory/search \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"user_id": "alice",
|
||||
"app_id": "default",
|
||||
"project_id": "default",
|
||||
"query": "Where do I like to climb?",
|
||||
"top_k": 5
|
||||
}'
|
||||
```
|
||||
|
||||
You should see the Yosemite memory in the response. If the result is empty on
|
||||
the first try, wait a moment and retry; Markdown is written synchronously, while
|
||||
the local index catches up in the background.
|
||||
|
||||
> [!TIP]
|
||||
> **First memory unlocked.**
|
||||
> You just gave EverOS a fact, flushed it into durable Markdown-backed memory,
|
||||
> and searched it back through the local index. That is the core loop.
|
||||
> Want to see the source of truth? Open `~/.everos` and inspect the generated
|
||||
> Markdown files.
|
||||
|
||||
For annotated responses and the Markdown files EverOS creates, see
|
||||
[QUICKSTART.md](QUICKSTART.md).
|
||||
|
||||
### Optional: Ingest Multimodal Files
|
||||
|
||||
|
|
@ -260,126 +336,12 @@ make test
|
|||
|
||||
</div>
|
||||
|
||||
## Architecture At A Glance
|
||||
|
||||
```
|
||||
┌───────────────────────────────────────────────┐
|
||||
│ entrypoints/ (CLI + HTTP API) │ presentation
|
||||
├───────────────────────────────────────────────┤
|
||||
│ service/ (use cases: memorize/retrieve) │ application
|
||||
├───────────────────────────────────────────────┤
|
||||
│ memory/ (extract + search + cascade) │ domain
|
||||
├───────────────────────────────────────────────┤
|
||||
│ infra/ (markdown / sqlite / lancedb) │ infrastructure
|
||||
└───────────────────────────────────────────────┘
|
||||
↑ ↑
|
||||
component/ core/
|
||||
(LLM/Embedding) (observability/lifespan)
|
||||
```
|
||||
|
||||
DDD 5 layers, single-direction dependency. See [docs/architecture.md](docs/architecture.md).
|
||||
|
||||
<br>
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
## Storage Layout
|
||||
|
||||
```
|
||||
~/.everos/
|
||||
├── default_app/ # app_id ("default" → "default_app" on disk)
|
||||
│ └── default_project/ # project_id ("default" → "default_project")
|
||||
│ ├── users/<user_id>/
|
||||
│ │ ├── user.md # profile
|
||||
│ │ ├── episodes/ # daily-log episodes (visible)
|
||||
│ │ ├── .atomic_facts/ # nested facts (dotfile-hidden)
|
||||
│ │ └── .foresights/ # predictive memory (dotfile-hidden)
|
||||
│ └── agents/<agent_id>/
|
||||
│ ├── agent.md
|
||||
│ ├── .cases/ # one task case per entry
|
||||
│ └── skills/ # named procedural memories
|
||||
├── .index/ # derived indexes (rebuildable from md)
|
||||
│ ├── sqlite/system.db # state + queue + audit
|
||||
│ └── lancedb/*.lance/ # vector + BM25 + scalar
|
||||
└── .tmp/ # transient working files
|
||||
```
|
||||
|
||||
Open any `<app>/<project>/users/<user_id>/` folder in Obsidian — your
|
||||
agent's brain is just files. The dotfile directories (`.atomic_facts/`,
|
||||
`.foresights/`, `.cases/`) stay hidden by default so the visible folder
|
||||
is the user-facing memory surface, while extracted derivatives sit
|
||||
quietly alongside.
|
||||
|
||||
<br>
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
## Features
|
||||
|
||||
- **Hybrid retrieval**: BM25 + cosine vector ANN + scalar filters, backed by LanceDB
|
||||
- **Cascade index sync**: edit a `.md` → file watcher → entry-level diff → LanceDB sync, sub-second
|
||||
- **Multi-source extraction**: conversations / agent trajectories / file knowledge
|
||||
- **Dual-track memory**: user-track (Episodes / Profiles) + agent-track (Cases / Skills)
|
||||
- **Async-first**: full asyncio, single event loop
|
||||
- **Multi-modal**: text + small image / audio inline; large media via S3/OSS reference
|
||||
|
||||
<br>
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
everos/ # repo root
|
||||
├── src/everos/ # main package (src layout)
|
||||
│ ├── entrypoints/ # cli + api
|
||||
│ ├── service/ # use case orchestration
|
||||
│ ├── memory/ # domain: extract + search + cascade + prompt_slots
|
||||
│ ├── infra/ # storage: markdown + lancedb + sqlite
|
||||
│ ├── component/ # cross-cutting: llm / embedding / config / utils
|
||||
│ ├── core/ # runtime: observability / lifespan / context
|
||||
│ └── config/ # configuration data + Settings schema
|
||||
├── tests/ # unit / integration / golden / fixtures
|
||||
├── docs/ # design docs
|
||||
└── .claude/ # team-shared rules + skills (auto-loaded by Claude Code)
|
||||
```
|
||||
<br>
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
## Documentation
|
||||
|
||||
- [docs/overview.md](docs/overview.md) — Project overview & vision
|
||||
- [docs/architecture.md](docs/architecture.md) — DDD layered architecture & dependency rules
|
||||
- [docs/engineering.md](docs/engineering.md) — Engineering & dev-efficiency infrastructure (CI / tooling / Claude Code)
|
||||
- [docs/use-cases.md](docs/use-cases.md) — Full use-case gallery and integration examples
|
||||
- [docs/migration-to-1.0.0.md](docs/migration-to-1.0.0.md) — Legacy API and infrastructure migration notes
|
||||
- [CHANGELOG.md](CHANGELOG.md) — Release notes
|
||||
- [CONTRIBUTING.md](CONTRIBUTING.md) — How to contribute
|
||||
- [.claude/rules/](.claude/rules/) — Detailed coding conventions (auto-loaded by Claude Code)
|
||||
|
||||
<br>
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
## Use Cases
|
||||
|
||||
Now that you have had your first successful EverOS moment, explore what people
|
||||
are building with persistent memory across agents, apps, and community
|
||||
integrations.
|
||||
|
||||
Use cases show what persistent memory makes possible in real products and
|
||||
workflows. Some examples are packaged in this repository; others point to
|
||||
external demos or integrations you can study and adapt.
|
||||
|
|
@ -707,6 +669,125 @@ Explore stored entities and relationships in a graph interface. Frontend demo; b
|
|||
|
||||
</div>
|
||||
|
||||
## Architecture At A Glance
|
||||
|
||||
```
|
||||
┌───────────────────────────────────────────────┐
|
||||
│ entrypoints/ (CLI + HTTP API) │ presentation
|
||||
├───────────────────────────────────────────────┤
|
||||
│ service/ (use cases: memorize/retrieve) │ application
|
||||
├───────────────────────────────────────────────┤
|
||||
│ memory/ (extract + search + cascade) │ domain
|
||||
├───────────────────────────────────────────────┤
|
||||
│ infra/ (markdown / sqlite / lancedb) │ infrastructure
|
||||
└───────────────────────────────────────────────┘
|
||||
↑ ↑
|
||||
component/ core/
|
||||
(LLM/Embedding) (observability/lifespan)
|
||||
```
|
||||
|
||||
DDD 5 layers, single-direction dependency. See [docs/architecture.md](docs/architecture.md).
|
||||
|
||||
<br>
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
## Storage Layout
|
||||
|
||||
```
|
||||
~/.everos/
|
||||
├── default_app/ # app_id ("default" → "default_app" on disk)
|
||||
│ └── default_project/ # project_id ("default" → "default_project")
|
||||
│ ├── users/<user_id>/
|
||||
│ │ ├── user.md # profile
|
||||
│ │ ├── episodes/ # daily-log episodes (visible)
|
||||
│ │ ├── .atomic_facts/ # nested facts (dotfile-hidden)
|
||||
│ │ └── .foresights/ # predictive memory (dotfile-hidden)
|
||||
│ └── agents/<agent_id>/
|
||||
│ ├── agent.md
|
||||
│ ├── .cases/ # one task case per entry
|
||||
│ └── skills/ # named procedural memories
|
||||
├── .index/ # derived indexes (rebuildable from md)
|
||||
│ ├── sqlite/system.db # state + queue + audit
|
||||
│ └── lancedb/*.lance/ # vector + BM25 + scalar
|
||||
└── .tmp/ # transient working files
|
||||
```
|
||||
|
||||
Open any `<app>/<project>/users/<user_id>/` folder in Obsidian — your
|
||||
agent's brain is just files. The dotfile directories (`.atomic_facts/`,
|
||||
`.foresights/`, `.cases/`) stay hidden by default so the visible folder
|
||||
is the user-facing memory surface, while extracted derivatives sit
|
||||
quietly alongside.
|
||||
|
||||
<br>
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
## Features
|
||||
|
||||
- **Hybrid retrieval**: BM25 + cosine vector ANN + scalar filters, backed by LanceDB
|
||||
- **Cascade index sync**: edit a `.md` → file watcher → entry-level diff → LanceDB sync, sub-second
|
||||
- **Multi-source extraction**: conversations / agent trajectories / file knowledge
|
||||
- **Dual-track memory**: user-track (Episodes / Profiles) + agent-track (Cases / Skills)
|
||||
- **Async-first**: full asyncio, single event loop
|
||||
- **Multi-modal**: text + small image / audio inline; large media via S3/OSS reference
|
||||
|
||||
<br>
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
## Project Structure
|
||||
|
||||
```
|
||||
everos/ # repo root
|
||||
├── src/everos/ # main package (src layout)
|
||||
│ ├── entrypoints/ # cli + api
|
||||
│ ├── service/ # use case orchestration
|
||||
│ ├── memory/ # domain: extract + search + cascade + prompt_slots
|
||||
│ ├── infra/ # storage: markdown + lancedb + sqlite
|
||||
│ ├── component/ # cross-cutting: llm / embedding / config / utils
|
||||
│ ├── core/ # runtime: observability / lifespan / context
|
||||
│ └── config/ # configuration data + Settings schema
|
||||
├── tests/ # unit / integration / golden / fixtures
|
||||
├── docs/ # design docs
|
||||
└── .claude/ # team-shared rules + skills (auto-loaded by Claude Code)
|
||||
```
|
||||
<br>
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
## Documentation
|
||||
|
||||
- [docs/overview.md](docs/overview.md) — Project overview & vision
|
||||
- [docs/architecture.md](docs/architecture.md) — DDD layered architecture & dependency rules
|
||||
- [docs/engineering.md](docs/engineering.md) — Engineering & dev-efficiency infrastructure (CI / tooling / Claude Code)
|
||||
- [docs/use-cases.md](docs/use-cases.md) — Full use-case gallery and integration examples
|
||||
- [docs/migration-to-1.0.0.md](docs/migration-to-1.0.0.md) — Legacy API and infrastructure migration notes
|
||||
- [CHANGELOG.md](CHANGELOG.md) — Release notes
|
||||
- [CONTRIBUTING.md](CONTRIBUTING.md) — How to contribute
|
||||
- [.claude/rules/](.claude/rules/) — Detailed coding conventions (auto-loaded by Claude Code)
|
||||
|
||||
<br>
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
## Watch EverOS
|
||||
|
||||
EverOS 1.0.0 is the first release of a larger memory-system roadmap.
|
||||
|
|
|
|||
328
README.zh-CN.md
328
README.zh-CN.md
|
|
@ -25,12 +25,12 @@
|
|||
- [EverOS: One Memory For All](#everos-one-memory-for-all)
|
||||
- [EverOS 的差异](#everos-的差异)
|
||||
- [快速开始](#快速开始)
|
||||
- [使用场景](#使用场景)
|
||||
- [架构概览](#架构概览)
|
||||
- [存储布局](#存储布局)
|
||||
- [功能](#功能)
|
||||
- [项目结构](#项目结构)
|
||||
- [文档](#文档)
|
||||
- [使用场景](#使用场景)
|
||||
- [关注 EverOS](#关注-everos)
|
||||
- [EverMind 生态](#evermind-生态)
|
||||
- [参与贡献](#参与贡献)
|
||||
|
|
@ -168,39 +168,113 @@ Agent 记忆(<code>cases</code> / <code>skills</code>)与用户记忆(<cod
|
|||
|
||||
## 快速开始
|
||||
|
||||
### 1. 安装 EverOS
|
||||
> 目标:启动 EverOS,写入一条记忆,然后把它搜索回来。
|
||||
|
||||
### 0. 前置条件
|
||||
|
||||
- Python 3.12+
|
||||
- 默认 provider 需要 API keys:OpenRouter 用于 chat / multimodal,
|
||||
DeepInfra 用于 embedding / rerank。也可以通过 `.env` 里的
|
||||
`*__BASE_URL` 字段切换到其他 OpenAI-compatible providers。
|
||||
|
||||
### 1. 安装
|
||||
|
||||
```bash
|
||||
uv pip install everos
|
||||
# or: pip install everos
|
||||
```
|
||||
|
||||
### 2. 初始化配置
|
||||
### 2. 配置
|
||||
|
||||
生成一个 starter `.env` 文件,然后根据生成的注释填入 API key 字段。
|
||||
生成一个 starter `.env` 文件,然后根据生成的注释填入四个 API key slots。
|
||||
默认配置只需要两把不同的 key:OpenRouter 用于 `LLM` / `MULTIMODAL`,
|
||||
DeepInfra 用于 `EMBEDDING` / `RERANK`。
|
||||
|
||||
```bash
|
||||
everos init
|
||||
# or, from a source checkout:
|
||||
cp .env.example .env
|
||||
```
|
||||
|
||||
`everos init` 默认写入 `./.env`。也可以使用 `everos init --xdg`
|
||||
写入 `${XDG_CONFIG_HOME:-~/.config}/everos/.env`。
|
||||
|
||||
### 3. 启动服务
|
||||
### 3. 启动 EverOS
|
||||
|
||||
```bash
|
||||
everos --help
|
||||
everos server start
|
||||
```
|
||||
|
||||
保持服务运行,然后打开第二个 terminal 检查:
|
||||
|
||||
```bash
|
||||
curl http://127.0.0.1:8000/health
|
||||
```
|
||||
|
||||
预期响应:
|
||||
|
||||
```json
|
||||
{"status":"ok"}
|
||||
```
|
||||
|
||||
`everos server start` 会按以下顺序查找 `.env`:`--env-file <path>` →
|
||||
`./.env`(当前目录)→ `${XDG_CONFIG_HOME:-~/.config}/everos/.env` →
|
||||
`~/.everos/.env`。端点栈兼容 OpenAI protocol(OpenAI / OpenRouter /
|
||||
vLLM / Ollama / DeepInfra)。你可以覆盖生成的 `.env` 中的 `*__BASE_URL`
|
||||
来指向任意这些模型服务。
|
||||
|
||||
完整 walkthrough(添加对话、flush、search,然后读取 Markdown)见
|
||||
[QUICKSTART.md](QUICKSTART.md)。
|
||||
### 4. 试写第一条记忆
|
||||
|
||||
添加一个很小的 conversation:
|
||||
|
||||
```bash
|
||||
TS=$(($(date +%s)*1000))
|
||||
|
||||
curl -X POST http://127.0.0.1:8000/api/v1/memory/add \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d "{
|
||||
\"session_id\": \"demo-001\",
|
||||
\"app_id\": \"default\",
|
||||
\"project_id\": \"default\",
|
||||
\"messages\": [
|
||||
{\"sender_id\": \"alice\", \"role\": \"user\", \"timestamp\": $TS, \"content\": \"I love climbing in Yosemite every spring.\"},
|
||||
{\"sender_id\": \"alice\", \"role\": \"user\", \"timestamp\": $((TS+10000)), \"content\": \"My favorite coffee shop is Blue Bottle in SOMA.\"}
|
||||
]
|
||||
}"
|
||||
```
|
||||
|
||||
为了本地 demo,手动触发一次 extraction:
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:8000/api/v1/memory/flush \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{"session_id":"demo-001","app_id":"default","project_id":"default"}'
|
||||
```
|
||||
|
||||
再把这条记忆搜索回来:
|
||||
|
||||
```bash
|
||||
curl -X POST http://127.0.0.1:8000/api/v1/memory/search \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '{
|
||||
"user_id": "alice",
|
||||
"app_id": "default",
|
||||
"project_id": "default",
|
||||
"query": "Where do I like to climb?",
|
||||
"top_k": 5
|
||||
}'
|
||||
```
|
||||
|
||||
响应里应该能看到 Yosemite 相关记忆。如果第一次搜索为空,稍等片刻再试;
|
||||
Markdown 会同步写入,本地索引会在后台追上。
|
||||
|
||||
> [!TIP]
|
||||
> **第一条记忆已经写入。**
|
||||
> 你刚刚把一个事实交给 EverOS,把它整理进可持久化的 Markdown-backed memory,
|
||||
> 并通过本地索引把它搜索回来。这就是 EverOS 的核心闭环。
|
||||
> 想看看 source of truth?打开 `~/.everos`,直接检查生成的 Markdown 文件。
|
||||
|
||||
带完整响应和 Markdown 文件说明的 walkthrough 见 [QUICKSTART.md](QUICKSTART.md)。
|
||||
|
||||
### 可选:摄取多模态文件
|
||||
|
||||
|
|
@ -248,125 +322,11 @@ make test
|
|||
|
||||
</div>
|
||||
|
||||
## 架构概览
|
||||
|
||||
```
|
||||
┌───────────────────────────────────────────────┐
|
||||
│ entrypoints/ (CLI + HTTP API) │ presentation
|
||||
├───────────────────────────────────────────────┤
|
||||
│ service/ (use cases: memorize/retrieve) │ application
|
||||
├───────────────────────────────────────────────┤
|
||||
│ memory/ (extract + search + cascade) │ domain
|
||||
├───────────────────────────────────────────────┤
|
||||
│ infra/ (markdown / sqlite / lancedb) │ infrastructure
|
||||
└───────────────────────────────────────────────┘
|
||||
↑ ↑
|
||||
component/ core/
|
||||
(LLM/Embedding) (observability/lifespan)
|
||||
```
|
||||
|
||||
DDD 5 层架构,单向依赖。详见 [docs/architecture.md](docs/architecture.md)。
|
||||
|
||||
<br>
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
## 存储布局
|
||||
|
||||
```
|
||||
~/.everos/
|
||||
├── default_app/ # app_id ("default" → "default_app" on disk)
|
||||
│ └── default_project/ # project_id ("default" → "default_project")
|
||||
│ ├── users/<user_id>/
|
||||
│ │ ├── user.md # profile
|
||||
│ │ ├── episodes/ # daily-log episodes (visible)
|
||||
│ │ ├── .atomic_facts/ # nested facts (dotfile-hidden)
|
||||
│ │ └── .foresights/ # predictive memory (dotfile-hidden)
|
||||
│ └── agents/<agent_id>/
|
||||
│ ├── agent.md
|
||||
│ ├── .cases/ # one task case per entry
|
||||
│ └── skills/ # named procedural memories
|
||||
├── .index/ # derived indexes (rebuildable from md)
|
||||
│ ├── sqlite/system.db # state + queue + audit
|
||||
│ └── lancedb/*.lance/ # vector + BM25 + scalar
|
||||
└── .tmp/ # transient working files
|
||||
```
|
||||
|
||||
在 Obsidian 中打开任意 `<app>/<project>/users/<user_id>/` 文件夹即可。
|
||||
你的 Agent 大脑本质上就是一组文件。dotfile 目录(`.atomic_facts/`、
|
||||
`.foresights/`、`.cases/`)默认保持隐藏,因此可见文件夹仍然是面向用户的
|
||||
记忆表面,而提取出的衍生信息则安静地放在旁边。
|
||||
|
||||
<br>
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
## 功能
|
||||
|
||||
- **混合检索**: BM25 + vector(HNSW/IVF-PQ)+ scalar filter,在 LanceDB 中完成单次查询
|
||||
- **级联索引同步**: 编辑 `.md` → file watcher → entry-level diff → LanceDB sync,亚秒级同步
|
||||
- **多源提取**: conversations / agent trajectories / file knowledge
|
||||
- **双轨记忆**: user-track(Episodes / Profiles)+ agent-track(Cases / Skills)
|
||||
- **异步优先**: 完整 asyncio,单一 event loop
|
||||
- **多模态**: text + 小图片 / audio inline;大媒体通过 S3/OSS reference
|
||||
|
||||
<br>
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
everos/ # repo root
|
||||
├── src/everos/ # main package (src layout)
|
||||
│ ├── entrypoints/ # cli + api
|
||||
│ ├── service/ # use case orchestration
|
||||
│ ├── memory/ # domain: extract + search + cascade + prompt_slots
|
||||
│ ├── infra/ # storage: markdown + lancedb + sqlite
|
||||
│ ├── component/ # cross-cutting: llm / embedding / config / utils
|
||||
│ ├── core/ # runtime: observability / lifespan / context
|
||||
│ └── config/ # configuration data + Settings schema
|
||||
├── tests/ # unit / integration / golden / fixtures
|
||||
├── docs/ # design docs
|
||||
└── .claude/ # team-shared rules + skills (auto-loaded by Claude Code)
|
||||
```
|
||||
<br>
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
## 文档
|
||||
|
||||
- [docs/overview.md](docs/overview.md) - 项目概览与愿景
|
||||
- [docs/architecture.md](docs/architecture.md) - DDD 分层架构与依赖规则
|
||||
- [docs/engineering.md](docs/engineering.md) - 工程与开发效率基础设施(CI / tooling / Claude Code)
|
||||
- [docs/use-cases.md](docs/use-cases.md) - 完整使用场景 gallery 和集成示例
|
||||
- [docs/migration-to-1.0.0.md](docs/migration-to-1.0.0.md) - Legacy API 与基础设施迁移说明
|
||||
- [CHANGELOG.md](CHANGELOG.md) - 发布记录
|
||||
- [CONTRIBUTING.md](CONTRIBUTING.md) - 如何贡献
|
||||
- [.claude/rules/](.claude/rules/) - 详细代码规范(Claude Code 会自动加载)
|
||||
|
||||
<br>
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
## 使用场景
|
||||
|
||||
现在你已经完成了第一个成功的 EverOS moment,可以继续看看大家如何把持久记忆
|
||||
用在 agents、apps 和社区集成里。
|
||||
|
||||
这些使用场景展示了持久记忆可以在真实产品和工作流中带来什么能力。
|
||||
有些示例已经打包在本仓库中,另一些则指向外部 demo 或集成,你可以研究并复用。
|
||||
|
||||
|
|
@ -693,6 +653,124 @@ Claude Code 的持久记忆插件。自动保存并回忆过去 coding sessions
|
|||
|
||||
</div>
|
||||
|
||||
## 架构概览
|
||||
|
||||
```
|
||||
┌───────────────────────────────────────────────┐
|
||||
│ entrypoints/ (CLI + HTTP API) │ presentation
|
||||
├───────────────────────────────────────────────┤
|
||||
│ service/ (use cases: memorize/retrieve) │ application
|
||||
├───────────────────────────────────────────────┤
|
||||
│ memory/ (extract + search + cascade) │ domain
|
||||
├───────────────────────────────────────────────┤
|
||||
│ infra/ (markdown / sqlite / lancedb) │ infrastructure
|
||||
└───────────────────────────────────────────────┘
|
||||
↑ ↑
|
||||
component/ core/
|
||||
(LLM/Embedding) (observability/lifespan)
|
||||
```
|
||||
|
||||
DDD 5 层架构,单向依赖。详见 [docs/architecture.md](docs/architecture.md)。
|
||||
|
||||
<br>
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
## 存储布局
|
||||
|
||||
```
|
||||
~/.everos/
|
||||
├── default_app/ # app_id ("default" → "default_app" on disk)
|
||||
│ └── default_project/ # project_id ("default" → "default_project")
|
||||
│ ├── users/<user_id>/
|
||||
│ │ ├── user.md # profile
|
||||
│ │ ├── episodes/ # daily-log episodes (visible)
|
||||
│ │ ├── .atomic_facts/ # nested facts (dotfile-hidden)
|
||||
│ │ └── .foresights/ # predictive memory (dotfile-hidden)
|
||||
│ └── agents/<agent_id>/
|
||||
│ ├── agent.md
|
||||
│ ├── .cases/ # one task case per entry
|
||||
│ └── skills/ # named procedural memories
|
||||
├── .index/ # derived indexes (rebuildable from md)
|
||||
│ ├── sqlite/system.db # state + queue + audit
|
||||
│ └── lancedb/*.lance/ # vector + BM25 + scalar
|
||||
└── .tmp/ # transient working files
|
||||
```
|
||||
|
||||
在 Obsidian 中打开任意 `<app>/<project>/users/<user_id>/` 文件夹即可。
|
||||
你的 Agent 大脑本质上就是一组文件。dotfile 目录(`.atomic_facts/`、
|
||||
`.foresights/`、`.cases/`)默认保持隐藏,因此可见文件夹仍然是面向用户的
|
||||
记忆表面,而提取出的衍生信息则安静地放在旁边。
|
||||
|
||||
<br>
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
## 功能
|
||||
|
||||
- **混合检索**: BM25 + vector(HNSW/IVF-PQ)+ scalar filter,在 LanceDB 中完成单次查询
|
||||
- **级联索引同步**: 编辑 `.md` → file watcher → entry-level diff → LanceDB sync,亚秒级同步
|
||||
- **多源提取**: conversations / agent trajectories / file knowledge
|
||||
- **双轨记忆**: user-track(Episodes / Profiles)+ agent-track(Cases / Skills)
|
||||
- **异步优先**: 完整 asyncio,单一 event loop
|
||||
- **多模态**: text + 小图片 / audio inline;大媒体通过 S3/OSS reference
|
||||
|
||||
<br>
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
everos/ # repo root
|
||||
├── src/everos/ # main package (src layout)
|
||||
│ ├── entrypoints/ # cli + api
|
||||
│ ├── service/ # use case orchestration
|
||||
│ ├── memory/ # domain: extract + search + cascade + prompt_slots
|
||||
│ ├── infra/ # storage: markdown + lancedb + sqlite
|
||||
│ ├── component/ # cross-cutting: llm / embedding / config / utils
|
||||
│ ├── core/ # runtime: observability / lifespan / context
|
||||
│ └── config/ # configuration data + Settings schema
|
||||
├── tests/ # unit / integration / golden / fixtures
|
||||
├── docs/ # design docs
|
||||
└── .claude/ # team-shared rules + skills (auto-loaded by Claude Code)
|
||||
```
|
||||
<br>
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
## 文档
|
||||
|
||||
- [docs/overview.md](docs/overview.md) - 项目概览与愿景
|
||||
- [docs/architecture.md](docs/architecture.md) - DDD 分层架构与依赖规则
|
||||
- [docs/engineering.md](docs/engineering.md) - 工程与开发效率基础设施(CI / tooling / Claude Code)
|
||||
- [docs/use-cases.md](docs/use-cases.md) - 完整使用场景 gallery 和集成示例
|
||||
- [docs/migration-to-1.0.0.md](docs/migration-to-1.0.0.md) - Legacy API 与基础设施迁移说明
|
||||
- [CHANGELOG.md](CHANGELOG.md) - 发布记录
|
||||
- [CONTRIBUTING.md](CONTRIBUTING.md) - 如何贡献
|
||||
- [.claude/rules/](.claude/rules/) - 详细代码规范(Claude Code 会自动加载)
|
||||
|
||||
<br>
|
||||
<div align="right">
|
||||
|
||||
[](#readme-top)
|
||||
|
||||
</div>
|
||||
|
||||
|
||||
|
||||
## 关注 EverOS
|
||||
|
||||
EverOS 1.0.0 是更大规模记忆系统路线图的第一个发布版本。Watch 这个仓库,
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ PRIMARY_LINK_RE = re.compile(
|
|||
r"^\[(?:Code|Plugin|Live Demo|Learn more)\]\(([^)]+)\)",
|
||||
flags=re.M,
|
||||
)
|
||||
ENV_EXAMPLE = Path(".env.example")
|
||||
ENV_TEMPLATE = Path("src/everos/templates/env.template")
|
||||
|
||||
|
||||
def _markdown_files() -> list[Path]:
|
||||
|
|
@ -88,10 +90,21 @@ def _check_use_case_banner_links() -> tuple[list[str], list[str]]:
|
|||
return failures, warnings
|
||||
|
||||
|
||||
def _check_env_example_matches_template() -> list[str]:
|
||||
if not ENV_EXAMPLE.exists():
|
||||
return [f"{ENV_EXAMPLE}: missing"]
|
||||
|
||||
if ENV_EXAMPLE.read_text() != ENV_TEMPLATE.read_text():
|
||||
return [f"{ENV_EXAMPLE}: must match {ENV_TEMPLATE}"]
|
||||
|
||||
return []
|
||||
|
||||
|
||||
def main() -> int:
|
||||
failures = _check_active_relative_links()
|
||||
use_case_failures, warnings = _check_use_case_banner_links()
|
||||
failures.extend(use_case_failures)
|
||||
failures.extend(_check_env_example_matches_template())
|
||||
|
||||
if warnings:
|
||||
print("\n".join(f"warning: {warning}" for warning in warnings))
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ Contract per the final design:
|
|||
|
||||
* ``owner_type`` is a hard partition. ``user`` returns ``episodes``
|
||||
(and optionally ``profiles``); ``agent`` returns ``agent_cases`` +
|
||||
``agent_skills``. The four ``data.*`` arrays always exist; routes not
|
||||
``agent_skills``. The five ``data.*`` arrays always exist; routes not
|
||||
applicable to the current ``owner_type`` stay as ``[]``.
|
||||
* ``atomic_facts`` are **nested** inside :class:`SearchEpisodeItem`,
|
||||
never returned as a top-level array.
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
# =====================================================
|
||||
#
|
||||
# Setup:
|
||||
# 1. cp env.template .env
|
||||
# 1. Create .env with `everos init` or `cp .env.example .env`
|
||||
# 2. Edit .env with your values
|
||||
# 3. .env is gitignored (never commit)
|
||||
#
|
||||
|
|
|
|||
|
|
@ -12,8 +12,9 @@
|
|||
the keyword / vector / hybrid recall tests so the assertion logic
|
||||
is in one place.
|
||||
|
||||
* :func:`flatten_hits` — collapses ``SearchData``'s four arrays into
|
||||
one ``(owner_id, score, text)`` tuple list for relevance checks.
|
||||
* :func:`flatten_hits` — collapses ``SearchData``'s four scored result
|
||||
arrays into one ``(owner_id, score, text)`` tuple list for relevance
|
||||
checks.
|
||||
|
||||
The helpers do **not** hardcode topical keywords ("hiking" / "work")
|
||||
— they are derived from what the pipeline produced. This keeps the
|
||||
|
|
@ -144,7 +145,7 @@ def _extract_fact_sections(md: Path) -> list[str]:
|
|||
|
||||
|
||||
def flatten_hits(data: dict[str, Any]) -> list[tuple[str | None, float, str]]:
|
||||
"""Collapse ``SearchData``'s four arrays into ``(owner_id, score, text)``.
|
||||
"""Collapse the four scored arrays into ``(owner_id, score, text)``.
|
||||
|
||||
Stable shape across track-kinds so the recall / partition tests
|
||||
don't have to branch. Episodes / profiles carry ``user_id`` on the
|
||||
|
|
@ -200,7 +201,7 @@ async def assert_recall(
|
|||
"""Hit ``/search`` and lock the four standard recall invariants.
|
||||
|
||||
1. **Status** 200 — the route compiled.
|
||||
2. **Existence** — ``total >= 1`` across the four arrays.
|
||||
2. **Existence** — ``total >= 1`` across the four scored arrays.
|
||||
3. **Owner partition** — every non-``None`` ``owner_id`` matches
|
||||
the queried owner. Profile hits may carry ``None`` so they're
|
||||
skipped from the check.
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ async def test_partition_respects_owner_id(
|
|||
async def test_unknown_owner_returns_empty_200(
|
||||
search_client: httpx.AsyncClient,
|
||||
) -> None:
|
||||
"""An owner that the corpus never saw → 200 with four empty arrays."""
|
||||
"""An owner that the corpus never saw → 200 with empty result arrays."""
|
||||
resp = await search_client.post(
|
||||
"/api/v1/memory/search",
|
||||
json={
|
||||
|
|
@ -209,6 +209,7 @@ async def test_unknown_owner_returns_empty_200(
|
|||
assert data["profiles"] == []
|
||||
assert data["agent_cases"] == []
|
||||
assert data["agent_skills"] == []
|
||||
assert data["unprocessed_messages"] == []
|
||||
|
||||
|
||||
# ── 6. Filter DSL ──────────────────────────────────────────────────────
|
||||
|
|
|
|||
|
|
@ -127,6 +127,7 @@ def test_response_default_arrays_present() -> None:
|
|||
assert resp.data.profiles == []
|
||||
assert resp.data.agent_cases == []
|
||||
assert resp.data.agent_skills == []
|
||||
assert resp.data.unprocessed_messages == []
|
||||
|
||||
|
||||
def test_method_enum_serialises_to_lowercase() -> None:
|
||||
|
|
|
|||
Loading…
Reference in New Issue