ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

PostHog Skills 管理实战:使用 `skill-*` MCP 工具高效创建、更新与维护团队技能

PostHog Skills 管理实战:使用 `skill-*` MCP 工具高效创建、更新与维护团队技能 PostHog Skills 管理实战使用skill-*MCP 工具高效创建、更新与维护团队技能【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog本篇指南围绕 PostHog 项目中面向 Agent 的 Working with Skills 技能文档展开系统讲解如何通过 PostHog 暴露的skill-*系列 MCP 工具发现、读取、创建、更新与重构团队共享技能Skill。阅读完本文你将掌握工具选型决策树、渐进式披露progressive disclosure的读取纪律、基于base_version的乐观并发控制、大技能多文件的高效维护套路以及从本地 SKILL.md 目录整体迁移到 PostHog Skills Store 的完整流程并了解这些操作背后的服务层与数据模型实现。一、定位这份技能文档教什么working-with-skills是 PostHog 仓库中随构建管线hogli build:skills一起发布的 Agent 技能之一其入口文件位于 products/skills/skills/working-with-skills/SKILL.md。它的定位很明确不重复介绍工具本身那是配套技能 skills-store 的职责而是补充「决策树、效率原则、常见陷阱」三块进阶指导帮助 Agent 在调用任何skill-*工具、撰写或编辑共享技能、或排查技能写入被拒问题时少走弯路。仓库中这两份技能的关系正如 products/skills/skills/README.md 所概括skills-store—— 发现和使用 PostHog 中存储的团队共享技能覆盖skill-list到skill-archive的全部工具面working-with-skills—— 管理技能的实操手册选哪个写入原语、何时用什么、大技能如何规模化维护、并发怎么处理。本文默认你已了解工具面本身可先阅读 skills-store/SKILL.md重点展开 working-with-skills 的方法论并下沉到 PostHog 源码验证每一个结论。二、四条核心操作原则原文档开篇给出四条贯穿全文的原则它们是后面所有决策树的出发点渐进式披露不可妥协Progressive disclosure is non-negotiable。列表接口只返回描述skill-get只返回正文 文件清单skill-file-get才返回单个文件内容。永远不要「以防万一」地预加载 bundled 文件——每个被预加载的脚本都是当前任务的无效上下文。选择能完成工作的最小写入原语。一次针对性的edits或file_edits比整体替换 body 或整个 bundle 更便宜、更安全、在版本历史里更清晰。读取是廉价的并发覆盖是昂贵的。任何写操作前必须先通过skill-get或上一次写操作的响应拿到最新的version并作为base_version传入。创作遵循 Agent Skills 规范。name保持 kebab-casedescription要富含触发词body 保持简短臃肿内容放进 bundled 文件。第 2、3 条原则在后端有直接对应的实现约束我们在第六、八节结合源码详细展开。三、工具选型决策树先想清楚再调用原文档给出了一棵完整的决策树这里完整保留Need to know whats available? └─► skill-list (names descriptions only) Need to use / inspect a specific skill? └─► skill-get (body file manifest, NO file contents) └─► skill-file-get (one file, on demand, only as referenced) Authoring a brand new skill? └─► skill-create (body all initial files in one call) Editing an existing skill? ├─ Body change? │ ├─ Substantial rewrite ............. update(body...) │ └─ Surgical tweak .................. update(edits[{old, new}, ...]) ├─ Bundled file content change? │ └─ update(file_edits[{path, edits:[...]}, ...]) ├─ Add / remove / rename a file? │ ├─ Add ............................. skill-file-create │ ├─ Delete .......................... skill-file-delete │ └─ Rename .......................... skill-file-rename └─ Wholesale bundle reset (rare!) ....... update(files[...]) # replaces ALL files Renaming the skill itself? └─► skill-rename (keeps versions, files, and owners) Want a fork as the starting point? └─► skill-duplicate (then update the copy) Done with a skill entirely? └─► skill-archive (hides ALL versions; cannot be undone)决策树隐含了一个最常见的反模式为了改一段正文和一个脚本就同时调用update(body...)加一大坨files[...]。正确的做法是把它拆成两次更窄的调用update(edits[...])加update(file_edits[...])甚至一次update同时携带edits与file_edits——第六节会给出合并调用的完整示例。从源码看工具的真实面这套skill-*工具在仓库中的实现位于 products/skills/backend/tools/skills.py共 8 个工具类全部继承ee.hogai.tool.MaxToolMCP 工具后端实现类所需权限skill-listListLLMSkillsToolllm_skillviewerskill-getGetLLMSkillToolllm_skillviewerskill-file-getGetLLMSkillFileToolllm_skillviewerskill-createCreateLLMSkillToolllm_skilleditorskill-updateUpdateLLMSkillToolllm_skilleditorskill-file-create/skill-file-delete/skill-file-rename走skill_services.py中的create_skill_file/delete_skill_file/rename_skill_filellm_skilleditorskill-archiveArchiveLLMSkillToolllm_skilleditor工具在 MCP 层的完整定义含 scopes、annotations、参数描述见 products/skills/mcp/tools.yaml。其中skill-get/skill-list的idempotent: true与readOnly: true标注从工具面印证了「读取是廉价的」这一原则。四、先发现再获取Discover before you fetch正确用法是先用skill-list找到技能而不是直接抓取posthog:skill-list { search: fractal }skill-list只返回名字和描述。阅读描述本身就是全部目的——在拉取任何 body 之前先选对技能。如果search不足以收窄结果可以不带参数列出再人工扫描但不要盲目地逐个抓取候选技能正文。从源码看ListLLMSkillsTool的实现印证了这一点_list_skills对name和description做大小写不敏感的子串过滤Q(name__icontainssearch) | Q(description__icontainssearch)返回时通过_format_skill_summary只格式化- {name} (v{version}): {description}一行摘要并且结果上限为MAX_LIST_RESULTS 50超出会在响应末尾提示传入search收窄。对应的测试见 products/skills/backend/tools/test_skills.py如test_search_filters_by_name_and_description验证按fractal搜索只命中make-fractals。而skill-get应当每个任务、每个技能只调用一次而不是每问一次就调一次。把 body 缓存到工作记忆里只有当怀疑技能在你手中发生了变化例如写入时收到409见第八节「并发」才重新获取。五、高效阅读大技能把清单当索引大技能长 body、大量 bundled 文件正是惰性加载最关键的场景。推荐流程skill-get(skill_name...)—— 读取bodyfiles[]文件清单清单只含路径和 content_type不含内容。扫描 body 的目录 / 标题。body 应当已经告诉你哪个文件对应哪个任务——这正是「body 保持简短、按路径引用文件」的原因。对 body 为当前任务明确指向的每个文件调用skill-file-get(file_path...)其余全部跳过。如果 body 写着「罕见场景 Y 见 scripts/X」而你并不处于场景 Y就不要去取scripts/X。拿不准时宁少勿多——下一轮随时可以再取一个文件。源码佐证skill-get 确实不含文件内容GetLLMSkillTool的_fetch_skill_with_files拉取技能及其文件行后_format_skill_detail只把清单渲染成- scripts/mandelbrot.py (text/x-python)这样的行随后才输出 body。测试 test_skills.py 的test_returns_full_skill_with_file_manifest专门断言scripts/mandelbrot.py (text/x-python) in result同时print(mandelbrot) not in result——即 body 被加载、文件内容绝不随skill-get返回。GetLLMSkillFileTool则按需返回单文件内容。值得注意的是它在读取前做了路径清洗posixpath.normpath折叠..段、拒绝绝对路径、拒绝../../前缀因此用scripts/../foo.py这类变体是无法绕过路径校验的。前端同样遵循按需加载products/skills/frontend/skillFileLogic.ts中的loadContent只在用户展开文件时通过llmSkillsNameFilesRetrieve拉取单个文件内容——「点到哪取到哪」是整个产品层的统一约定。六、创作新技能一次调用、完整落地创建一个新技能时应该用一次skill-create调用同时带上 body和初始文件——技能直接以version: 1完整落地。不要先建空技能、再做 N 次skill-file-create追加那是 N 个多余版本和 N 次多余往返毫无收益。posthog:skill-create { name: my-skill, description: What it does AND when to use it. Include trigger keywords., body: # my-skill\n\n## When to use\n...\n## Workflow\n..., license: MIT, compatibility: Requires Python 3.10, allowed_tools: [Bash, Write], metadata: { author: me, category: ... }, files: [ { path: scripts/foo.py, content: ..., content_type: text/x-python }, { path: references/primer.md, content: ..., content_type: text/markdown } ] }创作规则要点description是发现面。它是skill-list唯一返回的东西。要富含触发词用户可能怎么说并诚实描述边界这个技能做什么、不做什么。源码中SPEC_DESCRIPTION_MAX_LENGTH为 1024 字符数据库列宽 4096 仅为兼容历史行create_skill/publish_skill_version都会在超长时抛出LLMSkillDescriptionTooLongError。name—— kebab-case、最多 64 字符、无首尾或连续连字符。服务层用正则^[a-z0-9](https://link.gitcode.com/i/4cb0b03aa5246ac2914cd1274d30dbad)?$加-- not in value校验skill_name_is_well_formed见 skill_services.py。另外有两类名字会被拒绝RESERVED_SKILL_NAMESnew、scouts、review-hog、community与/skills路由冲突和与 PostHog 内置技能重名的名字bundled_skill_name_error内置名单由 bundled_skills.py 扫描products/*/skills目录与 context-mill 技能集合得出。body ≤ ~500 行。冗长前言、完整 SQL、整段示例载荷、可运行代码都应放进references/、assets/或scripts/。body 的职责是路由到这些文件而不是内联它们。文件布局约定——scripts/放可执行代码references/放散文式文档和示例assets/放模板 / 数据。Agent 只凭清单也能据此定位。allowed_tools是请求而非授权。从文件读取技能的 harnesszip 导出、git marketplace、contentfullbundle会把该列表视为预授权而通过 MCP 加载技能默认contentstubbundle的 harness 会忽略它直到用户批准该授权。只列技能真正使用的工具因为扩充列表就是扩大请求面。未声明的工具不会被预授权——harness 可能询问用户、拒绝调用或不暴露该工具部分产品会强制执行该列表漏掉的工具会让调用直接失败。从源码看CreateSkillArgs.allowed_tools的字段描述原样写明了「a harness that loads the skill over MCP ignores the list until the user approves that grant」且服务层check_allowed_tool_name会拒绝含空白的工具名因为 Agent Skills 规范将 allowed-tools 序列化为空格分隔字符串含空格的名字导出后会碎裂成多个工具。以## Related skills结尾。当存在相邻技能时用短列表给出skill-name条目每条配一句交接理由when to jump there让一次技能调用种下下一个技能的发现线索。只按名字引用不要带路径——相邻技能常常位于其他产品且只列真正的下一步不要罗列产品里的一切。从源码看 create 的落库行为create_skillskill_services.py在事务内对(team, name)加行锁以阻止并发创建写入version1、is_latestTrue通过LLMSkill.objects.bulk_create批量落文件并借SkillDigestManager自动盖章内容摘要digest同时用seed_skill_owner把创建者设为默认 owner。创建成功后响应会带上新版本号技能立即对全团队可见get_latest_skills_queryset只筛is_latestTrue的行。七、更新既有技能最小原语优先最常见的错误是用update(body..., files[...])做一个小改动。这虽然能工作但会往返传输整个技能、让版本历史中的 diff 难以阅读、而且一旦files不完整就有丢文件的风险。应当始终选用最小原语。7.1 先读后写捕获versionposthog:skill-get { skill_name: my-skill }记下返回的version——它要作为每次写入的base_version。一次写入成功后响应会包含新的version后续写入要用它继续链式推进。7.2 body整体替换 vs 增量编辑重构 body 结构时用整体替换posthog:skill-update { skill_name: my-skill, body: # my-skill\n\nNew body..., base_version: 7 }只微调几行时用增量编辑小改动首选——更易审查、错误面更小posthog:skill-update { skill_name: my-skill, edits: [ { old: Use Pillow for rendering., new: Use Pillow ≥10.0 for rendering. }, { old: ## Old section title, new: ## New section title } ], base_version: 7 }约束每条edits[].old必须在当前 body 中恰好匹配一次body与edits在同一调用中互斥。源码层面apply_skill_body_editsskill_services.py逐条顺序应用编辑old匹配 0 次抛「未找到」、匹配多次抛「请提供更多上下文使其唯一」附带edit_index定位第几条编辑出错最终结果超过MAX_SKILL_BODY_BYTES1,000,000 字节也会被拒绝。UpdateLLMSkillTool则在一开始就强制body与edits二选一Pass either body or edits, not both.。7.3 bundled 文件内容编辑file_edits原地修补一个或多个既有文件——未涉及的文件原样结转。这是修改脚本逻辑或修正 reference 文档错别字时的正确原语posthog:skill-update { skill_name: my-skill, file_edits: [ { path: scripts/foo.py, edits: [{ old: ITERATIONS 100, new: ITERATIONS 250 }] }, { path: references/primer.md, edits: [{ old: ## Outdated header, new: ## Updated header }] } ], base_version: 7 }file_edits不能新增、删除或重命名文件——只能修补既有文件。结构性变更请用按文件工具。实现上_resolve_file_edits先从当前版本取出全部文件逐条校验目标路径存在不存在抛LLMSkillEditError并附file_path再对每条路径调用apply_skill_file_edits同样要求old恰好匹配一次、结果不超过MAX_SKILL_FILE_BYTES即 1MB。7.4 单次调用合并 body 与文件编辑当一个变更同时跨 body 与既有文件时可以在一次skill-update中合并edits与file_edits发布一个连贯的新版本posthog:skill-update { skill_name: my-skill, edits: [{ old: ## Configuration, new: ## Setup }], file_edits: [ { path: scripts/run.py, edits: [{ old: DEBUG False, new: DEBUG True }] } ], base_version: 7 }7.5 文件路径参数命名动手前先读这段同一个概念——bundled 文件的路径——在请求中的位置不同、字段名就不同这是最容易凭记忆出错的地方。只有一条规则file_path—— 当路径属于URL的一部分时skill-file-get、skill-file-delete。这两个工具按路径读/删单个文件也都接受path并归一化为file_path所以清单里的 key 可以直接抄用。path—— 当路径是body 字段时skill-file-create、files[{path, content, content_type}]数组、以及file_edits[{path, edits}]。old_path/new_path——skill-file-rename的 body 字段。记忆口诀path是文件对象上的字段名它紧挨着content所以一切携带文件对象的调用都用path那两个用 URL 寻址文件的工具才用file_path。拿不准时查工具输入 schema而不是猜。tools.yaml中skill-file-get/skill-file-delete的param_overrides明确给file_path配了aliases: [path]与文档描述完全一致。7.6 新增、删除、重命名文件每个操作独立成一次调用每次都发布一个新版本posthog:skill-file-create { skill_name: my-skill, path: scripts/julia.py, content: ..., base_version: 7 }posthog:skill-file-delete { skill_name: my-skill, file_path: scripts/old.py, base_version: 8 }posthog:skill-file-rename { skill_name: my-skill, old_path: scripts/julia.py, new_path: scripts/julia_set.py, base_version: 9 }skill-file-rename是真正的移动——它把既有内容原样带过去无需重新发送。内容不变时永远优先于「删除 创建」。从服务层看create_skill_file/delete_skill_file/rename_skill_file都复用_select_latest_for_write做版本校验含base_version对比、MAX_SKILL_VERSION上限检查并通过_create_next_version_with_files将旧版本is_latestFalse、新版本version 1、文件集按需增删改名全程事务保护。文件数量上限为MAX_SKILL_FILE_COUNT 200同名文件路径会抛LLMSkillFilePathConflictError。7.7 什么时候才用update(files[...])罕见向skill-update传files会替换整个 bundle——数组里没列出的文件全部被丢弃。它只适合有意的整体清空重灌例如导入一棵全新的本地 SKILL.md 目录树。几乎其他所有情况都应优先file_edits 按文件 CRUD。这与服务层行为一致publish_skill_version中当files is not None时直接bulk_create全新文件集只有当files is None时才走_copy_files把旧文件结转并按需应用file_edits覆盖部分内容。八、大技能10 文件的工作纪律把清单当作索引。skill-get的files[]就是你的地图。把每个任务步骤映射到一个文件只取那一个。把结构性变更串成序列而不是开叉。比如要重命名三个文件就顺序执行rename → rename → rename每一步都用上一步响应里的version链式推进。这产生三个可审查的小版本而不是一个巨大的update(files[...])大杂烩。保持编辑局部化。一次skill-update用file_edits同时改五个文件是没问题的但一次update(files[...])携带十个完整文件体几乎总是说明你本该用file_edits。先重构 body 本身。如果 body 超过约 500 行正确的下一步通常是先把内容拆进新的 bundled 文件再继续加料而不是放任 body 膨胀。从模型层看LLMSkillmodels/skills.py的版本字段version、is_latest、deleted、version_description和约束unique_llm_skill_version_per_team、unique_llm_skill_latest_per_team为「每个写入产生一个不可变版本」提供了数据库级保障每个技能最多 2000 个版本MAX_SKILL_VERSION触顶后需要归档重建才能继续发布——这进一步说明了「用最小原语少产生版本」的工程价值。九、并发控制base_version是必填项每个写工具都接受base_version永远传入它服务端把base_version与当前最新版本比较。一致则写入成功新版本号 base_version 1。不一致则拒绝写入说明技能被别人更新了。此时重新skill-get把改动对账到新 body 上用新的version重试。一次写入成功后响应包含新的version。你控制范围内的连续写入直接用该版本号链式推进即可——不要在连续写入之间重新get。跳过base_version不会更快——它只是把干净的「别人赢了竞态」错误变成对别人工作的静默覆盖。从源码看publish_skill_version在事务内用select_for_update锁定当前最新行然后严格比较base_version ! current_latest.version不等即抛LLMSkillVersionConflictError(current_version...)UpdateLLMSkillTool会把它转译为带当前版本号的友好提示Refetch withget_llm_skilland retry the update with the new base_version.。409 冲突正是第八节提到的「技能在你手中变了」的信号此时重新skill-get一次即可。十、常见陷阱清单原文档列出的陷阱逐条记录如下skill-list不带 search 就把每个 body 都抓一遍—— 违背渐进式披露。先读描述。skill-get之后预取每个 bundled 文件—— 在内层犯同样的错。按 body 的指示按需取。用update(body..., files[...])做一行修复—— 往返整个技能、diff 不可读、有丢文件风险。用edits/file_edits。本意是加一个文件却用update(files[...])—— 会把没列出的文件全部丢掉。用skill-file-create。用「删除 创建」代替重命名—— 丢失内容历史还多涨一个版本号。链式写入后仍用陈旧的base_version—— 要从上一次写入的响应里读version而不是最初那次get的。漏掉base_version—— 等于接受静默覆盖。一旦做过get就始终带上它。description为空或含糊—— 技能通过skill-list搜索时几乎不可发现。把 description 当作触发契约。长 body 却没有任何 bundled 文件—— body 超过约 500 行时重构进references/和scripts/别让它继续膨胀。一次 update 混用body和edits—— 两者互斥二选一。瞎猜pathvsfile_path——skill-file-get和skill-file-delete用file_path在 URL 里create、renameold_path/new_path、files、file_edits用path是 body 字段。参见第七节 7.5。十一、归档技能不可撤销skill-archive按名字隐藏某个技能的每一个活跃版本。它不是按版本作用域的且无法撤销——该技能会从整个团队的skill-list和skill-get中消失。posthog:skill-archive { skill_name: my-skill }归档前如果需要检视或复制先skill-get。归档适合彻底退役一个技能要移除单个 bundled 文件用skill-file-delete要回滚内容则发布新版本而不是归档。实现上archive_skill在事务内把所有版本行置为deletedTrue, is_latestFalse软删除并顺带clear_skill_owners——owner 行按(team, skill_name)逻辑键绑定若不清理后来复用该名字的技能会继承归档技能的 owner。ArchiveLLMSkillTool的响应会明确提示「This cannot be undone」。十二、把本地 SKILL.md 目录树移植进 PostHog把本地技能文件夹例如my-skill/SKILL.md加scripts/、references/、assets/迁入 PostHog 时读取本地SKILL.md。其 frontmatter 映射到name、description、license、compatibility、allowed_tools、metadatafrontmatter 之后的正文就是body。遍历 bundled 子目录把每个文件收集为{ path, content, content_type }。一次posthog:skill-create带上全部内容——技能直接以version: 1完整落地。不要拆成一次 create N 次 file-create。创建完成后技能立即通过skill-get对全团队可用。这也与仓库的构建管线呼应hogli build:skills把products/*/skills渲染进dist/skills.zip随宿主分发见 bundled_skills.py 的注释本地的创作流程走hogli init:skill -- --product skills --name my-new-skill本地联调用hogli sync:skill -- --name working-with-skills同步到.agents/skills/见 products/skills/skills/README.md。十三、什么时候不该用技能不是所有持久化提示都该进 Skills Store一次性任务指令属于对话不属于技能。个人草稿本属于 Agent 记忆或本地文件。代码不是技能——如果它是某个服务要运行的东西它属于仓库。一个合格的技能应当可复用、能被 description 发现、并且值得长期维护其正确性的成本。用第八节的版本纪律和第九节的并发规范去维护它base_version会替你挡住并发写入的竞态而渐进式披露则让你的每次任务只消耗真正需要的上下文。【免费下载链接】posthog:hedgehog: PostHog is the leading platform for building self-driving products. Our developer tools – AI observability, analytics, session replay, flags, experiments, error tracking, logs, and more – capture all the context agents need to diagnose problems, uncover opportunities, and ship fixes. Steer it all from Slack, web, desktop, or the MCP.项目地址: https://gitcode.com/GitHub_Trending/po/posthog创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表