pstack benny 分诊自动化:面向 Slack 问题报告的线程级判定、去重与 Fail-Closed 工单创建
【免费下载链接】pluginsCursor plugin specification and official plugins项目地址: https://gitcode.com/GitHub_Trending/plugins125/plugins
本文围绕 pstack 插件中 benny 自动化套件的核心操作文件 triage-issue-reports/SKILL.md 展开,完整讲解这条“单线程判定(thread-only verdict)”式问题报告分诊流水线的设计与实现:从冻结源线程坐标、读取全量报告证据、原因追踪、五类分类,到路由、去重、Fail-Closed 工单创建、单条判定消息与[benny:*]标记契约,以及后续跟进窗口。读完本文后,你可以理解一套多智能体 Slack 自动化如何在“绝不越权发言、绝不猜单、绝不重复建单”的安全约束下闭环运行,并能参照 configuration.example.yaml 与 triage-automation-prompt.md 在自己的仓库中落地同类配置。
一、定位:一条只读进、单条出的分诊流水线
benny 是 pstack 插件(位于 pstack/ 目录)提供的一组 Cursor 自动化源码包,专门处理 Slack 问题报告。整个套件包含两条相互协作的自动化:一条负责分诊(triage),另一条负责复现已确认的 bug 并可能准备小的草稿修复(见 benny/README.md 与 benny/FOR_AGENTS.md)。
分诊这一条的职责边界在 SKILL.md 开篇就写死:
对一条 Slack 报告做分类,并在其源线程里发布一条有用的判定。只有当一个清晰、全新的 bug 时,才创建 tracker issue。不在此处复现或修复它。
这决定了整条流水线的形态:输入是多条 Slack 消息加附件,输出恰好是一条线程内回复(外加至多一次 tracker 写操作)。文件 frontmatter 中的disable-model-invocation: true表明它不是可被模型随意调用的 slash skill,而是“仅从已配置好的 Benny 分诊自动化加载”的操作指令文件——这也与 FOR_AGENTS.md 中“SKILL.md文件是直接的自动化指令,而非注册的插件技能”的说明一致。
硬性安全规则(Hard safety rules)
SKILL.md 第一节列出 16 条不可违背的规则,可归纳为四个层面:
线程坐标不可变。
- 源频道与根线程坐标是不可变的(immutable)。
- 永不向源频道发布根消息(root message)。
- 不向其他频道发消息、不广播回复、不发 DM、不另开替代线程。
写操作前必须预检(preflight)。
- 在任何 tracker 写入之前、以及在判定消息发出之前,都要对源父消息做 preflight 校验。
- 如果父消息缺失、被删除、不可访问或状态不确定,立即停止,不做任何写入。
发言权唯一。
- 只发一条有实质内容的判定,不做过程播报("Do not narrate progress")。
- 协调者(coordinator)是唯一的 Slack 发言者。
- 委派出去的 worker 只能返回发现(findings only),必须只读,且不授予任何 Slack 凭据或写动作;每个子任务提示词都必须明令禁止
SendSlackMessage、PostToSlack、chat.postMessage以及一切其他 Slack 写操作;如果 worker 隔离无法强制这些限制,就在协调者内部完成工作。
宁缺毋滥地建单。
- 绝不创建无法回链到源线程的 issue。
- 宁可不建单,也不建一个靠猜或重复的工单("Prefer no ticket over a guessed or duplicate ticket")。
规则中还显式引用了两条 pstack 原则技能与一个写作技能:对源坐标应用 principle-separate-before-serializing-shared-state,对最终判定应用 principle-minimize-reader-load 与 unslop。其中前者要求“先消除共享可变状态,再谈串行化”——把SOURCE_CHANNEL_ID/SOURCE_THREAD_TS冻结为不可变值,正是让所有后续读写只依赖同一份“拥有者状态”、避免多个参与者各自推算线程坐标这一结构性手段;后者与 unslop 则保证判定消息短、可读、无 AI 腔。
二、第 1 步:冻结源坐标
在建立工作清单或委派任何 worker 之前,必须先完成一次坐标冻结,共 7 小步:
- 从触发器(trigger)读取
source_channel_id; - 要求它必须等于配置中的源频道,否则停止;
- 若
trigger.thread_ts存在则令SOURCE_THREAD_TS取它,否则回退取trigger.ts; - 要求
SOURCE_THREAD_TS非空; - 将
SOURCE_CHANNEL_ID与SOURCE_THREAD_TS存为不可变值; - 读取线程,验证其根消息恰好就是这组坐标;
- 抓取一个稳定的源 permalink(用于后续去重与工单回链)。
关键约束是:此后所有对源的读取与发布都只能用这两个已存值,绝不换成某条回复的时间戳或操作线程(operations thread)的时间戳。触发器的 JSON 结构在 templates/triage-automation-prompt.md 中给出:
{ "source_channel_id": "{{SLACK_CHANNEL_ID}}", "message_ts": "{{SLACK_MESSAGE_TS}}", "thread_ts": "{{SLACK_THREAD_TS_OR_EMPTY}}" }该模板同时要求:“把源频道与根线程时间戳视为不可变。任一缺失或与配置不符时,停止且不发布、不写 tracker。”——这正是“fail closed(失败即关闭)”在入口处的体现。
三、第 2 步:读完整份报告
决策前必须读完根消息和当前所有回复,并按清单取证:
- 报告人的原话措辞(reporter wording);
- 产品版本、app build、环境、平台(若存在);
- 期望行为;
- 实际观察到的行为;
- 频率与触发条件;
- 错误文本或堆栈签名(stack signature);
- 已有的 issue、commit 或 pull request 链接;
- 任何“已有人在修”的明确陈述。
附件处理有独立的一节,要求“检查每一个相关附件”:
- 截图按可用的最大有效分辨率阅读;
- 视频要定位出“把正确行为与损坏行为分开的那次状态迁移”;
- 日志、trace、崩溃文本要找具体签名;
- 媒体需要专家级审查时,启用只读 media worker,并只问一个狭窄问题,worker 仅返回 findings;
- 若某个附件读不了,就在判定中明说,不得臆造其内容。
结尾一句是取证总原则:在再向报告人追问之前,先用线程里已有的证据。
四、第 3 步:路由之前先做原因追踪
选择 owner 或目的地之前,先做一次“有界的(bounded)”源码与历史排查,并显式复用 pstack 的两个技能:
- 用 how 技能,追踪“报告中的动作 → 观察到的结果”这条路径。
how定位是回答“X 如何工作”的架构走读,简单问题单解释者一趟完成,复杂子系统则并行派 2–4 个只读 explorer 再综合; - 当报告像回归(regression)或涉及防御性代码时,用 why 技能。
why以“谨慎的调查者”姿态,通过 git blame、git log --follow -p、PR 讨论等并行取证设计动机与变更原因。
具体五步:
- 从报告的动作到观察到的结果,识别最可能的代码路径;
- 判断可见症状是否属于该代码路径,还是属于其下的某个依赖;
- 报告疑似回归时,检查最近的变更;
- 检查是否已有合并的 commit 或开放的 PR 处理了同一症状;
- 把确认事实与假设分开。
这一遍不要求完整根因,只要求“强到足以避免把一个可见症状路由给错误的 owner”。如果仓库读不了,则不要猜代码 owner,继续做保守分类,并说明“原因追踪不可用”。这与 FOR_AGENTS.md 中“两条自动化在频道坐标、tracker 访问、控制适配器或 feature map 缺失/不确定时都 fail closed”的共享规则一致。
五、第 4 步:五选一分类
每条报告只允许落入一个类别:
| 类别 | 判据 | 后续动作倾向 |
|---|---|---|
| Bug | 行为违反预期:错误输出、状态损坏、报错、崩溃、挂起、静默 no-op、回归 | 可建单 |
| Performance | 可度量的慢、内存/电量超额、卡顿等资源问题 | 按 bug 处理,但必须保留测量值与 profile |
| Feature request | 当前行为看起来是有意为之,报告人想要不同行为或入口 | 不建单 |
| Question or feedback | 询问机制、表达偏好而无具体缺陷、一般性反馈 | 不建单 |
| Reroute | 原因追踪表明另一个已配置的目的地才是归属 | 告知去向,不交叉转发 |
边界处理规则明确:当 bug 与 feature request 的界限不清时,不建单;那条唯一的判定可以问一个聚焦问题,并使用other标记。
六、第 5 步:应用配置路由与 owner ping 策略
路由图从routing.map_path读取,是可选数据。references/routing.example.md 给出了完整示例结构:routes数组中每条路由带name、match(product_areas/code_paths/error_signatures三类匹配键)、destination(Slack 频道与 tracker team)、owners与allow_feature_owner_ping,外加全局fallback(目的地默认为空)与ping_policy(默认off,允许configured-feature-owner与confirmed-regression-author,拒绝broad-on-call-group与unverified-owner)。该示例强调“triage 技能把它当作数据:路由需要来自报告或原因追踪的证据,单纯的关键词匹配不够”。
匹配与处置规则:
- 按确认过的产品区域、代码路径或错误签名匹配;
- 当原因追踪指向别处时,仅凭可见症状不足以命中路由;
- 没有任何路由匹配时,明说 owner 不清,不猜;
- 不交叉发帖(cross-post),只在源线程里告诉报告人应该把问题带到哪里。
Owner ping(@人)默认关闭,只有以下四条全部成立才允许:
- 路由图里显式点名了该 owner;
- 配置允许该类型的 ping;
- 该条目是需要 owner 输入的 feature request,或最近历史中有强证据指向疑似回归作者;
- owner 不是宽泛的 on-call 组。
其他任何情形都不 ping。这一策略与 configuration.example.yaml 中routing.owner_pings_default: false、allow_feature_owner_ping: false、allow_confirmed_regression_author_ping: false三个默认全关的字段一一对应。
七、第 6 步:issue-tracker 适配器契约
文档把 tracker 明确定义为适配器(adapter)而非指定厂商:Linear 只是“一种有效示例”,GitHub Issues 或其他 tracker 只要实现同一契约即可。配置契约要求适配器提供七类能力:
- 按文本、状态、label、源 URL、日期区间搜索 issue;
- 读取单个 issue 及其链接;
- 以标题、正文、状态、labels、源 URL 创建 issue;
- 更新既有 issue 时不替换无关字段;
- 追加源链接与复发(recurrence)备注;
- 若 Slack 交接失败,能取消/关闭/删除本次运行所建的 issue(补偿动作);
- (隐含于 fail-closed 要求)任一必需操作不可用时,该写操作 fail closed。
运行时规则:配置的 team、project、status、labels 一律在运行时解析;不发明 ID、不新建 label、不指派 owner、不设优先级,除非配置显式要求。对应配置段为:
tracker: type: "linear" adapter_skill_name: "issue-tracker-adapter-placeholder" team: "team-placeholder" project: "project-placeholder" labels: bug: "bug-label-placeholder" performance: "performance-label-placeholder" intake: "intake-label-placeholder" needs_repro: "needs-repro-label-placeholder" status: "intake-status-placeholder" source_link_title: "Slack report" require_compensation_action: true其中require_compensation_action: true把“补偿动作可用”写成了硬性配置项——这是第 8 步建单前置条件 7 的配置侧对应物。
八、第 7 步:去重
去重有两层。
permalink 层(无条件执行):先检查本条源 permalink 是否已经挂到某个 tracker issue 或某条历史分诊回复上;若是,既不发布也不建重复单。
语义层(针对 bug 与 performance):用以下维度在 tracker 中搜索——精确错误/崩溃签名、产品区域、触发条件、症状、版本或日期窗口、疑似回归 commit、源 permalink。
搜索结果归入四种结论之一:
- Confident duplicate:同一签名,或同区域+同触发+同症状,或确认的共同原因;
- Possibly related:共同原因可能但未证实;
- Weak resemblance:相似只是表面;
- No match。
处置规则:
- Confident duplicate:在既有 issue 上追加源 permalink 与一条简短复发备注;除非配置另有说明,不重新打开、不改 label、不重新指派;
- Possibly related:在判定中作为“不确定”链接给出,什么都不创建;
- 一个长期关闭的 issue 是“回归线索”,不自动等于活跃重复项。
九、第 8 步:七条件建单门禁
只有以下 7 条全部为真才创建 issue:
- 分类是 bug 或 performance;
- 行为明确是坏的;
- 该问题仍然活跃,或没有被已知已修复;
- 去重没有发现任何“确信”或“疑似”的活跃匹配;
- 源父消息与 permalink 已通过 preflight;
- tracker 目标字段已成功解析;
- 适配器具备在判定发布失败时的补偿能力。
反向清单同样明确:feature request、问题、反馈、reroute、疑似重复、确信重复、已修复的 issue,一律不建单。
新建 issue 必须是自包含的,正文包含:
- 一个朴素标题:点名区域与症状(不把猜测的根因写进标题);
- 报告人原话引用;
- 期望与实际行为;
- 版本与环境(没有就写
unknown); - 触发条件与频率;
- 源线程 permalink;
- 简短的原因追踪发现,且假设必须标注为假设;
- 支持时内联截图或代表性视频帧;
- 其余产物的链接;
- 配置的 intake 状态与 labels。
十、第 9 步:发布唯一判定与标记契约
发布流程:先做一次全新的源父消息 preflight,然后恰好发布一条回复,且必须满足channel=SOURCE_CHANNEL_ID、thread_ts=SOURCE_THREAD_TS;对源频道的任何发布动作,若thread_ts为空则永不调用。
回复保持短:结论先行;有 issue 就链接既有或新建的 tracker issue;需要时提一句 reroute 或一个缺失事实;至多包含一个允许的 owner ping;以恰好一行标记(marker)收尾。
标记契约(marker contract):
[benny:bug] [benny:bug] tracker=https://tracker.example/issue/123 [benny:performance] [benny:performance] tracker=https://tracker.example/issue/123 [benny:other]约束:只使用配置里给出的标记字符串(对应 configuration.example.yaml 的verdict_markers段:bug、performance、other三个字符串加tracker_attribute: "tracker")。下游的 repro 自动化只在该标记来自本源线程中已配置的分诊身份时才信任它——这就是两条自动化之间的信任握手协议。
发布后的校验与补偿:
- 重新读取同一源线程,确认判定出现在
SOURCE_THREAD_TS之下;若没有出现,永不改在根消息重试; - 若本次运行建过 tracker issue 而判定没落地,就用适配器的补偿动作,并验证 issue 已被取消、关闭或删除;若补偿无法验证,只在自动化运行输出中报告失败(不向 Slack 发声)。
十一、第 10 步:单个跟进窗口
最后一步是守住一个有界的跟进窗口,然后停止:
- 只回答指向分诊身份的直接提问;
- 在安全时对 tracker issue 做具体更正;
- 同一次运行中不发出第二个标记;
- 不介入人工协调与闲聊;
- 有人要求停止时提前结束;
- 窗口最多延长一次;新报告应触发新的一次运行。
窗口时长由配置budgets.triage_follow_up_minutes(示例值 10 分钟)与triage_total_minutes(30 分钟)控制,示例预算段如下:
budgets: poll_seconds: 45 verdict_wait_minutes: 45 triage_follow_up_minutes: 10 triage_total_minutes: 30十二、落地方式:配置、提示词与部署
配置。分诊运行所需的全部外部值来自 templates/configuration.example.yaml。与分诊直接相关的段有:slack(源频道 ID、分诊身份triage_identity_user_id、读/线程发布/文件下载等已配置动作、prefer_cursor_actions、可选 bot token 环境变量、allow_source_root_posts: false与allow_worker_slack_writes: false两个默认关闭的开关——后者即“worker 永不持有 Slack 写权”的配置侧落点)、tracker(见第七节)、routing、verdict_markers、status_emoji、budgets、models(triage/reproduce/code/media_review四个模型槽位)。文档要求配置缺失、畸形或不完整时“停止且不发布、不写 tracker”,因此这些占位符必须在启用前全部填实。
自动化提示词。templates/triage-automation-prompt.md 是供/automate起草自动化时“改写进草稿”的意图模板:它要求运行时直接读取并遵循仓库内已提交的.cursor/automations/benny/skills/triage-issue-reports/SKILL.md(禁止使用插件缓存路径或拷贝摘录),声明触发语义为“配置源 Slack 频道中出现一条新的顶层报告”,并重申:协调者唯一发言、worker 只读且明确禁写、判定以恰好一个标记收尾、bug/performance 标记可附加tracker=<URL>。
部署路径。按 benny/README.md:先把 Cursor 指向 FOR_AGENTS.md 并指明目标仓库;setup 会把整个目录合并进目标仓库的.cursor/automations/benny/,保留目标端独有文件、对冲突走“审阅差异再合并”而非覆盖;然后在目标仓库的.cursor/settings.json中启用 pstack("plugins": { "pstack": { "enabled": true } }),使how、why、tdd、unslop等共享依赖在项目作用域内可解析。用户自有的配置(含routing.md、feature map)放在打包目录之外,例如.cursor/benny/,避免包刷新覆盖。启用前先提交.cursor/settings.json、.cursor/automations/benny/与所有无密钥配置,再各建一次自动化草稿,并“发一条无害的测试报告,验证每条源频道发布都留在原线程内”。具体流程详见 setup-benny/SKILL.md。
与 repro 自动化的衔接。分诊只负责判定,复现与修复由 reproduce-and-fix-issues/SKILL.md 承接:它在同一源线程等待被信任的[benny:bug]/[benny:performance]标记后才进入工作,标记里附带的tracker=<URL>即成为复现流程的入口凭据。两条自动化共享同一组不可变坐标与 fail-closed 规则,这正是 FOR_AGENTS.md “shared rules”一节描述的架构意图。
小结
这份 triage SKILL.md 的价值在于把“AI 自动处理用户报障”这一高风险场景的全部危险面逐一压住:发言权集中在协调者、线程坐标先冻结后使用、写操作前 preflight、路由与 ping 全部由配置与证据驱动、建单七条件门禁、去重四结论、标记契约作为下游信任锚点、失败时用 tracker 补偿动作回滚。它以纯文本操作指令加 YAML 数据的形式,把多智能体协作的隔离边界、失败语义和可验证性写成了可审计、可复现的工程约定,是研究 Slack 场景多智能体自动化时一个完整的参考实现。
【免费下载链接】pluginsCursor plugin specification and official plugins项目地址: https://gitcode.com/GitHub_Trending/plugins125/plugins
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考