Kilo Agent Manager 多项目配置架构:不可变绑定与版本化写入的设计实践
【免费下载链接】kilocodeKilo is the all-in-one agentic engineering platform. Build, ship, and iterate faster with the most popular open source coding agent.项目地址: https://gitcode.com/GitHub_Trending/ki/kilocode
Agent Manager 为 Kilo 引入多项目管理后,Settings 面板的读写目标从"当前激活项目"转向"显式、不可变、带版本"的绑定契约,避免 A 项目的草稿被错误写入 B 项目的配置文件。本文以仓库内设计规范文档 .kilo/plans/agent-manager-multi-project-configuration.md 为骨架,结合 Kilo 源码中的绑定实现(config-bindings.ts)、webview 保存流程(config.tsx)与后端 overlay 冲突测试(config-overlay.test.ts),完整讲解四层配置存储的所有权划分、设置标签的目标映射、不可变绑定契约、后端 SHA-256 版本校验与 CAS 写入,以及保证多项目发布的阻塞测试清单。读完本文,你将掌握 Kilo 多项目设置读写为何必须"绑定目标、校验版本",以及这套契约在扩展端与后端各层的落地方式。
背景:多项目时代的 Settings 读写困境
Kilo 当前维护着四个互不相同的配置存储层:
| 存储 | 示例 | 归属 |
|---|---|---|
| VS Code preferences | VS Codesettings.json | 当前 VS Code 用户/安装 |
| Kilo user config | ~/.config/kilo/kilo.json | 跨项目、跨 Kilo 客户端的用户默认值 |
| Kilo project config | <repo>/.kilo/kilo.jsonc | 仓库行为与覆盖项 |
| Runtime/session state | 内存中,按目录限定 | 单个项目、worktree 或会话 |
共享的 Settings 保存路径目前调用splitConfigByScope()自动切分草稿(config-scope.ts):
commit_message写入项目配置;indexing.enabled写入项目配置;- 其余通用 Settings 字段写入用户配置。
问题在于这类"隐藏的自动切分"并不足以支撑多项目:Indexing 标签虽然提供了显式的 Global/Project 选择器,却可以把整个indexing对象写入任意一层,导致 provider/model/vector-store 凭据和基础设施设置可能被塞进仓库内的项目配置。而另一些控件(autocomplete UI、browser automation、notifications、max auto-approve cost、commit-message 输出语言、indexing 按钮可见性)则完全绕过 Kilo 配置,属于 VS Code preferences。
确认的阻塞缺陷:读有目录、写无目标
这是当前协议最根本的失效模式——读取按目录限定,写入却没有绑定到读取目标:
KiloProvider.fetchAndSendConfig()解析一个可变的当前目录,发出不带限定信息的configLoaded状态(KiloProvider.ts);- webview 持有唯一的 global/project/effective 草稿;
- Agent Manager 把激活项目从 A 切换到 B;
- webview 发出不带限定信息的
updateConfig; KiloProvider.handleUpdateConfig()在保存时再次解析当前目录,可能把 A 的草稿写入 B(KiloProvider.ts)。
后端同样缺少"预期目标/版本"前置条件,外部编辑器或另一个窗口可以在读取与保存之间覆盖配置。这是多项目发布的 release blocker。
配置所有权策略
设计决策是:保留用户设置与项目设置的有用拆分,但让每次 Settings 读写的目标显式、不可变、带版本,并且完全独立于 Agent Manager 的激活状态。绝不允许"Settings 为项目 A 加载草稿,却从可变的激活项目 B 解析保存目标"。
VS Code preferences(不参与项目配置)
以下项留在 VS Code settings 中:
- 扩展语言与展示偏好;
- autocomplete 的启用、快捷键、provider、model;
- browser automation 的启用、系统 Chrome/headless 模式;
- 通知启用与声音;
- 最大自动批准成本(max auto-approve cost);
- commit-message 输出语言;
- indexing 禁用时的按钮可见性;
- 多项目功能的启用开关。
Kilo user config(个人默认值或安全策略)
以下项属于 User 范围编辑:
- default、small、subagent 模型及变体;
- provider 启用、自定义 provider、凭据与端点;
- 用户/全局 agent 与默认 agent;
- 权限默认值与用户工具默认值;
- sandbox 策略、网络访问、可写路径、允许的主机;
- compaction、checkpoint/snapshot、tool-output 默认值;
- 用户名与展示行为;
- sharing、remote control、telemetry、实验性功能;
- 用户/全局 formatter、LSP、MCP、skills、instructions、commands、workflows;
- indexing 的 provider、model、凭据、向量存储与全局调优默认值。
可信项目可以在运行时覆盖其中许多项,但在 User 范围编辑永远不会写入这些覆盖项。
Kilo project config(仓库行为)
以下项描述仓库行为,属于合法的项目设置:
- commit-message prompt;
- 仓库索引的文件扩展名、include/ignore 规则与有意的项目调优覆盖;
- 仓库 instructions;
- 仓库 skill 路径;
- 仓库 commands/workflows;
- 可信项目 agent;
- 可信项目 MCP servers;
- 仓库 formatter/LSP 覆盖;
- 仓库 watcher ignores;
- 仓库特定的工具限制与权限请求。
项目配置可以覆盖用户层的 model/agent/tool 默认值,但provider 凭据与削弱安全策略的内容绝不能被静默写入仓库。
机器本地的项目索引同意(machine-local consent)
索引启用本质上是隐私同意(consent),不是仓库配置,因此必须存到仓库之外,以规范的ProjectId为键存入机器本地扩展状态:
- 新观察到的项目默认索引禁用;
- 用户在本机针对单个项目显式启用索引;
- 仓库配置无法开启索引;
- 仓库配置只能描述"索引什么",而同意(consent)决定"是否启动索引";
- 规范的项目身份可以防止通过 symlink 或别名路径绕过同意。
有效索引需要同时满足:用户全局索引配置有效 + 该项目的机器本地同意。
设置标签与写入目标映射
规范文档给出了每个 Settings 标签的"正确可编辑目标":
| Settings 标签 | 正确的可编辑目标 |
|---|---|
| Models | 默认 User;显式 Project 范围可覆盖 models/agents |
| Providers | 凭据/端点仅 User;项目提供的条目需标记来源(source-labelled) |
| Agent Behaviour | User 或显式可信 Project 范围 |
| Auto Approve | User Kilo 配置;max cost 仍是 VS Code preference |
| Browser | VS Code preferences |
| Checkpoints | User 默认或显式 Project 覆盖 |
| Display | User 配置 |
| Autocomplete | VS Code preferences |
| Notifications | VS Code preferences |
| Context | User 默认;仓库 watcher/instruction 规则在显式 Project 范围 |
| Commit Message | Prompt 在 Project 范围;语言仍是 VS Code preference |
| Indexing | Provider/model/凭据/存储 在 User;启用走机器本地同意;仓库规则在 Project |
| Experimental | User 配置;多项目还镜像到 VS Code preference |
| Sandboxing | 仅 User 配置 |
| Language | VS Code preference |
| MCP/Commands/Skills | User 默认或显式可信 Project 范围 |
核心原则:任何字段在保存时都不能静默选择文件,UI 必须展示其 scope。
Settings UX:显式范围与项目选择器
UI 采用显式 scope 与项目控件:
Scope: User | Project Project: backend Target: /projects/backend/.kilo/kilo.jsonc- User 范围永远指向用户配置;
- Project 范围要求显式选择可信项目;
- Settings 的项目选择器与 Agent Manager 的激活项目相互独立;
- 打开 Settings 时可以用当前项目初始化选择器一次,但之后 Agent Manager 的切换不会改变它;
- 脏的项目草稿不能迁移到另一个项目:选择器变更必须走 Save、Discard 或 Stay;
- 继承值显示来源徽标(如
User、Project: backend、Managed); - Project 范围提供 Override 与 Reset to inherited;
- 项目来源的 provider 不能被 User 范围静默地从项目配置中删除。
需要特别注意运行时配置与 Settings 目标的分离:runtime config 仍精确跟随会话目录,而 Settings 的 Project 范围指向注册的项目根(registered project root),不是激活的 worktree。worktree 配置编辑属于需要显式WorktreeRef的未来独立功能。
不可变绑定契约(Immutable Binding Contract)
Settings 读取返回一个不透明绑定(opaque binding),扩展端持有其权威副本:
interface SettingsBinding { id: string connectionGeneration: number scope: "global" | "project" project?: { projectId: string root: string generation: number } directory: string target: { scope: "global" | "project" path: string revision: string exists: boolean writable: boolean } }写入只携带该不透明绑定与补丁:
interface WriteSettingsConfig { type: "settingsConfig.write" requestId: string bindingId: string set: Record<string, unknown> unset: string[][] }扩展端在写入时必须:
- 拒绝未知/过期的绑定;
- 校验项目存在性、generation 与信任状态;
- 在第一个 await 之前捕获绑定;
- 使用绑定中存储的 directory 与 scope;
- 绝不调用
getWorkspaceDirectory()、contexts.active()或使用 worktree/session 回退; - 只在匹配的
{ requestId, bindingId }响应中清除草稿。
绑定在保存、重连、信任撤销、项目移除或 context generation 变化后失效。
这套契约在仓库中已经有对应的落地实现:config-bindings.ts 中的ConfigBindings类以Map<string, ConfigBinding>保存绑定,create()会给每个绑定生成randomUUID()作为 id,并在同一 scope+directory 下丢弃被取代的旧绑定(避免只读刷新导致绑定无限增长);get()校验 id、connection代数以及项目合法性(通过validConfigProject回调),consume()在写入成功后删除绑定(一次性语义),clear()用于连接级清理。在 KiloProvider.ts 的handleUpdateConfig中,扩展端正是通过configBindings.get(globalBindingId / projectBindingId, this.connectionGeneration, ...)来拒绝未知或过期绑定,失败时直接回发configUpdateFailed("Settings changed or expired. Reload before saving.")。
后端版本契约(Backend Revision Contract)
GET /config/overlay
必须返回:精确的 global/project 目标路径、解析后的原始目标配置、有效配置/来源元数据,以及一个revision。
revision 是对"规范目标路径 + 存在标记 + 文件精确字节"的 SHA-256 指纹。它能捕获三类变化:
- 内容变化(文件字节不同);
- JSONC 仅注释编辑(字节不同 → revision 变化);
- 目标路径变化(路径参与指纹)。
PATCH /config/overlay
只接受一个 scope,请求体:
{ scope: "global" | "project" set: Record<string, unknown> unset: string[][] expected: { path: string revision: string } }后端必须:重新解析权威目标 → 在目标锁(target lock)下校验 path/revision → 修补原始目标层 → 校验配置 →原子替换文件→ 返回全新快照。后端绝不接受任意的客户端路径。
预期失败类型包括:过期绑定、未知/不可信项目、目标变化、版本冲突、非法配置、目标不可写、I/O 失败——每次失败都必须保留草稿(draft 不丢失)。
源码侧可以验证这套契约已被扩展端采用:handleUpdateConfig对 global/project 分别调用client.config.overlayUpdate(...),并携带directory: globalBinding!.directory与expected: { path, revision },写入成功后再consume()绑定并回发configUpdated(KiloProvider.ts)。
版本冲突的测试证据
config-overlay.test.ts 印证了 revision 契约的关键语义:
- 目标请求会自动回填
expected: { path, revision }(测试夹具层); - 缺失文件具有稳定的 revision:对同一不存在的项目配置连续两次取 target,revision 相同;
- 保存后 revision 必然变化;
- 仅注释的外部编辑会被判为版本冲突:先读取 overlay,让外部把文件改成仅注释不同的内容,再用旧的
expected提交 PATCH,返回code: "revision-conflict"——这正是"外部编辑器覆盖读-写窗口"这一阻塞缺陷的回归防护; - 项目 scope 支持按路径
unset(如[["indexing", "enabled"]]),并验证保存后该字段确实从原始层消失而其余字段(provider、ollama baseUrl)保留。
实施清单:从协议到代码的九项改造
规范文档要求落地以下九项:
- 为 Kilo 配置 overlay API 增加带版本的 target 描述符与 compare-and-swap 写入;
- 把与激活绑定的 runtime config 状态与按绑定键控的 Settings 编辑器状态分离;
- 用携带 request/binding ID 的 settings 读/写消息替换不带限定的
configLoaded/updateConfig; - 用每个可编辑控件上的显式 scope 替换隐藏的
splitConfigByScope保存; - 把 Indexing 的 Project 范围限制为仓库规则;provider/model/凭据/存储留在 User 范围;
- 把索引启用从项目配置迁移为按规范
ProjectId键控的机器本地同意,默认关闭; - 审计保存条之外的直接配置修改者(provider disconnect、imports/resets、自定义 provider、work styles、权限规则、索引操作);
- 让 Open Project Config 接受
ProjectRef,解析不可变的注册根并校验信任; - 按 scope、directory、target、revision 与 activation generation 分区配置缓存/事件。
当前仓库中第 1~3 项的扩展端骨架已经可见:webview 端 config.tsx 维护bindings()(global/project 两个 binding)、globalDraft/projectDraft分区草稿、configBindingExpired处理(项目变化时提示 "Discard or reload before saving")、configUpdateFailed的部分成功处理(按completedScopes保留未完成部分的草稿);saveConfig()已把globalBindingId/projectBindingId随updateConfig消息发送,但仍在使用splitConfigByScope做隐藏切分——这正是实施清单第 4 项要继续消除的部分。config-scope.ts中PROJECT_SCOPED_KEYS目前只有commit_message一个顶层键,也印证了"自动切分过于粗糙"的现状。
阻塞测试清单(Blocking Tests)
多项目配置可以发布的前提,是以下测试全部通过:
- 为 A 加载 Settings,切换到 B,保存:只有 A 绑定的目标发生变化;
- 同一测试在 A/B 的 worktree 与会话选择下成立;
- User 范围保存只改用户配置;
- Project 范围保存要求显式可信项目,且只改其注册根配置;
- 脏草稿在 Agent Manager 切换后存活,且不能迁移到其他 Settings 项目;
- 乱序的读/写只更新匹配的 request/binding;
- 外部文件修改触发版本冲突但不丢失草稿;
- 配置目标路径变化触发目标冲突;
- 项目移除、generation 变化或信任撤销会使绑定过期;
- 表单中的 indexing provider/model/凭据/存储永不进入项目配置;
- 仓库文件中出现
indexing.enabled: true不能授予索引同意; - 新项目默认索引禁用,直到在本机显式启用;
- 同意跟随规范项目身份跨越 symlink/路径别名,且绝不泄漏到另一项目;
commit_message.prompt与仓库索引规则仍支持显式项目写入;- runtime worktree 配置使用 worktree 目录,而 Project Settings 仍绑定注册项目根。
发布门槛(Release Gate)
在多项目默认关闭(disabled by default)之前,必须完成不可变绑定/版本契约与上述阻塞测试。规范同时强调:保留而非移除现有的项目本地行为——只是其写入目标必须变为显式且不可变。这既保护了现有用户既有的<repo>/.kilo/kilo.jsonc工作流,又为 Agent Manager 的多项目切换提供了确定性的读写语义,让"Settings 草稿写错项目"这类数据污染问题从架构上不再可能发生。
小结
Kilo 多项目配置架构的核心,是把 Settings 从"面向当前目录的共享草稿"重构为"绑定目标 + 版本校验的 CAS 写入":所有权上严格区分 VS Code preferences、用户配置、项目配置与机器本地索引同意四层;读写协议上引入不透明绑定与 SHA-256 revision,让扩展端无法在写入时重新解析目标,让后端在目标锁下原子替换文件并拒绝任何版本不一致的写入;测试上以"跨项目切换不串写、外部编辑触发版本冲突、索引同意默认关闭且不可由仓库授予"等阻塞用例锁死行为。这一契约在 .kilo/plans/agent-manager-multi-project-configuration.md 中定义,在 config-bindings.ts、KiloProvider.ts、config.tsx 与 config-overlay.test.ts 中逐步落地,感兴趣的读者可以沿着这条链路继续深入。
【免费下载链接】kilocodeKilo is the all-in-one agentic engineering platform. Build, ship, and iterate faster with the most popular open source coding agent.项目地址: https://gitcode.com/GitHub_Trending/ki/kilocode
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考