EverOS 记忆工作原理Markdown 为源、SQLite 与 LanceDB 为派生索引的分层存储与同步管线【免费下载链接】EverOSOne portable memory layer for every AI agent: local-first, Markdown-native, user-owned, and self-evolving across apps, tools, and workflows.项目地址: https://gitcode.com/gh_mirrors/ev/EverOS导读EverOS 将一串普通消息沉淀为持久化、可搜索的长期记忆其核心设计是一套「Markdown 为唯一真相源、其余层全部可重建」的分层存储栈。本文围绕 docs/how-memory-works.md 展开完整讲解磁盘路径布局、write → index → read 三阶段管线、八种业务记忆类型的存储策略、cascade 守护进程与 Offline Memory EngineOME的协作方式以及写强一致/读最终一致的一致性模型。读完本文你将能够熟练使用everosCLI 运维记忆根目录、诊断并重建索引并在自己项目中复现这套 local-first 的记忆分层方案。一、分层存储栈谁是真相源谁可重建EverOS 的记忆持久化由三个嵌入式组件组成各自负责自己最擅长的部分层底层实现承载内容可重建Markdown YAML frontmatter普通.md文件记忆内容本身——唯一可移植、可人工编辑的资产—它就是真相源SQLiteaiosqlite.index/sqlite/*.db系统状态、审计日志、cascade 队列、boundary 缓冲、OME 引擎状态✅ 可从 markdown 重建LanceDBArrow.index/lancedb/*.lance向量 BM25 标量列用于检索✅ 可从 markdown 重建这条分层结构导出一条关键规则删除整个.index/目录不会丢失任何记忆——它可以从.md树重建。因此 EverOS 没有单独的「导出」功能markdown 本身就是导出物。从源码看这套布局由路径管理器MemoryRoot统一封装index_dir、sqlite_dir、lancedb_dir、ome_db、ome_aps_db、tmp_dir全部是该类上的 propertyMemoryRoot.ensure()只创建运行时必需目录.index/{sqlite,lancedb}/、.tmp/而用户可见目录users/、agents/、knowledge/不预创建首次写入时才出现见 memory_root.py 的ensure()实现。补充SQLite 与 LanceDB 的详细派生逻辑分别位于 infra/persistence/sqlite/tables/ 与 infra/persistence/lancedb/tables/目录树、frontmatter 编码与 entry-id 格式见 docs/storage_layout.md。二、磁盘路径布局按app_id/project_id隔离默认记忆根目录是~/.everos/可通过EVEROS_ROOT环境变量或 CLI--root覆盖。配置文件位于记忆根目录内的everos.toml由everos init生成。记忆在用户可见目录之前就按app_id/project_id分区因此不同(app, project)空间永不共享目录、检索时也不会交叉。保留 iddefault在磁盘上物化为default_app/default_project使默认空间与用户命名空间在视觉上可区分。~/.everos/ ← 记忆根目录 (EVEROS_ROOT) ├── default_app/ ← app_id (default → default_app) │ └── default_project/ ← project_id (default → default_project) │ ├── users/ ← 用户可见真相源 │ │ └── user_id/ │ │ ├── user.md 单文件用户画像 │ │ ├── episodes/ │ │ │ └── episode-YYYY-MM-DD.md 日志追加 │ │ ├── .atomic_facts/ 日志追加隐藏 │ │ │ └── atomic_fact-YYYY-MM-DD.md │ │ └── .foresights/ 日志追加隐藏 │ │ └── foresight-YYYY-MM-DD.md │ ├── agents/ │ │ └── agent_id/ │ │ ├── .cases/ 日志追加隐藏 │ │ │ └── agent_case-YYYY-MM-DD.md │ │ └── skills/ 按技能命名目录 │ │ └── skill_name/SKILL.md ( references/ scripts/) │ └── knowledge/ ← 共享 / 全局 │ ├── .index/ ← 系统管理、可重建gitignore │ ├── sqlite/ │ │ ├── system.db 状态 / 审计 / cascade 队列 (md_change_state) / 缓冲 / LSN │ │ ├── ome.db Offline Memory Engine 状态 │ │ ├── ome.aps.db APScheduler jobstore拆分以避免锁争用 │ │ └── ome.db.lock OME 单引擎守护portalocker │ └── lancedb/ │ └── kind.lance/ 每个 kind 一张 Arrow 表 │ ├── ome.toml ← 用户可编辑的 OME 策略覆盖热重载 └── .tmp/ 原子写入暂存与旧版 PRD 时代文档的差异重要索引目录是.index/点前缀不是_index/cascade 队列与 LSN/审计状态位于SQLitesystem.db表md_change_state当前实现不存在.cascade.log/.manifest.json文件app/project嵌套真实存在且始终存在默认作用域为default_app/default_project没有字面意义上的reindex命令everos cascade rebuild从 markdown 重建索引见第五节。目录名映射的源码实现MemoryRoot的映射是对称的memory_root.pyapp_dir_name()/project_dir_name()负责写入侧default→default_app/default_projectapp_id_from_dir()/project_id_from_dir()负责 cascade 路径解析侧的逆向还原。写入侧与读取侧必须保持同步否则重建出的行会携带与写入时不一致的 app/project——因此default_app/default_project是保留目录名。MemoryRoot.resolve()的优先级为explicit_root--rootEVEROS_ROOT环境变量 默认~/.everos。三、一条记忆的诞生write → index → read 管线消息不会立即变成记忆——它会先累积检测到边界boundary由 LLM 抽取出一个 MemCell写入器持久化 markdown索引再异步追赶。POST /add ──▶ unprocessed_buffer (SQLite) ← 消息按 (session, app, project) 累积 │ ├─ 边界检测器触发 ─┐ POST /flush ─────────┤ (或你手动强制) │ 一次 LLM 调用 │ ▼ │ 提取 MemCell ──▶ memcell 行 (SQLite) │ │ │ ┌─────────────┴──────────────┐ │ ▼ ▼ │ UserMemoryPipeline (同步) AgentMemoryPipeline (即发即忘) │ 立即写入 episode .md 发出 AgentPipelineStarted ▼ │ │ (md 落盘后响应即返回) ▼ ▼ ┌─────────────────── Offline Memory Engine (OME) ───────────────────┐ │ 异步策略写入派生 .md │ │ atomic_facts · foresight · user profile · agent cases · agent skills │ └───────────────────────────────┬──────────────────────────────────────┘ ▼ cascade 守护进程监听 .md 树 ▼ md_change_state 队列 (SQLite, 持久化) ▼ 重建 LanceDB 行 ──▶ 可搜索各环节要点/add把消息追加进按(session_id, app_id, project_id)划分的缓冲返回accumulated若本次调用恰好触发边界则返回extracted。HTTP 契约见 docs/api.md。/flush立即强制边界一次抽取 LLM 调用用于聊天/Agent 运行结束时。episode markdown 是同步写入的当/flush返回extracted时episode 文件已落盘。其余一切atomic facts、foresight、profile、agent cases/skills由 OME异步产出。cascade 守护进程把每次.md写入转成 LanceDB 行使内容变得可搜索。从实现看同步侧对应 service/memorize.py 的用户记忆管线UserMemoryPipeline 同步写 episode异步侧对应 memory/extract/pipeline/agent_memory.py 的 Agent 管线fire-and-forget 并发出AgentPipelineStarted。四、记忆类型与三种存储策略当前共有八种业务记忆类型分别归属于用户、Agent 或全局每种选用三种磁盘模式之一类型归属目录 / 文件策略产出方episodeuserepisodes/episode-date.mddaily-log抽取同步atomic_factuser.atomic_facts/atomic_fact-date.md隐藏daily-logOMEforesightuser.foresights/foresight-date.md隐藏daily-logOMEprofileuseruser.md单文件重写OMEagent_caseagent.cases/agent_case-date.md隐藏daily-logOMEagent_skillagentskills/skill_name/SKILL.md技能命名目录OME聚类knowledge_documentglobalknowledge/category_id/title_dirname/index.mdknowledge treeknowledge serviceknowledge_topicglobalknowledge/category_id/title_dirname/N_topic_slug.mdknowledge treeknowledge service三种存储策略的设计意图策略形态为什么日志追加daily-log appendprefix-YYYY-MM-DD.md每条记忆追加一个条目把成千上万个逐条文件折叠为每天一个文件单文件重写single-file rewrite固定文件名原地覆盖适合单一演化中的文档用户/Agent 画像技能命名目录skill-named dir每个技能一个目录技能是更丰富的单元正文 可选references/scripts/注意单文件写入器还支持agent.md/soul.md/tools.md/behaviors.md但当前没有已发布的 OME 策略产出这些——今天只写user.md。frontmatter 与 entry-id 编码细节见 docs/storage_layout.md。cascade 的 kind 注册表是单一真相源memory/cascade/registry.py 中的KIND_REGISTRY元组把每个 kind 名映射到 frontmatter schema LanceDB schema/repo handler 工厂watcher / scanner / worker / CLI 全部读这张表路径 glob 与 handler 分发表不会出现在代码其他位置。match_kind()按声明顺序做首次匹配今天的 glob 按目录名互斥顺序无实际影响。五、cascade 守护进程md → LanceDB 的同步引擎cascade 子系统让 LanceDB 与 markdown 树保持同步。它与服务器同进程运行由应用 lifespan 启动的协程而不是独立的 OS 守护进程。原生文件系统监听器watchdogmacOS 用 FSEventsLinux 用 inotify侦测到.md的创建/修改变更进入md_change_state表SQLite——持久化因此同步中途崩溃可在重启后重放worker 以**条目级entry-level**粒度排空队列diff 文件、仅对变更条目重新嵌入以content_sha256为键、upsert 对应的 LanceDB 行。由于 markdown 是真相源直接编辑文件完全受支持——在 VSCode / Obsidian / Vim 中打开一个 episode修改某条 entry保存守护进程只会重新索引那一条。运维队列使用everos cascade见第八节深度 runbook 见 docs/cascade_runbook.md。编排器与 worker 的源码结构memory/cascade/orchestrator.py 的CascadeOrchestrator是子系统复合所有者start()依次启动 watcher同步线程、scanner、worker并在启动前把残留的processing行恢复为pendingcascade 当前单进程运行启动时处于processing的行是上次崩溃的遗留stop()按逆序关闭。CLI 的cascade sync不会启动后台任务而是构造实例后只调用drain_once()。worker 的 upsert 语义可从 memory/cascade/worker.py 印证一行在 upsert 提交的瞬间即可搜索并无额外可见性延迟。六、Offline Memory EngineOME异步派生引擎大多数记忆类型不在请求路径上抽取而是由 OME 稍后派生。OME 是一个进程内的异步策略引擎抽取出一个 MemCell 时发出事件OME 策略在就绪时拾取并写入各自的 markdownextract_atomic_facts—— 从 episode 抽取单句事实extract_foresight—— 前瞻性笔记extract_user_profile—— 聚合出user.mdextract_agent_case—— 可复用的 Agent 轨迹仅当该 cell 内容足够充实过薄的轨迹按设计跳过extract_agent_skill—— 把相关 case 聚类为命名技能trigger_profile_clustering—— 触发用户画像聚类trigger_skill_clustering—— 触发 Agent 技能聚类reflect_episodescron默认关闭—— 离线记忆整合把聚类内的碎片化 episode 合并为连贯叙事、重新抽取原子事实、并通过deprecated_by废弃原 episode。通过ome.toml启用。策略可无需改代码地通过记忆根目录的ome.toml配置约 2 秒内热重载。示例——关掉两个策略[strategies.extract_foresight] enabled false [strategies.extract_user_profile] enabled falseOME 自身的状态放在.index/sqlite/ome.db运行记录、计数器调度器 jobstore 放在.index/sqlite/ome.aps.db拆分是为了让同步 APScheduler 写入器与异步 OME 写入器永不争抢同一个文件锁。这个拆分意图同样体现在 core/persistence/memory_root.py 的ome_db/ome_aps_dbproperty 注释中。OME 配置模型与策略注册infra/ome/config.py 定义了完整的 TOML 覆盖 schemaStrategyOverride支持enabled、max_retries、cron、idle_seconds、scan_interval_seconds与嵌套gatethreshold/cooldown_seconds/event_fieldcron字段会用CronTrigger.from_crontab在加载时校验idle_seconds与scan_interval_seconds存在一致性校验scan 必须 ≤ idle/2。所有模型extraforbid因此配置拼写错误会在启动时以StartupValidationError暴露而不是被静默忽略。引擎级配置还包括max_concurrent_runs20Runner 中的 asyncio.Semaphore、默认max_retries1、指数退避retry_backoff_base_seconds1.0上限10.0抖动0.5、每策略 1000 条运行记录的环形缓冲、以及 1800 秒的崩溃恢复超时。策略注册表 infra/ome/_dispatch/registry.py 在启动时做 Kahn 拓扑排序以检测策略事件流 DAG 的环并校验gate.event_field确实存在于可接收的事件类字段中防止拼写错误把限流门变成全局共享桶。default_ome.tomlsrc/everos/config/default_ome.toml是实际生成到~/.everos/ome.toml的模板其中extract_foresight自 1.2.3 起默认关闭尚无任何检索路由或 prompt slot 消费 foresight运行它只是为每个 sender 每个 memcell 多花一次 LLM 调用。策略触发链示例源码佐证extract_atomic_factsmemory/strategies/extract_atomic_facts.py通过Immediate(on[EpisodeExtracted])触发一次append_entries批量写入该 owner 的所有事实max_retries2extract_agent_casememory/strategies/extract_agent_case.py通过Immediate(on[AgentPipelineStarted])触发并按「多 Agent 扇出」设计同一 case 正文会为 memcell 中每个不同的 assistant sender 各写一条 owner_id 作用域的 md 条目每个 Agent 各发一个AgentCaseExtracted使下游技能聚类在各自作用域内运行reflect_episodesmemory/strategies/reflect_episodes.py以Cron(expr0 2 * * 1)触发、enabledFalse默认关闭运行时枚举cluster表中所有 distinct owner 作用域并对每个作用域运行ReflectionOrchestrator且做了嵌入能力 body-guard无嵌入器时静默 no-op。Reflection离线整合reflect_episodes策略按 cron 调度运行默认0 2 * * 1——每周一 02:00默认关闭。它选择含多个成员的聚类调用 LLM 把它们的 episode 合并为单一叙事把合并后的 episode 写入 markdown重新抽取原子事实并通过deprecated_by废弃原 episode。合并后的 episode 使用parent_typecluster和session_idNone。在ome.toml中启用[strategies.reflect_episodes] enabled true对客户端的含义/flush返回extracted后episode很快即可查询等 cascade 索引完但atomic facts / profile / agent cases要等各自 OME 策略运行后才会出现——通常晚几秒。如需立即使用请轮询或重试。Reflection 的审计轨迹由reflection_report表承载cluster_id、mode、source_members、merged_entry_id、status详见 docs/storage_layout.md 第五节。七、一致性模型写强一致、读最终一致两条路径两种保证路径保证细节写/add、/flush强一致episode.md在调用返回extracted前已落盘绝不阻塞等待 LanceDB读/search、/get最终一致读 LanceDB落后于 md 约一个 cascade 处理时间——通常亚秒级负载下最多约 10–15 秒因此紧跟在产出该记录的/flush之后的/search可能查不到它。无论索引如何滞后markdown 都是持久化的索引滞后永远不会丢数据。如果需要 read-your-write请带退避重试或用everos cascade sync强制排空队列。完整性由若干不变式锚定细节见 docs/storage_layout.mdfrontmatter 的id/entry_id是不可变连接键content_sha256决定条目是否需要重新嵌入LSN 水位线在system.db中为重建排序持久化的md_change_state队列是可重放的审计轨迹。八、零外部服务无需运行数据库服务器、消息代理或向量服务。向量 ANN、全文 BM25 与标量过滤全部在嵌入式 LanceDB引擎内一次查询完成SQLite 只是本地文件。整个栈就是单个目录可以整体复制、备份或把用户可见部分检入 git。注意当前没有自动的「grep 扫描 markdown」搜索回退——如果 LanceDB 索引不可用应从 markdown 重建它它是派生的、可丢弃的而不是依赖降级搜索路径。九、运维everos cascade命令族CLIdocs/cli.md刻意保持精简命令作用everos init生成启动配置everos.tomlome.tomleveros server start运行 HTTP APIcascade 与 OME 随它一起启动everos cascade status队列 / LSN 摘要everos cascade sync立即排空 cascade 队列强制 md → LanceDBeveros cascade fix列出失败行 / 重新入队可重试的行everos cascade rebuild从 markdown 重建整个索引漂移 / 损坏恢复没有everos reindex或everos flushReindex索引可从 markdown 重建。要重建整个索引运行everos cascade rebuild——它会丢弃 LanceDB 表并从 md 重新索引连队列已标记done的条目也会重新填充并保留未抽取的缓冲消息。仅rm -rf memory-root/.index/lancedb不够cascade 队列仍显示这些文件为donescanner 会跳过它们索引会重建为空。增量追赶请用everos cascade sync。Flush是 HTTP 端点POST /api/v2/memory/flush不是 CLI 命令——它强制的是会话缓冲的抽取与强制索引同步cascade sync是两回事。cascade status/cascade fix的分工也反映在健康检查上CascadeOrchestrator.health()memory/cascade/orchestrator.py中的healthy只反映运维健康drain / optimize / prune 是否卡住而failed_permanent被刻意排除在健康判定之外——少量 md 文件索引失败是正常的数据质量积压不应把健康信号钉死在红色它作为信息性计数上报单文件排查交给cascade fix。十、总结与延伸阅读EverOS 的记忆层以「Markdown 真相源 双派生索引」的架构同时满足了人可直接编辑 .md、机向量/全文/标量一体检索、运维索引可整体重建、队列可重放、崩溃可恢复三方面的诉求。核心心智模型可浓缩为三句话消息先累积、边界再抽取episode 同步落盘、其余异步派生cascade 保证 md 最终投影为可搜索索引。进一步深入docs/storage_layout.md —— 精确文件编码、frontmatter 骨架、entry-id 格式、原子写入语义docs/architecture.md —— DDD 分层与依赖规则docs/api.md —— HTTP 契约/add/flush/search/getdocs/cascade_runbook.md —— 同步队列的运维手册源码入口core/persistence/memory_root.py、memory/cascade/orchestrator.py、infra/ome/config.py【免费下载链接】EverOSOne portable memory layer for every AI agent: local-first, Markdown-native, user-owned, and self-evolving across apps, tools, and workflows.项目地址: https://gitcode.com/gh_mirrors/ev/EverOS创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
