架构
本文描述 Origin 当前的设计:它要解决的问题、数据模型、每一处改动都必须保持的不变量,以及采集、条目、检索、MCP server 和个人中枢的工作方式。它是贡献者的参考文档;如果某项改动改变了数据模型、某条不变量或某个公共接口,请在同一个提交中更新本文档。
目录
问题
开发者越来越多地同时使用多个 AI Agent:Claude Code、Codex、Cursor、ZCode、Grok Build。每个 Agent 都有自己的记忆,范围限定在一个仓库、一台机器或一个云端账户。换一个工具或项目,就得从零开始。这些记忆难以查看,难以纠正,其他 Agent 看不到,还常常被悄悄清除(例如 Claude Code 默认在 30 天后删除本地会话记录)。
与此同时,这些 Agent 都会把纯文本、可解析的会话记录写到本地磁盘,也都能与本地的 MCP server 通信。Origin 正是建立在这两点之上:它把本机所有 Agent 会话归档进同一个本地库,再通过同一个 MCP server 把这个库提供给每个 Agent。
长远的方向,是一个长期陪伴一个人的 AI:记忆、知识以及对这个人的理解,都能跨越工具和模型代际延续下去。Origin 从这个核心做起。
范围
当前目标——一份通过 MCP 提供的跨 Agent 个人记忆:
- 把 Claude Code、Codex、Cursor、ZCode、Grok Build 的本地会话归档进同一个只追加的库。
- 让任何 Agent 都能通过 MCP 检索归档、浏览时间线、把任何回答展开到它的原始来源,并记录持久的条目(记忆 / 知识 / 认知)。
- 让人通过
originCLI 或自己中枢的管理界面查看、纠正和遗忘条目;遗忘必须持久生效。 - 一个人常常在多台机器上运行 Agent(笔记本、服务器、云端 Agent)。可选的自托管个人中枢把每台设备的会话汇集到一个库中,并为任何地方的 Agent 提供服务。
达成目标的标志是:在你任何一台机器上,从任何一个已接入的 Agent 问“上周在项目 X 里我决定了什么,为什么?”,都能得到附带来源的回答,而且被遗忘的条目永远不会再回来。
目前明确不做: 多租户托管(Origin 只服务一个人,每个人运行自己的一份)、由本项目运营的托管服务、端到端加密、副本之间的双向同步、自己中枢上供主人使用的管理界面之外的任何 Web 界面(没有公开页面、没有分享、没有多账户)、屏幕 / 邮件 / 浏览器采集、知识图谱、向量检索、主动提醒,以及任何对模型 API 的调用。
记忆、知识与认知
Origin 区分三类持久条目。每个条目都带有自己的来源,并且可以被纠正或遗忘。
| 类型 | 含义 | 示例 |
|---|---|---|
memory | 发生过什么(情景记忆) | “10 月 20 日,为了保持代码简单,我们决定在首个版本发布前不做 schema 版本管理。” |
knowledge | 稳定的事实与关系 | “项目 A 依赖库 B。” |
cognition | 关于这个人的模型:偏好、方法、目标、边界 | “偏好不带署名 trailer 的提交信息。” |
核心对象
| 对象 | 含义 | 关键字段 |
|---|---|---|
| Source(来源) | 某个 Agent 的适配器:claude-code、codex、cursor、zcode、grok | id、Agent 版本、发现根目录 |
| Raw(原始镜像) | 原始记录文件的只追加镜像 | 来源、原始路径、代次、字节 / 行游标、尾部哈希 |
| Event(事件) | 规范化后的对话事件:用户消息、助手消息、工具调用、工具结果 | id、来源、会话、时间戳、项目、cwd、git 分支、角色、类型、正文、原始定位 |
| Entry(条目) | 一条持久的 memory / knowledge / cognition 陈述 | id(ent_…)、类型、正文、置信度、作者、项目、来源(事件 id + 引文)、supersedes、valid_from / valid_to、created_at;状态(active / superseded / forgotten)由推导得出,从不存储 |
| Tombstone(遗忘记录) | 一次遗忘操作的记录 | 目标 id、原因、时间;它遗忘的条目和它排除的事件 |
数据流
agent session logs on disk ──scan──▶ Raw (append-only mirror)
│ deterministic adapter parsing (no LLM)
▼
Event store (SQLite + FTS5) ◀── Entries · Tombstones
│
agents ◀── MCP (stdio): search · timeline · get · sources · remember · correct · forget
people ◀── CLI `origin`: sync · status · timeline · search · show · entries · correct · forget
people ◀── admin UI (hub, /admin): overview · timeline · search · entries · remember · correct · forget · tokens使用个人中枢时,每台设备照常在本地采集,并把原始镜像复制到中枢,由中枢用同样的适配器解析。Agent 通过所在设备上的本地 origin mcp(它会转发给中枢)访问中枢;云端 Agent 则通过中枢的 HTTP 端点访问:
device A: logs ─▶ local store ──push raw mirrors──▶ ┐
device B: logs ─▶ local store ──push raw mirrors──▶ ├─▶ hub store ◀── MCP over HTTP ── cloud agents
▲ │ ▲
└──── pull tombstones ───────┘ └── forwarded MCP ── local origin mcp ◀── agents on A, B不变量
这些是产品层面的保证。每一条都必须有测试覆盖,代码注释按编号引用它们。
- 溯源。 每个条目至少引用一个事件;每个事件都指向某个 Raw 文件中的某一行。任何回答都能一路展开到原文。
- 遗忘粘性。 执行
forget之后,条目立即从所有查询中消失,它的来源事件加入一个排除集,此后任何写入都无法让它们复活。 - 只追加。 Raw 镜像和事件永远不会被改写。条目通过取代(
supersedes)来变更,从不原地修改。 - 你的数据,留在你的机器上。 所有数据都存放在你控制的机器上:你的各台设备上的
~/.origin/,以及可选的你自己的中枢。Origin 从不调用第三方服务;唯一的网络流量发生在你的设备和你的中枢之间。 - 单一入口。 无论来自哪个界面,每一次读写都经过同一组 core 操作:Agent 使用 MCP(本地 stdio,或中枢的 HTTP 端点);人使用 CLI 或自己中枢的管理界面。中枢的复制 API 在你自己的各个库之间传输原始镜像和遗忘记录,它既不是 Agent 的入口,也不是人的入口。
设计原则
- 内核不含 LLM。 Origin 从不调用模型 API。它只负责归档、规范化、建立索引和存储。分析发生在提问的那个 Agent 里——它本身就是一个能力很强的模型。Agent 通过 MCP
remember保存它学到的东西;人通过 CLI 或管理界面纠正和遗忘。如果将来需要周期性的整合,它会作为无头的编码 Agent 任务运行(例如claude -p),而不是直接集成模型 API。这是当前的立场;如果评估表明它力有不逮,尤其是在认知层,可以重新考虑。 - 确定、容错的适配器。 每个来源都有一个纯粹、确定性的解析器。Agent 的记录格式变化频繁,所以适配器会记录 Agent 版本,跳过无法解析的行并统计失败次数,绝不中止采集。
- 以 MCP 作为互通点。 MCP 是所有目标 Agent 唯一共有的接口。其中几个 Agent 还会读取 Claude Code 的 MCP 配置,所以一次注册就能覆盖不止一个 Agent。
- 小而保守的管理界面。 中枢的管理界面只供它的主人使用。页面在服务器端由一个对每个值都做转义的模板渲染,外加一点可选的脚本。它的样式表、字体和图标来自 OpenLinkOS 设计系统(
@openlinkos/ui),在构建时复制进这个包;不从别处加载任何东西,也没有打包工具。CLI 能做的它都能做,但不做任何会破坏不变量的事:不原地编辑,不撤销遗忘,不在屏幕上显示被遗忘的文本。 - 小而可运行的增量。 每个来源适配器都单独交付、单独可用;设计只深入到当前增量所需的程度。
包
pnpm workspace,TypeScript(strict、ESM、NodeNext),Node.js ≥ 22.13。core、adapters 和 admin 只使用 Node 内置模块;mcp 依赖官方的 MCP TypeScript SDK 和 zod。中枢及其管理界面使用 Node 内置的 node:http。
| 包 | 路径 | 职责 | 依赖 |
|---|---|---|---|
@openlinkos/origin-core | packages/core | 存储、schema、采集(本地与远程)、条目、遗忘、令牌、管理密码与会话、审计日志、查询 | — |
@openlinkos/origin-adapters | packages/adapters | 每个来源一个确定性解析器 | core |
@openlinkos/origin-mcp | packages/mcp | MCP 工具;基于 stdio 和无状态 HTTP 的 server;转发代理 | core |
@openlinkos/origin-admin | packages/admin | 中枢的管理界面:服务器端渲染的页面、JSON API 及其静态资源 | core |
@openlinkos/origin | packages/cli | origin 命令,包括 origin mcp、origin serve(中枢)和 origin push | core, adapters, mcp, admin |
core 不得依赖任何其他包,mcp 和 admin 都不得依赖 adapters:CLI 以回调的形式把采集注入各个 server,中枢的 HTTP server 位于 CLI 中,并由它挂载管理界面的请求处理函数。适配器实现 core 中的 SourceAdapter 接口:
interface SourceAdapter {
id: string; // 'claude-code' | 'codex' | 'cursor' | 'zcode' | 'grok'
displayName: string;
version(): string | null; // agent version, if known
discover(): Promise<SourceFile[]>; // { absPath, relPath, compression?: 'zstd' }
parseLine(line: string, ctx: ParseContext): ParsedLine | null; // deterministic; throws on malformed input
}存储
~/.origin/ # or $ORIGIN_HOME
├── origin.db # SQLite (WAL)
└── raw/<source>/... # append-only mirrors of the original log files| 表 | 内容 |
|---|---|
raw_file | 每个镜像文件代次一行,带有它的采集游标 |
event, event_fts | 规范化后的事件及其 FTS5 trigram 索引 |
session | 基于可见事件的逐会话聚合(派生数据,可重建) |
sync_log | 每次同步运行一行 |
entry, entry_source, entry_fts | 条目、条目引用的事件(含引文),以及条目的 FTS5 索引 |
tombstone, forgotten_entry, excluded_event | 遗忘操作及其影响 |
api_token | 中枢访问令牌:名称和 SHA-256 哈希,从不保存令牌本身 |
setting | 少量键值状态,只属于各自的库,从不复制:某台设备已经应用到中枢的哪一条遗忘记录,以及中枢上管理密码的 scrypt 哈希 |
admin_session | 管理界面的会话:会话 id 的哈希、它的 CSRF 令牌和各个时间戳 |
audit_log | 管理操作的只追加记录,只含 id 和数量,从不包含内容;触发器拒绝任何更新和删除 |
- 存储使用 Node 内置的
node:sqlite,因此没有需要编译的原生模块。多个进程可以共享同一个数据库(CLI,以及每个 Agent 会话各一个 MCP server);WAL 模式和 busy timeout 负责处理这种情况。 session是派生数据,可以从event重建。FTS 索引是外部内容(external-content)表,由触发器保持同步。- 首个版本发布前不做迁移。 schema 是一组幂等语句,每次打开数据库时都会执行,所以新增的表和索引会自动出现在已有的数据库中。其他任何 schema 改动都直接原地修改,并且需要一个全新的
ORIGIN_HOME。请删除整个目录,而不要只删origin.db:镜像路径是确定的,重新同步到已有的raw/里会追加重复内容。带版本的迁移将随首个版本一起推出。
采集
origin sync(以及 MCP server,见下文)会运行每个适配器的 discover(),并在各自独立的 BEGIN IMMEDIATE 事务中采集每个文件,因此来自不同进程的并发同步会被串行化,绝不会把同样的字节采集两次:
- 游标。 每个
raw_file行记录bytes_ingested、lines_ingested和tail_hash(最后采集的 4 KB 的 SHA-256)。游标在写锁之下读取,并且只读取游标之后的字节。 - 改写检测。 如果文件变小了,或者尾部不再匹配,说明它是被改写而不是被追加。Origin 会开启一个新的代次,使用全新的镜像,并保持旧镜像完好无损(不变量 3)。
- 只取完整行。 截至最后一个换行符的字节会被追加到镜像中;末尾不完整的行留待下一次同步。镜像始终等于其代次已采集的字节,因此被中断的同步遗留在游标之后的字节,会在追加之前被丢弃(不变量 1)。
- 解析与插入。 每一行都经过
parseLine;失败只计数,不致命。事件以<source>:<native-id>[#<block-index>]为 id、用INSERT OR IGNORE插入,这使同步具有幂等性(不变量 3)。每个事件都保存它的镜像文件和行号(不变量 1)。 - 收尾记录。 受影响的会话被重新聚合,游标在同一个事务中前移,并由一行
sync_log记录这次运行。
压缩的来源。 适配器可以把文件标记为 compression: 'zstd'。Origin 随后使用 Node 内置的 zstd,采集并镜像解压后的内容,因此游标、行号和原始行的工作方式与普通文件相同。压缩文件只写入一次,所以自上次采集以来没有变化时会被跳过。
Claude Code 映射
- 文件:
<projects>/<cwd-slug>/<session-id>.jsonl,以及<session-id>/subagents/下的子 Agent 记录。memory/和tool-results/目录会被跳过。 user/assistant行变成事件。文本块合并为一个message事件;每个tool_use和tool_result块各自成为一个事件(分别截断到 2,000 / 4,000 个字符)。isMeta行变成meta事件。thinking块不建立索引,但保留在原始镜像中。summary行设置会话标题。- 项目名取
cwd的最后一段。
其他适配器的映射与各自的格式一起记录在 sources.md 中(Codex 见 Codex CLI)。
条目
- 记住(remember)。 一个条目是一条自成一体的陈述,带有类型、0 到 1 之间的置信度、作者(MCP 客户端的名称;通过 CLI 纠正时为
user)、可选的项目和有效期,以及至少一个来源(不变量 1)。来源可以是一个事件 id、一段精确引文,或者两者兼有:- 给出 id 时,该事件必须存在且可见;如果同时给出引文,引文必须出现在该事件中(忽略空白和大小写)。
- 只给出引文时,Origin 引用包含该引文的最新可见
message事件,找不到时退而查找tool_result事件。工具调用永远不参与匹配,因为 Agent 自己那次包含该引文的remember调用,本身就会作为工具调用被记录下来。
- 纠正(correct)。 纠正一个活跃条目,会插入一个
supersedes它的新条目(不变量 3)。原有来源会被继承,也可以添加新的来源。历史是线性的:一个条目最多只能被取代一次,只有最新版本是活跃的。 - 状态由推导得出:如果有遗忘记录覆盖该条目,则为
forgotten;如果存在更新的版本,则为superseded;否则为active。除非请求历史,查询只返回活跃条目;被遗忘的条目永远不会被返回。
遗忘
forget 接受一个条目 id 或事件 id,并写入一条遗忘记录(不变量 2):
- 遗忘一个条目会遗忘它的所有版本,并排除这些版本引用的所有事件。
- 遗忘一个事件会排除该事件,并像规则 1 那样遗忘所有引用它的条目。
- 不再有任何可见来源的活跃条目已经失去溯源,因此同样会被遗忘。这一步不会再排除其他事件,所以级联到此为止。
被排除的事件会从检索、时间线、会话计数、get 和 sources 中消失,并且再也不能被引用。重新同步——即使源文件已被改写——也不会让它们回来,因为事件 id 是稳定的,而排除按 id 生效。遗忘是隐藏,而不是擦除。原始镜像文件仍保留在磁盘上(不变量 3),但永远不会被展示。物理清除以后可能作为一个显式选项提供。CLI 会先展示将被遗忘的内容,并要求加上 --yes,因为遗忘无法撤销。
检索与时间线
- 全文检索使用带
trigram分词器的 FTS5,因此不区分大小写的子串检索对中日韩(CJK)文字和拉丁文字同样有效。每个以空白分隔的词都会成为一个加引号的短语,各词之间以 AND 组合。trigram 索引无法服务短于三个字符的词,所以这类查询会退回使用LIKE。 - 结果默认按 BM25 相关度排序,也可以按需改为按时间排序(
--recent);退回LIKE的查询总是按时间排序。事件检索只覆盖message事件,除非请求包含工具事件(--all/include_tools)。条目检索覆盖活跃条目。 - 时间线按时间列出会话,或按顺序列出单个会话的事件(工具事件默认隐藏,除非请求显示)。
MCP server
origin mcp 通过 stdio 提供以下工具。结果是 JSON 文本,时间戳为本地时间的 ISO 8601 格式。
| 工具 | 用途 |
|---|---|
search | 对条目和事件做全文检索;不带查询词时列出最近的条目。过滤条件:范围、类型、时间段、来源、项目、工具事件 |
timeline | 某个时间段内的会话(默认 7 天),或单个会话的事件 |
get | 条目及其来源和版本历史,或事件及其完整正文 |
sources | 条目或事件对应的原始记录行:文件、镜像、行号、原始文本 |
remember | 写入一个条目,包括类型、正文、置信度和来源 |
correct | 用纠正后的版本取代一个条目 |
forget | 按上述规则遗忘一个条目或事件;仅在用户明确要求时使用 |
- 新鲜度。 server 在启动时同步所有来源,在读取类工具之前也会同步(最多每 10 秒一次),在
remember和correct之前则总会同步,因此 Agent 能引用它当前所在对话中的记录行。 - 引导。 server 的 MCP
instructions会告诉 Agent 何时检索、何时记住(带理由的决定、稳定的事实、偏好),以及forget需要明确的请求。 - 错误。 无效输入、未知 id 和已遗忘的 id 会以工具错误的形式返回,并附带可读的说明。
个人中枢
中枢就是一个普通的 Origin 库,只是额外运行了 origin serve。它是自托管、单租户的:一个人,以及这个人的设备和 Agent。中枢中没有任何部分依赖特定的托管服务商;发布的容器镜像 ghcr.io/openlinkos/origin 可以在任何地方运行它。
角色
- 设备:任何有 Agent 会话记录的机器。它照常在本地采集(适配器、原始镜像、游标、改写代次、压缩都保持不变),并运行
origin push进行复制。它的本地库是一个暂存副本。 - 中枢:
origin serve为 Agent 提供基于 HTTP 的 MCP,为设备提供一个小型复制 API,启用后还为它的主人提供一个管理界面。对 Agent 而言,中枢是唯一的事实来源:检索、时间线、条目和遗忘都在那里进行。
复制:原始镜像上行,遗忘记录下行
- 设备推送的是原始镜像字节,而不是解析后的事件。中枢把它们存为自己的只追加镜像,并用同样的适配器解析,因此溯源(不变量 1)在中枢上同样成立,解析器的修复也会在所有地方生效。事件 id 在每个库中都完全相同。
- 每个远程文件以
(source, "@<device>:<original path>", generation)为键;本地路径永远不会以@开头。中枢上的镜像位于raw/devices/<device>/<source>/<relPath>[.g<n>]。 - 推送可续传且幂等:设备先向中枢查询游标,再把每个镜像的
[cursor, local size)区间按完整行分块发送(每块最多 1 MiB,或者单独一个更长的行)。偏移量与中枢游标不一致的块会被拒绝,并返回当前游标,设备从那里继续。 - 推送之后,设备取回自己尚未应用的中枢遗忘记录,并应用到自己的暂存库中,从而让遗忘到达每一个副本(不变量 2)。在中枢上被遗忘的事件即使被再次推送,也会保持排除,因为排除按事件 id 生效。
访问
- 发往
/mcp和/v1/*的每个请求都需要Authorization: Bearer <token>;/admin下的管理界面有自己的登录(见管理界面)。令牌在中枢上创建(origin token create <name>),只显示一次,只以 SHA-256 哈希的形式存储,并且可以吊销。每台设备或每个云端 Agent 各用自己的令牌。 - 中枢使用明文 HTTP。在 localhost 之外开放之前,请把它放在 TLS(反向代理或 CDN)之后。
- 通过中枢写入的条目会把 Agent 和令牌记为作者,例如
claude-code@laptop:Agent 名称来自 MCP 客户端(或转发代理设置的X-Origin-Client请求头),后缀来自令牌名称。
HTTP 接口
| 方法与路径 | 用途 |
|---|---|
GET /healthz | { "ok": true, "version": … },无需鉴权 |
POST /mcp | MCP Streamable HTTP,无状态,JSON 响应;与 stdio server 相同的七个工具 |
GET /v1/cursors?device=<name> | 该设备每个文件在中枢上的字节游标 |
POST /v1/ingest | 追加一个块:JSON { device, source, path, relPath, generation, offset, mtimeMs, agentVersion, data },其中 data 为 base64;请求体可以使用 gzip 编码。返回 200 { bytes, eventsAdded, … },偏移量不匹配时返回 409 { bytes } |
GET /v1/tombstones?after=<id> | id 大于 after 的遗忘记录,每条都附带它遗忘的条目和排除的事件 |
GET, POST /admin/… | 启用时的管理界面:需要管理员登录的服务器端渲染页面。未启用时返回 404 |
GET, POST /admin/api/… | 管理界面的 JSON API:与页面相同的操作、会话和检查 |
中枢会校验每个字段:设备名必须匹配 [a-z0-9][a-z0-9._-]{0,62},来源必须是已知的适配器,relPath 必须是相对路径且不含 .. 段,因此设备永远无法写到它的镜像目录之外。
从设备使用中枢
origin hub connect <url> --token <token> [--device <name>]把连接信息保存在<ORIGIN_HOME>/hub.json中(设备名默认为主机名)。ORIGIN_HUB_URL、ORIGIN_HUB_TOKEN和ORIGIN_DEVICE会覆盖其中的配置。- 配置了中枢之后,
origin mcp就成为一个转发代理:它通过 stdio 提供同样的工具,每次调用前先同步和推送(读取类调用最多每 10 秒一次,remember和correct之前总会进行),再把调用转发给中枢。Agent 的注册无需改动,Agent 也仍然能引用刚刚说过的内容。 - 没有本地记录的云端 Agent 直接用 bearer 令牌注册中枢的
/mcpURL。 - 在当前版本中,CLI 管理命令(
entries、correct、forget等)作用于它们所运行的那个库。请通过 Agent、中枢的管理界面,或在中枢上运行 CLI 来管理记忆。
管理界面
中枢可以为它的主人提供一个 Web 界面。它默认关闭;它是同一个库的一个视图,而不是另一套系统:每个页面、每次 JSON 调用都经过 MCP 和 CLI 所用的同一组 core 操作(不变量 5)。@openlinkos/origin-admin 包提供一个请求处理函数,由中枢挂载。
- 提供方式。
origin serve --admin把界面挂载在中枢端口的/admin下;origin serve --admin-listen 127.0.0.1:7078则改在一个单独的监听地址上提供,通过 SSH 隧道或kubectl port-forward访问。如果界面会在非回环地址上提供,除非--public-url(ORIGIN_PUBLIC_URL)是 https URL,或者是回环主机上的 http URL(例如发布在127.0.0.1上的容器端口),否则中枢拒绝启动。没有公开 URL 时,界面只响应发往回环主机名的回环连接,这同时也能防御 DNS 重绑定。界面未启用时,/admin返回 404。 - 登录。 每个中枢只有一个管理密码,没有用户名。设置了
ORIGIN_ADMIN_PASSWORD时,它就是管理密码,并且优先于其他来源;它至少要有 10 个字符,否则中枢拒绝启动。否则,管理密码在中枢上用origin admin password设置(在终端中输入两次,或从标准输入读取一行),或用origin admin password --generate生成。如果中枢在启用管理界面的情况下启动时两者都没有,它会生成一个密码(20 个随机 base32 字符,100 位),保存它的哈希,并在日志中只打印这一次。没有首次访问时的设置向导,所以新建的中枢不会被第一个访问到它的人占用。密码以 scrypt 哈希的形式(N = 2^15、r = 8、p = 3,随机的 16 字节盐)写成scrypt$N$r$p$<salt>$<hash>,保存在只留在中枢上的setting中;中枢也会保存ORIGIN_ADMIN_PASSWORD的这种哈希,以便发现它是否变化。校验逐个进行,并以恒定时间比较。管理密码和中枢令牌互不接受:密码不能以ogn_开头,/mcp和/v1/*只接受 bearer 令牌,/admin只接受它自己的会话 cookie。 - 会话。 会话保存在服务器端的
admin_session中,以会话 id 的哈希为键。会话在 30 分钟没有请求或登录满 12 小时后结束;管理密码变化时,所有会话都会结束:用origin admin password设置新密码时,以及中枢启动时ORIGIN_ADMIN_PASSWORD与上次不同,或者在它和保存的密码之间切换时。在 https 公开 URL 之后,cookie 是__Host-origin-admin(Secure、HttpOnly、SameSite=Lax);在回环 http 上,cookie 是origin-admin(HttpOnly、SameSite=Strict,路径/admin)。在 https 公开 URL 之后,普通 cookie 只在回环隧道上有效,所以从兄弟子域名种下的 cookie 无法固定会话。创建或吊销令牌、遗忘、结束会话,要求最近 10 分钟内输入过密码。 - 请求。 只有 GET 和 POST,所有写操作都是 POST。POST 需要会话的 CSRF 令牌(表单字段
_csrf或请求头X-CSRF-Token),以及与中枢源相同的Origin请求头;带有Sec-Fetch-Site: cross-site的请求会被拒绝。请求体上限为 64 KiB。密码校验失败的次数受到限制:每个客户端每 10 分钟 10 次,总计每 10 分钟 100 次(返回 429 和Retry-After);只有设置了ORIGIN_CLIENT_IP_HEADER时才从该请求头取客户端地址,否则取连接的地址。在代理或 CDN 之后,必须设置这个请求头:否则所有客户端共用代理的地址和同一个限额,任何人的失败尝试都可能把主人锁在外面。 - 响应。 页面在服务器端由一个对每个插值都做转义的模板渲染;一小段脚本提供一些便利,例如就地加载原始行,但没有页面依赖它。每个响应都带有严格的 Content-Security-Policy(没有内联脚本或样式,启用 Trusted Types 且不允许任何策略)、
Cache-Control: no-store, private、Vary: Cookie,以及防止嵌入、类型嗅探、引用来源泄露和被搜索引擎收录的请求头;在 https 之后还带有 HSTS。所有资源,包括设计系统的样式表、字体和图标,都由中枢自己以带内容哈希的文件名提供;每个页面只内联它用到的图标符号,界面从不发起外部请求。 - 能做什么。 概览(数量统计、设备及其最近推送、存储、解析错误)、令牌(创建:令牌只显示一次,旁边附有现成的接入命令;吊销)、设备、时间线、会话、带溯源链的事件(原始行按需加载,截断到 4,000 个字符;工具事件默认隐藏)、检索、条目及其历史、
correct(未指定时置信度为 1)、从事件页面remember(引用该事件,作者为user),以及预览之后的forget:请求带上所展示计划的摘要,并再次输入 id;如果计划在此期间发生了变化,就不会遗忘任何内容(409)。 - 不能做什么。 编辑或删除事件、原始镜像、条目或遗忘记录;撤销遗忘;显示被遗忘内容的任何文本(遗忘记录只保存目标 id、填写的原因和被移除内容的 id;审计日志只保存 id 和数量);保存没有可见来源的条目;再次显示令牌;修改管理密码或其他安全设置;执行任意查询,或读取可见事件的镜像行以外的任何文件。
- 审计日志。 来自界面的登录和失败尝试(每个客户端在每个时间窗口内合并为一条记录)、令牌变更、写入的条目、遗忘操作和被结束的会话(操作者
owner),来自 CLI 的令牌和密码变更(操作者cli),以及中枢启动时生成或变更的密码(操作者hub),都会连同渠道、客户端地址和 user agent 一起追加到audit_log。详情只含 id 和数量,从不包含文本或密码,因此遗忘保持粘性;触发器拒绝任何更新和删除。
待解决的问题
- 低成本的认知。 如何在没有专门抽取流水线的情况下,构建并维护一个关于这个人的模型?目前由 Agent 在注意到某些东西时自行写入认知条目。这是否足够,仍是一个悬而未决的问题。
- 与原生记忆互通。 Claude Code、ZCode 和 Grok Build 都维护一个
MEMORY.md索引,外加每条记忆一个 Markdown 文件。与这种布局的双向导入 / 导出已在计划中,但尚未设计。 - 变更通知。 文件监听在某些平台上并不可靠。MCP server 按需同步,CLI 则在运行
origin sync时同步;可能仍然需要一个由定期全量扫描兜底的后台监听器,尤其是为了从 Agent 闲置的设备上推送。 - 远程管理。 管理界面已经可以在浏览器中管理中枢上的记忆。在设备的 CLI 上针对它的中枢运行
entries、correct和forget,并保持与本地相同的“预览—确认”流程,仍有待解决;设备上只监听回环地址的origin ui也是如此。
相关工作
调研于 2026 年 10 月。Origin 借鉴了这些项目的想法,但不依赖其中任何一个。
| 项目 | Origin 借鉴的想法 |
|---|---|
| Hindsight | 条目引用自己的证据,并能展开到原文;一个显式的“心智模型”层 |
| Graphiti | 双时态有效期(valid_from / valid_to),但不构建图 |
| GitHub Copilot Memory | 使用前会重新验证的引用 |
| MacPaw Portable Memory | 把删除墓碑(tombstone)作为一等概念 |
| Codex memories | 从本地会话记录派生的记忆,并带有指回会话的引用 |
这一领域的其他系统还包括 mem0、Letta、Supermemory、Cognee,以及参考实现 MCP memory server。它们大多要么缺少细粒度的溯源,要么缺少可以验证的遗忘操作;而各厂商的记忆(Claude、ChatGPT、Gemini、Copilot、Codex)都只限于各自的产品,没有面向第三方的读取 API。