Merged
Conversation
|
Failed to generate code suggestions for PR |
fix: reuse lifecycle lock during content write sync test: add write test update: update write Refine content write refresh flow and docs fix: add sync client write wrapper
0bf1fc1 to
7afb966
Compare
qin-ctx
approved these changes
Apr 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Content Write 接口设计
背景
OpenViking 现有的语义增量更新链路,核心由
SemanticMsg.changes和SemanticDagExecutor驱动。此前缺少的是一个一等公民的write接口,用来编辑一个已经存在的文件,同时还要保持以下能力:本文描述本次实现采用的设计。
目标
write接口,目录必须被拒绝。replace和append。resource、memory和skill。非目标
.abstract.md、.overview.md、.relations.json。viking://resources/...viking://user/.../memories/<type>/...viking://agent/.../memories/<type>/...viking://agent/skills/<skill>/...API 形态
HTTP 接口:
POST /api/v1/content/write请求字段:
uri: strcontent: strmode: "replace" | "append",默认replacewait: bool,默认falsetimeout: Optional[float]telemetry返回字段:
uriroot_uricontext_typemodewritten_bytessemantic_updatedvector_updatedwait=true时返回queue_status客户端侧也同步补齐了统一入口,包括:
AsyncOpenViking校验规则
write在真正执行写入前会先校验目标:mode必须是replace或append。租户与权限模型
所有文件系统操作都运行在调用方提供的
RequestContext下。关键性质如下:
stat、read、write_file、append_file、temp copy、队列消息构造以及最终 sync,全部沿用同一个带租户语义的ctx。NotFoundError,从而避免暴露外部租户路径是否真实存在。这与 OpenViking 现有的多租户行为保持一致,而不是在
write里再引入第二套权限系统。根节点解析
write 协调器会从目标文件 URI 反推出一个语义刷新根节点:
resources:根节点是最上层 resource 节点,例如viking://resources/foouser/.../memories/<type>/...:根节点是 memory type 目录agent/.../memories/<type>/...:根节点是 memory type 目录agent/skills/<skill>/...:根节点是 skill 目录这个根节点就是:
Write 模式
write现在只有一种行为:写入后自动刷新相关语义与向量,保证叶子节点、父目录摘要以及检索状态保持一致。1. Resource / Skill 写入
对于
resource和skill,本实现尽可能复用了现有的增量更新链路:SemanticMsg入队,其中包含:uri=temp_root_uritarget_uri=original_root_urichanges={"modified": [temp_target_uri]}skip_vectorization=falselifecycle_lock_handle_id=<subtree lock>SemanticDagExecutor以 incremental 模式运行。_sync_topdown_recursive()对 temp 和 target 做 diff,再只把变化的文件/目录同步回目标树。这条路径直接复用了之前已经存在的增量 DAG 逻辑。
父目录是怎么更新的
父目录的更新并不是在本地手工拼一个完整 overview 字符串。当前流水线的工作方式是:
.overview.md来复用旧摘要。_generate_overview(...)。这一步仍然是基于 LLM/VLM 的目录级摘要生成,输入包括:_extract_abstract_from_overview(...)从 overview 文本中本地提取.abstract.md。因此答案是:
overview的生成仍然是模型驱动的。abstract不是再单独调用一次模型生成,而是从 overview 中本地提取。增量更新时也是同样的原理。所谓“增量”,本质上是尽量避免重算未变化节点,以及避免把未变化内容重新写回目标树,而不是换了一套新的父目录生成方式。
增量更新复用的细节
已有的增量更新行为,已经提供了本次
write所需的大部分基础设施:SemanticMsg.changes用来标记被修改的文件。SemanticDagExecutor._check_file_content_changed(...)用来比较 temp 和 target 的文件内容是否变化。.overview.md中复用已有摘要。.overview.md和.abstract.md。_sync_topdown_recursive(...)只把差异部分从 temp 推回 target。本次
write设计的核心,就是把写操作接入到这条现有流水线,而不是重新实现一套父节点传播逻辑。为什么 Memory 走了不同路径
memory目前在SemanticProcessor._process_memory_directory(...)里已经有一条专用语义处理链路。它的当前行为是:
.overview.md来复用未变化文件的摘要_generate_overview(...)重新生成目录级 overview.abstract.md和.overview.md做向量化问题在于,这条链路本身并不依赖
target_uri + temp-root sync模式,而是直接在原目录工作。因此在不做更大范围重构之前,memory 不能安全复用 resource/skill 那套 temp subtree 流程。所以当前实现对 memory 的处理是:
changes={"modified": [file_uri]}为输入,对 memory 目录入队一次语义刷新。_process_memory_directory(...)更新.overview.md和.abstract.md。这样做的好处是:虽然 memory 没有直接走 temp-root sync,但它仍然复用了自己现有的增量摘要复用机制,并与当前 memory 架构保持一致。
向量数据库写回
向量写回在两条路径中都被显式考虑了。
Resource / Skill
SemanticDagExecutor调度文件级向量化。.abstract.md和.overview.md的向量化。Memory
_process_memory_directory(...)会对重新生成的.abstract.md和.overview.md执行目录级向量化。锁设计
本设计复用了现有 subtree lifecycle lock,目的是保证:
对于 memory,本次额外增加了显式的 lock release helper,因为原地处理路径中存在多个 early return 分支。
错误语义
InvalidArgumentErrorInvalidArgumentErrorInvalidArgumentErrorNotFoundErrorDeadlineExceededError考虑过的替代方案
对所有 context type 都采用原地重建
不采用。因为 resource/skill 已经有一套成熟的 temp-root incremental DAG + sync 路径;再原地重做一遍,只会带来逻辑重复和更高的不一致风险。
强行把 memory 接到 temp-root sync 路径上
当前也不采用。因为现有 memory 语义处理是原地执行的,并不使用
target_uri。如果强行改过去,就需要对 memory processor 做一轮更大的重构。后续可选演进
target_uri。write测试。关键代码入口
openviking/server/routers/content.pyopenviking/service/fs_service.pyopenviking/storage/content_write.pyopenviking/storage/queuefs/semantic_processor.pyopenviking/storage/queuefs/semantic_dag.py