个人写作分身 Agent 架构演进问答

字数 11,946|阅读时长 ≈ 30 分钟

版本:2026-07-16

定位:用“先解决一个写作问题,再被下一个工程问题追上”的方式,讲清 CursorBlinking Blog 的个人写作分身为什么会演进成今天这套架构。

0. 先把答案放在门口

问:当前项目里的个人写作分身,究竟是不是 Agent?

答:产品形态上是,执行机制上仍是一套受约束的显式工作流。

它不是一个可以随意挑工具、自己循环推理、顺手把文章发布出去的通用 Agent Runtime。它更像一位坐在编辑器旁边的写作搭档:会读作者认可的历史文章,会使用风格、修订偏好和个人事实,会提交修改候选,但没有文章保险柜的钥匙。

当前系统已经实现:

  • Writing Persona 聚合根;
  • 风格画像、修订偏好快照和个人记忆快照;
  • 按写作分身隔离的来源与版本;
  • 持久化写作会话、消息、AI Run、Task 和 Suggestion;
  • 选区、块级和全文三类已接入 UI 的写作入口;
  • 服务端内部的 selection / block / section / document 范围协议;
  • 自动参考文章召回和自动 Personal Memory 检索;
  • Personal Memory Grounding 与一次修复重试;
  • 不可变文章版本、块修订、Diff、恢复和并发保护;
  • 接受 / 拒绝建议产生的 Learning Observation;
  • 新分身版本生成时重建并固定 Learning Snapshot;
  • 模型结构化输出修复、FlowDocument 领域校验和复杂块保护;
  • 中性的 AI Provider 传输层,以及彼此独立的阅读 / 写作资源策略;
  • better-sqlite3 + WAL + busy_timeout 数据库运行时;
  • 只允许 Persona 级 Style / Learning / Memory 生效的严格隔离;
  • 服务端拥有迁移和 Seed、npm 单一包管理器与统一 CI 门禁。

当前尚未实现:

  • 真正工作的通用 Writing Agent Orchestrator;
  • Personal Mind;
  • 独立 Review Agent;
  • 通用 Tool Registry 和自主工具循环;
  • 向量数据库或语义向量召回;
  • AI 自动发布;
  • 多节点分布式 Worker。

一句话总结:

当前个人写作分身不是“让模型接管编辑器”,而是“让模型在可追溯、可拒绝、可恢复的版本系统里提交候选变更”。

1. 写作分身、风格、学习和记忆到底是什么关系?

问:写作分身是不是换了名字的风格画像?

答:不是。风格画像只是写作分身的一项能力。

当前领域关系是:

Codemermaid
图表将在进入视口后显示

三项能力平行存在:

  • Style 回答“这个分身通常怎么写”;
  • Learning 回答“这个分身对助手修改有哪些稳定偏好”;
  • Memory 回答“这个分身可以使用哪些有来源证据的个人事实”。

Writing Persona Version 不负责把三者熬成一锅“人格浓汤”,它只固定一次可复现的组件组合。

在当前完整能力配置下,这个组合包含 Style、Learning 和 Memory;从表结构与功能开关看,Style 是生成分身版本的必需能力,Learning 和 Memory 关闭时,对应指针允许为空。

问:为什么 Memory 和 Learning 不直接挂在风格画像下面?

答:因为它们不是风格,而且更新节奏不同。

  • Style 常在代表作或来源语料变化时更新;
  • Learning 随建议接受 / 拒绝积累观察;
  • Memory 随来源文章版本、事实冲突和证据资格变化而重建。

如果三者共用一条版本链,新增一段工作经历也会看起来像“文风突变”。数据库会很礼貌地保存这个误会,排查的人就没那么礼貌了。

2. 当前能力状态,先讲清再开麦

问:哪些能力是真落地,哪些只是架构插槽?

答:

表格将在进入视口后显示3 columns / 17 rows

问:既然还没有通用 Orchestrator,为什么仍然叫 Agent?

答:因为产品已经围绕“理解作者并协助写作”形成完整闭环。只是工程实现没有急着把所有控制流塞进一个会自己转圈的框架里。

先把事实、权限、版本和失败边界做实,再讨论自主性,通常比先搭一座“智能指挥中心”、然后发现它没有刹车更划算。

3. 整个演进过程,可以压成一张图吗?

问:从选区润色到写作分身,中间发生了什么?

答:

Codemermaid
图表将在进入视口后显示

每一步都不是为了“看起来更像 AI”,而是为了回答前一步回答不了的问题。

4. 第一阶段:我只想让 AI 改一下选中的文字

问:最简单的 AI 编辑是什么样?

答:选中文字,点击“改善表达”“精简”或“扩写”,模型返回一段纯文本建议。

当前项目仍保留 editor-assistant 兼容路径。当新版 Writing Assistant 或版本能力关闭时,浮动工具栏可以退回这条直接流式路径。

Codemermaid
图表将在进入视口后显示

优点很明显:短、快、演示时不容易冷场。

问题也很明显:它只能回答“这次生成了什么”,回答不了“它后来有没有成为文章事实”。

5. 第一次转向:正文不是字符串,而是 FlowDocument

问:为什么不能让模型直接返回 Markdown 或 HTML?

答:因为当前项目的正文主事实是 FlowDocument。

Codejson
{  "schemaVersion": 1,  "blocks": [    {      "id": "block-stable-id",      "type": "paragraph",      "richText": [{ "text": "正文" }]    }  ]}

它不仅有段落,还包含标题、引用、列表、代码、公式、媒体、表格、details、columns、bookmark、file、embed 等结构。

如果模型只交回一大段自由文本,系统就不知道:

  • 修改的是哪个稳定块;
  • 链接、加粗和行内公式是否保留;
  • 图片、表格和代码是否被吞了;
  • 新增、删除、移动如何进入块级历史;
  • 接受后该形成局部修订还是全文新版本。

FlowDocument 因此是正文世界的“海关”。模型可以带东西回来,但先开箱检查。

6. 第二次转向:模型生成的不是正文,而是候选操作

问:系统怎样描述一次 AI 修改?

答:使用共享的 Suggestion Operation 协议。

当前支持的核心操作包括:

操作含义
replace_text_range替换同一文本块内的选区
replace_block保留块 ID,替换整个块
insert_blocks_before在目标块前插入块
insert_blocks_after在目标块后插入块
delete_block删除块,需要明确确认
replace_document用合法 FlowDocument 形成全文候选

建议还会固定 baseVersionIdbaseRenderHash 和 operations。

Codemermaid
图表将在进入视口后显示

问:为什么操作逻辑放在 `shared`?

答:客户端要预览,服务端要真正应用,两端必须说同一种“修改语言”。

共享层只做纯逻辑,不碰数据库、不碰 DOM,也不趁机看看环境变量。纯函数的职业操守相当稳定。

7. 第三个问题:AI 改完以后,历史在哪里?

问:只覆盖 `posts.content_document` 不够吗?

答:它适合保存当前文章,不适合解释写作历史。

当前版本底座包括:

数据作用
post_versions完整文章版本,如 v1.r0、v1.r1、v2.r0
post_version_blocks一个版本中块修订的顺序
post_blocks稳定块身份与删除状态
post_block_revisions某块不可变的内容修订
post_block_lineagerevise、split、merge、restore 等来源关系
post_edit_transactions一次用户可感知的提交
post_edit_events事务内的新增、删除、修改和移动
Codemermaid
图表将在进入视口后显示

posts.content_document 负责快读,版本表负责讲清来龙去脉。一个负责上班,一个负责写回忆录。

8. 谁真正拥有正文写入权?

问:AI 服务、版本服务和页面都碰正文,会不会出现多条写入路径?

答:所以持久化写入收敛到 posts.commitContentChange

Codemermaid
图表将在进入视口后显示

模型、TaskRunner、Session、React 面板都不能直接写正文。

Agent 系统里最危险的能力通常不是“生成”,而是“写入”。Prompt 里写十遍“请谨慎”,也没有一个数据库事务靠谱。

9. 当前界面到底支持哪些修改范围?

问:聊天框里还需要用户手工选择“全文 / 小节 / 块”吗?

答:当前 UI 已把范围改成由入口自动决定。

入口当前范围是否选择 Writing Persona
文章页打开写作助手全文 document可以
编辑器区块菜单打开写作助手当前块 block可以
浮动选区工具栏同一文本块选区 selection当前不传 Persona,属于快速建议
section服务端协议已支持当前主 UI 未开放入口

主聊天框默认全文,区块入口自动块级。用户负责说“想怎么改”,系统负责知道“改哪里”。这比让用户先填写一份小型 API 表单更像写作工具。

Codemermaid
图表将在进入视口后显示

问:浮动工具栏还是旧路径吗?

答:分情况。

  • 新版 Assistant 与 Versioning 开启时,它会创建隐藏会话、Run、Task 和 pending Suggestion,接受后走统一版本事务;
  • 新版能力关闭时,才降级到旧 editor-assistant 流式路径。

兼容路径不是另一套未来,只是旧桥还没拆。桥可以留,导航不能写错。

10. 为什么需要 Session、Run、Task 和 Suggestion 四个对象?

问:这四个名字看起来像开会名单,分别负责什么?

答:

对象回答的问题
Assistant Session这篇文章里的对话上下文是什么?
Assistant Message用户和助手分别说过什么?
AI Run这次业务运行用了什么模型、Prompt 和上下文?
AI Task谁执行、执行到第几次、能否取消和重试?
Suggestion哪组候选操作可以被接受、拒绝或判定冲突?

Message 适合展示,Suggestion 适合做决定。助手消息可以说“候选已经生成”,真正能进入正文的是它关联的 operations。

11. TaskRunner 已经是什么,又不是什么?

问:当前已经是分布式任务队列了吗?

答:不是。当前是 SQLite 持久任务表加进程内执行器。

它能:

  • 持久化 queued / running / succeeded / failed / cancelled;
  • 用 handler registry 分发任务;
  • setImmediate 或定时器调度;
  • AbortController 取消当前进程内任务;
  • 指数退避重试;
  • 服务启动时把遗留 running 恢复为 queued;
  • 保存顺序 Run Event。

它不能:

  • 提供多节点租约;
  • 保证跨节点恰好一次执行;
  • 代替独立消息队列;
  • 在服务进程关闭后继续计算。
Codemermaid
图表将在进入视口后显示

它是一辆可靠的小货车,不是跨国物流集团。对当前单实例个人内容系统来说,小货车正合适。

11.1 小货车为什么换了发动机?

问:SQLite 不是一直都在吗,为什么还要专门治理运行时?

答:数据库文件相同,不代表连接语义相同。

旧兼容路径在 Node 原生 SQLite 不可用时会退回 sql.js:每个连接各自把整库加载进内存,关闭时再把整份文件写回。单连接演示很安静,两个连接先后写入时就可能上演“最后关门的人覆盖全场”。任务队列、文章保存和 AI 回调一旦并发,这不是性能小瑕疵,而是事实可能丢失。

当前运行时统一为 better-sqlite3

Codemermaid
图表将在进入视口后显示

它带来的不是“数据库突然分布式了”,而是更朴素也更重要的保证:所有连接面对的是同一个 SQLite 文件和真实锁,不再各自抱着一份内存副本互相蒙在鼓里。

专项并发测试会让 Worker 持有 BEGIN IMMEDIATE 写事务,主线程同时写入;第二个写者必须等待前一个提交,最后两笔数据都在。测试要验证的不是“两个函数都返回了”,而是“谁也没有把谁写没”。

问:为什么迁移也从 client 搬到 server?

答:因为 Schema 是服务端事实,不是前端的副业。

当前归属已经收敛为:

Codemermaid
图表将在进入视口后显示

客户端不再拥有迁移和 Seed。CI 每次从空库顺序执行全部迁移,再跑架构、类型和专项回归;部署也改用锁文件驱动的 npm ci,并允许 better-sqlite3 安装原生绑定。原生依赖的安装脚本被禁掉以后,数据库通常不会因此变得更纯洁,只会变得打不开。

12. 为什么现在不只剩一个 Model Gateway?

问:领域服务直接调用 DeepSeek 不行吗?

答:能跑,但很快会出现一种经典架构:阅读助手有一套请求代码,写作助手复制一套,第三个 AI 功能再复制一套;半年后大家都声称自己那套才是“公共实现”。

当前代码把模型边界拆成三层:

Codemermaid
图表将在进入视口后显示

ai-provider 不知道什么是文章、阅读助手或写作分身,它只负责供应商协议和可实例化的资源策略基元;业务含义留在上层。

写作侧的 Model Gateway 负责:

  • generateText / generateStructured 门面;
  • Prompt 与 Trace Context;
  • JSON 对象提取;
  • 一次通用结构化修复;
  • 写作 usage 和错误映射。

阅读助手则保留自己的公开文章校验、访客身份、响应缓存和阅读额度,不再充当全项目的“兼职模型基础设施部”。

问:阅读和写作共享 API Key,为什么额度还要分开?

答:共享供应商账号不等于共享业务预算。

公开阅读请求可能突然变多,后台长文生成又可能一次消耗大量 token。如果它们共用一个内存计数器,读者多翻译几次,作者就会在编辑器里收到“今天先别写了”。这个产品联动很有戏剧性,但没有产品价值。

因此当前两侧分别拥有:

  • 分钟 / 每日请求上限;
  • 用户 / 访客并发;
  • 总任务并发;
  • 每日 token 预算;
  • 独立错误码。

写作配置优先读取 WRITING_AI_*,未单独配置的供应商凭据和通用参数可以回退到 CONTENT_AI_*;写作模型默认仍是 deepseek-v4-pro,阅读模型默认是 deepseek-v4-flash

问:现在能无缝切换任意供应商吗?

答:还不能。边界已经中性化,当前实际传输实现仍是 DeepSeek SSE。把插座从墙里独立出来,不代表抽屉里已经备齐全世界的转换头。

13. “模型结构化输出无效”后来怎样治理?

问:模型返回了 JSON,为什么全文候选仍可能失败?

答:JSON 合法只说明括号对上了,不说明 FlowDocument 合法。

当前修复分三层:

Codemermaid
图表将在进入视口后显示

领域修复明确要求:

  • heading.level 只能是 1~4;
  • 新文本块只能是 paragraph、heading、quote;
  • 未知标题块转 heading,其他未知文本块转 paragraph;
  • 原文字不得丢失或改写;
  • media、table、code、list、details、equation 等复杂块必须原样保留。

修复失败就失败,不会用“清洗成空文档”这种方式假装成功。空白文章很干净,但通常不是用户想要的那种干净。

14. Writing Persona 怎样生成新版本?

问:用户点“生成分身版本”后,底层发生什么?

答:这是一个 writing_persona_generation 父任务。它固定来源修订,顺序生成已启用的组件,最后原子激活。Style 始终生成,Learning 和 Memory 由能力开关决定。

Codemermaid
图表将在进入视口后显示

关键边界:

  • 来源必须仍是已发布文章;
  • 私有 / 口令来源必须经过显式部署开关;
  • 生成期间来源变化会返回冲突;
  • 任一已启用组件失败,旧 Persona Version 继续 active;
  • 所有已启用组件的候选都成功后才切换;
  • 用户以后只选择 Persona,不手工拼组件 ID。

15. Style Profile 到底学了什么?

问:风格学习是不是微调模型?

答:不是。当前是生成可版本化、可追溯的结构化画像。

画像主要包含:

  • 风格摘要;
  • language、syntax、paragraph、structure、narrative、format 等维度;
  • 带权重的风格规则;
  • 代表性表达模式;
  • 不确定项;
  • 来源证据;
  • learner / Prompt 版本。

来源会固定到文章不可变版本和 content hash。文章后来改了,旧画像不会偷偷“穿越”到新正文。

问:为什么必须有 evidence?

答:因为“作者常用时间线结构”和“模型觉得他应该喜欢时间线”不是同一件事。

画像可以不完美,但不能完全失忆自己从哪里得出的结论。

16. Personal Memory 为什么有两层?

问:既然 Memory 按 Persona 隔离,为什么还存在用户级 Memory Space?

答:为了复用昂贵的来源抽取,同时保持使用边界隔离。

当前分为:

  1. 用户级抽取层:按用户维护来源版本和 Memory Observation,可供多个 Persona 复用;
  2. Persona 级投影层:只用该 Persona 选择的来源,生成独立 Memory Snapshot。
Codemermaid
图表将在进入视口后显示

抽取结果可以复用,active Memory 不能串台。后台可以省电,分身不能借错人生。

问:系统怎么保证“不能串台”不是一句团队口号?

答:服务层和数据库层各上一把锁。

服务层现在遵守三条硬规则:

  • 写作请求没有 writingPersonaId,就不加载 Style、Learning 或 Memory;
  • Memory 检索必须同时匹配用户、Persona 和被固定的 Snapshot;
  • Learning 的读取、重建和规则查询都必须带 Persona,普通通用写作不会悄悄写一条全局偏好。

数据库迁移 0014_persona_component_isolation.sql 还会:

  • 清空 personal_memory_spaces.active_snapshot_id,用户级 Space 只保留来源与抽取缓存职责;
  • 把旧的全局 active Memory / Learning 投影降为 superseded;
  • 用触发器拒绝新增或改回 persona_id IS NULL 的 Memory Snapshot、Learning Snapshot、Rule 和 Observation。

同时,客户端和 Admin HTTP 已移除全局 /admin/writing-memory/*/admin/writing-learning* 管理路径。现在的入口是 /admin/writing-personas,不是在角落里再藏一套“临时全局人格”。架构里的临时后门,往往比正式大门活得更久,所以干脆把门框也拆了。

17. Personal Memory 保存的是什么?

问:它是不是把整篇文章塞进一个向量库?

答:不是。

当前 Memory 会把来源文章版本转换为结构化 Observation,再投影为 Memory Item。条目保留:

  • memory key 和类型;
  • 事实陈述;
  • 时间及精度;
  • 置信度;
  • 敏感度;
  • supported / conflicting / restricted 等状态;
  • 对应来源文章版本、块位置和证据 hash;
  • 补强、冲突、替代等关系;
  • 可检索文本。

Memory 的职责是保存“有出处的个人事实”,不是保存另一份全文,也不是让模型给作者编一本未经授权的传记。

18. Memory 怎样自动进入一次写作?

问:用户写作时需要手工勾选 Memory 吗?

答:不需要。写作分身默认启用,系统会自动完成 Memory 检索和使用;用户只需要关心写作。

Codemermaid
图表将在进入视口后显示

当前检索不是向量检索,主要使用:

  • SQLite FTS(存在时);
  • 词项匹配;
  • 置信度;
  • 证据数量;
  • 条数和字符预算。

它还会排除仅由当前文章自己提供证据的记忆,避免“拿正在写的内容证明正在写的内容”,形成文学版循环引用。

19. Grounding 怎样阻止模型编经历?

问:Memory 已经提供给模型,为什么还要 Grounding?

答:因为模型看过证据,不代表一定只写证据。

当前流程是:

Codemermaid
图表将在进入视口后显示

第一人称身份、经历、时间、地点、数量和结果,必须由当前输入或检索到的 Memory 支持。

生成完成后,系统还会记录:

  • 提供了多少条 Memory;
  • 实际使用了多少条;
  • 使用了哪些 memory key;
  • Grounding 是否通过;
  • Memory 来源文章。

界面默认只显示“自动参考 N 篇 · 使用 Memory N 条”,需要核对时再展开。日常写作不应该被一份审计报告挡住,但审计报告必须找得到。

20. Learning Snapshot 是怎样来的?

问:用户接受一次建议,系统会立刻修改人格吗?

答:不会。一次接受可能代表喜欢,也可能代表今天赶着下班。

当前 Learning Projection 只把接受和拒绝先保存为不可变 Observation,再在生成新 Persona Version 时重建。

Codemermaid
图表将在进入视口后显示

当前激活门槛包括至少 3 次正向、至少 4 个来源 Observation、置信度不低于 0.7,并经过确定性评估。

问:人工编辑也会成为修订偏好吗?

答:人工编辑会进入文章版本和 manual_edit Observation,但当前 Learning Projection 真正用于生成规则的数据,仍主要是与 Suggestion 关联的 accepted / rejected 决策。

“人工改了哪里”和“为什么这样改”之间还隔着一层语义。系统没有假装自己已经读心,这一点值得保留。

21. 自动参考文章从哪里来?

问:当前还需要用户手工挑参考文章吗?

答:写作助手主路径已经改成自动选择。

服务端会把以下信息组合成查询:

  • 当前文章标题、摘要、分类和标签;
  • 写作指令;
  • 目标文本;
  • 邻近上下文。

然后从其他已发布文章中按 metadata + lexical 规则排序,最多选择 3 篇。

Codemermaid
图表将在进入视口后显示

当前没有向量召回。算法很朴素,但可解释、可测试,而且不会在半夜突然声称“这两篇文章在灵魂深处相似”。

22. 一次真实写作请求的完整链路

问:用户在聊天框输入“结合我的 Android 经历重写全文”后发生什么?

答:

Codemermaid
图表将在进入视口后显示

本次 Run 会固定:

  • 文章 base version 和 render hash;
  • Writing Persona Version;
  • Style Profile Version;
  • Learning Snapshot;
  • Personal Memory Snapshot;
  • 自动参考文章版本;
  • Writing Context Snapshot;
  • Prompt / provider / model 摘要。

所以历史解释不需要拿“今天最新的 Persona”去猜“昨天生成时用了什么”。

23. 接受建议时发生什么?

问:点击接受只是前端把内容塞进编辑器吗?

答:不是。接受由服务端再次校验并创建新版本。

Codemermaid
图表将在进入视口后显示

拒绝时只更新 Suggestion 状态并写 Learning Observation,正文不动。拒绝不是失败,它是系统终于听见了一次“不”。

24. 为什么版本分 v1.r2 和 v2.r0?

问:局部修改和全文改写为什么版本语义不同?

答:

  • 局部人工修改、选区或块级建议,形成当前主版本的新 Revision;
  • 全文 replace_document 接受后创建新 Major Version;
  • 恢复历史会追加 restored 版本,不修改旧行。
Codemermaid
图表将在进入视口后显示

候选在接受前不创建正式版本,否则每次试写都会把版本列表变成模型的草稿箱。

25. 旧建议怎样避免覆盖新正文?

问:模型生成期间用户继续修改,怎么办?

答:生成时固定基线,接受时再次检查。

并发保护包括:

基线作用
baseVersionId当前 head 是否仍是生成时版本
baseRenderHash当前物化正文是否变化
expectedRevisionId精确保护目标块修订
idempotencyKey防止重复点击制造重复事务

任一关键基线过期,接受返回 409,Suggestion 标记为 conflicted,不会自动“聪明合并”。

自动合并听起来体贴,事实冲突时通常只是把两个错误搅匀。

26. 全文改写怎样保护图片、表格和代码?

问:候选通过 FlowDocument 校验就安全了吗?

答:不够。结构合法不等于业务安全。

当前全文候选只允许模型自由重组文本内容。复杂块会比较:

  • 数量;
  • 稳定块 ID;
  • 顺序;
  • 稳定序列化内容。

media、table、code、list、callout、details、columns、bookmark、file、embed、equation 等复杂块被改变,就拒绝候选。

服务端内部的 section 候选更严格:必须保留原块数量、顺序、ID 和 type,文本块只改 richText,复杂块原样返回。

27. 私有文章为什么需要单独开关?

问:管理员能看私有文章,不就能把它发给模型吗?

答:不能这样推断。

“有权在本站阅读”和“同意发给外部模型供应商”是两种权限。

当前规则:

  • 来源必须是 published;
  • public 来源默认可用;
  • private / password 来源需要 WRITING_ALLOW_PRIVATE_MODEL_SOURCES
  • Memory 使用私有来源还需要 WRITING_MEMORY_PRIVATE_SOURCES_ENABLED
  • 服务端在任务开始时重新检查状态和可见性;
  • 客户端选择不构成最终授权。

权限不能靠复选框的乐观主义。

28. 功能开关之间有什么依赖?

问:当前有哪些主要开关?

答:

Code
WRITING_PERSONA_ENABLED=trueWRITING_ALLOW_PRIVATE_MODEL_SOURCESWRITING_MEMORY_PRIVATE_SOURCES_ENABLEDWRITING_AI_MODEL

其中:

  • WRITING_PERSONA_ENABLED 是唯一的写作分身功能总开关,默认开启;
  • Versioning、Style、Assistant、Learning、Memory、Memory Usage、Memory Auto Refresh 和 Orchestrator 随总开关统一启停;
  • Memory Usage 仍要求模型配置完整,否则不会发起模型调用;
  • WRITING_ALLOW_PRIVATE_MODEL_SOURCESWRITING_MEMORY_PRIVATE_SOURCES_ENABLED 是数据出境安全授权,不属于普通功能开关,因此继续独立且默认关闭。

模型资源配置已经单独形成 WRITING_AI_* 命名空间,包括 ENABLEDPROVIDERAPI_KEYBASE_URL、并发、频率、每日 token 预算、超时和重试等参数。未设置的通用供应商参数可以回退到 CONTENT_AI_*,但运行时计数器不会回退成阅读助手那一只。

设置 WRITING_PERSONA_ENABLED=false 后,文章手工编辑、保存、发布、导出仍应正常。AI 是副驾驶,不是点火系统。

29. 当前总体架构应该怎么画?

问:怎样画一张不夸大现状的架构图?

答:

Codemermaid
图表将在进入视口后显示

图里没有一个“万能 Agent 大脑”,因为代码里也没有。真正的控制中心仍是明确的领域服务、资源边界和数据库事务。图很大,但至少每个方框都真有文件,不是拿虚线框出来融资。

30. 各模块在演讲中怎么一句话解释?

表格将在进入视口后显示2 columns / 20 rows

31. 为什么现在还没有通用 Agent Loop?

问:能力已经这么多,为什么不上 LangGraph 或通用工具循环?

答:因为当前主要任务仍能用显式流程可靠完成。

Codemermaid
图表将在进入视口后显示

这些流程分支有限,失败语义明确。引入通用 Agent Loop 会增加隐藏控制流,却暂时没有带来同等收益。

未来 Orchestrator 可以决定“下一步调用哪个稳定命令”,但不能:

  • 绕过 FlowDocument;
  • 绕过权限;
  • 绕过版本基线;
  • 自己接受 Suggestion;
  • 自己恢复版本;
  • 自己发布文章。

Agent 可以负责路线规划,不能顺手把道路产权也办到自己名下。

32. Personal Mind 为什么还没有进入当前架构?

问:有了 Personal Memory,为什么还需要 Personal Mind?

答:两者解决的问题不同。

  • Memory 是有证据的事实层:做过什么、经历过什么、什么时候发生;
  • Mind 更接近长期观点、价值判断、认知模型和当前立场。

当前项目已经能安全使用个人事实,但尚未建立足够稳定的观点冲突、时效、反思和修订协议。把观点过早写成“长期人格”,比记错一个年份更难纠正。

所以当前顺序是合理的:先把 Memory 做成有证据、可冲突、可撤回的事实系统,再讨论 Mind。先学会记笔记,再考虑写哲学。

33. 系统失败时怎样降级?

问:模型失败会影响普通编辑吗?

答:不会。AI Run / Task 失败,正文保持不变。

问:结构修复失败呢?

答:Run 失败,不保存可接受 Suggestion。

问:Grounding 两次都不过呢?

答:Run 失败,不允许候选进入接受阶段。

问:接受时版本冲突呢?

答:Suggestion 变成 conflicted,正文不变。

问:服务重启呢?

答:遗留 running Task 恢复为 queued,并记录 recovered_after_restart

问:版本、head 或搜索索引写到一半失败呢?

答:SQLite 事务整体回滚。

这些降级有一个共同点:宁可少生成一次,也不偷偷多改一笔。

34. 如果 Agent 做错了,怎样排查?

问:最短排查链是什么?

答:

Codemermaid
图表将在进入视口后显示

依次问:

  • 谁接受了这条建议?
  • 接受时的 base version 和 hash 是什么?
  • operations 改了哪些块?
  • 生成时使用哪个 Persona Version?
  • 提供和实际使用了哪些 Memory?
  • 自动参考了哪些文章版本?
  • 结构化修复或 Grounding 是否发生过?
  • Run 是否重试、取消或从重启中恢复?

把“AI 怎么突然这样写”拆成可查询事实,情绪会下降,定位速度会上升。

35. 常见现场质疑怎么回答?

问:这不就是工作流,不是 Agent 吗?

答:

当前执行层主要是显式工作流,这是刻意的阶段选择。系统先把上下文、模型、任务、建议、版本和权限做成可独立验证的能力,再让未来 Orchestrator 编排它们。Agent 这个名字不能拿来给自由文本接口镀金。

问:为什么不用大模型一次完成所有事情?

答:一次大 Prompt 无法替代来源版本、隐私策略、任务恢复、输出校验、建议决策、并发冲突和数据库事务。它只是把系统复杂度塞进一段更难调试的文字里。

问:为什么不用 Git 保存文章版本?

答:产品需要领域版本:块身份、Run、Message、Suggestion、接受者、幂等键和在线事务。Git 擅长文件历史,不负责理解“这条建议为什么形成 v2.r0”。

问:SQLite 撑得住吗?

答:当前单实例、个人内容系统和受控并发下可以,而且现在不是靠祈祷撑住。

运行时已经统一到 better-sqlite3,文件库启用 WAL、外键和 5 秒 busy timeout;并发测试覆盖两个真实写者争抢事务锁后都成功提交。迁移、Seed 和运行时也都归服务端所有。

但这不等于 SQLite 被授予了“永不迁移”勋章。真正的升级信号包括:持续锁竞争、长事务阻塞、数据库体量与备份窗口失控、需要多节点 Worker 租约,或者写入吞吐开始由单机磁盘决定。表多不是罪证,写入模型失配才是。

问:项目接入 CI 了吗?

答:仓库侧已经接入统一门禁。

npm run ci:validate 会创建临时空库,执行全部迁移,再运行架构检查、客户端类型检查、服务端健康检查、共享层检查、旧来源检查,以及写作 Agent、阅读助手和搜索回归。GitHub 镜像有现成 Workflow;主远端是 Codeup,因此云效 Flow 还需要在控制台做一次仓库关联并执行同一命令。

Codemermaid
图表将在进入视口后显示

CI 不是用来证明代码永远没问题,它负责让同一种低级错误不要每周轮流复活。这个岗位不浪漫,但很省咖啡。

36. 下一步最合理的演进顺序

问:现在最该做多 Agent,还是补现有闭环?

答:更稳妥的顺序是:

Codemermaid
图表将在进入视口后显示

数据库运行时、AI 资源边界和 Persona 隔离这三个高风险地基已经收紧;下一步先把门禁真正挂到 Codeup 提交事件,再继续降低大文件的变更半径。admin-routes 已拆出分身路由,assistant-sessions 已拆出 Prompt,但它们还没有小到可以放进口袋里。

原因很简单:写作分身的长期价值来自“越来越理解作者”,而不是“工具调用次数越来越多”;工程稳定性也来自边界持续变窄,而不是文件名越来越有气势。

37. 十分钟架构演讲怎么讲?

问:只有十分钟,主线怎么安排?

答:可以分五段。

第一分钟,讲产品:

这是住在博客编辑器里的个人写作分身。它从用户认可的已发布文章生成风格、修订偏好和个人记忆,但模型只能提交候选。

第二到三分钟,讲第一个转折:

选区 AI 只需要“文字进、文字出”。一旦要回答谁改了什么、能否恢复、能否学习,就必须升级成 FlowDocument 操作和不可变版本。

第四到六分钟,讲核心链路:

写作请求固定 Persona Version、自动参考和 Memory,经过 Run / Task、结构校验和 Grounding 后保存 pending Suggestion。接受时唯一提交命令原子更新版本、head 和索引。

第七到八分钟,讲个性化:

Persona 下 Style、Learning、Memory 平行版本化。Memory 抽取可在用户级复用,但 active Snapshot 按 Persona 隔离。

最后两分钟,讲边界:

当前没有通用自主 Agent。模型传输可以共享,但阅读和写作额度分开;SQLite 用 WAL 保证单机并发写入,迁移和 CI 从服务端事实开始。未来 Orchestrator 只编排稳定命令,不拥有正文、权限和发布权。

收尾可以说:

个人写作分身真正的难题,不是让模型多写几段话,而是让每一次 AI 参与都可解释、可拒绝、可恢复,并且最终仍然属于作者。

38. 常见追问速答

问:AI 会自动发布文章吗?

答:不会。

问:AI 会直接覆盖正文吗?

答:不会。先生成 pending Suggestion,接受后才进入版本事务。

问:聊天框默认改哪里?

答:默认全文;从编辑器区块入口打开时自动块级。

问:选区 AI 走什么路径?

答:新版能力开启时走隐藏 Session + Run + Task + Suggestion;关闭时降级到旧 editor-assistant

问:小节修改已经有 UI 吗?

答:服务端协议支持,当前主 UI 没有开放入口。

问:参考文章需要手选吗?

答:不需要。服务端自动召回最多 3 篇相关已发布文章。

问:Memory 需要手工维护吗?

答:不需要逐条维护。用户只选择 Persona 的已发布来源文章;提取、投影、检索和使用自动进行。

问:不选择写作分身时,会不会偷偷使用某个全局 Memory?

答:不会。不选择 Persona 时,Style、Learning 和 Memory 都为空;全局 active Snapshot 和管理 API 已退出运行路径。

问:怎么知道用了几条 Memory?

答:助手消息会显示使用数量,展开可查看来源解释。

问:风格学习是微调吗?

答:不是,是有版本、来源和证据的结构化画像。

问:Learning 会在一次接受后立刻改变当前分身吗?

答:不会。Observation 先积累,生成新 Persona Version 时才重建和激活 Snapshot。

问:现在有向量数据库吗?

答:没有。参考文章用 metadata + lexical;Memory 用 SQLite FTS / 词项排序。

问:现在有 Personal Mind 吗?

答:没有。

问:现在有通用多 Agent 吗?

答:没有。当前是显式领域服务和持久任务工作流。

问:阅读助手会抢光写作助手额度吗?

答:不会共用运行时额度。两者可以回退使用同一组供应商凭据,但频率、并发和每日 token 预算分别计算。

问:SQLite 还有 `sql.js` 降级吗?

答:没有。当前统一使用 better-sqlite3,文件库启用 WAL 和 busy timeout。

问:模型能修改图片和表格吗?

答:当前自动改写主要面向文本块,复杂块必须原样保留。

39. 术语表

表格将在进入视口后显示2 columns / 24 rows

40. 代码事实索引

问:讲完以后去哪里核对?

答:

  • 能力开关:server/src/modules/writing-agent/writing-config.js
  • Writing Persona:server/src/modules/writing-persona/writing-persona.service.js
  • 写作模型门面:server/src/modules/ai-gateway/model-gateway.js
  • 写作资源策略:server/src/modules/ai-gateway/writing-resource-policy.js
  • 中性模型 Provider:server/src/modules/ai-provider
  • 公开阅读额度与缓存:server/src/modules/reading-assistant/reading-assistant.service.js
  • AI Run:server/src/modules/ai-runs/ai-runs.service.js
  • TaskRunner:server/src/modules/task-runner/task-runner.service.js
  • 自动参考文章:server/src/modules/content-context/content-context.service.js
  • Style Profile:server/src/modules/style/style.service.js
  • Learning Projection:server/src/modules/learning/learning.service.js
  • Personal Memory:server/src/modules/personal-memory
  • 会话和 Suggestion:server/src/modules/assistant-sessions/assistant-sessions.service.js
  • 助手 Prompt:server/src/modules/assistant-sessions/assistant-prompt.js
  • 当前正文提交:server/src/modules/posts/posts.service.js
  • 不可变版本:server/src/modules/versioning/versioning.service.js
  • FlowDocument 操作与 Diff:shared/src/content/document/flow-document-operations.js
  • Admin API 总入口:server/src/http/admin-routes.js
  • Writing Persona Admin API:server/src/http/admin-writing-persona-routes.js
  • SQLite 运行时:server/src/db/sqlite-runtime.js
  • 数据库迁移与 Seed:server/scripts/db-migrate.mjsserver/scripts/db-seed.mjs
  • CI 统一入口:scripts/run-ci.mjs.github/workflows/ci.yml
  • 分身管理 UI:client/components/admin/writing-agent/WritingAgentHome.js
  • 写作助手 UI:client/components/admin/assistant/WritingAssistantPanel.js
  • 编辑页入口:client/pages/admin/posts/[id]/edit.js
  • 基础迁移:server/db/migrations/0010_writing_agent_foundation.sql
  • Task / Learning 迁移:server/db/migrations/0011_writing_agent_completion.sql
  • Personal Memory 迁移:server/db/migrations/0012_personal_memory.sql
  • Writing Persona 迁移:server/db/migrations/0013_writing_personas.sql
  • Persona 强隔离迁移:server/db/migrations/0014_persona_component_isolation.sql
  • 写作 Agent 测试:tests/feature/20260715/writing-agent-foundation.test.mjs
  • Personal Memory 测试:tests/feature/20260715/personal-memory.test.mjs
  • Writing Persona 测试:tests/feature/20260716/writing-persona.test.mjs
  • AI 资源边界测试:tests/feature/20260716/ai-resource-boundary.test.mjs
  • SQLite 并发测试:tests/feature/20260716/sqlite-runtime-concurrency.test.mjs

进一步阅读:

  • 项目架构
  • 文章数据架构
  • 编辑器架构
  • 个人写作分身 Agent 技术说明
  • 端侧分层架构
  • 前台长文阅读渲染架构
  • OpenClaw 架构演进问答

41. 最后的架构判断

问:这套架构最值得保留的是什么?

答:不是某个模型名,也不是某一版 Prompt,而是这些边界:

  • FlowDocument 是正文唯一主协议;
  • 客户端不直接写 SQLite,也不持有模型密钥;
  • 模型只生成候选,不拥有正文写入权;
  • 用户接受后才进入统一内容提交命令;
  • 版本、块修订、事件、head 和索引保持事务一致;
  • Persona 下 Style、Learning、Memory 平行且独立版本化;
  • Memory 抽取可以复用,Persona Snapshot 必须隔离;
  • 没有 Persona 就没有 Style、Learning 和 Memory 上下文,不存在全局人格兜底;
  • 自动参考和 Memory 使用都固定到一次 Run;
  • 结构化输出、复杂块和个人事实分别有校验护栏;
  • 阅读与写作只共享中性 Provider,不共享运行时额度;
  • SQLite 文件连接统一使用 better-sqlite3 + WAL,不回退到整库覆盖写回;
  • 数据库迁移和 Seed 归服务端,客户端不拥有 Schema;
  • npm run ci:validate 从空库迁移开始执行统一门禁;
  • Run 是业务事实,Task 是执行事实;
  • 原始版本和行为事实不可覆盖,投影可以重算;
  • 未来 Orchestrator 只编排稳定能力,不成为第二套内容系统。

只要这些边界不丢,模型可以换,TaskRunner 可以升级,检索可以演进,Learning 算法也可以重算。

这就是当前项目的核心选择:

先让 AI 的每一次参与都能被系统负责,再逐步让它拥有更多能力。
0