资讯动态

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

发布时间:2026/9/13 11:46:29 来源:尧图企业网站定制
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/kilocodeKilo 是一个开源的全栈智能体agentic工程平台其代码库正处于 v2 大规模重构期。specs/v2/instructions.md 是本次 v2 移植工作的核心纲领它定义了把行为从巨型应用服务中剥离、下沉到插件的总体方向并给出了服务容器、插件 Hook、Schema 域模型、状态与事件、代码风格等一整套落地规范。阅读本文后你将掌握 v2 服务的标准形态以Catalog、AccountV2、AgentV2为样板、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。以Catalog、AccountV2、AgentV2为样板一个标准服务模块自上而下依次是模块顶部定义 Schema 与品牌化brandedID用Schema.TaggedErrorClass定义预期失败的类型化错误定义包含小操作集合的Interface暴露Context.Service用私有内存状态实现layer暴露带显式依赖的defaultLayer用export * as Name from ./file自导出。在 packages/core/src/catalog.ts 中可以看到这一模板的完整落地文件首行即export * as Catalog from ./catalog随后定义了ProviderRecord、DefaultModel等类型用Schema.Literals([provider.use])声明策略动作并用export const Event Catalog.Event复用领域事件。Service通过Context.ServiceService, Interface()(opencode/v2/Catalog)声明接口里只有provider与model两组小操作get、all、available、default、small完全符合dumb container的定位。AccountV2packages/core/src/account.ts则展示了 Schema 层的写法ID、OrgID、AccessToken、RefreshToken等全部通过Schema.String.pipe(Schema.brand(...))得到品牌化类型Info、Org、Login用Schema.Class建模预期失败被建模为AccountRepoError、AccountServiceError、AccountTransportError三个TaggedErrorClass并组合成AccountError联合类型。2.1 容器 API 的动词约束v2 容器 API 应当哑优先使用get、all、available、default、update、remove、activate这类小型领域动词而不是透出内部数据结构。两条关键约定是update(id, draft ...)用于注册与变更调用方通过回调拿到可变草稿并就地修改由容器负责持久化与归一化。Catalog的 draft 中provider.update、model.update都遵循这一形态且会在回调之后调用normalizeApi做baseURL→api.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 继承api与requestheaders、body配置并保留模型自身的variant而决定注册哪些 provider是插件拥有的职责——packages/core/src/plugin/internal.ts的启动批次中逐个add(...)了ConfigReferencePlugin、AgentPlugin、CommandPlugin、ModelsDevPlugin、ConfigAgentPlugin、ConfigCommandPlugin、ConfigSkillPlugin、所有ProviderPlugins、ConfigExternalPlugin、ConfigProviderPlugin、VariantPlugin这些注册决策都不属于任何单个核心服务的领域不变量。三、Plugin Hooksv2 的扩展边界插件是 v2 的扩展边界。当某个逻辑应当由集成方而不是容器自身提供时就应把 Hook 加入PluginV2.HookSpec。文档给出了完整的 Hook 约定输入不可变、输出可变Hook 接收不可变输入外加可变输出可变对象输出以 Immer 草稿draft形式暴露插件在草稿上就地修改需要允许插件阻止变更时包含cancel: booleanHook 必须顺序触发保证排序确定性Hook 命名面向领域如provider.update、model.update、account.activate、agent.generateHook 的载荷要小且用核心 Schema 类型化。3.1 应该使用 Hook 的场景文档明确列出五类应该使用 Hook 的场景注册 provider 与模型registering providers and models应用由 env/account/config 推导出的启用状态enablement转换 SDK/provider 选项transforming SDK/provider options实现生成类行为如 agent generation在选择属于策略而非状态时决定默认值。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。当一个新核心服务需要开放给插件使用时文档给出四条步骤把服务加入 boot layer 的依赖类型Requirements见 packages/core/src/plugin/internal.ts目前包含AgentV2、Catalog、CommandV2、Config、EventV2、FileSystem、FSUtil、Global、HttpClient、Integration、Location、ModelsDev、Npm、Reference、SkillV2在 layer 内 yield 出该服务在add中把服务provideService给每个插件 effectpackages/core/src/plugin/internal.ts 中loaded.effect通过Effect.provideService注入了全部服务仅当不会产生循环依赖时才把该服务的 default layer 加入PluginBoot.defaultLayer。文档强调boot 只做组合composition本身不应包含 provider、account、agent 或 model 的策略。在运行时侧packages/core/src/plugin.ts 的PluginV2.Service提供了add、remove、wait三个操作add用KeyedMutex按插件 ID 加锁、在State.batch内关闭旧 Scope、fork 新 Scope 执行插件 effect并发布Event.Added它还内置了加载循环检测Plugin load cycle detected与失败记录wait则允许调用方等待插件完成加载或拿到失败结果。这印证了文档所述服务天然热重载、更新不需要拆除整个进程的设计插件生命周期完全由 Scope 管理替换即关闭旧 Scope 开启新 Scope。五、边界Boundariescore 不反向依赖 opencodev2 的模块边界是硬性的packages/core不得从packages/opencode导入。如果 core 需要某个类型或概念应先在 core 中移动或重塑领域形态避免整包搬运旧服务先移植领域形态与容器 API把具体行为留在 Hook 之后由插件实现移植一个 opencode 服务时的标准步骤是识别它持有的状态 → 识别调用方真正需要的操作 → 识别哪些分支属于策略或集成行为 → 在packages/core中建模状态与操作 → 为策略/集成分支添加 Hook → 在旧包代码保持可用、调用方逐步迁移期间继续工作。也就是说v2 允许新旧并行在调用方完成增量迁移之前旧包代码必须继续工作。六、Schemas And Types以 Effect Schema 作为公开契约v2 把Effect Schema 当作公开契约具体约定包括用品牌化 Schemabranded schemas表达 ID用Schema.Class或Schema.Struct建模领域数据用Schema.TaggedErrorClass建模预期错误在合适处复用 core 已有辅助工具如DeepMutable、statics和整数 Schema。文档还建议优先把Info对象作为存储的领域记录当 update API 需要在首次变更时创建记录时为Info添加静态empty(...)构造器。这一点在源码中得到了严格执行Catalog的provider.update/model.update在记录不存在时分别调用ProviderV2.Info.empty(providerID)与ModelV2.Info.empty(providerID, modelID)惰性创建见 packages/core/src/catalog.tsAgentV2的update同样用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私有状态 已提交事件状态管理遵循三条约定状态对服务 layer 私有持久化或并发需求下使用不可变替换immutable replacement或 Effect refs只为已提交的领域变更发布事件不为尝试中的变更发布事件事件名描述领域事实例如catalog.model.updatedv2 的目标是细粒度重配置一次模型更新应让依赖方只对该模型更新做出反应而不是触发全局重载。实现层面packages/core/src/state.ts 提供了State.create初始状态 草稿工厂 finalize 回调、State.batch将多个 transform 的 reload 批量合并执行见 packages/core/src/state.ts以及Transformable接口transformreload。Catalog的finalize会在策略存在时对所有 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.get、Plugin.add、Plugin.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),仅供参考

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价