v2-refactor-temp/docs/knowledge/rfc-knowledge-workflow-architecture.zh-CN.md
定位:Knowledge runtime 架构评审 RFC。
目标是把 add / delete / reindex / file processing / indexing 的职责边界讲清楚, 不作为逐文件实施清单。
Canonical reference: docs/references/knowledge/workflow-architecture.md. Operation guard reference: docs/references/knowledge/operation-guards.md.
v2-refactor-temp下的文档会在 v2 收尾时移除。 当前实现已经移除 V2sitemap数据源;本文中涉及sitemap的内容只代表早期 RFC 背景,当前实现以 canonical reference 为准。
Knowledge 不再建模成简单 pipeline,而是一个轻量 workflow:
用户动作 / API
|
v
KnowledgeWorkflowService
|
v
JobManager -> Knowledge job handlers
|
v
KnowledgeLockManager
|
v
SQLite / VectorStore / FileManager
架构主轴只保留三部分:
1. KnowledgeWorkflowService
决定下一步做什么。
2. KnowledgeLockManager
保证同一 base 下状态、向量库、artifact、破坏性清理安全串行。
3. Knowledge job handlers
执行当前阶段的短Job。
其他能力先作为 helpers/modules,不作为独立 service:
source planning helpers
item lifecycle helpers
artifact helpers
file-processing adapter helpers
只有当 helper 需要持有长期状态、注册 IPC/event/timer、管理生命周期或长期资源时, 才升级为 service。
当前实现状态:
knowledge_base.fileProcessorId 已持久化,但当前 Knowledge indexing 不读取它;对 indexing 是 inert。分轮范围:
index-documents、delete-subtree、reindex-subtree。不接 FileProcessing。needsFileProcessing source planning、check-file-processing-result、FileProcessing adapter。KnowledgeWorkflowService 是唯一的流程决策 owner。
class KnowledgeWorkflowService {
addItems(baseId, inputs): Promise<AddResult>
deleteItems(baseId, itemIds): Promise<JobHandle>
reindexItems(baseId, itemIds): Promise<JobHandle>
scheduleItem(baseId, itemId): Promise<void>
// Round 2
scheduleFileProcessingCheck(baseId, itemId, fileProcessingJobId, sourceFileEntryId, options): Promise<void>
scheduleIndexing(baseId, itemId, source): Promise<void>
}
调用边界:
addItems / deleteItems / reindexItems 给 API/service 层调用。scheduleItem / scheduleFileProcessingCheck / scheduleIndexing 给 job handlers 调用。scheduleFileProcessingCheck 和 needs file processing 分支属于 Round 2。公开 API 语义:
addItems / deleteItems / reindexItems 都是异步 workflow 入口。addItems resolve:item rows 已创建,首批 Knowledge jobs 已入队。reindexItems resolve:目标 top-level subtree 已确认全为 completed / failed,且 reindex-subtree job 已入队。deleteItems resolve:subtree 已同步标记为 deleting,默认 UI 查询和检索不再返回这些 items,delete-subtree cleanup job 已入队。scheduleItem(baseId, itemId) 的决策:
directory / sitemap
-> enqueue knowledge.prepare-root
file / note / url
-> source planning
direct
-> enqueue knowledge.index-documents
needs file processing
-> start FileProcessing
-> enqueue knowledge.check-file-processing-result
invalid
-> mark item failed
第一版 Knowledge job types:
Round 1:
knowledge.prepare-root
knowledge.index-documents
knowledge.delete-subtree
knowledge.reindex-subtree
Round 2:
knowledge.check-file-processing-result
knowledge.prepare-root输入:
baseId
itemId
职责:
directory / sitemap。workflowService.scheduleItem(baseId, childId)。directory / sitemap;此时由 workflow service 再次分派到 knowledge.prepare-root,形成递归展开。file / note / url child 会进入 source planning 和 indexing。不负责 child type 分支、source planning、reader、embedding、vector write。
knowledge.check-file-processing-result(Round 2)输入:
baseId
itemId
fileProcessingJobId
sourceFileEntryId
pollRound / firstScheduledAt / parentJobId
职责:
pending / delayed / running:调用 scheduleFileProcessingCheck(...) 延迟检查。completed:校验 markdown artifact,attach processed_artifact ref,再调用
scheduleIndexing(baseId, itemId, artifactSource)。failed / cancelled / missing / invalid:标记 item failed。knowledge.index-documents输入:
baseId
itemId
parentJobId
职责:
reader -> chunk -> batched embed -> serialized vector write
index-documents 不负责追踪外部源文件的最新状态。Knowledge item 的语义是:
用户添加或 reindex 时选择/生成一次输入,当前 workflow 消费这份输入;后续外部文件修改或
删除不会自动 invalidate 已启动的 indexing job。用户需要显式 reindex 才会重新取源。
这个 job 是完整 indexing job。aiCore embed 不是独立 job。
执行策略:
EMBED_BATCH_SIZE = 32 chunks。defaultTimeoutMs = 30min。AbortSignal。replaceByExternalId 写入 vectors。replaceByExternalId(itemId, []) 并标记 item completed。KnowledgeLockManager 的 per-base lock 内执行,并在写入前做 final stale guard。reportProgress 用于诊断,但第一版不做用户可见实时进度 UI,也不新增 workflow/run 聚合进度。knowledge_item.status 承载 reading / embedding 等粗粒度阶段,不记录 batch 级进度。final stale guard:
under KnowledgeLockManager base lock:
re-read item
assert item exists
assert item.status != deleting
replaceByExternalId(...)
mark item completed
不要求 source snapshot 对比,也不要求 KnowledgeLockManager 维护 delete/reindex
barrier。未来如果产品语义改为自动跟踪源文件最新内容,再引入 source generation/version
校验。
如果 final stale guard 不满足:
knowledge.delete-subtree输入:
baseId
rootItemIds
前置条件:
deleting。deleting items。职责:
cancel/drain active subtree jobs
-> delete vectors
-> detach processed_artifact refs
-> delete knowledge_item rows by resolved ids
幂等规则:
delete-subtree 是 at-least-once cleanup job;DB rows、vectors 和 FileRef cleanup 在 crash 后重跑必须收敛。JobManager.cancel();没有 active jobs 是 no-op。status = deleting 的 subtree rows;subtree 已不存在时视为 cleanup 已完成。FileEntry;无引用 FileEntry 按 FileManager 默认策略保留,由文件管理界面或后续孤儿文件能力处理。deleteItemsByIds 必须放在最后;它先按调用方传入的 ids 展开完整 subtree 并清理 Knowledge FileRef,再删除传入 ids 对应 rows,descendants 由 knowledge_item.groupId cascade 删除;找不到 rows 时视为已完成,不能用 missing-row error 让 job 永久失败。完成后流程结束。
knowledge.reindex-subtree输入:
baseId
itemId
职责:
delete vectors
-> delete stale container descendants when selected roots are containers
-> reset knowledge_item subtree rows
-> workflowService.scheduleItem(baseId, itemId)
规则:
reindex-subtree 只处理入口已确认全为 completed / failed 的 terminal subtree。deleting 后跳过。deleting guard 是必要防御,不是重复校验;它们覆盖 enqueue 到 job reset 之间的竞态,并维持 delete 随时可用。preparing / processing 再调度后续 job;如果调度失败,必须补偿为 failed,避免没有 durable job 的 active 状态卡住。index-documents 会按 knowledge_item.data 对账并重建 Knowledge source FileRef。本节只描述入口 guard 和状态边界。三条入口不要强行抽成一个通用 validation pipeline:
addItems -> 新建 rows,先写 active 状态,再调度首批 jobs
deleteItems -> 先写 durable deleting intent,再调度 cleanup job,enqueue 失败后等待下次启动恢复扫描
reindexItems -> 只接受全 terminal subtree 的 durable job,不提前写 active 状态
共享 guard 应只覆盖语义完全一致的部分,例如 base 失败态拦截、item/base 归属检查、 嵌套选择归并、queue name 和 idempotency key。状态写入、enqueue 失败补偿和恢复策略必须保留在各自 workflow 中显式表达。
addItems(baseId, inputs)
-> preflight
-> create item rows
-> workflowService.scheduleItem(baseId, itemId)
入口预检只做便宜、同步、属于入口职责的检查:
failed base。入口不做这些检查:
这些检查放到 job/runtime guard 中处理,因为 enqueue 后 base/item/source/runtime 仍可能变化。 入口做过重 source preflight 会制造 TOCTOU 假安全,也会把可恢复的单 item indexing 失败放大成整批 add reject。
调度补偿规则:
failed,然后把原 enqueue 错误抛给调用方。deleteItems(baseId, itemIds)
-> preflight
-> mark knowledge_item subtree status = deleting
-> enqueue knowledge.delete-subtree
-> return JobHandle
delete 是破坏性 workflow,但用户可见删除必须在 API resolve 前完成。
规则:
deleting status,不新增 deleted status、deletedAt 或 tombstone table。deleting 是用户不可见状态。deleting 时 error = null。deleting items。delete-subtree 只负责 cleanup:cancel/drain、vectors、Knowledge FileRef、最终按已解析 ids 删除 rows。delete-subtree 失败,items 保持 deleting,不重新出现在 UI;通过 job retry 继续 cleanup。deleting 是 durable delete intent marker。正常路径会立即创建 delete-subtree job;如果 enqueue 失败或进程在两步之间退出,下次启动恢复扫描会为残留 deleting roots best-effort 补 enqueue。deleting,不回滚成用户可见状态。itemIds 先去重并折叠为 top-level roots;如果同时选中目录和其 descendant,只保留目录,避免同一 subtree 被重复 cleanup。reindexItems(baseId, itemIds)
-> preflight
-> assert every selected subtree item is completed or failed
-> enqueue knowledge.reindex-subtree
reindex 不负责抢占 active work。只要 selected subtree 内还有 idle / preparing /
processing / reading / embedding / deleting,入口就拒绝。用户可以随时 delete;
只有全部完成或失败后才能 reindex。
reindex 与 delete 共享物理清理步骤,但不共享 active job cancellation:
delete vectors
-> delete stale container descendants and their refs
区别只在最后:
delete -> mark deleting at API boundary -> cleanup job deletes resolved knowledge_item rows
reindex -> reset knowledge_item subtree rows -> scheduleItem
对 directory / sitemap,descendants 的重建发生在后续 prepare-root 阶段,不在
reindex-subtree 的 reset 阶段做。
规则:
itemIds 先去重并折叠为 top-level roots;如果同时选中目录和其 descendant,只保留目录,避免重复 reindex。completed / failed;任何 active 或 deleting item 都拒绝整批 reindex。preparing / processing。状态 reset 和重新调度由 reindex-subtree job 负责。reindex-subtree 内部 reset 后的 follow-up job 调度失败必须把 reset roots 标记为 failed;这是 reset 已经写入 active 状态后的必要补偿。KnowledgeLockManager 负责同一 base 下的安全写入和清理。
第一版实现使用 per-base mutex:
private readonly baseLocks = new Map<string, Mutex>()
具体 public API 暂不在本 RFC 中定死;实现时只要求所有同 base 的 mutation 进入同一把
baseId 锁。
职责:
不维护 deleting/reindexing barrier。delete 通过 durable deleting status、active job
cancel/drain 和 cleanup job 幂等性保证收敛;reindex 通过入口 terminal-subtree guard
避免与 active indexing/expansion job 竞争。
规则:
completed;目录 list 还要拒绝包含 deleting descendant 的 subtree。KnowledgeLockManager 是进程内串行化机制,不是 crash-safety 机制;进程重启后的一致性依赖 durable item state、durable jobs、JobManager recovery 和 cleanup 幂等性。无引用 FileEntry 的资源回收不属于 Knowledge workflow 的 crash-safety 承诺。KnowledgeLockManager 不能替代 DbService.withWriteTx;前者串行同 base 的 Knowledge mutation,后者串行主 SQLite 的所有写事务,避免 Knowledge 写与 JobService 写竞争。KnowledgeItemService / KnowledgeBaseService 的写事务必须迁到 DbService.withWriteTx,不能继续使用 raw db.transaction。Helpers 只封装规则,不持有长期状态,不注册 IPC/timer/event,不进入 service registry。
用于 KnowledgeWorkflowService.scheduleItem。
输出:
Round 1:
direct
invalid
Round 2:
needsFileProcessing
规则:
base.fileProcessorId 对 indexing 仍是 inert。base.fileProcessorId 只影响未来索引,不自动 reindex 已有 vectors。集中封装 status/error 写入,避免 handler 裸写 updateStatus。
只保留 status 和 error:
status = item 的持久生命周期事实;reading/embedding/preparing 表示 Knowledge 粗粒度阶段;
deleting 表示用户不可见、等待后台物理清理
JobManager state/progress = job 执行状态和实时进度
status 不是 JobManager progress 的替代品,也不由 JobManager state 反推。deleting 不能被
container reconcile 改回 processing / completed / failed。
职责:
processed_artifact FileRef。cleanup 规则:
detach current Knowledge refs
-> keep detached FileEntry rows
目标模型不主动跨 item/base 共享 processed artifact,但 Knowledge 仍只维护自己的 FileRef。 本轮不引入 durable artifact cleanup queue;无引用 FileEntry 由后续 FileManager/文件管理界面统一发现和清理。
隔离 Knowledge 与 FileProcessing API:
startJob() -> JobSnapshot
JobSnapshot.id -> fileProcessingJobId
completed output -> markdown artifact FileEntry
Knowledge 每次需要转换时都使用本次 fileProcessingJobId 绑定 continuation。
FileProcessing 只负责转换文件,Knowledge 负责转换后的继续索引。
规则:
FileProcessing completed 后:
check-file-processing-result
-> validate markdown artifact
-> attach processed_artifact ref
-> scheduleIndexing(..., artifactSource)
JobManager queue concurrency 已改为只统计 running jobs。
pending / delayed = backlog
running = worker slot
因此 Knowledge 不需要为了避免 pending backlog 自锁实现准入控制。
大 fan-out 仍可能带来 DB rows、UI 刷新和日志压力,但这是性能/可运维性问题,不是 correctness 前置。第一版不引入复杂 capacity budget 或 backlog schema。
第一版也不依赖 JobManager completion subscription:
JobHandle.finished 是 enqueue 调用方持有的内存 Promise。handler.onSettled 是 best-effort terminal hook,不承载必须成功的下一步调度。execute 内完成本阶段动作后调用 workflow service,由 workflow service enqueue 下一步。handler 内调度下一步时必须使用 deterministic idempotencyKey,避免 execute retry 重复创建子Job。
规则:
idempotencyKey 保留现有 knowledge: 前缀。idempotencyKey alone,不是 (type, idempotencyKey);不同 job type 也会互相 dedup。推荐 key 形态:
knowledge:${baseId}:${itemId}:prepare
knowledge:${baseId}:${itemId}:fp-check
knowledge:${baseId}:${itemId}:index
knowledge:${baseId}:${itemId}:delete
knowledge:${baseId}:${itemId}:reindex
例如 check-file-processing-result.execute 里 enqueue index-documents 时,父 job 仍可能是
running。如果二者都用 knowledge:${baseId}:${itemId},index-documents 会被父 job 的
non-terminal idempotency key 去重掉,导致下一步永远不会创建。
本 RFC 不新增 persisted attempt table,也不新增 generation token。
旧 indexing result 回来时,Round 1 用现有 durable state 判断是否仍有效:
item 是否仍存在
item.status 是否不是 deleting
本 workflow 是否仍可继续写入
FileProcessing 接入后也沿用同一语义:每个 Knowledge workflow 消费本次 FileProcessing job 产出的 artifact;后续外部文件修改/删除不 invalidate 已启动的 workflow。只有当未来 产品语义改为自动跟踪源文件最新内容时,才需要新增 source generation/version 校验。
这些检查必须至少执行两次:
KnowledgeLockManager per-base lock 内重新检查,并与 replaceByExternalId
和 mark completed 保持同一临界区。如果不满足:
skip continuation
do not write vectors
do not mark completed
Round 1 final stale guard 只缩小 race window。delete 的主要保护来自 cancel/drain 和
deleting durable state;reindex 的主要保护来自入口 terminal-subtree guard,不允许
active indexing/expansion job 与 cleanup/reset 并发。processed artifact FileEntry orphan
处理作为文件管理层的后续资源回收问题,不影响 Knowledge delete/reindex 的可见状态收敛。
失败边界:
deleting 标记成功后,后续 cleanup 失败不回滚用户可见删除。本轮不做:
deleted status、deletedAt 或单独 tombstone table。indexingGeneration / owner token。maxQueueDepth。base.fileProcessorId 变更后的自动 reindex。建议顺序:
KnowledgeItemService / KnowledgeBaseService 写事务到 DbService.withWriteTx。KnowledgeWorkflowService 和 KnowledgeLockManager 边界。knowledge_item.status = deleting,并让默认 item list、search、RAG hydration 排除 deleting items。scheduleItem。check-file-processing-result 和 FileProcessing adapter(Round 2)。delete-subtree / reindex-subtree handlers。保持不变:
addItems / deleteItems / reindexItems 都是异步 workflow 入口;调用方不能把 API resolve 当作 indexing / reindex / cleanup 全部完成。knowledge_item.phase 移除;status 扩展为 idle / preparing / processing / reading /
embedding / completed / failed / deleting。base.fileProcessorId 对 indexing 仍是 inert;Round 2 后只影响未来索引。check-file-processing-result 是否需要最大等待时间?KnowledgeWorkflowService、KnowledgeLockManager 和
Knowledge job handlers。prepare-root、check-file-processing-result、
index-documents、delete-subtree、reindex-subtree。KnowledgeItemService / KnowledgeBaseService 写路径使用 DbService.withWriteTx,不使用 raw db.transaction。index-documents 使用固定 32 chunk batch、30min timeout、串行 batch、batch 间 cancellation check,写入仍是最终一次 replaceByExternalId。index-documents 的 final stale guard 与 vector write 必须在 KnowledgeLockManager lock 内同一临界区完成。knowledge_item.status 支持 deleting,默认 item list、search、RAG hydration 排除 deleting items。deleting 并入队 delete-subtree cleanup job。delete-subtree 的 DB/vector/ref/row cleanup steps 可重复执行;crash 后由 JobManager recovery 重跑并收敛。KnowledgeLockManager 只承诺进程内串行化,不承诺 crash-safety。completed / failed;active 或 deleting subtree 必须拒绝。