news 2026/9/13 11:46:18

Kilo v2 核心架构指南:插件化服务容器、Hook 契约与 Effect Schema 域模型设计

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Kilo v2 核心架构指南:插件化服务容器、Hook 契约与 Effect Schema 域模型设计

Kilo v2 核心架构指南:插件化服务容器、Hook 契约与 Effect Schema 域模型设计

【免费下载链接】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

Kilo 是一个开源的全栈智能体(agentic)工程平台,其代码库正处于 v2 大规模重构期。specs/v2/instructions.md 是本次 v2 移植工作的核心纲领:它定义了"把行为从巨型应用服务中剥离、下沉到插件"的总体方向,并给出了服务容器、插件 Hook、Schema 域模型、状态与事件、代码风格等一整套落地规范。阅读本文后,你将掌握 v2 服务的标准形态(以CatalogAccountV2AgentV2为样板)、Hook 的触发约定与适用边界、插件启动(boot)的组装方式,以及如何在packages/core中用 Effect Schema 建模领域、用 Immer 草稿暴露可变状态。

一、v2 的总体方向:瘦身核心,插件承载策略

v2 重构的第一条原则是把行为从大型应用服务中移出,下沉到插件中。核心服务应当变成"小而类型化的容器":自己持有状态、暴露简单操作、在合适的位置触发 Hook,把策略(policy)与集成(integration)相关的逻辑留给插件实现。文档给出的目标形态是:

  • packages/core只承载领域 Schema、类型化错误、状态容器、事件与插件 Hook 契约;
  • 插件负责实现 provider 特定、config 特定、auth 特定、模型发现(model-discovery)与生成(generation)等行为;
  • 服务在设计中天然支持热重载(hot-reload):更新是细粒度的、可观测的,不需要拆除整个进程;
  • packages/opencode会逐渐变薄:UI、服务端路由、CLI、存储胶水与旧版兼容代码应调用核心服务,而不是自己持有领域逻辑。

换句话说,v2 的目标不是"在新包里复刻旧架构",而是"让服务更容易被替换、更容易被理解"。文档明确要求:不要整包搬运旧服务,应当先移植领域形态与容器 API,再把具体行为留在 Hook 之后交给插件实现。

二、Service Shape:以 Catalog、AccountV2、AgentV2 为样板的标准服务形态

文档要求 v2 核心服务遵循统一的"Service Shape"。以CatalogAccountV2AgentV2为样板,一个标准服务模块自上而下依次是:

  1. 模块顶部定义 Schema 与品牌化(branded)ID;
  2. Schema.TaggedErrorClass定义预期失败的类型化错误;
  3. 定义包含小操作集合的Interface
  4. 暴露Context.Service
  5. 用私有内存状态实现layer
  6. 暴露带显式依赖的defaultLayer
  7. export * as Name from "./file"自导出。

在 packages/core/src/catalog.ts 中可以看到这一模板的完整落地:文件首行即export * as Catalog from "./catalog",随后定义了ProviderRecordDefaultModel等类型,用Schema.Literals(["provider.use"])声明策略动作,并用export const Event = Catalog.Event复用领域事件。Service通过Context.Service<Service, Interface>()("@opencode/v2/Catalog")声明,接口里只有providermodel两组小操作(getallavailabledefaultsmall),完全符合"dumb container"的定位。

AccountV2(packages/core/src/account.ts)则展示了 Schema 层的写法:IDOrgIDAccessTokenRefreshToken等全部通过Schema.String.pipe(Schema.brand(...))得到品牌化类型;InfoOrgLoginSchema.Class建模;预期失败被建模为AccountRepoErrorAccountServiceErrorAccountTransportError三个TaggedErrorClass,并组合成AccountError联合类型。

2.1 容器 API 的动词约束

v2 容器 API 应当"哑":优先使用getallavailabledefaultupdateremoveactivate这类小型领域动词,而不是透出内部数据结构。两条关键约定是:

  • update(id, draft => ...)用于注册与变更:调用方通过回调拿到可变草稿并就地修改,由容器负责持久化与归一化。Catalog的 draft 中provider.updatemodel.update都遵循这一形态,且会在回调之后调用normalizeApibaseURLapi.url的字段归一化(见 packages/core/src/catalog.ts)。
  • 提交变更前触发 Hook,提交后发布事件:Hook 用于让插件有机会丰富(enrich)、取消(cancel)或校验(validate)变更;事件则用于其他服务或前端对已提交的领域事实做出反应。

2.2 策略的归属边界

文档给出一条重要边界:不要把应用策略直接写进核心服务,除非它是领域不变量(domain invariant)。例如:

  • 解析模型端点的继承关系(endpoint inheritance)是Catalog拥有的职责——这正是 packages/core/src/catalog.ts 中projectModel在做的事:模型未显式声明 API 时,从 provider 继承apirequest(headers、body)配置,并保留模型自身的variant
  • 而"决定注册哪些 provider"是插件拥有的职责——packages/core/src/plugin/internal.ts的启动批次中逐个add(...)ConfigReferencePluginAgentPluginCommandPluginModelsDevPluginConfigAgentPluginConfigCommandPluginConfigSkillPlugin、所有ProviderPluginsConfigExternalPluginConfigProviderPluginVariantPlugin,这些注册决策都不属于任何单个核心服务的领域不变量。

三、Plugin Hooks:v2 的扩展边界

插件是 v2 的扩展边界。当某个逻辑应当由集成方而不是容器自身提供时,就应把 Hook 加入PluginV2.HookSpec。文档给出了完整的 Hook 约定:

  • 输入不可变、输出可变:Hook 接收不可变输入,外加可变输出;
  • 可变对象输出以 Immer 草稿(draft)形式暴露,插件在草稿上就地修改;
  • 需要允许插件阻止变更时,包含cancel: boolean
  • Hook 必须顺序触发,保证排序确定性;
  • Hook 命名面向领域,如provider.updatemodel.updateaccount.activateagent.generate
  • Hook 的载荷要小且用核心 Schema 类型化

3.1 应该使用 Hook 的场景

文档明确列出五类应该使用 Hook 的场景:

  1. 注册 provider 与模型(registering providers and models);
  2. 应用由 env/account/config 推导出的启用状态(enablement);
  3. 转换 SDK/provider 选项(transforming SDK/provider options);
  4. 实现生成类行为,如 agent generation;
  5. 在"选择属于策略而非状态"时决定默认值。

3.2 不应该使用 Hook 的场景

文档同时划出红线:不要用 Hook 作为传输层关注点(transport concerns)、UI 行为或兼容性垫片(compatibility shims)的垃圾场。也就是说,Hook 是领域扩展点,不是万能后门。

从源码看,Hook 的触发被设计成"可重放(replayable)的变换":State.transform接受一个对草稿的回调,该回调可以在 reload 时被重新执行(见 packages/core/src/state.ts 中对"replayable transform"的注释)。插件对草稿的修改因而具备幂等、可重放的性质,这正是细粒度重配置(granular reconfiguration)的基础。

四、Plugin Boot:组合优先的启动组装

内置核心插件由启动模块负责注册。文档指向的路径为packages/core/src/plugin/boot.ts;在本次检查的仓库结构中,该职责实际由 packages/core/src/plugin/internal.ts 承载(模块自导出为PluginInternal,其 layer 在State.batch中批量注册内置插件,并打上PluginInternal.bootspan)。

当一个新核心服务需要开放给插件使用时,文档给出四条步骤:

  1. 把服务加入 boot layer 的依赖类型(Requirements,见 packages/core/src/plugin/internal.ts:目前包含AgentV2CatalogCommandV2ConfigEventV2FileSystemFSUtilGlobalHttpClientIntegrationLocationModelsDevNpmReferenceSkillV2);
  2. 在 layer 内 yield 出该服务;
  3. add中把服务provideService给每个插件 effect(packages/core/src/plugin/internal.ts 中loaded.effect通过Effect.provideService注入了全部服务);
  4. 仅当不会产生循环依赖时,才把该服务的 default layer 加入PluginBoot.defaultLayer

文档强调:boot 只做组合(composition),本身不应包含 provider、account、agent 或 model 的策略。

在运行时侧,packages/core/src/plugin.ts 的PluginV2.Service提供了addremovewait三个操作:addKeyedMutex按插件 ID 加锁、在State.batch内关闭旧 Scope、fork 新 Scope 执行插件 effect,并发布Event.Added;它还内置了加载循环检测("Plugin load cycle detected")与失败记录,wait则允许调用方等待插件完成加载或拿到失败结果。这印证了文档所述"服务天然热重载、更新不需要拆除整个进程"的设计:插件生命周期完全由 Scope 管理,替换即关闭旧 Scope + 开启新 Scope。

五、边界(Boundaries):core 不反向依赖 opencode

v2 的模块边界是硬性的:

  • packages/core不得从packages/opencode导入。如果 core 需要某个类型或概念,应先在 core 中移动或重塑领域形态;
  • 避免整包搬运旧服务:先移植领域形态与容器 API,把具体行为留在 Hook 之后由插件实现;
  • 移植一个 opencode 服务时的标准步骤是:识别它持有的状态 → 识别调用方真正需要的操作 → 识别哪些分支属于策略或集成行为 → 在packages/core中建模状态与操作 → 为策略/集成分支添加 Hook → 在旧包代码保持可用、调用方逐步迁移期间继续工作。

也就是说,v2 允许新旧并行:在调用方完成增量迁移之前,旧包代码必须继续工作。

六、Schemas And Types:以 Effect Schema 作为公开契约

v2 把Effect Schema 当作公开契约,具体约定包括:

  • 用品牌化 Schema(branded schemas)表达 ID;
  • Schema.ClassSchema.Struct建模领域数据;
  • Schema.TaggedErrorClass建模预期错误;
  • 在合适处复用 core 已有辅助工具,如DeepMutablestatics和整数 Schema。

文档还建议:优先把Info对象作为存储的领域记录;当 update API 需要在首次变更时创建记录时,为Info添加静态empty(...)构造器。这一点在源码中得到了严格执行:

  • Catalogprovider.update/model.update在记录不存在时分别调用ProviderV2.Info.empty(providerID)ModelV2.Info.empty(providerID, modelID)惰性创建(见 packages/core/src/catalog.ts);
  • AgentV2update同样用Info.empty(id)兜底(见 packages/core/src/agent.ts)。

此外,Schema 要保持稳定和显式:不要拿 opencode 的 config 形态当 core 领域形态,除非该 config 形态本身就是领域模型。领域 ID 的 Schema 定义在共享的 schema 包中(如 packages/schema/src/agent.ts 的Schema.String.pipe(Schema.brand("AgentV2.ID"))),core 侧通过export const ID = Agent.ID复用。

七、State And Events:私有状态 + 已提交事件

状态管理遵循三条约定:

  1. 状态对服务 layer 私有:持久化或并发需求下使用不可变替换(immutable replacement)或 Effect refs;
  2. 只为已提交的领域变更发布事件,不为"尝试中的变更"发布事件:事件名描述领域事实,例如catalog.model.updated
  3. v2 的目标是细粒度重配置:一次模型更新应让依赖方只对该模型更新做出反应,而不是触发全局重载。

实现层面,packages/core/src/state.ts 提供了State.create(初始状态 + 草稿工厂 + finalize 回调)、State.batch(将多个 transform 的 reload 批量合并执行,见 packages/core/src/state.ts)以及Transformable接口(transform+reload)。Catalogfinalize会在策略存在时对所有 provider 执行policy.evaluate("provider.use", ...),移除被拒绝的 provider,然后发布Event.Updated(见 packages/core/src/catalog.ts)——这正是"Hook 先于提交、事件后于提交"的实例:策略裁决发生在 finalize 阶段,事件只在最终提交后发出。

八、Style:核心代码风格清单

文档给出了可逐条对照的本地风格要求:

  • 组合用Effect.gen(function* () { ... })
  • 公开服务方法用Effect.fn("Domain.method"),便于可观测性打点(如CatalogV2.provider.getPlugin.addPlugin.load);
  • 小型内部变更辅助函数用Effect.fnUntraced
  • 类型化失败用yield* new ErrorClass(...)抛出;
  • 除非辅助函数真的命名了一个概念,否则保持最少;
  • 除非现有插件边界确实需要,否则禁止any
  • 没有具体持久化或外部消费需求时,不写兼容代码

整体原则是"最小的正确移植(the smallest correct port)":目标是让服务更容易被替换、更容易被推理,而不是在新包里重建旧架构。

九、小结:从指令到代码的落地闭环

specs/v2/instructions.md虽然以"移植工作笔记"的形式存在,但它实际上定义了 Kilo v2 整个核心包的架构契约:

关注点规范要求仓库落地示例
服务形态Schema + 类型化错误 + Interface + Service + layer + 自导出catalog.ts、account.ts、agent.ts
容器 API小领域动词、update(id, draft => ...)、先 Hook 后事件catalog.ts
插件 Hook输入不可变、输出为 Immer 草稿、顺序触发、领域命名state.ts、plugin.ts
插件启动纯组合、服务注入、防循环plugin/internal.ts
模块边界core 不反向导入 opencode、逐步增量迁移plugin/internal.ts 依赖清单
SchemaEffect Schema 为公开契约、branded ID、Info.empty(...)packages/schema/src/agent.ts、catalog.ts
状态与事件状态私有、事件只发已提交事实、细粒度重配置state.ts、catalog.ts
代码风格Effect.gen/Effect.fn/TaggedErrorClass、禁any上述全部源码文件

对于希望参与 Kilo v2 开发的工程师,这份文档就是"如何为 core 新增一个服务、如何把一个旧 opencode 服务移植成 v2 形态"的操作手册:先照 Service Shape 搭出容器,把策略留在 Hook 之后,把启动交给 boot 层组合,剩下的交给 Effect Schema 与细粒度事件去保证类型安全与可观测性。你可以在 specs/v2/instructions.md 阅读原始指令全文,并对照上述源码文件逐条验证实现。

【免费下载链接】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),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 11:44:42

机场出租车调度建模:SimPy仿真与多目标优化实战

简介&#xff1a;本资源是2022年第十二届MathorCup高校数学建模挑战赛D题的完整解题方案&#xff0c;面向数学建模初学者、竞赛备赛学生及指导教师&#xff0c;聚焦弱覆盖区域基站优化这一典型通信建模问题。压缩包共24个文件&#xff0c;含9个Python脚本&#xff08;如kmeans.…

作者头像 李华
网站建设 2026/9/13 11:42:34

模糊小波神经网络在机器人实时威胁评估中的工程实现

简介&#xff1a;本资源是面向智能控制与机器人竞赛领域的工程实践项目&#xff0c;聚焦模糊小波神经网络&#xff08;FWNN&#xff09;在目标威胁评估中的Matlab实现&#xff0c;特别适配RoboMaster等实时对抗类机器人系统的攻击优先级决策需求。资源提供完整可运行的算法框架…

作者头像 李华
网站建设 2026/9/13 11:41:19

SSD1306 OLED驱动开发:STM32工程与I2C时序解析

简介&#xff1a;一份围绕STM32F103C8T6微控制器的OLED显示屏驱动程序资源&#xff0c;面向嵌入式开发、物联网及智能硬件爱好者&#xff0c;帮助解决OLED屏与STM32之间的接口驱动与显示控制问题。资源包共134个文件&#xff0c;包含C源文件与H头文件、Keil工程配置、编译生成的…

作者头像 李华