DeepCode Skills 产品架构解析:从 Skill 目录发现到 Provider 边界与单轮调用语义

发布时间:2026/9/14 8:02:12
DeepCode Skills 产品架构解析:从 Skill 目录发现到 Provider 边界与单轮调用语义 DeepCode Skills 产品架构解析从 Skill 目录发现到 Provider 边界与单轮调用语义【免费下载链接】DeepCodeDeepCode: Open Agentic Coding (Agent Harness Loop Engineering Multi-Agent Orchestration)项目地址: https://gitcode.com/GitHub_Trending/deepc/DeepCode导读本文以仓库根目录下的 docs/SKILLS_PRODUCT_ARCHITECTURE.md 为骨架系统拆解 DeepCode 的 Skills技能产品架构它如何用统一的目录约定承载可复用工作流、如何以 Provider 边界隔离列表/读取/搜索三类操作、如何保证一次 Turn 内不可变快照与权限收窄的调用语义以及 Desktop、交互式 CLI 与无头 CLI 如何共享同一套目录、解析器与运行时。读完本文你将掌握 DeepCode 中 Skill 的目录布局与发现优先级、SKILL.md与agents/openai.yaml的完整编写契约、$name/ 结构化SkillSelection/skill工具三种调用方式及其预算规则以及对应的资源与安全限制并能在自己的项目或用户目录中落地一套可验证的 Skill 工作流。一、范围与产品不变量什么算 Skill什么不算该文档首先划定了本架构的边界范围是 Skills、本地 Agent Plugins 1.0 包以及它们的 MCP 组件明确不在范围内的是 Marketplace、远程分发、Hooks、Apps 以及任意 Plugin 生命周期代码。这意味着本文讨论的所有机制——目录发现、Provider 边界、单轮快照、权限收窄——都服务于本地可复用的 Agent 工作流指令而不是一个可插拔的远程插件分发系统。架构开篇给出了六条产品不变量它们是后续所有设计决策的锚点三端共享同一套运行时Desktop、交互式 CLI、无头 CLI 使用同一个 Skill catalog、resolver 与 runtime。前端只负责渲染选择结果永远不直接读取或注入SKILL.md。这一点在 core/harness/tools/init.py 中有印证当注入skill_runtime时统一注册SkillTool静态的SkillRegistry仅作为兼容输入。向后兼容现有的UserInput(text...)、Agent loop、规范SessionStore与跨目录 Session 发现保持兼容旧客户端、旧 Session 回归测试是发布门禁之一。Skill 不授予权限一个 Skill 永远不会授予文件系统、进程、网络或工具权限其allowed-tools声明只能收窄Session 策略已经允许的工具集合。项目 Skills 是工作树的代码项目未被信任时Desktop 只能暴露其元数据不能暴露指令或执行 Turn而显式指定工作区启动 CLI 本身就是信任授予。用户别名可以指向插件/缓存位置项目别名只能指向受信任工作区内的路径。运行中的 Turn 消费不可变快照文件系统或配置变化只影响下一个 Turn。历史 Skill 引用只是审计数据恢复一个 Session 永远不会重新调用历史 Skill。二、身份与修订SkillKey、skillId与revision每个 Skill 的身份由SkillKey唯一确定包含四个分量见 core/skills/models.py 中的SkillKey实现catalog_skill_path解析符号链接目标之前的 catalog 条目绝对路径仅被哈希进 ID永不作为协议身份暴露——这让有意的别名可以独立寻址scopeproject、user或只读的systemsource_root规范agents、兼容deepcode/claude或捆绑systemrelative_pathSkill 目录在其配置根目录之下的规范化相对路径。后端从 key 推导出不透明的skillId。SkillKey.id的实际算法是把deepcode-skill-v1、scope、source_root、skill_file、relative_path 用\0连接后取 SHA-256再截取前 24 个十六进制字符得到sk_...形式models.py。因此协议客户端绝不能把绝对路径当作 Skill 身份提交结构化选择只接受sk_形式的 IDSkillSelection.__post_init__用^sk_[0-9a-f]{24}$校验见 models.py移动项目或 Skill 会产生新身份原地编辑则保留身份、只改变 SHA-256revision。目录中的所有有效候选都会被 catalog 保留发现优先级固定为见 core/skills/roots.py 的discover_skill_roots项目.agents/skills——从工作目录向上直到注册的工作区边界每一层目录都是一个候选根项目.deepcode/skills与.claude/skills兼容根用户~/.agents/skills用户~/.deepcode/skills与~/.claude/skills兼容根捆绑的 system Skillscore/skills/builtin/如 skill-creator。新导入和 creator 输出只使用.agents/skills兼容根永远不会被自动迁移、覆盖或删除以保证既有 ID 与 Session 始终有效。同名候选的处理规则隐式名字查找只激活第一个 enabled 候选其余标记为shadowedshadow 状态记录在SkillStatus.SHADOWED见 models.py影子归属写入shadowed_by按skillId的结构化选择无歧义而一个裸$name若在可选条目间有歧义会在模型执行前失败而不是猜测。三、Provider 边界list / read / search 三操作契约SkillRuntime依赖一个SkillProvider协议只包含三个操作见 core/skills/provider.pylist返回用于一次 Turn 的不可变 catalog 快照read通过列出该包的 Provider 解析一个不透明包资源search执行有界的 catalog 或包资源搜索。首批实现是LocalSkillProvidercore/skills/catalog.py它拥有项目、用户、兼容与捆绑 system 根并保留既有发现顺序、文件系统缓存、策略分层、ID、修订、状态与警告旧的SkillCatalogProvider名称保留为兼容别名catalog.py。架构上有两条硬性约束运行时代码不得把 Provider 记录转成环境本地路径后再绕过 Provider 去 read/search——这保证了将来加入远程 Provider、联邦或市场时源码边界依然可用每次 catalog 记录都携带一个SkillReference由三部分组成authorityProvider 类型 不透明 Provider ID、package不透明包 ID、resourceProvider 拥有的不透明资源 ID。本地 authority 是local:local包仍是既有不透明skillId主资源是SKILL.md——引入 Provider 身份不改变既有 Skill ID、修订、优先级或来源标签。SkillProviderSource声明某个 Provider 拥有哪些 authoritySkillProviders只把read/search路由给拥有该 authority 的 source拒绝未拥有 authority 的 catalog 条目拒绝重复包身份并校验返回资源仍属于请求的 authority/package/resourceprovider.py。从源码看SkillProviders.list会逐 source 收集 catalog若某个 Provider 显式报告临时不可用SkillProviderUnavailableError会变成一条有界警告而其余 catalog 继续校验、所有权与策略错误绝不吞掉。配置的 Provider catalog 按 source 顺序合并。跨 Provider 时第一个 active 的同名包胜出其余候选保持shadowed可见。跨 authority 碰撞的skillId在 catalog 中可见但不能按 ID 选择——因为SkillSelection为保持向后兼容故意不携带 Provider authority。四、宿主生命周期与变更传播Host、Workspace Registry 与 MonitorSkillCatalogHost是一个规范工作区在应用生命周期内的属主core/skills/host.py持有分层的SkillPolicyStore、Provider 路由器和 Provider 发现缓存。SkillWorkspaceRegistry负责路径规范化保证 Application 服务、交互式 CLI Session、无头 Turn 与 App Server 对同一工作区拿到同一个 host。关键隔离点AgentSession之间永不共享可变 Turn 上下文。注册表为每个 Session 在 host 的共享 Provider 后端之上创建一个独立的SkillRuntimenew_runtime()见 host.pybegin_turn仍然把不可变的 catalog 与指令快照放进 runtime 本地的ContextVarcore/skills/runtime.py。因此替换 Provider、编辑 Skill 或改策略都只影响下一个 Turn。host 有两类源core runtime 拥有的 base sources当前即本地 Provider与扩展系统独立拥有的 keyed contributors。register_contributor给每个属主一个生命周期句柄刷新或注销某个 contributor 时只重算组合源集合不会覆盖另一个属主的 Providerhost.py。第一个生产级 contributorLocalPluginHostLocalPluginHostcore/plugins/host.py读取用户拥有的注册表把 Agent Plugins 1.0.0 包解析为格式中立元数据与组件模型并为每个启用的 Plugin 安装贡献一个绑定 authority 的源。要点Plugin 发现是惰性的绝不 import Plugin 代码、绝不启动进程Agent Plugins 1.0 的固定mcp.json组件在发现期做 schema 校验只在 Agent Session 组装时转换为不可变 server 贡献传输启动、工具发布、超时、取消与关闭由通用 MCP 运行时而非 Plugin host负责注册表把安装身份与包元数据分离每条记录有plg_...ID、声明包名、启用状态与类型化链接目录源Skill authority 使用安装 ID因此包改名不能静默复用旧 Provider 身份禁用/移除 Plugin 只替换下一个 Turn 的源活动 Turn 保留其 catalog 与 provider 路由快照移除注册从不删除Plugin 源目录。SkillCatalogMonitor可移植的轮询式变更检测SkillCatalogMonitorcore/skills/monitor.py是每个注册工作区共用的一个监视器线程默认间隔 0.5 秒。它从可变 Provider 轮询有界、可等值比较的 change token本地 token 覆盖每个可发现的SKILL.md、可选 OpenAI 元数据以及有效的用户/项目策略token 实际由文件 mtimesize 指纹与策略层构成见 catalog.py 的skill_change_token。token 变化会使共享 host 失效并发布一条工作区事件。这套设计避免打包 sidecar 对平台原生 watcher 的依赖同时保留测试中的确定性手动轮询poll_once对契约测试公开。自带 push 通道的 Provider 可以省略 token改由 contributor 属主调用注册表的显式invalidate边界。最后SkillService把变化的工作区映射回注册的 Project IDApp Server 发出不含 body 的skills.changed通知只含 Project IDapp_server/protocol/notifications.py。Desktop 保留一个 Composer 与 Skills 工作区共享的运行时 Catalog Store对并发加载去重收到通知后强制刷新该项目当 Skill 修订消失或变化时详情视图被丢弃。五、Catalog、内容与包资源渐进式披露Catalog 记录包含身份、描述、策略、依赖元数据、状态、字节数与修订但永不保留SKILL.md正文SkillRecord.instructions在 catalog 阶段恒为None见 models.py。选择与渐进式披露会向列出该包的同一个 Provider发起read返回的包修订必须与 catalog 修订一致正文才能进入 Turn 快照——list 与 read 之间若正文变化则直接失败关闭绝不混合修订_load_entry中result.package_revision ! entry.revision即抛SkillResolutionError见 runtime.py。SkillReadResult携带文本、包修订、资源修订与精确的SkillReferenceprovider.pySkillSearchMatch携带精确引用加上有界的标题与片段provider.pyProvider 响应在共享契约层做大小检查自定义 Provider 也无法绕过本地限制SkillReadResult.__post_init__限制 1 MiB见 provider.py。skill工具接受一个 Skill 名字可选resource或query二选一见 core/harness/skills.py 的SkillTool两者都不传时加载主指令resource读取一个不透明资源query在所选包内搜索。调用者永不把远端资源 ID 转成本地文件系统路径。本地 Provider 只接受相对 POSIX 路径拒绝穿越与符号链接逃逸_normalize_resource拒绝绝对路径与.._resource_path解析后强制path.relative_to(root)与信任边界校验见 core/skills/resources.py只读 UTF-8 文本且受固定字节上限约束搜索只扫确定数量的文件。六、依赖与能力dependencies.tools与dependencies.skills可选的agents/openai.yaml元数据可声明dependencies.tools与dependencies.skills。工具依赖记录保留 Codex 兼容的type、value、description、transport、command、url字段SkillToolDependency见 models.py。DeepCode 当前解析tool、Codex 兼容的mcp/cli与command别名不支持的 type 会被显式拦截而不是猜测_tool_issue中落入else分支返回unsupported问题见 core/skills/capabilities.py。Turn 开始时AgentSession提供实际注册的工具名。SkillCapabilityResolvercapabilities.py报告三种状态ready、unavailable、blocked校验allowed-tools一致性按拓扑序展开 Skill 依赖、检测环并在第一次模型请求前失败。依赖 Skill 与所选 Skill 共用同样的 Provider read、修订校验、数量预算、字符预算、调用台账与权限收窄。依赖绝不注册工具、不绕过 Session 权限、不把资源变成可执行的环境路径。七、调用语义结构化选择、$name与skill工具的统一预算一次显式选择以结构化SkillSelection提交turn/start与turn/enqueue新增可选skills数组旧客户端仍然有效。后端对照 Session 的不可变 catalog 快照解析并在第一次模型请求前注入精确指令。提示词分层core/skills/prompting.py基础 DeepCode prompt、权限策略与安全边界保持为system 指令Skill catalog 元数据是developer 优先级的能力引导选中的 Skill 正文是当前 Turn 的瞬态 user 优先级上下文。runner 把 privileged 引导折叠进 Provider 的单一指令块把选中 Skill 上下文重新附加到每个任务请求两者都不进入 Session 历史与压缩摘要。这一设计让 OpenAI、Anthropic 与兼容 Provider 获得一致的角色模型同时不提升用户编写的 Skill 正文的权限。裸$name提及只是兼容路径在core runtime解析绝不在前端且仅当名字无歧义时才变成显式选择可见的用户消息不被改写text_mentions的正则(?![\w$])\$([^\s$.,;:!?()\[\]{}])见 catalog.py。模型仍可走渐进式披露的skill工具做隐式选择。每 Turn 的调用台账SkillTurnContext.loaded保证同一 Skill 修订最多注入一次——无论它是先被显式选中还是后来被模型请求。显式、$name与渐进式披露三类加载共享一个 Turn 预算。多个 Skill保留用户选择顺序按(authority, package)去重每 Turn 最多加载 8 个 SkillMAX_SKILLS_PER_TURN 8见 runtime.py共享有界的指令预算MAX_INJECTED_SKILL_CHARS 48_000见 runtime.py若所选 Skill 被禁用、缺失、无效、有歧义或超预算在模型执行前失败。Skill 指令是上下文工作流引导优先级低于系统、安全、沙箱、审批与显式用户要求——这与第一节的产品不变量 3 一脉相承。注入文本的实际格式在SkillTurnSnapshot.injected_instructions中生成models.py明确声明不覆盖系统、安全、沙箱、审批、hook 或显式用户约束并要求仓库操作保持在environment_contextcwd内、只针对 Skill 目录解析 Skill 相对文件。八、编写契约SKILL.md与可选agents/openai.yamlSKILL.md 的必需结构LocalSkillProvider的解析read_skill_candidate/_parse_text见 catalog.py要求每个 Skill 目录直接包含一个SKILL.md且必须以 YAML frontmatter 开头frontmatter 之后必须有非空指令正文。DeepCode 兼容配置文件的 frontmatter 字段字段说明name1–80 字符首字符必须为字母或数字只允许字母、数字、.、_、-缺省回退为目录名description必填非空最多 1000 字符会进入模型目录摘要allowed-tools/allowed_tools可选字符串 CSV 或列表最多 128 项每项不含空白allowed-tools的作用是收窄而非授权运行时把声明工具小写化后与 Session 已允许工具求交集allowed_tool_names还强制保留skill工具本身可用见 runtime.py。此外Skill 解析过程会做路径与字节级校验SKILL.md超过 64 KiB 直接判INVALID解析失败不会让进程崩溃而是产生一条带error的 invalid 记录进入 catalogcatalog.py。可选的 agents/openai.yamlagents/openai.yaml是可选元数据解析见 core/skills/metadata.py支持的 interface 字段interface.display_name≤120 字符interface.short_description≤300 字符优先于 SKILL.md 描述进入模型目录相对图标路径interface.icon_small/interface.icon_large必须解析到 Skill 目录内且为文件interface.brand_color#RRGGBB格式自动大写interface.default_prompt≤2000 字符策略与依赖字段policy.allow_implicit_invocation默认true设为false会把该 Skill 从模型驱动的发现中移除同时保留结构化与$name选择SkillCatalog.implicit()过滤见 catalog.pydependencies.tools与dependencies.skills声明能力需求与可组合的 Skill 前置依赖两者合计最多 64 条MAX_METADATA_DEPENDENCIES见 metadata.py。无效的可选元数据只会产生有界 catalog 警告绝不使一个本已合法的SKILL.md失效。一个最小可运行的 Skill 目录结构示例my-project/ └── .agents/ └── skills/ └── security-review/ ├── SKILL.md # 必需frontmatter 指令正文 ├── agents/ │ └── openai.yaml # 可选界面/策略/依赖元数据 └── checklist.md # 可选通过 skill 工具按 resource 读取九、配置用户与项目层的skills.disabled用户与项目deepcode_config.json都可包含{ skills: { disabled: [sk_...] } }有效禁用 ID 是用户层与项目层的并集SkillPolicyStore.effective_disabled合并两个ConfigStore见 core/skills/config.py项目不能重新启用被用户禁用的 Skill。配置写入经过校验、加锁、原子替换且保留无关配置set_enabled通过store.mutate实现见 config.py。system 作用域没有可变策略存储调用会直接报错config.py。catalog 中发现被禁 ID 的记录会被标记为DISABLED且policy_enabledFalsecatalog.py。十、协议与持久化方法清单与消息元数据turn/start与turn/enqueue保留prompt并新增可选skills数组旧客户端不受影响。Skill 管理方法在 app_server/protocol/methods.py 中定义对应SkillServicecore/application/skill_service.py的list/read/select/set_enabled/import_directory/deleteskills/list—— 返回目录快照force可强制重建skill/read—— 读取单个 Skill 详情解析到具体记录不注入正文到 Sessionskills/import—— 导入目录见第十一节限制skills/set-enabled—— 写入用户或项目策略skills/delete—— 删除可写本地 Skillskills/reload—— 强制刷新目录。Catalog 响应还暴露 Provider 中立的表现层与能力字段originKind、originLabel、location展示用、providerKind、providerId、packageId不透明属主、configurableScopes前端不再猜测策略可否修改、authoringSkillId由后端专属的 bundled-role 注册表解析。前端不得从路径、source 字符串或 Skill 名字推断这些值。skills.changed是瞬态失效通知不是持久 Session 历史也从不包含指令。用户 Session 消息保持普通文本新客户端追加版本化元数据{ schemaVersion: 2, client: desktop, turnId: turn_..., skillInvocations: [ { skillId: sk_..., name: security-review, revision: sha256:..., source: project:deepcode, invocation: explicit } ] }这个 JSON 与SkillInvocation.to_metadata()models.py的输出一一对应invocation取值explicit/text_mention/implicit/dependency。旧读者忽略该元数据新读者把缺失元数据当作旧版纯文本消息。运行时生命周期事件从不包含 Skill 正文skill.loaded与skill.load_failed。日志与遥测可以包含不透明 ID、来源标签、修订哈希与错误类但绝不包含 Skill 指令或用户提示。十一、资源与安全限制总表以下限制全部可以在 catalog.py、resources.py、provider.py、metadata.py 中找到对应常量限制项数值单个SKILL.md64 KiB UTF-8 上限单个 catalog 响应/运行时快照至多 256 条超限显式截断并警告模型发现目录已知上下文窗口的至多 2%或窗口未知时 8000 字符先缩短描述再省略条目单 Turn 加载 Skill 数至多 8 个所有调用路径合计注入 Skill 指令至多 48,000 字符运行时拥有模型 token 估算器之前catalog 警告每个响应至多 100 条导入目录一个 Skill、无符号链接、无显式请求不覆盖、原子替换目标可选元数据32 KiB 上限资源必须是目录内相对路径可选依赖工具与 Skill 合计至多 64 条单个 Provider 资源响应1 MiB UTF-8 上限本地资源搜索至多 256 个文件、每文件 256 KiB、每请求 100 个匹配安装方式本版本无 Git 或网络安装导入的安全语义在SkillService.import_directory中落实仅允许导入单个 Skill 目录、拒绝符号链接、默认不覆盖、目标原子替换。这些限制与第八节的Skill 不授予权限不变量共同构成纵深防御即便 Skill 正文来自项目工作树也无法逃逸信任边界或扩大会话能力。十二、发布门禁怎样才算完整该架构把feature 完成定义为一组可验证的行为门禁全部可从当前源码状态回归验证Desktop 与 CLI 对同一工作区返回一致的 ID 与修订同一进程内所有界面都解析到同一 workspace host同时各AgentSession的 Turn 上下文保持隔离外部 Skill 或策略变化使 host 失效、发出skills.changed并在下一 Turn 可见而不改变当前 TurnDesktop Composer 与管理视图共享一份去重后的 catalog 状态一个假造的 contributed Provider 能走公开 source seam 工作不需要本地路径访问或前端特判结构化与无歧义$name调用解析到同一快照显式指令在第一次模型 Provider 调用前就已就位catalog 列表不保留任何指令正文每次内容读取都停留在列出该包的 Provider 上Provider 故障隔离不掩盖校验、所有权或策略错误资源穿越/符号链接逃逸与超大 Provider 响应确定性失败缺失、冲突与循环依赖在模型执行前失败Skill 正文绝不进入 system 消息或持久历史项目/用户/system 的.agents发现与旧根保持确定性捆绑的 creator 脚本拒绝覆盖并通过生产校验重复、禁用、无效、过期、超大与未信任案例确定性失败权限与 Hook 行为保持不变旧 Session 与纯文本客户端通过回归测试Python 单元/契约/E2E 测试、Desktop Vitest/typecheck/build、lint 与 pre-commit 全部通过。十三、从架构到实践落地一份 Skill 的完整链路把以上机制串起来一次真实的 Skill 使用可以这样描述编写在项目.agents/skills/name/SKILL.md写入带 frontmatter 的指令按需补充agents/openai.yaml界面与依赖元数据。也可以用捆绑的只读 system Skillskill-creator生成——CLI 中通过$skill-creator或/skill skill-creator调用Desktop 的 Create Skill 动作则会以同一个 Skill 开启一个普通 Thread因此创建过程同样走AgentSession、工具、信任、审批与审计链见 core/skills/builtin/skill-creator/。发现LocalSkillProvider.list按 roots.py 的优先级聚合所有候选生成去重、标ACTIVE/SHADOWED/DISABLED/INVALID状态的不可变 catalog该快照会被SkillCatalogMonitor的 change token 持续跟踪。选择用户显式选择结构化SkillSelection、在消息里$name提及、或模型通过skill工具隐式加载三类路径共享每 Turn 8 个、48,000 字符的预算。执行begin_turn通过列出该包的 Providerread到修订匹配的正文注入为瞬态 user 优先级上下文工具面按allowed-tools收窄当前 Turn 对任何磁盘/策略变化不可见。审计每次加载都记入skillInvocations元数据与skill.loaded事件日志只含不透明 ID、来源与修订哈希。这条链路覆盖了从可复用工作流指令到可审计、可回滚、跨三端一致的 Agent 能力的全部环节也是 docs/SKILLS_PRODUCT_ARCHITECTURE.md 所描述的 Skills 产品架构在 DeepCode 中的完整落地方式。【免费下载链接】DeepCodeDeepCode: Open Agentic Coding (Agent Harness Loop Engineering Multi-Agent Orchestration)项目地址: https://gitcode.com/GitHub_Trending/deepc/DeepCode创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考