检查当前 Git 改动和近期提交记录,再按 Conventional Commits 格式创建聚焦的提交。可只暂存相关文件,并处理钩子检查失败的问题。
- 检查改动并创建提交
- 从差异中选择相关文件
- 按约定编写提交说明
指导开发者新增可持久化的业务记忆类型:按需选择 Markdown、SQLite 和 LanceDB,确定 Markdown 文件策略,再配置所需的架构、仓库和写入器。
Add a new business memory kind end-to-end. Pick the storage combination (Markdown / SQLite / LanceDB), pick the markdown strategy (daily-log / skill-named / single-file), then wire up the schema(s), repo(s), and writer(s).
实测评分 未实测
查看安装方法指导开发者为新的业务数据类型添加持久化能力,选择需要的存储层和实现位置。可组合使用 Markdown 作为便于阅读的原文、SQLite 保存结构化数据,以及 LanceDB 支持向量检索等搜索。若使用 Markdown,还需选择按日期追加记录、覆盖命名文件或重写单个文件的策略。之后添加相应的数据架构和写入逻辑,并按需增加数据库表和仓库。流程还包括重新导出新增组件,供项目其他部分使用。
适合需要持久保存 Episode、Case 等新业务记录的开发者。它可以帮助判断数据是否需要检索、结构化状态或可由人编辑的原文,并将选定的存储层连接起来。
按日追加的 Markdown 方案提供了具体步骤。命名文件和单文件方案只有概略说明;在基础写入器可用之前,需要直接封装 MarkdownWriter。仅在确有需要时添加 SQLite 或 LanceDB。
本介绍由 AI(OpenAI Codex)根据公开的 SKILL.md 撰写。准确的规格请查看下方原文。
AI 根据 SKILL.md 的内容检查了需要注意的操作和使用前提。评分只依据实际运行的结果(实测对比)。
不计入评分。 由 AI(OpenAI Codex)于 2026年10月29日 评估。仅供参考,安装前请同时查看原文。
安装前可以查看交给 AI 代理的指令内容。
Adding a new persisted business entity (Episode, Case, Skill, AtomicFact, Foresight, Profile, or something custom). Multiple storage layers may be involved; this skill walks the decision then the wiring.
A memory kind does not have to use all three layers. Pick by what the kind actually needs:
| Need | Markdown | SQLite | LanceDB |
|---|---|---|---|
| Human-readable / agent-editable source-of-truth text | ✅ | ||
| Structured state, ACID transactions, joins, predicates | ✅ | ||
| Vector / BM25 / hybrid retrieval | ✅ |
Common combinations seen in EverOS:
| Combo | Example | Rationale |
|---|---|---|
| md only | scratch notes / dump bins | text-of-truth, no index needed |
| md + lancedb | episode / memcell / case | text-of-truth + semantic retrieval |
| md + sqlite | profile / playbook / soul.md state | text-of-truth + structured state to query |
| md + sqlite + lancedb | full-blown business records | when you need both transactional state AND retrieval |
| sqlite only | audit log / task queue / LSN watermark | system state, never user-facing |
| lancedb only | rare; usually you still want md | derived embeddings without text-of-truth |
Rule of thumb: markdown is the truth; sqlite and lancedb are derived indexes that can be rebuilt from md. Drop md only when the kind has no human-readable form (pure system state).
Three strategies — declared in the EverOS Markdown First spec:
| Strategy | Filename | Mutation | Examples |
|---|---|---|---|
| Daily-log append | <prefix>-YYYY-MM-DD.md | append entries | memcell / episode / case / atomic_fact / foresight |
| Skill-named in-place | skill_<name>.md | overwrite the file | skills (procedural memory) |
| Single-file rewrite | user.md / agent.md / soul.md / behaviors.md / tools.md | overwrite the file | profiles / playbooks |
This skill currently has a complete recipe for daily-log append.
Skill-named and single-file recipes are sketched at the bottom — their
base writers (BaseSkillWriter / BaseProfileWriter) land later in the
project; until then build a thin wrapper over MarkdownWriter
directly.
infra/persistence/markdown/mds/<name>.py"""Episode daily-log frontmatter."""
from __future__ import annotations
import datetime as _dt
from typing import ClassVar, Literal
from everos.core.persistence.markdown import UserScopedFrontmatter
class UserEpisodeDailyFrontmatter(UserScopedFrontmatter):
"""``users/<u>/episodes/episode-<YYYY-MM-DD>.md``."""
ENTRY_ID_PREFIX: ClassVar[str] = "ep"
DIR_NAME: ClassVar[str] = "episodes"
FILE_PREFIX: ClassVar[str] = "episode"
type: Literal["user_episode_daily"] = "user_episode_daily"
date: _dt.date
entry_count: int = 0
last_appended_at: _dt.datetime | None = None
For agent-track kinds subclass AgentScopedFrontmatter instead. If
user-track and agent-track share a kind name (e.g. memcell), give
each a distinct ENTRY_ID_PREFIX (e.g. umc vs amc) so reverse
lookup is unambiguous.
mds/__init__.pyfrom .episode import UserEpisodeDailyFrontmatter as UserEpisodeDailyFrontmatter
infra/persistence/markdown/writers/<name>.py"""Episode appender."""
from __future__ import annotations
from pathlib import Path
from everos.core.persistence import MarkdownReader
from ..mds import UserEpisodeDailyFrontmatter
from .base import BaseDailyWriter
class UserEpisodeAppender(BaseDailyWriter):
schema = UserEpisodeDailyFrontmatter
# OPTIONAL: override the count strategy. Default is len(entries);
# override to trust the frontmatter field instead.
def _current_count(self, path: Path) -> int:
if not path.exists():
return 0
return MarkdownReader.read(path).frontmatter.get("entry_count", 0)
writers/__init__.pyfrom .episode import UserEpisodeAppender as UserEpisodeAppender
from everos.infra.persistence.markdown.writers import UserEpisodeAppender
appender = UserEpisodeAppender(memory_root)
eid = appender.append("u_jason", "I went to the doctor today.")
# → users/u_jason/episodes/episode-<today>.md
# → entry markers carry an auto-generated EntryId (e.g. ep_20260507_001)
Skip this section if the kind doesn't need structured state beyond markdown.
infra/persistence/sqlite/tables/<name>.pyfrom everos.core.persistence.sqlite import BaseTable, Field
class EpisodeState(BaseTable, table=True):
__tablename__ = "episode_state" # type: ignore[assignment]
id: int | None = Field(default=None, primary_key=True)
entry_id: str = Field(index=True, unique=True)
cluster_id: str | None = Field(default=None, index=True)
status: str = Field(default="active")
BaseTable already provides created_at / updated_at (auto-bumped).
tables/__init__.pyfrom .episode import EpisodeState as EpisodeState
infra/persistence/sqlite/repos/<name>.pyfrom sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker
from everos.core.persistence.sqlite import RepoBase
from ..sqlite_manager import get_session_factory
from ..tables import EpisodeState
class _EpisodeStateRepo(RepoBase[EpisodeState]):
model = EpisodeState
def _factory_lookup(self) -> async_sessionmaker[AsyncSession]:
return get_session_factory()
episode_state_repo = _EpisodeStateRepo()
repos/__init__.pyfrom .episode import episode_state_repo as episode_state_repo
Skip this section if the kind doesn't need vector / BM25 / hybrid retrieval.
infra/persistence/records/<name>.pyfrom typing import ClassVar
from everos.core.persistence.lancedb import BaseLanceTable, Vector
class EpisodeIndex(BaseLanceTable):
TABLE_NAME: ClassVar[str] = "episode_index"
PRIMARY_KEY: ClassVar[tuple[str, ...]] = ("app_id", "project_id", "id")
id: str
app_id: str = "default"
project_id: str = "default"
entry_id: str
text: str
tags: list[str]
vector: Vector(1024) | None = None # type: ignore[valid-type]
TABLE_NAME / BM25_FIELDS / PRIMARY_KEY are the backend-neutral
declarations every index backend reads (IndexRecordMeta). PRIMARY_KEY
has no default: check_primary_keys refuses to start, and LanceDB
upsert refuses to write, a table that does not declare one. id is unique
only within (app_id, project_id), so every business table declares the
three-column key — LanceDB merges on it, Milvus keys the collection on a
composed __pk and rejects key components containing /. Vector(N) must
match your embedding dimension.
records/__init__.pyfrom .episode import EpisodeIndex as EpisodeIndex
infra/persistence/lancedb/repos/<name>.pyfrom lancedb import AsyncTable
from everos.core.persistence.lancedb import LanceRepoBase
from everos.infra.persistence.records import EpisodeIndex
from ..lancedb_manager import get_table
class _EpisodeIndexRepo(LanceRepoBase[EpisodeIndex]):
schema = EpisodeIndex
async def _table_lookup(self) -> AsyncTable:
return await get_table(self.schema.TABLE_NAME, self.schema)
episode_index_repo = _EpisodeIndexRepo()
Business code writes through the everos.infra.persistence.index facade:
upsert(records) merges on the declared key and is the only write; look a
row up with find_one_where(primary_key(app_id, project_id, row_id)).
repos/__init__.pyfrom .episode import episode_index_repo as episode_index_repo
When the new memory kind needs:
BaseSkillWriter, or use MarkdownWriter.write_markdown directly
with a thin wrapper.user.md) — wait for
BaseProfileWriter, same fallback.These strategies do not use entry markers; their frontmatter schema
does not need ENTRY_ID_PREFIX (only id / type / schema_version plus
the scope mixin fields).
make lint — ruff + import-linter cleanmake test — existing manager / lifespan / writer tests still passTests by layer:
| Tests for | Location |
|---|---|
| Markdown frontmatter schema | tests/unit/test_infra/test_markdown/test_mds/ |
| Markdown business appender | tests/unit/test_infra/test_markdown/test_writers/ |
| SQLite RepoBase logic | tests/unit/test_core/test_persistence/test_sqlite/ |
| SQLite manager / lifespan | tests/unit/test_infra/test_sqlite/ |
| LanceDB LanceRepoBase logic | tests/unit/test_core/test_persistence/test_lancedb/ |
| LanceDB manager / lifespan | tests/unit/test_infra/test_lancedb/ |
| Mistake | Symptom | Fix |
|---|---|---|
Forgot ENTRY_ID_PREFIX / DIR_NAME / FILE_PREFIX on a daily-log schema | BaseDailyWriter.__init__ raises TypeError | Add all three ClassVars |
Same ENTRY_ID_PREFIX on user + agent variants | MemoryLayout.locate_for_entry collision error | Use distinct prefixes (e.g. umc vs amc) |
Imported RepoBase from infra.persistence.sqlite | ImportError | Lives in core.persistence.sqlite (moved earlier) |
| Skipped one of the four files (schema / writer / table / repo) | One side silently absent | Re-export both/all from each __init__.py |
Vector(N) mismatched with embedding dim | LanceDB raises on insert | Make N exactly match the model output |
Imported MemoryLayout from a writer (infra) | import-linter fails (infra → memory reverse dep) | Use MemoryRoot (in core) and let the schema's ClassVars drive paths |
Hand-rolling datetime.now() instead of today_with_timezone() | Day-boundary drift across timezones | Always go through everos.component.utils.datetime |
__init__.py re-export rules: ../../rules/init-py-and-reexport.md还没有评价。欢迎分享你的使用感受。
无需登录即可发表。欢迎分享使用感受、提问,或说明在哪些环境下无法运行。
还没有评论。来写第一条吧。
检查当前 Git 改动和近期提交记录,再按 Conventional Commits 格式创建聚焦的提交。可只暂存相关文件,并处理钩子检查失败的问题。
在更新后的 `main` 分支上创建用途明确的 Git 分支,并按改动类型选择前缀,如功能、修复、文档、CI、维护或重构,再添加简短的小写连字符名称。
使用 GitHub CLI `gh`,按仓库模板将当前分支的改动提交为面向 `main` 的拉取请求。创建前运行 `make ci`,通过后推送分支并填写改动摘要、所属领域、验证结果、检查清单和审阅备注。
指导将 everos 新版本发布到 PyPI,包括更新版本号和变更日志、创建触发自动工作流的标签,并完成审批。工作流上传后,还需检查并发布 GitHub Release 草稿。