memU 工作流流水线架构解析:ADR 0001 如何把 memorize 与 retrieve 拆成可观察、可定制的阶段化执行
【免费下载链接】memUPersonal memory across agents项目地址: https://gitcode.com/GitHub_Trending/mem/memU
本篇基于 memU 仓库中的架构决策记录 ADR 0001: Use Workflow Pipelines for Core Operations 展开,完整解读该决策的背景、决策要点与正负后果,并结合仓库中的MemoryService、memorize 阶段化模块与宿主适配层源码,说明"每个核心操作 = 一条具名工作流流水线"这一设计在 memU 中是如何落地、如何被后续 ADR 持续复用,以及如何通过状态键校验、步骤级配置和拦截器等扩展点支撑可观测性与自定义。
一、背景:核心操作天然是多阶段流程,单一大函数难以驾驭
ADR 0001(状态:Accepted,日期 2026-02-24)的 Context 部分开宗明义:
memU has multiple high-level operations (
memorize,retrieve, and CRUD/patch operations) that each require multi-stage execution, LLM calls, storage writes, and optional short-circuit behavior.
也就是说,memU 的三类核心操作——写入记忆的memorize、检索记忆的retrieve、以及面向记忆记录的 CRUD/patch 操作——每一个都不是单步动作,而是需要多阶段执行(multiple multi-stage execution)的复合流程:
memorize:从仓库当前实现看,一条 memorize 流水线至少包含输入物化(把开发者会话写成 JSONL)、清单快照与 diff(manifest diff → changed only)、按文件类型的预处理(preprocess,chat/agent 日志与"其他文件"走不同分支)、写入记忆工作区、导出MEMORY.md/SKILL.md/INDEX.md等多个环节——这与 memorize 流程示意图 中标注的阶段完全对应;retrieve:查询 + 作用域校验、一次性向量化、分段排序(rank segments)、按文件汇总(roll up)、回溯资源(recall resources)、拼装返回上下文,是一条典型的线性多阶段链,见 retrieve 流程示意图;- CRUD/patch:对记忆记录的增删改查同样涉及校验、存储写入与可选的短路行为(short-circuit)。
ADR 指出的核心痛点是:如果每个操作用一个"巨石函数"(single monolithic function)承载,这些流程将难以扩展(hard to extend)、难以观察(observe)、难以定制(customize)。这正是 ADR 0001 做出架构决策的动机,也是后文所有设计要点的出发点。
二、决策:把每个核心操作建模为"具名流水线 + 有序 WorkflowStep"
ADR 0001 的 Decision 部分给出了核心判断:
Model each core operation as a named workflow pipeline composed of ordered
WorkflowStepunits.
即:每个核心操作不是一段函数体,而是一条具名(named)流水线,由有序的WorkflowStep单元组合而成。在这一总纲之下,ADR 列出了五条具体决策,下面逐条展开并结合仓库现状说明。
2.1 在 MemoryService 中通过 PipelineManager 集中注册流水线
第一条决策是"pipelines 的注册要集中化":所有具名流水线统一在MemoryService中经由PipelineManager注册。这样做的工程价值在于:
- 注册即清单:
MemoryService是流水线的单一登记处,任何时刻都能查清"系统里有哪些流水线、各自包含哪些步骤",而无需在散落的业务代码中翻找; - 与存储/模型解耦:注册点位于服务层,不依赖具体数据库或模型后端的实现细节。
从源码结构看,仓库当前的组合根(composition root)仍是MemoryService(见 service.py)。它的__init__负责装配DatabaseConfig、EmbeddingProfilesConfig、ProgressiveRetrieveConfig、UserConfig等配置,并通过build_database构建可插拔存储、通过ClientPool管理 embedding 客户端。当前版本中MemoryService的对外面是 agentic 接口(list_all_recall_files、progressive_retrieve、commit_results,实现见 agentic.py),这反映了 ADR 0001 之后(见 ADR 0005、ADR 0007)的持续演进;但"服务层作为流水线与能力装配的中心"这一 ADR 0001 定位保持不变。
2.2 注册/变更时校验 required / produced 状态键
第二条决策是流水线间的"契约检查":每个步骤声明它需要(required)哪些状态键、产出(produced)哪些状态键,并在流水线注册或结构变更(mutation)时就完成校验。这意味着:
- 类型/拼写层面的错误在启动期(注册时)暴露,而不是在执行到某一步时才发现上游没产出某个键;
- 流水线的输入输出依赖关系是显式、可审查的(inspectable stage boundaries,见后文 Consequences)。
值得对照的是 ADR 0001 自己在 Negative 一栏的坦白——"dict-based workflow state relies on key naming discipline"(基于 dict 的工作流状态依赖键命名纪律)。而 memU 对这条风险的实际应对,正体现了状态契约思想:memorize 的输入端使用严格 Pydantic 模型而非裸 dict。input.py 中的基类InputModel设置了model_config = ConfigDict(extra="forbid"),即禁止任何未声明字段;MemorizeInput(input.py#L45-L56)还带schema_version: Literal["1.0"]版本字段和"至少包含一条 message"的模型级校验器。换言之,ADR 担心的"键命名纪律"问题,在关键数据通道上被收敛为 schema 级强约束。
2.3 通过 WorkflowRunner 抽象执行,默认 local
第三条决策将执行机制抽象为WorkflowRunner,默认实现是local(进程内直接按序执行各步骤)。把"步骤定义"与"如何执行步骤"分离的收益:
- 本地顺序执行是默认路径,无额外开销、无额外依赖;
- 未来可以替换 runner 实现(如远端/分布式执行、带重试与断点的执行器),而流水线定义本身不需要改动——这正是 Decision 中"extension points for custom runners"的含义。
2.4 运行时自定义:步骤级配置 + 结构性变更(插入/替换/移除)
第四条决策允许两种粒度的运行时定制:
- 步骤级配置(step-level config):不改变流水线结构,只调整某个步骤的行为参数;
- 结构性变更(structural mutation):对流水线本身做insert / replace / remove——插入新步骤、替换已有步骤、移除步骤。
这一条是 ADR 0001 中扩展性最强、也最需要纪律的设计。它对应 memU 后来多宿主(multi-host)扩展的现实需求:不同宿主(Codex、Claude Code、Cursor、OpenClaw、Hermes 等)接入时"不 fork 流水线、只在既定结构上变化",ADR 0010 给出的量化结论是"four hosts, zero pipeline forks"(四个宿主、零个流水线分叉)。当然,ADR 也如实记录了代价:结构性变更会增大不同部署环境之间的行为差异(behavioral variance between deployments)——这是 Negative 一栏明确写出的风险,使用步骤级 mutation 的能力时需要对变更做版本化与回归验证(memU 用schema_version字段与 manifest 快照机制 管理这类状态差异,见 lifecycle.py#L124 的snapshot_tracked调用)。
2.5 before / after / on_error 步骤拦截器
第五条决策为每个步骤提供三类拦截器(interceptors):before(步骤执行前)、after(步骤执行后)、on_error(步骤出错时)。这是面向 instrumentation(插桩)与 control(控制)的扩展点:
- 日志、指标、事件上报可以挂在 before/after 上,无需侵入步骤逻辑本身;
- 短路、重试、降级等控制策略可以挂在 on_error 上。
这一点与 Context 中提到的"optional short-circuit behavior"直接呼应:短路不再是散落在 if 分支里的特判,而是可以统一通过拦截器实现的横切能力。
三、决策后果(Consequences)的完整盘点
ADR 0001 的 Consequences 部分给出了正反两列,完整继承如下。
正面后果:
| 后果 | 含义与仓库印证 |
|---|---|
| uniform execution model across memorize/retrieve/CRUD | memorize、retrieve、CRUD 三类操作共用同一执行模型:同为"具名流水线 + 有序步骤 + 共享工作流状态"。从 structure-v2 架构图 可见 memorize 侧(record:session logs → prepare → self-evolve → commit)与 retrieve 侧(installed instruction → retrieve(query) → relevant context)共享同一MemoryService组合根与共享存储,执行模型是统一的 |
| explicit, inspectable stage boundaries | 阶段边界显式化:memorize 各阶段在 lifecycle.py 中可逐段审查——输入物化(materialize_memorize_inputs)、recall files 镜像(_mirror_recall_files)、清单快照(snapshot_tracked)、任务指令生成(prepare_instruction_jobs/prepare_resource_job)、active run 落盘;每步的输入输出都可见 |
| extension points for custom runners and step customization | 即 2.3 / 2.4 两节:runner 可替换、步骤可配置可结构变更 |
| easier interception and observability around stage execution | 即 2.5 节的 before/after/on_error 拦截器 |
负面后果(ADR 的诚实记录):
- dict-based workflow state relies on key naming discipline——工作流状态若以 dict 承载,就依赖键命名纪律。如 2.2 节所述,memU 在关键输入通道上用严格 Pydantic 模型(
extra="forbid"+ 版本字段)来把"纪律"变成"校验"; - pipeline mutation can increase behavioral variance between deployments——结构性变更会让不同部署的行为漂移。应对方式是注册期状态键校验(2.2 节)加输入 schema 版本化(
schema_version),让"改了流水线"这件事可被检测、可回归; - more framework code compared to direct function calls——相比直接函数调用,流水线抽象引入了更多框架代码。这是所有此类架构决策的固有成本,ADR 用前四条正面后果证明其收益大于成本,并以此支撑 Accepted 状态。
四、从 ADR 到实现:memorize 流水线中"阶段"的具体形态
ADR 0001 给出的是"怎么建模",仓库给出的是"长成什么样"。以 memorize 为例,当前实现把一条流水线切成职责单一的模块:
- 输入契约层(input.py):
MessageInput/ToolCallInput/ToolResultInput三类对话项以type判别字段的联合类型组织,MemorizeInput是"一条有序开发者会话"的版本化输入;project_memory/project_skill两个投影函数把同一输入分流给 memory 线与 skill 线——这正体现了 ADR 0008 所说的"同一管线、按线消费"; - 物化层(materialize.py):把会话输入落成 JSONL transcript 文件(对应流程图中的 folder input 阶段),
_atomic_write_text保证写入原子性; - 生命周期层(lifecycle.py):
prepare_memorize(L104-L152)与commit_memorize(L155-L176)构成流水线的两个显式阶段边界——prepare 阶段检查"是否已有 active run"(一个典型的短路检查,对应 Context 中的 short-circuit behavior)、镜像 recall files、快照清单、生成作业指令文件并落盘.memorize_run.json;commit 阶段读取 diff、提交结果、清理临时产物。阶段之间用磁盘上的状态文件(Manifest、active_run)传递状态,边界清晰、可断点审查。
各阶段职责单一、边界显式,正是 ADR 0001 中 "explicit, inspectable stage boundaries" 的实现形态。对应的测试用例(test_memorize_lifecycle.py、test_memorize_input.py、test_memorize_materialization.py)也按同一阶段边界组织,每个阶段可独立验证。
五、后续 ADR 如何站在 ADR 0001 之上:统一执行模型的复用证据
ADR 0001 的价值在后续决策记录中被反复引用,这本身就是"uniform execution model"落地程度的最佳证据:
- ADR 0007 将 memorize/retrieve 拆分为三条独立记忆线时明确写道:内核仍然以 ADR 0001 的方式组合("Builds on
docs/adr/0001-workflow-pipeline-architecture.md(the kernel still composes as …)"),且每条线的 memorize 流水线把preprocess作为注入的首个步骤(preprocess → …)——步骤级注入正是 2.4 节结构性变更能力的直接应用; - ADR 0008 声明两个集成面(零代码 Hooks 与程序化 API)"both surfaces drive the same workflow pipelines"——两个入口驱动同一条流水线,调用方不预排序、不理解内部阶段;
- ADR 0010 为四个宿主新增适配时明确"not revisit the pipeline",并以"four hosts, zero pipeline forks"作为该决策的兑现证明;
- ADR 0013 处理服务端模板下发时,把"一条畸形模板不能 crash 流水线或到达 agent"("A malformed server template cannot crash the pipeline or reach an agent")列为设计约束——这是 on_error 拦截面思想在输入侧的延伸。
可以看到,ADR 0001 不是一次性架构:它是 docs/adr 目录 中后续十几条 ADR 的共同地基——流水线结构稳定(zero pipeline forks),变化只发生在步骤注入、宿主适配与输入校验层面。
六、小结:什么场景下值得采用这种设计
把 ADR 0001 的决策、后果与仓库证据合起来看,memU 的选择可以概括为:
- 适用条件:当核心操作是多阶段的(多步执行、多外部调用、多存储写入)、且需要短路行为、可观测性、跨环境部署时,"巨石函数 → 具名流水线 + 有序步骤"的抽象收益显著;
- 配套纪律不可省:注册期状态键校验、输入 schema 强约束与版本字段(
extra="forbid"、schema_version)、步骤级拦截器,三者共同抵消 ADR 自己列出的三条负面后果; - 演进方式:后续需求(新记忆线、新集成面、新宿主)通过"注入/替换步骤"而非"fork 流水线"来扩展,从而把行为方差控制在注册与校验可覆盖的范围内。
想进一步深入,建议按 docs/adr/README.md 的索引顺序阅读后续 ADR(重点 0005、0007、0008、0010),并对照 service.py 与 memorize 模块 的源码,观察"注册中心 → 阶段模块 → 阶段边界测试"这条从决策到实现的完整链条。
【免费下载链接】memUPersonal memory across agents项目地址: https://gitcode.com/GitHub_Trending/mem/memU
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考