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:
Elliot Chen 2026-06-17 13:15:59 +08:00 committed by GitHub
parent a10cdcd197
commit 9c7c9d7316
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 565 additions and 265 deletions

114
.env.example Normal file
View File

@ -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

View File

@ -33,6 +33,9 @@ Generate a starter `.env` and drop in your two keys:
```bash ```bash
everos init # writes ./.env (use --xdg for ~/.config/everos/.env) 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): # Edit .env and fill four API key slots (only two distinct keys needed):
# EVEROS_LLM__API_KEY (OpenRouter — chat LLM) # EVEROS_LLM__API_KEY (OpenRouter — chat LLM)
# EVEROS_MULTIMODAL__API_KEY (OpenRouter — same key works) # 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 ```json
{ {
@ -119,7 +122,9 @@ Response:
`status: "accumulated"` means the three messages are in the session `status: "accumulated"` means the three messages are in the session
buffer, but the boundary detector hasn't decided to extract a memory 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 ## 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 `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 > `/flush` is **OSS-only**. The cloud edition decides boundary timing
> server-side and does not expose this endpoint. > server-side and does not expose this endpoint.
@ -184,7 +189,8 @@ Response (trimmed):
], ],
"profiles": [], "profiles": [],
"agent_cases": [], "agent_cases": [],
"agent_skills": [] "agent_skills": [],
"unprocessed_messages": []
} }
} }
``` ```
@ -192,8 +198,13 @@ Response (trimmed):
The hybrid retrieval (BM25 + vector + scalar) returns the episode The hybrid retrieval (BM25 + vector + scalar) returns the episode
that contains the climbing fact, with the matching atomic fact nested that contains the climbing fact, with the matching atomic fact nested
under it. Other response arrays (`profiles` / `agent_cases` / under it. Other response arrays (`profiles` / `agent_cases` /
`agent_skills`) are always present for client-side symmetry, populated `agent_skills` / `unprocessed_messages`) are always present for
only when the requested kind matches. 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 ## 7. Your memory is just Markdown

335
README.md
View File

@ -25,12 +25,12 @@
- [EverOS: One Memory For All](#everos-one-memory-for-all) - [EverOS: One Memory For All](#everos-one-memory-for-all)
- [How EverOS Is Different](#how-everos-is-different) - [How EverOS Is Different](#how-everos-is-different)
- [Quick Start](#quick-start) - [Quick Start](#quick-start)
- [Use Cases](#use-cases)
- [Architecture At A Glance](#architecture-at-a-glance) - [Architecture At A Glance](#architecture-at-a-glance)
- [Storage Layout](#storage-layout) - [Storage Layout](#storage-layout)
- [Features](#features) - [Features](#features)
- [Project Structure](#project-structure) - [Project Structure](#project-structure)
- [Documentation](#documentation) - [Documentation](#documentation)
- [Use Cases](#use-cases)
- [Watch EverOS](#watch-everos) - [Watch EverOS](#watch-everos)
- [EverMind Ecosystems](#evermind-ecosystems) - [EverMind Ecosystems](#evermind-ecosystems)
- [Contributing](#contributing) - [Contributing](#contributing)
@ -176,40 +176,116 @@ Search independently by <code>user_id</code>, <code>agent_id</code>,
## Quick Start ## 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 ```bash
uv pip install everos uv pip install everos
# or: 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 Generate a starter `.env` file, then fill the four API key slots shown in the
the generated comments. generated comments. Only two distinct keys are needed with the defaults:
OpenRouter for `LLM` / `MULTIMODAL`, and DeepInfra for `EMBEDDING` / `RERANK`.
```bash ```bash
everos init everos init
# or, from a source checkout:
cp .env.example .env
``` ```
`everos init` writes `./.env` by default. Use `everos init --xdg` to `everos init` writes `./.env` by default. Use `everos init --xdg` to
write `${XDG_CONFIG_HOME:-~/.config}/everos/.env` instead. write `${XDG_CONFIG_HOME:-~/.config}/everos/.env` instead.
### 3. Start The Server ### 3. Start EverOS
```bash ```bash
everos --help
everos server start 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>` `everos server start` searches for `.env` in this order: `--env-file <path>`
`./.env` (cwd) → `${XDG_CONFIG_HOME:-~/.config}/everos/.env``~/.everos/.env`. `./.env` (cwd) → `${XDG_CONFIG_HOME:-~/.config}/everos/.env``~/.everos/.env`.
The endpoint stack is OpenAI-protocol compatible (OpenAI / OpenRouter / vLLM / The endpoint stack is OpenAI-protocol compatible (OpenAI / OpenRouter / vLLM /
Ollama / DeepInfra) - override `*__BASE_URL` in the generated `.env` to point Ollama / DeepInfra) - override `*__BASE_URL` in the generated `.env` to point
at any of them. at any of them.
For a step-by-step walkthrough (add a conversation, flush, search, then ### 4. Try Your First Memory
read the markdown), see [QUICKSTART.md](QUICKSTART.md).
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 ### Optional: Ingest Multimodal Files
@ -260,126 +336,12 @@ make test
</div> </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">
[![](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#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">
[![](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#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">
[![](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#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">
[![](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#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">
[![](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#readme-top)
</div>
## Use Cases ## 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 Use cases show what persistent memory makes possible in real products and
workflows. Some examples are packaged in this repository; others point to workflows. Some examples are packaged in this repository; others point to
external demos or integrations you can study and adapt. 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> </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">
[![](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#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">
[![](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#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">
[![](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#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">
[![](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#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">
[![](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#readme-top)
</div>
## Watch EverOS ## Watch EverOS
EverOS 1.0.0 is the first release of a larger memory-system roadmap. EverOS 1.0.0 is the first release of a larger memory-system roadmap.

View File

@ -25,12 +25,12 @@
- [EverOS: One Memory For All](#everos-one-memory-for-all) - [EverOS: One Memory For All](#everos-one-memory-for-all)
- [EverOS 的差异](#everos-的差异) - [EverOS 的差异](#everos-的差异)
- [快速开始](#快速开始) - [快速开始](#快速开始)
- [使用场景](#使用场景)
- [架构概览](#架构概览) - [架构概览](#架构概览)
- [存储布局](#存储布局) - [存储布局](#存储布局)
- [功能](#功能) - [功能](#功能)
- [项目结构](#项目结构) - [项目结构](#项目结构)
- [文档](#文档) - [文档](#文档)
- [使用场景](#使用场景)
- [关注 EverOS](#关注-everos) - [关注 EverOS](#关注-everos)
- [EverMind 生态](#evermind-生态) - [EverMind 生态](#evermind-生态)
- [参与贡献](#参与贡献) - [参与贡献](#参与贡献)
@ -168,39 +168,113 @@ Agent 记忆(<code>cases</code> / <code>skills</code>)与用户记忆(<cod
## 快速开始 ## 快速开始
### 1. 安装 EverOS > 目标:启动 EverOS写入一条记忆然后把它搜索回来。
### 0. 前置条件
- Python 3.12+
- 默认 provider 需要 API keysOpenRouter 用于 chat / multimodal
DeepInfra 用于 embedding / rerank。也可以通过 `.env` 里的
`*__BASE_URL` 字段切换到其他 OpenAI-compatible providers。
### 1. 安装
```bash ```bash
uv pip install everos uv pip install everos
# or: pip install everos # or: pip install everos
``` ```
### 2. 初始化配置 ### 2. 配置
生成一个 starter `.env` 文件,然后根据生成的注释填入 API key 字段。 生成一个 starter `.env` 文件,然后根据生成的注释填入四个 API key slots。
默认配置只需要两把不同的 keyOpenRouter 用于 `LLM` / `MULTIMODAL`
DeepInfra 用于 `EMBEDDING` / `RERANK`
```bash ```bash
everos init everos init
# or, from a source checkout:
cp .env.example .env
``` ```
`everos init` 默认写入 `./.env`。也可以使用 `everos init --xdg` `everos init` 默认写入 `./.env`。也可以使用 `everos init --xdg`
写入 `${XDG_CONFIG_HOME:-~/.config}/everos/.env` 写入 `${XDG_CONFIG_HOME:-~/.config}/everos/.env`
### 3. 启动服务 ### 3. 启动 EverOS
```bash ```bash
everos --help
everos server start everos server start
``` ```
保持服务运行,然后打开第二个 terminal 检查:
```bash
curl http://127.0.0.1:8000/health
```
预期响应:
```json
{"status":"ok"}
```
`everos server start` 会按以下顺序查找 `.env``--env-file <path>` → `everos server start` 会按以下顺序查找 `.env``--env-file <path>` →
`./.env`(当前目录)→ `${XDG_CONFIG_HOME:-~/.config}/everos/.env` `./.env`(当前目录)→ `${XDG_CONFIG_HOME:-~/.config}/everos/.env`
`~/.everos/.env`。端点栈兼容 OpenAI protocolOpenAI / OpenRouter / `~/.everos/.env`。端点栈兼容 OpenAI protocolOpenAI / OpenRouter /
vLLM / Ollama / DeepInfra。你可以覆盖生成的 `.env` 中的 `*__BASE_URL` vLLM / Ollama / DeepInfra。你可以覆盖生成的 `.env` 中的 `*__BASE_URL`
来指向任意这些模型服务。 来指向任意这些模型服务。
完整 walkthrough添加对话、flush、search然后读取 Markdown ### 4. 试写第一条记忆
[QUICKSTART.md](QUICKSTART.md)。
添加一个很小的 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> </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">
[![](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#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">
[![](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#readme-top)
</div>
## 功能
- **混合检索**: BM25 + vectorHNSW/IVF-PQ+ scalar filter在 LanceDB 中完成单次查询
- **级联索引同步**: 编辑 `.md` → file watcher → entry-level diff → LanceDB sync亚秒级同步
- **多源提取**: conversations / agent trajectories / file knowledge
- **双轨记忆**: user-trackEpisodes / Profiles+ agent-trackCases / Skills
- **异步优先**: 完整 asyncio单一 event loop
- **多模态**: text + 小图片 / audio inline大媒体通过 S3/OSS reference
<br>
<div align="right">
[![](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#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">
[![](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#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">
[![](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#readme-top)
</div>
## 使用场景 ## 使用场景
现在你已经完成了第一个成功的 EverOS moment可以继续看看大家如何把持久记忆
用在 agents、apps 和社区集成里。
这些使用场景展示了持久记忆可以在真实产品和工作流中带来什么能力。 这些使用场景展示了持久记忆可以在真实产品和工作流中带来什么能力。
有些示例已经打包在本仓库中,另一些则指向外部 demo 或集成,你可以研究并复用。 有些示例已经打包在本仓库中,另一些则指向外部 demo 或集成,你可以研究并复用。
@ -693,6 +653,124 @@ Claude Code 的持久记忆插件。自动保存并回忆过去 coding sessions
</div> </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">
[![](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#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">
[![](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#readme-top)
</div>
## 功能
- **混合检索**: BM25 + vectorHNSW/IVF-PQ+ scalar filter在 LanceDB 中完成单次查询
- **级联索引同步**: 编辑 `.md` → file watcher → entry-level diff → LanceDB sync亚秒级同步
- **多源提取**: conversations / agent trajectories / file knowledge
- **双轨记忆**: user-trackEpisodes / Profiles+ agent-trackCases / Skills
- **异步优先**: 完整 asyncio单一 event loop
- **多模态**: text + 小图片 / audio inline大媒体通过 S3/OSS reference
<br>
<div align="right">
[![](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#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">
[![](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#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">
[![](https://img.shields.io/badge/-Back_to_top-gray?style=flat-square)](#readme-top)
</div>
## 关注 EverOS ## 关注 EverOS
EverOS 1.0.0 是更大规模记忆系统路线图的第一个发布版本。Watch 这个仓库, EverOS 1.0.0 是更大规模记忆系统路线图的第一个发布版本。Watch 这个仓库,

View File

@ -14,6 +14,8 @@ PRIMARY_LINK_RE = re.compile(
r"^\[(?:Code|Plugin|Live Demo|Learn more)\]\(([^)]+)\)", r"^\[(?:Code|Plugin|Live Demo|Learn more)\]\(([^)]+)\)",
flags=re.M, flags=re.M,
) )
ENV_EXAMPLE = Path(".env.example")
ENV_TEMPLATE = Path("src/everos/templates/env.template")
def _markdown_files() -> list[Path]: def _markdown_files() -> list[Path]:
@ -88,10 +90,21 @@ def _check_use_case_banner_links() -> tuple[list[str], list[str]]:
return failures, warnings 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: def main() -> int:
failures = _check_active_relative_links() failures = _check_active_relative_links()
use_case_failures, warnings = _check_use_case_banner_links() use_case_failures, warnings = _check_use_case_banner_links()
failures.extend(use_case_failures) failures.extend(use_case_failures)
failures.extend(_check_env_example_matches_template())
if warnings: if warnings:
print("\n".join(f"warning: {warning}" for warning in warnings)) print("\n".join(f"warning: {warning}" for warning in warnings))

View File

@ -4,7 +4,7 @@ Contract per the final design:
* ``owner_type`` is a hard partition. ``user`` returns ``episodes`` * ``owner_type`` is a hard partition. ``user`` returns ``episodes``
(and optionally ``profiles``); ``agent`` returns ``agent_cases`` + (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 ``[]``. applicable to the current ``owner_type`` stay as ``[]``.
* ``atomic_facts`` are **nested** inside :class:`SearchEpisodeItem`, * ``atomic_facts`` are **nested** inside :class:`SearchEpisodeItem`,
never returned as a top-level array. never returned as a top-level array.

View File

@ -4,7 +4,7 @@
# ===================================================== # =====================================================
# #
# Setup: # Setup:
# 1. cp env.template .env # 1. Create .env with `everos init` or `cp .env.example .env`
# 2. Edit .env with your values # 2. Edit .env with your values
# 3. .env is gitignored (never commit) # 3. .env is gitignored (never commit)
# #

View File

@ -12,8 +12,9 @@
the keyword / vector / hybrid recall tests so the assertion logic the keyword / vector / hybrid recall tests so the assertion logic
is in one place. is in one place.
* :func:`flatten_hits` collapses ``SearchData``'s four arrays into * :func:`flatten_hits` collapses ``SearchData``'s four scored result
one ``(owner_id, score, text)`` tuple list for relevance checks. arrays into one ``(owner_id, score, text)`` tuple list for relevance
checks.
The helpers do **not** hardcode topical keywords ("hiking" / "work") The helpers do **not** hardcode topical keywords ("hiking" / "work")
they are derived from what the pipeline produced. This keeps the 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]]: 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 Stable shape across track-kinds so the recall / partition tests
don't have to branch. Episodes / profiles carry ``user_id`` on the 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. """Hit ``/search`` and lock the four standard recall invariants.
1. **Status** 200 the route compiled. 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 3. **Owner partition** every non-``None`` ``owner_id`` matches
the queried owner. Profile hits may carry ``None`` so they're the queried owner. Profile hits may carry ``None`` so they're
skipped from the check. skipped from the check.

View File

@ -192,7 +192,7 @@ async def test_partition_respects_owner_id(
async def test_unknown_owner_returns_empty_200( async def test_unknown_owner_returns_empty_200(
search_client: httpx.AsyncClient, search_client: httpx.AsyncClient,
) -> None: ) -> 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( resp = await search_client.post(
"/api/v1/memory/search", "/api/v1/memory/search",
json={ json={
@ -209,6 +209,7 @@ async def test_unknown_owner_returns_empty_200(
assert data["profiles"] == [] assert data["profiles"] == []
assert data["agent_cases"] == [] assert data["agent_cases"] == []
assert data["agent_skills"] == [] assert data["agent_skills"] == []
assert data["unprocessed_messages"] == []
# ── 6. Filter DSL ────────────────────────────────────────────────────── # ── 6. Filter DSL ──────────────────────────────────────────────────────

View File

@ -127,6 +127,7 @@ def test_response_default_arrays_present() -> None:
assert resp.data.profiles == [] assert resp.data.profiles == []
assert resp.data.agent_cases == [] assert resp.data.agent_cases == []
assert resp.data.agent_skills == [] assert resp.data.agent_skills == []
assert resp.data.unprocessed_messages == []
def test_method_enum_serialises_to_lowercase() -> None: def test_method_enum_serialises_to_lowercase() -> None: