关于 posthog-customer-deep-dive
posthog-customer-deep-dive 是面向客户研究和跟进准备的技能,可基于账户数据与文档搜索生成邮件、通话准备简报或其他所需内容。它要求核实每项事实,并将客户系统保持为只读。
主要功能
- 01按邮箱、域名或账户定位客户
- 02并行收集账户与产品数据
- 03核验数字、产品事实和链接
- 04生成邮件或通话准备简报
- 05客户及账户系统只读
适用场景
- 01准备首次联系或跟进邮件
- 02为客户通话整理简报
- 03研究账户使用情况与产品背景
MCP 相关
使用 PostHog 与 Vitally MCP 获取账户及产品相关信息;PostHog MCP 是主要途径,脚本可作为特定情形下的后备。该技能不通过这些系统发送或写入内容。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ posthog/ skills --skill posthog-customer-deep-dive安装后在 Claude Code 中输入/posthog-customer-deep-dive确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
.gitignoreconfig.mdREADME.md.claude-plugin/plugin.jsonreferences/agent-briefs.mddata-rules.mdlevers.mdmode-call-prep.mdmode-email.mdqueries-account.mdqueries-money.mdqueries-products.mdsite-scan.mdvoice.mdscripts/phq.pysite-scan.shversion-check.sh详细介绍
PostHog 客户深度研究
研究一个账户,然后生成一封邮件(首次联系、跟进或回复)、一份附有笔记本提示词的通话准备简报,或针对其他请求,根据同一研究生成所需形式的内容。输出到聊天中。客户和账户系统为只读:绝不发送、绝不发布、绝不向 Vitally 或 PostHog 写入。仅允许本地写入:Setup 期间写入 config.md,以及每次运行的临时工作区(会话临时工作区下的 deep-dive-<account>-<HHMM>,或 /tmp),用于存放文档缓存、上下文文件和采集器摘要。交付内容只生成一次,输出到聊天中,绝不同时写入文件。
输入:$ARGUMENTS,通常是电子邮件地址;也可以是域名、账户名称或 Vitally 账户 ID(任一种 UUID)。
写入前核实每一项说法。 所有数字都必须来自本次运行执行的查询;所有产品事实和链接都必须来自本次运行执行的文档搜索。行内展示推导数字的计算过程(1.72M polls/day / 2,880 per instance = ~600 instances),以便发现输错的数字。不得凭记忆,不得根据未提取的数字推断。无法核实的说法应改写为问题:未解答的问题会带来一次跟进,错误而笃定的事实则会损害关系。此技能中的每张表都说明了需要检查的内容;没有一张表是引用依据。
关于客户代码或 SDK 配置的说法必须引用代码,引文须来自网站扫描获取的页面或代码包。数据能显示结果,只有代码能显示原因,因此不能仅凭事件和账单数据就断定身份识别或计费机制。SDK 发出的事件($identify、$create_alias、明确的 $set 事件)可以根据这些事件本身进行判断;初始化选项和配置值则必须引用代码。
首先阅读 config.md:其中包含每位用户的值和工具绑定。然后,如果 config.local.md 存在,也要阅读,并以其中指定的键为准;该文件包含此机器的个人值,并被 git 忽略,因此大多数安装中都没有它,这是正常的。覆盖后,若必填值仍为 <SET THIS>,则在执行需要该值的步骤前先询问,或运行 Setup。来源设为 none 时静默跳过;这是配置,不算跳过任务,所有可选来源均设为 none 的运行也算完整。
| 参考资料 | 阅读时机 |
|---|---|
config.md,以及存在时的 config.local.md | 每次运行,在步骤 1 之前 |
references/agent-briefs.md | 首先,在 Round 1a 之前。 其中包含 main 自己的阅读地图行,决定 main 还需打开哪些内容;此外还包含约定区块、各角色地图、上下文文件规范和返回规则 |
references/data-rules.md | 步骤 1 和 2,由 main 仅阅读 main 的地图行指定的章节。采集器绝不打开此文件;它们会收到内嵌的约定 |
references/queries-account.md | 步骤 1 和 2,由 main(Round 1 的 slug)及分配给该文件的采集器阅读 |
references/queries-products.md、references/queries-money.md | 步骤 2,由地图行指定相应章节的采集器阅读。每个文件的标题即为目录 |
references/site-scan.md | Round 1a:批次 1 扫描域名,后续每个团队分别使用一个批次;公共服务提供商的管理员邮箱则在 Round 2 扫描 |
references/levers.md | 撰写任何建议之前阅读;docs-prewarm 用它来挑选页面 |
references/voice.md | 撰写任何客户可见内容之前阅读 |
references/mode-email.md、references/mode-call-prep.md | 步骤 5,只阅读与检测到的交付类型相对应的文件。其他请求仍执行相同的研究,并按请求所需的形式输出 |
将 main 的参考资料阅读合并为一个区块。完整打开 data-rules.md 是关键路径中耗时最长的读取,其中大部分内容面向采集器;采集器会收到内嵌内容。
三个内置脚本。其中两个只是起点,绝不是终点:运行它们,然后继续检查账户指向但脚本未覆盖的所有位置,并说明你在脚本之外运行了什么。如果运行恰好停在脚本覆盖的范围内,那你回答的就是脚本的问题,而不是客户的问题。
scripts/phq.py会通过 HTTP API 在四个目标上执行任意 HogQL 查询(us项目 2、euEU 项目 1、ch-us/ch-eu直接连接 ClickHouse)。--batch <file.jsonl>可一次执行多个查询,每行一个 JSON 对象,包含name、target、sql键以及可选的connection;任何其他键集合都会在第一个查询运行前触发KeyError。MCP 仍是首选;网关故障时用它回退,且它是 EU 的唯一途径。scripts/site-scan.sh <domain>会运行常见的网站扫描流程。scripts/version-check.sh不接受参数,是第三个脚本:读取config.md时同时运行一次;如果有输出,请原样转达。它会比较此副本的.claude-plugin/plugin.json版本与已发布版本,每天最多检查一次;若版本是最新的、处于离线状态或领先于main,则不输出任何内容。通常不会有输出,无需提及。
其他所有 HTTP 调用,包括 Vitally REST:在 Python 中使用单引号 heredoc(python3 << 'PYEOF')构建 JSON,将其写入文件,再通过 curl --data @file 发送。Bash 会在内联字符串中展开 $1、$group_0,而此技能中充满了 $ 开头的名称;Python 的 urllib 会遇到 CERTIFICATE_VERIFY_FAILED,因此由 curl 负责发送,Python 只负责构建和解析。
设置(首次运行,或配置不属于你的时候)
先探查,再询问;确认后再写入。一次性完成以下操作:检查工具列表中是否有 PostHog exec 网关和 Vitally MCP;检查环境变量中是否有 POSTHOG_PERSONAL_API_KEY、POSTHOG_PERSONAL_API_KEY_EU、VITALLY_API_KEY;检查 config.md 中每个可选类别对应的工具,并记录确切的工具名称;检测时区。发送一条消息说明已找到的内容、缺失项和保留的默认值;只询问无法探查到的信息(日历 ID、预约链接),然后再写入:个人值(工作日历 ID、时区、会议记录文件夹)写入 config.local.md,如果文件不存在则创建;其他所有内容写入 config.md。绝不要把个人路径或地址写入 config.md;这是共享文件,会原样发布。缺少的可选工具记为 none;缺少必需工具则停止运行。
缺少内容时,要提供解决方法,而不是只给出诊断。 README.md 包含完整安装说明,每种工具各有一个编号章节,每章末尾都有一个验证安装成功的检查;它不会在运行时加载,因此请阅读缺失项对应的章节,并直接提供其中的命令:Shell 和密钥设置(第 0 节)、MCP 注册、获取共享凭据的 op item get 命令,或个人凭据的签发 URL。说明哪些凭据是共享的,哪些是个人的,因为重复使用他人的 PostHog 个人密钥是最常见的错误。然后重新探查并确认,再开始运行。首次运行若只以“缺少 Vitally MCP”作结,就会让尝试使用这项共享技能的人失去信心。
第 1 步:解析为 Vitally 账户
| 输入 | 路径 |
|---|---|
| 电子邮件 | mcp__vitally__get_user_details。获取 accounts[0].id 和 accounts[0].externalId(PostHog 组织 ID) |
| 域名 | sibling-sweep 查询。MCP search_users 会忽略 limit,并返回数百 KB 的数据;请避免使用 |
| 账户名称 | billing_customer WHERE name ILIKE '%...%'。自助注册的公司名称可能相同:优先选择 crm_segments / plans_map 非空的行。Vitally 的名称工具不可靠(search_accounts_advanced 对精确名称返回 0 条,search_accounts 会忽略 showAllAccounts,find_account_by_name 仅限于调用者的 CSM) |
| 账户 ID | 直接使用 |
如果都无法解析,请询问账户名称。收到的 ID 可能是任一种 UUID:Vitally MCP 可以解析两种 ID,而数据仓库只会根据 vitally.accounts.external_id 匹配组织 ID。
同一客户在不同系统中的名称各不相同,因此请核实对方提供的名称指的是什么。 Vitally 的账户名称、stripe.name(通常是法定实体名称)、zendesk.name、sfdc.Website 和 resolve-teams 中每个项目的名称是五个相互独立的字段,有时彼此不一致;对方提供的名称可能对应的是第二个几乎没有数据的项目,而非该账户。只要名称指向不一致,就在标题中说明各名称分别对应什么,并以承载主要用量的项目为准进行报告。
get_user_details 有两个限制。它可能会对大型账户进行筛选(移除自定义特征,并输出“Removed N trait fields”;小型账户可能会完整返回,因此无论哪种情况,都应以数据仓库中的特征读取结果为准),也可能因账户有数百名用户而溢出。自定义特征带有 vitally.custom. 前缀。
| 读取来源 | 字段 |
|---|---|
数据仓库中的 vitally.accounts.traits(WHERE id = '<VITALLY_ACCOUNT_ID>'),get_user_details 会将其筛除 | onboardingPipeline、onboardingMinimumEligibility、onboardingUsageOutreachSentDate、onboarding_invoice_count、usEuInstance、csmId、accountExecutiveId |
get_user_details | healthScore、nextRenewalDate、contractRenewalDate、usersCount、usage_mrr、forecasted_mrr、forecasted_usage_mrr、diff_dollars、paidProducts + payingFor<Product> + <product>_forecasted_mrr、replayCountLast30DaysIfSendingData、group_types_total、active_hog_destinations、active_batch_exports、firstSeenTimestamp、roleAtOrganization |
始终运行域名关联组织扫描的两部分查询。 关联组织可能意味着重复付费、需要整合,或由团队成员负责的账户。这两条查询覆盖的群体不同(vitally.users 和 billing_customer),单独运行任何一条都不完整;漏跑其中一条,就可能让重复计费的关联组织继续隐藏。请在标题中标出任何关联组织:一个组织包含多个项目通常优于多个并行组织。
接着,探查扫描结果中所有组织的用量,并围绕承载主要用量的组织构建上下文。 get_user_details 会返回 Vitally 列表中的第一个账户(accounts[0]),而对于拥有多个组织的客户,这通常不是当前活跃的组织,因此后续所有数据都会描述一个已失效的组织。这是启动第 2 轮的前置条件:解析完成之前,任何数据收集器都不得启动,因为如果提供了错误的组织 ID,数据收集器就会在错误的公司上完美地完成工作。
SELECT organization_id, count() AS days, sum(event_count_in_period) AS events,
sum(recording_count_in_period) AS recordings, sum(mobile_recording_count_in_period) AS mobile_recordings,
sum(billable_feature_flag_requests_count_in_period) AS flag_requests
FROM billing_usage_by_org_date
WHERE organization_id IN (<every org id the sweep returned>) AND date >= today() - 30
GROUP BY organization_id ORDER BY events DESC LIMIT 20
未出现在结果中的组织表示其在该时间窗口内没有 Cloud 用量,这本身就是一个答案。如果解析出的组织与当前活跃组织不同,请在标题中说明,报告当前活跃组织,并将解析出的组织视为关联组织的发现:已失效组织上的付费订阅意味着钱白白流失。
步骤 2:并行拉取
将 PostHog MCP 配置到 项目 2(检查其活动环境区块;如果不是项目 2,就执行 switch-project 切换到 2)。它通常默认连接到一个开发项目,在那里每个查询都返回零行。
从 vitally.custom.usEuInstance(这是一个数组,例如 ["US"])检测区域;它的填充值远比 cloudRegion 广泛。若没有该值,则依次回退到 cloudRegion,再到 traits.site_url,其中 eu.posthog.com 表示 EU。对于这两个区域,项目 2 几乎都能返回所需结果;例外是 EU 实验定义和 EU 直连,这两者都需要使用 EU 密钥和 EU 项目 1。
如何并行展开
先并行收集,再由一个主 Agent 汇总,最后单独并行验证。 两次并行展开不可互换:收集发生在形成结论之前,验证发生在形成结论之后。
一轮耗时取决于其中耗时最长的 Agent,而每个 Agent 的耗时取决于其最长的依赖调用链。因此,先批量处理(所有仅依赖上下文的读取都在同一批中发出;HTTP 路径使用一次 phq.py --batch),再扩大范围(按照 data-rules.md,每个组织和区域执行一个分组查询,而不是每个团队执行一个查询),最后才拆分,且仅在存在真正依赖关系时拆分。每份任务简报中的串行调用链不得超过 8 到 10 次读取:数调用链长度,而不是调用次数。
- 以工具调用次数而非秒数设定预算。每个收集 Agent 的上限为 12 到 15 次;如果列表超过 15 次,应在启动前拆分任务简报。没人能估算 Agent 需要多少秒,但每个人都能数出简报中的读取次数。
- 预算限制的是结构,不是范围。 绝不要为了符合预算而删除、推迟或缩小读取范围。简报太大时就拆分;如果拆分后某项读取仍无法纳入预算,就让它超出预算,并在结尾说明。
- 按调用次数拆分;绝不要按主题合并简报。 并发 Agent 几乎不增加实际等待时间;同一个 Agent 内的调用会串行组成一条链。合并任务是用廉价资源换昂贵资源,因此,减少 Agent 数量的直觉恰恰相反。
- 同时运行 8 到 10 个 Agent,每个 Agent 将 4 个查询合并批处理。 超过这个范围后,ClickHouse 会返回 202,HTTP 路径会返回 429;被迫进行的串行重试比不拆分还慢。上限针对整台机器,因此预期偶尔会被拒绝,需要重试,而不是不处理被拒绝的简报就直接交付。真正的限制是并发查询数,而不是 Agent 数量:8 个 Agent 各自批处理 4 个查询,已有约 32 个查询在执行,这就是 Agent 数量看起来不多的原因。由耗时短、查询少的角色组成的一轮,可以比由重型角色组成的一轮并发更多 Agent。
- 调用次数少于约 12 次时,继续拆分就不划算,因为无论分配多少任务,每个 Agent 都有固定的启动成本。拆到 12 到 15 次后就停止。
- 默认情况下只运行一轮。轮次边界由依赖关系决定,而非 Agent 数量;一轮的耗时取决于其中最慢的成员。 按主题重要性分阶段是当前实际存在的失败模式:这会把耗时较长的角色留到第二轮,让它闲置,之后又独自成为关键路径。当任务确实超出上限时,按预期耗时分阶段,并将耗时最长的任务放在第一轮。
- 大致顺序按耗时从长到短排列,基于一次双项目 EU 账户的测量,实际操作时应以观察到的情况为准:
usage-trend、change-point、definitions-and-hygiene、internal-context、clay·event-mix、site-scan、replay-and-errors、flags-and-experiments、money-quotas·data-platform、identity-and-sdks、app-engagement、money-invoices、docs-prewarm。其中两项是根据判断而非耗时排列:clay和internal-context之所以都提前完成,仅仅是因为缺少相应工具;如果运行时 Clay 需要轮询积分,或 Gong 需要分页拉取文字记录,耗时就会长得多。money-quotas的优先级高于其耗时所应对应的位置,因为实际触及的限制会重新定义其他所有发现的意义。docs-prewarm排在最后,绝不占用必需收集 Agent 的名额。 | 轮次 | 执行者 | 工作内容 | |---|---|---| | 1a. 解阻 | 主 Agent,分三个有序批次 | 所有用于决定要研究谁以及运行哪个批次的工作,因为这两项都不能靠猜。批次 1:Vitally resolve、resolve-teams、sibling-sweep的两部分,以及在同一批次中,针对管理员邮箱域名运行的site-scan子 Agent。批次 2:多组织数量探测,用于确定实际组织,以及对首个团队以外的每个团队各运行一次site-scan,每次扫描都使用从resolve-teams获得的专属api_token。批次 3,且仅在此时已知实际组织后:范围探测、阶段 trait、get_account_conversations和日历读取。然后编写上下文文件(agent-briefs.md)、检测模式,并启动第 2 轮。这三个批次有先后顺序,不能合并成一个批次。如果在扫描的同时运行范围探测,它会分析accounts[0]恰好返回的组织;而对于设置此关卡的多组织账户,这个组织是已停用的,因此探测数据会写入上下文文件,之后每个采集 Agent 都会信任其中的数据 | | 1b. 解析 | 主 Agent,一个批次,与第 2 轮并行 |get_user_details、vitally.accounts.traits读取、account-state、account-spine、onboarding-state、other-account-tables、conversation-bodies、change-timeline、billing-limit-updates、account-context、touchpoint-timeline。明确列出这些项,才能避免它们被悄悄跳过。billing-limit-updates只需查询一次,就能确定每项限额背后的操作者;如果限额在通话前发生变动,资金情况的解读也会随之改变 | | 2. 采集 | 下方所列角色,在并发上限允许时于一个波次中运行,否则按耗时从长到短分批运行 | 执行模式要求的每个角色,最后运行docs-prewarm| | 2.(并行) | 主 Agent | 阅读对话正文和第 4 步的名单,并在internal-context返回时完善模式检测 | | 3. 对账 | 主 Agent,在一个并行批次中 | 点名核对、保留意见排查、指定配对核对,然后重新运行标题数据和驱动首要建议的数字。列出输出中将提出的每项结论。若两个来源之间存在分歧,须同时呈现两组数据,绝不自行消除分歧 | | 4. 验证 | 若缓存涵盖该结论则由主 Agent 验证;否则每个未覆盖的数据簇分配一个子 Agent,每个子 Agent 最多验证约 5 项结论 | 针对每项结论搜索实时文档,并返回 URL 和证明该结论的原文 | | 5. 撰写 | 主 Agent | 输出 |
没有单独的前置解析轮次,并且每个团队各扫描一次,而非每个账户扫描一次。 域名扫描只需要管理员邮箱域名;第 1 步在查询任何内容之前就能获取该域名,因此应放在批次 1,与解析同时进行,而不是提前执行。一个包含多个团队的组织需要使用多个令牌,因此一次扫描只会读取一个项目的 init(),其余项目仍然未知。resolve-teams 正是为此返回 api_token,首个团队以外的每个团队都应在批次 2 单独扫描。只有当第 1 步返回了不同域名时,才需要在此基础上重新扫描。
这是一条正确性规则,而非速度规则。不能要求采集 Agent 针对配置尚未读取的项目建议配置更改;而未扫描的项目反复成为持有调查结果的项目:在一个有两个项目的账户中,已扫描的项目早已优化,所有尚未使用的调整空间都在未扫描的项目中。
向每个子 Agent 提供上下文文件的路径、约定部分、读取映射表中的对应行以及返回规则,全部取自 agent-briefs.md。主 Agent 只解析一次上下文,任何采集 Agent 都不得重新解析其中的内容;如果采集 Agent 缺少某个值,应报告缺失项,而不是自行查询。site-scan Agent 是例外,因为它们在上下文文件创建之前运行:批次 1 的扫描只获取域名;批次 2 的每次扫描则获取域名以及该团队专属的团队 ID 和 api_token。
每个可选来源只探测一次。 Gong、Slack 和 Clay 的情况类似:一次调用即可确认是否为空,返回零条结果就停止对该来源的查询。报告“已搜索 X,结果为零”;这是调查结果,不是跳过。
角色清单、各角色负责的内容以及各自打开的章节,都列在 agent-briefs.md 的同一张表中。 之所以放在那里,是因为主流程会读取该文件来撰写简报;在两个始终加载的文件中列出相同角色的两张表,最终只会让它们逐渐不一致。
agent-briefs.md 记录了各角色打开的确切章节,这份映射也会写入简报。每位收集者只问 3 到 4 个问题,不要问六个:六个编号问题会引出六项调查,把一个需要 12 次调用的角色变成需要 30 次调用。
在你已经执行的代码块中探查不熟悉的属性,绝不要为此单独开一轮:为该事件添加 JSONExtractKeys(assumeNotNull(properties))。
范围探查及其三道防线
Round 1a 范围探查(一个 per-product-usage 聚合)能确认哪些产品有使用量。将其作为已知状态传入各产品简报,每一项都附上自己的依据(billing_usage_by_org_date 在该时间范围内的 billable_feature_flag_requests_count_in_period = 0,绝不能只写一句“标志为零”)。它会影响简报的内容,但绝不会影响是否运行。 必须满足以下三道防线,缺一不可,否则就不安全:
- 对于探查结果中没有对应列的每个产品,都写上“无法衡量,没有数据源”。实验使用量和 Web 分析使用量在任何地方都没有数据源;热图使用量只存在于使用情况报告的
teams映射中;ff_count则存在于组织使用情况报告中。 - 采信平坦的零值前,先检查
org-snapshot中的realm:该表仅涵盖 Cloud,因此自托管组织在所有地方都会读到零,即使它们每天都有使用量;而且某天没有使用量时,该表不会为那天生成记录。 - 每份简报都应写“对照产品自身的数据源进行核实,并报告核实结果”,绝不能写“确认后继续”。
绝不能用探查结果跳过模式要求的角色。 跳过会让零值和无法衡量变成同一回事,而这正是这项技能要防止的问题。运行过并确认结果为零的 Agent,不算跳过。
第 3 轮:点名核对、模糊表述排查、指定对象交叉核验
重新运行前要做三项检查,因为每项都能发现整项输入缺失,而不只是数字有误。
点名核对。 将模式要求的角色与已启动的角色对照,并将第 1 轮要求的 slug 与实际返回的 slug 对照。每个收集者都会说明自己跳过了哪些读取,但没有人检查它上一级的情况,因此,在时间紧张时被遗漏的角色会悄无声息地消失,而输出看起来却完整无缺。补上缺失部分,或在结尾说明中指出。时间紧张可以作为说明的理由,绝不能成为省略的理由。这对既不是邮件也不是简报的请求尤其重要,因为读者没有预期中的章节可供察觉缺漏。
排查模糊表述,并产出清单,而非停留在意向上。 阅读每份摘要,找出收集者表达不确定性的内容(“可能,但未经证实”“无法衡量”“需要确认”“值得核对”“最大的遗漏是”“在通话中询问”),并在关闭任何一项之前,将每个命中项写入编号清单。每一项都要注明处理结果:用数字解决,或作为明确列出的问题保留在输出中。使用模糊表述的收集者几乎总会指出如何通过读取来解决问题,因此每项成本都不高,而且范围明确。
在脑中完成这件事正是失败之处。把排查当成意向,只会解决容易处理的模糊点,而被漏掉的通常正是支撑最高优先级建议的那一项,因为要回答它需要费功夫。未解决的模糊点会让发现结果夭折:Agent 完成了任务,信号留在摘要里,却始终没有变成一句话。要特别留意两种具体情况,这两种情况都曾以断言的形式交付:一种模糊点本可为你已经在建议的杠杆量化规模(你保留了建议,却丢掉了让建议更有说服力的数字);另一种模糊点指出了某个原因,而另一个收集者手中正好掌握着可用于确认该原因的数据。
指定对象交叉核验:必须相互吻合的来源:
- 站点扫描未发现某项内容,而客户自己的事件流却显示有相关事件。 对于扫描实际发现了什么,它具有权威性;但对于它没有发现什么,它并不具有权威性。如果他们的团队收到的事件中,
$current_url带有该主机名,那么无论扫描结果如何,工具都已安装在该处。在任何缺失声明进入输出之前,先执行一次查询。 - 标题中的金额数字与发票核对。
- 多个收集者针对同一对象所得的结果相互核对。 最容易发生冲突的是可访问性声明:一个角色探测某张表,得到
Unknown table并报告无法衡量;另一个角色却成功读取了名称极其相似的表,并据此发布发现结果。于是两份摘要都各自为真,但输出中却出现了矛盾。动笔前,列出每个被多个角色查看过的对象,并确认他们对该对象是否存在以及其中包含什么内容意见一致。
第 4 轮核验,以及文档缓存
核验针对最终的声明列表进行;第 2 轮预热只会增加缓存内容,绝不能缩减列表。**如果缓存涵盖某项声明,就在主流程中核验;只针对缓存未覆盖的声明分发任务。**无论采用哪种方式,不变的要求都是:每项声明都要进行各自的实时搜索,而针对声明的搜索次数就是证明这一过程确实发生的依据。
每位核验者最多负责约 5 项声明,增加 Agent,而不是增加每位 Agent 负责的声明。这个上限关乎故障影响范围,而不是核验深度:核验是本次运行的最后一步,此时已没有时间重做;负责核验 12 项声明的 Agent 会成为单点故障,而且一旦它中途卡住,就什么也没写下来。如果核验者确实失效,应从其记录稿中接续,而不是从头重来,并要求它先写入文件,再回复,且将每项尚未核验的声明标记为 NOT REACHED。遗漏的声明看起来像是已核验,而这正是整轮核验要防止的问题。在发现结果之前就从 paidProducts 开始搜索,核验的是你预测的杠杆,而不是数据得出的杠杆;因此要先建立声明列表(参见 voice.md 中的草稿前检查),再核验其中提到的每个杠杆和产品事实。
三条规则决定缓存的使用方式,每份第 4 轮任务说明都必须逐字写明:缓存页面只有在某项声明自己的实时搜索返回了该 URL 时,才能作为该声明的证据;缓存只提供页面正文,不提供结论,也不能作为引文;缓存只在本次运行内有效,绝不使用此前运行获取的页面。核验者应返回结论、规范 URL 和逐字摘录的句子。允许返回 not found;若某项声明得到此结果,就应删除该声明,或改写为问题,绝不能配上一个看似最接近的链接后发布。优先引用 posthog.com/docs;没有文档页面涵盖相关内容时,引用 PostHog 撰写的 posthog.com/tutorials 页面;绝不引用 Community Questions 或 GitHub issues。如果无法访问 docs-search,就用普通 HTTPS 抓取逐一核验每个 URL。
收尾说明
各次运行之间不会累积任何内容,因此这份说明是唯一的记录。在步骤 5 中,输出完成后,在聊天中补充一则简短说明:哪些 Agent 运行时间较长,是否有任务说明超出上限,以及本次运行发现的、应纳入技能的修正建议。仅供参考。说明中应包含以下两类数据,因为它们都是正确性检查,而非遥测数据:
- **已抓取其自身引文 URL 的声明数,以及声明总数。**每次核验时都将结果追加到列表中,并据此统计;绝不能在运行结束时凭记忆统计:最后重构出的数字会被夸大,而夸大的数字比没有数字更糟,因为这个比例是区分真正的第 4 轮核验与热缓存的唯一依据。统计已抓取的引文,而不是已执行的搜索,因为搜索返回一篇看似相关且结论笃定的页面,正是这里要防止的失败;这种情况看起来和真正回答问题的搜索一模一样。
- **已启动的角色数与所需角色数,以及第 1 轮返回的 slug 数与第 1 轮所需的 slug 数。**两组数字都要列出。角色遗漏是明显的问题,无论如何都会被注意到;第 1 轮的 slug 悄然未运行则不易察觉,最终会以一个无人质疑的缺失出现在输出中。
- 第 3 轮列表中的发现、已解决和保留的保留措辞数量,分别列出。
除各角色外,main 还负责什么
第 2 轮子 Agent 负责读取表格中的数据;main 不会重新执行这些读取。除此之外,还包括 Vitally 的 get_account_conversations(上限 20;仅元数据,source: "google" 表示 Gmail 或日历,zendesk 表示支持),get_account_feature_requests 和 get_account_health。对话正文、备注和任务从数据仓库(vitally.messages、vitally.notes、vitally.tasks)获取,避免 MCP 调用因大型客户账户而溢出。**与该客户账户相关的所有邮件都会同步到 Vitally,因此这两项读取就构成了完整的通信记录,搜索邮箱不会带来额外信息。**不要去搜索邮箱:对联系人的名字使用 gog gmail search,曾匹配到另一域名下其他客户的个人地址,差点把该账户的付款失败情况写进这份简报。即使仍要搜索邮箱,也要先查看收件人域名,再判断邮件是否属于该客户账户。对重叠的数据,同时保留 Vitally 特征和 postgres 读取结果,并进行交叉核对。每次运行日历查询时都要明确指定开始和结束时间窗口(不限定范围的文本搜索不会返回结果),先根据 Accepted: 主题中的与会者姓名搜索;否则按客户账户名称和管理员邮箱搜索。
**main 需要手动记住这四个名称,因为它会在不打开查询文件的情况下核验数字。**表明某个限制确实导致产品被禁用的事件是 org_quota_limited_until;更改限制的记录是 billing limits updated;实时预测来自 billing_customer 与 billing_upcominginvoice 按 customer_id 联接;当前限制来自 custom_limits_map。在这里猜测列名会返回空结果,看起来就像“没有任何限制”,而事实恰恰相反。这些只是指引,不是操作步骤:编写任一查询前,先打开 queries-money.md 中的 quota-limits 和 in-flight-period,因为其中的筛选条件和别名决定了结果是否有意义;只根据本段编写 SQL,曾导致结果列全是 null,并触发硬性 400 错误。
每种角色都会进行通话准备。运行邮件模式时,如果某个角色负责的产品与该客户账户无关,就跳过该角色,并说明跳过了哪些角色。在所有模式下,只有在某项读取不适用时才跳过,并说明每项跳过的原因。回溯 30 天;只有在更短的窗口会改变结论时,才针对单个查询重新读取 14 天(后续跟进)或 7 天(同一周的通话准备)。
第 3 步:模式识别
**此步骤只标记,不会阻止任何操作;任何模式或请求都适用于任何客户账户。**此技能只起草内容,因此所有输出都由人工评估;遇到矛盾时要明确指出,绝不能因此拒绝处理。
如果客户账户看起来已经有人跟进,则在开头标记:onboardingMinimumEligibility 为 false、处于终止阶段(3. Onboarding Completed、6c. Sales Handoff、7. Paid Call Purchased),或队友最近发送过同一主题的消息。分配任务本身不构成标记;只有活跃对话线程才算。
用户的明确要求始终优先:“为 X 起草联络邮件”无论客户账户当前状态如何都会生成草稿,并在开头标记相关情况,绝不拒绝。以下信号仅用于用户要求不明确时的判断。粘贴的来信按回复处理;明确指定的通话按通话准备处理。
| 信号 | 读取位置 | 注意事项 | |
|---|---|---|---|
| stage | 流水线阶段 | vitally.custom.onboardingPipeline | 唯一承载阶段的字段,且每个当前有效值都带有数字前缀:1. New Account、2. Onboarding Initiated、3. Onboarding Completed、6c. Sales Handoff、7. Paid Call Purchased。必须匹配包含前缀的完整字符串;单独的 New Account 不匹配,未加前缀的 trait 键也不匹配。仍在实际数据中出现的已弃用值(3. Customer Engaged、3a./3b. Engaged、6a./6b. Onboarded、6d. Churned)会被标记,而不会用于路由。缺失表示从未进入流水线:只有当账户符合资格且没有 AE 或 CSM 时,才按 1. New Account 路由。若缺失状态伴有已分配的 AE 或 CSM,说明销售团队已接手:记录下来,仍然起草,并在措辞中加入协同邀请。已分配的 OS 在此处不代表任何含义 |
| outreach | 已发送首次联系 | vitally.custom.onboardingUsageOutreachSentDate | 若此字段为空,则回退到 vitally.custom.initialOutreach;即使已发送联系,这个字段也经常为空 |
| threads | 入门团队的对话 | vitally.messages.from 管理员 ID | AE 和支持团队的对话不计入 |
| meeting | 已预订的未来会议 | 对话 source: "google",且主题包含接受标记(Accepted: 或本地化形式,例如 Angenommen:) | 仅作提示。请在日历上确认日期;如果会议已过期、已拒绝或找不到,或者过时的接受记录导致路由永久停留在通话准备,则继续检查后续条件 |
| inbound | 外发后收到的入站消息 | lastInboundMessageTimestamp 与 lastOutboundMessageTimestamp | 会统计 AE、CSM 和支持团队的对话,也会在系统消息上触发,因此日历接受记录经常会让它显示为真。这只是提示你阅读最新入站消息的实际内容,绝不能单独作为路由依据。如果最新入站消息是日历或系统消息,或者发给了其他负责人,则不满足此信号 |
请从 trait 读取阶段,而不是从 crm_segments / Vitally segments 读取。这是不同字段,使用不同的分类词汇(Onboarding Lead、Onboarding referral、CSM Managed、TAM、Top 20、Annual Plan),不包含 New Account 和 Sales Handoff,也没有任何值与 trait 中的值完全匹配。Segments 可用于佐证阶段,并保存持久的销售转介记录;但它们绝不定义阶段。
仅对尚待处理的请求进行路由:
- 终止阶段,或队友 OR 发件人本人正在处理同一主题的对话:以标记为准,仍要完成全面调研,并建议记录备注或邀请协同,而不是冷启动联系。这只决定措辞,不决定模式,因此继续检查列表后续项;若在此处停止,则只有标记,没有输出。发件人本人近期的对话与队友的对话一样,都构成冲突:这并不意味着不能起草,而是说明草稿必须避免重复内容。
- inbound 为真,且最新入站消息是真实的客户问题:email,回复状态
- 否则,若存在会议:call prep
- 否则,阶段为
1. New Account,或阶段缺失且onboardingMinimumEligibility为真、没有 AE 或 CSM、outreach 为空、threads 为 0:email,首次联系状态。若缺失阶段伴有已分配的 AE 或 CSM,则继续检查第 4 项 - 否则:email,跟进状态
最终确定前,请先阅读对话历史;线程上下文优先于任何单一信号。出现以下情况时进行标记并继续:已有 outreach,但阶段仍为 1. New Account;阶段为 2. Onboarding Initiated,却没有 outreach 记录;非终止阶段出现终止时间戳,这是销售资格取消重置的典型特征。
第 4 步:账户中的人员(报告信息,不据此分派)
每次运行都在标题中报告人员名单,无论是否已分配:OS <name or NONE> · CSM <name or NONE> · AE <name or NONE>。使用间隔点,不要用竖线,否则会破坏表格单元格。
分配情况是人员名单数据,不是警告;活跃对话才需要标记。 入职引导完成后,OS 仍会保持已分配状态,而 AE 或 CSM 通常只是未分配,因此 NONE 很常见且正确。不要把任何一种情况说成“这属于 X,请先确认”:这会让人误以为是阻碍,通常也不准确,还会掩盖调研内容。最近就当前主题联系过该账户的团队成员才算冲突:应查看对话历史,而不是所有权字段。
数据来源按优先顺序排列:Vitally 的 key_roles 数组(account-context 中的 keyrole 行)会同时提供角色标签、邮箱和姓名;accounts_replacement_v2.csm_name / ae_name 以及 Vitally 的 csmId / accountExecutiveId 可用于佐证;billing_customer.sales_info.owner_* 和 crm_segments 是备用来源。Top 20 是账户等级,不是人员。若已设置 sfdc.last_task_disqualified__c,但没有 AE 或 CSM,说明销售已放弃跟进,账户又回到了入职引导阶段:请注明日期。
回答“该账户是否曾被转介给销售”时,请查看 Onboarding referral 细分群组,而不是阶段。 添加该细分群组会触发交接工作流,并且在阶段推进后仍会保留,因此被转介的账户通常会显示为其他阶段。请结合 sfdc.last_task_disqualified__c 查看:若存在该细分群组且已取消资格,说明账户已转回并归你负责;若存在该细分群组但尚未取消资格,说明销售仍在跟进。
对于年度方案,请查看 purchasedCreditAmount、stripe.accountBalance、creditRunwayDays、projectedCreditExpiryDate、creditExpiryVsAnnualPlanExpiry。若预计积分会在方案到期前用完,请写入标题,因为这会影响沟通内容,而不只是草稿。
步骤 5:输出
任何输出中都不得使用 em dash、en dash 或双连字符,包括内部简报。
在起草任何面向客户的内容之前,先阅读 references/voice.md;在提出任何建议之前,先阅读 references/levers.md;并阅读与该产出对应的模式文件。
每封邮件、每份通话准备简报,以及其他任何产出为客户简报的请求,都必须使用通用表头。将其呈现为真正的 Markdown 表格,绝不要放在代码围栏中(下方的围栏仅为模板源码)。不适用的行应删除,不要填写 n/a。
# <Account> | <Artifact: first touch / follow-up / reply / call prep>
> **Flag:** <only when one applies: a teammate's live thread on this topic, a stage mismatch, a sibling org. Never a roster name.>
| | |
|---|---|
| **Pipeline** | <stage> |
| **Health** | <X>/10 |
| **Renewal** | <date> (<n> days, monthly cycle or contract) |
| **MRR** | $<actual> booked to $<forecast> forecast (<±delta>) |
| **Paid** | <products> |
| **Limits set** | <product $each>. Mark any at or near its forecast; say NONE where the largest line has no limit |
| **Limits biting** | Which products a limit has ACTUALLY cut off, from `quota-limits`, each with its start date and whether it is dropping now. NONE is an answer and is written out. A limit that is set and a limit that is destroying data are different facts |
| **People** | OS <name or NONE> · CSM <name or NONE> · AE <name or NONE> |
| **Users** | <total> listed, <n> with seats, <n> with real activity |
| **Org** | <link> |
表头字段规则,因多个属性容易混淆:
| 字段 | 规则 |
|---|---|
| Renewal | 年度套餐中的 nextRenewalDate 是月度计费周期的结束日期,并非合同续约日期:使用 contractRenewalDate(或 numberOfDaysUntilEndOfContract)。月度账户中,两者相同 |
| MRR | usage_mrr 是实际值,forecasted_mrr 是预测值,两者都已扣除任何抵扣折扣(forecasted_usage_mrr 是总额,不要混用)。差值为 forecasted_mrr - usage_mrr;年度账户中的负差值通常是年度套餐与用量之间的统计假象,并非客户流失。diff_dollars 是另一种比较值 |
| Health | get_user_details 中的 healthScore。有两个数据仓库列同名,但都不应在此处使用:vitally.accounts.health_score 是过时的副本;account_health_scores.health_score 是 PostHog 自有的参与度模型 |
| 用户总数 | 使用 get_user_details 中的 usersCount;按查询统计的数量可能不同 |
| 组织链接 | https://us.posthog.com/project/2/groups/0/<ORG_ID>(组织属于群组类型 0) |
交付输出前,确认以下八项,并将任何未勾选的项目退回给负责该项目的轮次:每项声明都能追溯到本次运行的查询或实时搜索;逐个子 Agent 列出每项跳过的内容及原因;报告每个命中的陷阱,并对声称未命中任何陷阱的运行进行核查;每个来源之间的分歧都同时列出双方数值且保持未决;表头是真正的表格,且包含 People 行;在负责某主题的 Agent 仍在运行时,不得将该部分作为定论发布;Round 3 点名核对已完成,并说明任何人员不足;结尾说明中包含两个数值。
时间紧迫时最容易被牺牲的正是写入门槛,因此必须明确:仍在运行的 Agent 在返回前始终负责其主题。 等待它返回,或者撰写该部分并在输出中标记为暂定。标记为暂定即满足检查清单;作为定论发布则不满足。自信地写出某部分,而尚未完成的 Agent 随后又与之矛盾,会让用户不得不更正已经读过、甚至可能已经据此采取行动的内容。
工作流提醒
提醒用户需要进行的操作,除非用户明确要求执行,否则绝不要代为操作。一旦用户表示已采取行动:已发送外联消息,则将 Onboarding Pipeline 更新为 2. Onboarding Initiated;已转交销售,则更新为 6c. Sales Handoff。只有研究结论为“不要联系”时,才建议添加 Vitally 备注,并记录原因。仅起草的运行无需写入。
改进技能
如果某次运行发现需要修复的地方,询问是否要将其纳入。用尽可能少的文字,将规则写入负责该规则的文件,并修复失效的规则,而不是添加例外。两条规则可确保这些文件保持可靠。每一行都会改变模型的行为;来源信息、测量数据和背景说明应写入运行时永不加载的 README.md,否则就删掉。数据结构是持久的,应记录下来(连接键、不存在的列、会在不同区域间冲突的 ID);产品状态会变化,不应记录在任何地方(哪些 SDK 支持某项功能、某个套餐的价格、设置的位置),所以每次都要搜索,绝不要记录搜索结果。