posthog-customer-deep-dive

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

PostHog customer deep dive

PostHog客户深度分析

Research an account, then produce an email (first touch, follow-up, or reply), a call-prep brief with its notebook prompt, or for any other ask whatever shape the ask needs on the same research. Output goes to chat. Customer and account systems are read-only: never send, never post, never write to Vitally or PostHog. The only writes are local:
config.md
during Setup, and a per-run scratchpad (
deep-dive-<account>-<HHMM>
under the session scratchpad, or
/tmp
) holding the docs cache, the context file and the gatherer digests. The deliverable is generated once, into chat, and never also written to a file.
Input:
$ARGUMENTS
, usually an email address; also a domain, an account name, or a Vitally account id (either UUID kind).
Verify every claim before writing it. Every number comes from a query you ran this run; every product fact and link from a docs search you ran this run. Show a derived figure's arithmetic inline (
1.72M polls/day / 2,880 per instance = ~600 instances
) so a slipped digit is visible. Nothing from memory, nothing inferred from a number you did not pull. Where a claim cannot be verified, write the question instead: an unanswered question costs a follow-up, a confident wrong fact costs the relationship. Every table in this skill tells you what to check; none is a citation.
A claim about the customer's code or SDK config must quote that code from the site scan's fetched page or bundle. Data shows the effect; only code shows the cause, so an identity or billing mechanism is never named from event and billing data alone. Events the SDK emits (
$identify
,
$create_alias
, an explicit
$set
event) are assertable from those events; init options and config values need the quoted code.
Read
config.md
first: every per-user value and tool binding. A required value reading
<SET THIS>
means ask before the step that needs it, or run Setup. A source set to
none
is skipped silently; that is configuration, not a skip, and a run with every optional source at
none
is complete.
ReferenceRead when
config.md
Before Step 1, every run
references/agent-briefs.md
First, before Round 1a. It carries main's own reading-map row, so it decides what else main opens, plus the conventions block, the per-role map, the context file spec and the return rules
references/data-rules.md
Steps 1 and 2, by main, only the sections main's map row names. Gatherers never open it; they get its conventions inline
references/queries-account.md
Steps 1 and 2, by main (Round 1 slugs) and by the gatherers assigned to it
references/queries-products.md
,
references/queries-money.md
Step 2, by the gatherer whose map row names the section. Each file's headings are its index
references/site-scan.md
Round 0 (Round 2 on a public-provider admin email)
references/levers.md
Before drafting any recommendation, and by
docs-prewarm
to pick pages
references/voice.md
Before drafting anything a customer will see
references/mode-email.md
,
references/mode-call-prep.md
Step 5, the one file for the detected artifact. Any other ask runs the same research and takes the shape the ask needs
Batch main's reference reads into one block. Opening
data-rules.md
whole is the largest read in the critical path and most of it belongs to gatherers, who get it inline.
Two bundled scripts, each a floor and never a ceiling: run them, then keep going wherever the account points somewhere they do not reach, and name what you ran beyond them. A run that stops exactly where the scripts stop answered the script's questions instead of the customer's.
  • scripts/phq.py
    sends any HogQL query over the HTTP API on four targets (
    us
    project 2,
    eu
    EU project 1,
    ch-us
    /
    ch-eu
    the direct ClickHouse connection).
    --batch <file.jsonl>
    fires many at once, one JSON object per line with the keys
    name
    ,
    target
    ,
    sql
    and optional
    connection
    ; any other key set is a
    KeyError
    before the first query runs. The MCP stays primary; this is the fallback when the gateway is down and the only path for EU.
  • scripts/site-scan.sh <domain>
    runs the common shape of the Round 0 scan.
Every other HTTP call, Vitally REST included: build the JSON in Python inside a single-quoted heredoc (
python3 << 'PYEOF'
), write it to a file, send with
curl --data @file
. Bash expands
$1
,
$group_0
inside inline strings and this skill is full of
$
-prefixed names; Python's
urllib
hits
CERTIFICATE_VERIFY_FAILED
, so curl sends and Python only builds and parses.
调研账户,然后生成邮件(首次联系、跟进或回复)、带笔记本提示的通话准备简报,或根据其他需求生成基于同一调研内容的对应格式输出。输出内容会发送至聊天窗口。客户和账户系统为只读模式:绝不发送、发布或写入Vitally或PostHog。仅允许本地写入:设置期间写入
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
:包含每个用户的专属值和工具绑定。若所需值显示
<SET THIS>
,则在需要该值的步骤前询问用户,或运行设置流程。若源设置为
none
则自动跳过;这属于配置项,而非跳过操作,即使所有可选源都设为
none
,运行仍视为完成。
参考文档读取时机
config.md
每次运行的步骤1之前
references/agent-briefs.md
首先,在第1a轮之前。它包含主程序自身的读取映射行,决定主程序还需打开哪些内容,同时包含约定块、角色映射表、上下文文件规范和返回规则
references/data-rules.md
步骤1和步骤2,由主程序读取仅主程序映射行指定的章节收集器绝不打开此文件;相关约定会内联传递给收集器
references/queries-account.md
步骤1和步骤2,由主程序(第1轮标识)和分配到该任务的收集器读取
references/queries-products.md
references/queries-money.md
步骤2,由映射行指定对应章节的收集器读取。每个文件的标题即为索引
references/site-scan.md
第0轮(若为公共提供商管理员邮箱则在第2轮)
references/levers.md
起草任何建议之前,以及由
docs-prewarm
用于选择页面时
references/voice.md
起草任何客户可见内容之前
references/mode-email.md
references/mode-call-prep.md
步骤5,根据检测到的输出类型选择对应文件。其他类型的请求会执行相同调研,并生成符合需求格式的内容
将主程序的参考文档读取操作批量处理为一个块。完整打开
data-rules.md
是关键路径中最大的读取操作,且大部分内容属于收集器,收集器会通过内联方式获取这些内容。
附带两个脚本,仅作为基础而非上限:运行脚本后,需继续根据账户指向的内容进行拓展,并记录超出脚本范围的操作。若运行仅在脚本结束时停止,则回答的是脚本的问题而非客户的问题。
  • scripts/phq.py
    通过HTTP API向四个目标(
    us
    项目2、
    eu
    欧盟项目1、
    ch-us
    /
    ch-eu
    直接ClickHouse连接)发送任意HogQL查询。
    --batch <file.jsonl>
    可批量发送多个查询,每行一个JSON对象,包含**
    name
    target
    sql
    **键,可选
    connection
    键;若设置其他键,会在首个查询运行前触发
    KeyError
    。MCP为主要方式;当网关故障时,此为备用方式,也是欧盟区域的唯一路径。
  • scripts/site-scan.sh <domain>
    运行第0轮扫描的通用流程。
所有其他HTTP调用(包括Vitally REST):在单引号heredoc(
python3 << 'PYEOF'
)内用Python构建JSON,写入文件后,使用
curl --data @file
发送。Bash会展开内联字符串中的
$1
$group_0
,而本技能中包含大量
$
前缀的名称;Python的
urllib
会触发
CERTIFICATE_VERIFY_FAILED
,因此使用curl发送请求,仅用Python构建和解析数据。

Setup (first run, or when
config.md
is not yours)

设置(首次运行,或
config.md
非本人所有时)

Probe before asking, confirm before writing. In one pass: check the tool list for the PostHog exec gateway and the Vitally MCP; check the env for
POSTHOG_PERSONAL_API_KEY
,
POSTHOG_PERSONAL_API_KEY_EU
,
VITALLY_API_KEY
; check the tool list for each optional category in
config.md
, recording exact tool names; detect the timezone. Show one message with what was found, what is missing and the defaults you keep, ask only what cannot be probed (calendar id, booking link), and write
config.md
back. A missing optional tool is
none
; a missing required tool stops the run.
When something is missing, hand the person the fix, not a diagnosis.
README.md
carries the whole install, one numbered section per tool, each ending in a check that proves it worked, and it is never loaded at runtime, so read the section for whatever is missing and give them the commands from it inline: the shell and key setup (section 0), the MCP registration, the
op item get
command for a shared credential, or the mint URL for a personal one. Say which credentials are shared and which are theirs, since reusing someone else's PostHog personal key is the common mistake. Then re-probe and confirm before running. A first run that ends in "the Vitally MCP is missing" and nothing else is how a shared skill loses the person who was trying it.
先探测再询问,确认后再写入。一次性完成以下操作:检查工具列表中是否有PostHog执行网关和Vitally MCP;检查环境变量中是否存在
POSTHOG_PERSONAL_API_KEY
POSTHOG_PERSONAL_API_KEY_EU
VITALLY_API_KEY
;检查工具列表中
config.md
的每个可选类别,记录确切工具名称;检测时区。展示一条消息说明已找到的内容、缺失的内容以及保留的默认值,仅询问无法探测的信息(日历ID、预约链接),然后写回
config.md
。缺失的可选工具设为
none
;缺失的必填工具会终止运行。
当缺少内容时,直接提供解决方案,而非仅诊断问题
README.md
包含完整的安装步骤,每个工具对应一个编号章节,每个章节结尾都有验证步骤证明安装成功,且该文件不会在运行时加载,因此读取缺失工具对应的章节,并内联提供其中的命令:Shell和密钥设置(第0节)、MCP注册、共享凭证的
op item get
命令,或个人凭证的生成URL。说明哪些凭证是共享的,哪些是个人专属的,因为重复使用他人的PostHog个人密钥是常见错误。然后重新探测并确认后再运行。若首次运行仅提示“缺少Vitally MCP”而无其他内容,会导致尝试使用该共享技能的用户放弃。

Step 1. Resolve to a Vitally account

步骤1:解析为Vitally账户

InputPath
Email
mcp__vitally__get_user_details
. Take
accounts[0].id
and
accounts[0].externalId
(the PostHog org id)
Domainthe
sibling-sweep
queries. The MCP
search_users
ignores
limit
and returns hundreds of KB; avoid it
Account name
billing_customer WHERE name ILIKE '%...%'
. Self-serve signups share company names: prefer the row with non-null
crm_segments
/
plans_map
. The Vitally name tools are unreliable (
search_accounts_advanced
returns 0 for exact names,
search_accounts
ignores
showAllAccounts
,
find_account_by_name
is filtered to the caller's CSM)
Account IDuse directly
If all fail, ask for the account name. An id handed in can be either UUID kind: the Vitally MCP resolves both, the warehouse matches org ids on
vitally.accounts.external_id
only.
One customer carries a different name in each system, so check what the name you were handed refers to. Vitally's account name,
stripe.name
(often a legal entity),
zendesk.name
,
sfdc.Website
and the per-project names in
resolve-teams
are five independent fields that sometimes disagree, and the name you were given can turn out to be a near-empty second project rather than the account. State in the header which name maps to what whenever they diverge, and report on the project carrying the volume.
get_user_details
hits two limits. It can filter on large accounts (stripping custom traits, printing "Removed N trait fields"; a small account can come back complete, so the warehouse traits read is the authority either way) and can overflow on a several-hundred-user account. Split the read by source; custom traits carry a
vitally.custom.
prefix.
Read fromFields
vitally.accounts.traits
in the warehouse (
WHERE id = '<VITALLY_ACCOUNT_ID>'
), which
get_user_details
filters out
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
Always run the domain sibling sweep, both halves. A sibling org is a duplicate paying twice, a consolidation question, or an account a teammate owns. The two queries see different populations (
vitally.users
and
billing_customer
) and neither alone is complete; skipping one is how a duplicate-billing sibling stays hidden. Flag any sibling in the header: one org with multiple projects usually beats parallel orgs.
Then probe usage across every org the sweep returned and build the context on the one carrying the volume.
get_user_details
returns
accounts[0]
, whichever account Vitally lists first, and on a multi-org customer that is routinely not the live one, so every downstream figure would describe a dead org. This gates the Round 2 launch: no gatherer starts until it has resolved, because a gatherer given the wrong org id does perfect work on the wrong company.
sql
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
An org missing from the result has no Cloud usage in the window, which is an answer. Where the resolved org and the live org differ, say so in the header, report on the live one, and treat the resolved one as a sibling finding: a paid subscription on a dead org is money leaving for nothing.
输入路径
邮箱
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可解析两种类型,数据仓库仅在
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
返回
accounts[0]
,即Vitally列出的第一个账户,但对于多组织客户,这通常不是活跃组织,因此后续所有数据都会描述一个已停用的组织。这是第2轮启动的前置条件:收集器必须在解析完成后才能启动,因为若收集器使用错误的组织ID,会针对错误的公司完成完美的工作。
sql
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使用量,这也是一种有效结果。若解析出的组织与活跃组织不同,在标题中说明这一点,报告活跃组织的情况,并将解析出的组织视为关联发现:已停用组织上的付费订阅会造成不必要的资金流失。

Step 2. Parallel pull

步骤2:并行拉取数据

Put the PostHog MCP on project 2 (check its active-environment block;
switch-project
to
2
if not). It often defaults to a dev project where every query returns zero rows.
Detect the region from
vitally.custom.usEuInstance
(an array, e.g.
["US"]
), populated far more widely than
cloudRegion
; fall back to
cloudRegion
, then
traits.site_url
, where
eu.posthog.com
means EU. Project 2 answers almost everything for both regions; the exceptions are EU experiment definitions and the EU direct connection, both on the EU key and EU project 1.
将PostHog MCP切换到项目2(检查其活跃环境块;若未在项目2则使用
switch-project
切换)。它通常默认指向开发项目,该项目中所有查询都返回零行。
vitally.custom.usEuInstance
(数组类型,例如
["US"]
)检测区域,该字段的覆盖范围远大于
cloudRegion
;若该字段不存在,回退到
cloudRegion
,再回退到
traits.site_url
,其中
eu.posthog.com
表示欧盟区域。项目2几乎能回答两个区域的所有问题;例外情况是欧盟实验定义和欧盟直接连接,这两者需使用欧盟密钥和欧盟项目1。

How to fan out

如何分散任务

Fan the gathering out, reconcile in one head, then fan the verification out separately. The two fan-outs are not interchangeable: gathering happens before there are claims, verification after.
A round costs what its deepest agent costs, and an agent costs its longest chain of dependent calls. So batch first (every read depending only on the context goes out in one block; on the HTTP path one
phq.py --batch
), widen second (one grouped query per org and region rather than one per team, per
data-rules.md
), split last and only along a true dependency. No brief carries a serial chain longer than 8 to 10 reads: count the chain, not the calls.
  • Budget in tool calls, never seconds. Cap each gatherer at 12 to 15, and split the brief BEFORE launching if the list runs past 15. Nobody can estimate an agent's seconds; everyone can count reads in a brief.
  • The budget caps shape, never scope. Never drop, defer or narrow a read to fit it. The move when a brief is too big is to split it, and a read that still will not fit runs over budget and is named in the closing note.
  • Split by call count; never merge briefs by topic. Concurrent agents are near-free in wall clock; calls inside one agent serialize into one chain. Merging trades a cheap resource for an expensive one, so the instinct to reduce agent count is backwards.
  • 8 to 10 concurrent agents, each batching 4 wide. Past that ClickHouse returns 202 and the HTTP path returns 429, and the forced serial retries are slower than not splitting. The cap is machine-wide, so expect an occasional refusal and retry rather than shipping without the refused brief.
  • Splitting stops paying below about 12 calls, because every agent carries a fixed startup cost whatever you give it. Split down to 12 to 15, then stop.
  • A full call-prep set exceeds the concurrency cap, so it runs in waves. Wave one:
    change-point
    ,
    money-quotas
    ,
    clay
    .
    The first two frame every other finding (what moved, and what a limit actually cut off); the third is slowest.
    docs-prewarm
    goes last and never takes a required gatherer's slot.
RoundWhoDoes what
0. Pre-resolve1 subagent, the moment the domain is known
site-scan
on the admin email domain
1a. UnblockMain, three ordered batchesEverything deciding who to research and which batch to run, because both are wrong to guess. Batch 1: the Vitally resolve,
resolve-teams
, and BOTH halves of
sibling-sweep
. Batch 2: the multi-org volume probe, which names the live org. Batch 3, and only now that the live org is known: the scope probe, the stage trait,
get_account_conversations
and the calendar read. Then write the context file (
agent-briefs.md
), detect the mode, and launch Round 2. The three batches are ordered, not one block. Running the scope probe beside the sweep profiles whichever org
accounts[0]
happened to return, and on the multi-org account this gate exists for that is the dead one, so its numbers land in the context file every gatherer then trusts
1b. ResolveMain, one block, concurrent with Round 2
get_user_details
, the
vitally.accounts.traits
read,
account-state
,
account-spine
,
onboarding-state
,
other-account-tables
,
conversation-bodies
,
change-timeline
,
billing-limit-updates
,
account-context
,
touchpoint-timeline
. Naming these is what stops them being silently skipped.
billing-limit-updates
is one query, it names the actor behind every limit, and a limit moved before a call reframes the money picture
2. GatherThe roles below, in wavesEvery role the mode requires,
docs-prewarm
last
2. (concurrently)MainReading the conversation bodies, Step 4 roster, refining mode detection as
internal-context
returns
3. ReconcileMain, in ONE parallel blockThe roll call, the hedge sweep, the named pairs, then re-run the header figures and the number driving the top recommendation. List every claim the output will make. Surface any disagreement between two sources with both figures, never resolve it
4. VerifyMain where the cache covers the claim, one subagent per uncovered clusterA live docs search per claim, returning the URL and the verbatim line that proves it
5. WriteMainThe output
Give every subagent the path to the context file, the conventions block, its reading-map row, and the return rules, all from
agent-briefs.md
. The context is resolved once by main and no gatherer re-resolves any of it; a gatherer missing a value reports the gap rather than querying for it. The Round 0 agent is the exception: it gets only the domain.
Probe an optional source once. Gong, Slack and Clay share a shape: emptiness is knowable in one call, and a zero ends that source. Report "searched X, zero results", which is a finding and not a skip.
The role roster, what each owns and which sections each opens, is one table in
agent-briefs.md
.
It lives there because that is the file main reads to write the briefs, and two tables listing the same roles in two always-loaded files is how they drift apart.
agent-briefs.md
holds the exact sections each role opens, and that map is what the brief carries. Ask each gatherer 3 to 4 questions, not six: six numbered questions invite six investigations and turn a 12-call role into a 30-call one.
Probe an unfamiliar property in the block you are already firing, never in its own round: add
JSONExtractKeys(assumeNotNull(properties))
for that event.
先分散收集任务,在一个主程序中协调,再单独分散验证任务。这两个分散操作不可互换:收集在声明生成前进行,验证在声明生成后进行。
一轮任务的耗时取决于最深层代理的耗时,而代理的耗时取决于其最长的依赖调用链。因此先批量处理(所有仅依赖上下文的读取操作一次性发出;HTTP路径使用一个
phq.py --batch
命令),再拓宽范围(根据
data-rules.md
,每个组织和区域使用一个分组查询,而非每个团队一个查询),最后拆分且仅在存在真实依赖关系时拆分。任何简报的串行调用链长度不得超过8至10次:统计调用链长度,而非调用次数。
  • 按工具调用次数预算,而非时间。每个收集器的调用次数上限为12至15次,若列表超过15次,在启动前拆分简报。无人能准确估算代理的耗时;但所有人都能统计简报中的读取次数。
  • 预算限制的是任务形式,而非范围。绝不能为了适应预算而删除、延迟或缩小读取范围。当简报过大时,应拆分简报;若某个读取操作仍无法适应预算,则超出预算执行,并在结束说明中提及。
  • 按调用次数拆分;绝不要按主题合并简报。并发代理的耗时几乎可以忽略;单个代理内的调用会串行化为一条链。合并简报会浪费廉价资源(并发)而消耗昂贵资源(串行时间),因此减少代理数量的想法是错误的。
  • 8至10个并发代理,每个代理批量处理4个操作。超过此数量,ClickHouse会返回202错误,HTTP路径会返回429错误,强制串行重试的速度比不拆分更慢。此上限为机器全局限制,因此偶尔会遇到拒绝请求,需重试,而非跳过该简报。
  • 当调用次数低于约12次时,拆分不再有意义,因为每个代理都有固定的启动成本,无论任务多少。拆分到12至15次即可停止。
  • 完整的通话准备任务集超出并发上限,因此分波次运行。第一波:
    change-point
    money-quotas
    clay
    。前两个任务为其他所有发现奠定基础(哪些数据发生了变化,哪些限制实际影响了业务);第三个任务耗时最长。
    docs-prewarm
    最后运行,绝不占用必填收集器的资源槽。
轮次执行者任务内容
0. 预解析1个子代理,域名确定后立即执行对管理员邮箱域名执行
site-scan
1a. 解除阻塞主程序,三个有序批次完成所有决定调研对象运行批次的操作,因为这两者不能猜测。批次1:Vitally解析、
resolve-teams
以及关联扫描的两部分批次2:多组织业务量探测,确定活跃组织。批次3,仅在确定活跃组织后执行:范围探测、阶段属性、
get_account_conversations
和日历读取。然后写入上下文文件(
agent-briefs.md
),检测模式,启动第2轮。三个批次是有序的,而非一个块。若将范围探测与扫描并行执行,会对
accounts[0]
返回的任意组织进行分析,而对于多组织客户,这可能是已停用的组织,导致其数据被写入上下文文件并被所有收集器信任
1b. 解析主程序,一个块,与第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.(并发)主程序读取对话内容、步骤4的人员名单,随着
internal-context
返回结果优化模式检测
3. 协调主程序,一个并行块角色点名、不确定性扫描、成对验证,然后重新运行标题数据和驱动核心建议的关键数字。列出输出将包含的所有声明。若两个来源存在分歧,需展示两个数据,绝不自行解决
4. 验证缓存覆盖的声明由主程序验证,未覆盖的集群每个分配一个子代理每个声明对应一次实时文档搜索,返回URL和证明该声明的原文内容
5. 写入主程序生成输出内容
为每个子代理提供上下文文件路径、约定块、其读取映射行以及返回规则,所有内容均来自
agent-briefs.md
上下文由主程序一次性解析,收集器绝不重新解析任何上下文内容;若收集器缺少某个值,需报告缺口而非自行查询。第0轮代理是例外:仅提供域名。
仅探测一次可选源。Gong、Slack和Clay的逻辑相同:一次调用即可确定是否为空,若返回零结果则终止该源。报告“已搜索X,无结果”,这是一个有效发现而非跳过操作。
角色名单、每个角色的职责以及每个角色需打开的章节,在
agent-briefs.md
中以表格形式呈现
。该表格放在此处是因为主程序读取此文件来撰写简报,若两个始终加载的文件中存在相同角色的表格,会导致内容不一致。
agent-briefs.md
包含每个角色需打开的确切章节,该映射会随简报传递。每个收集器仅需回答3至4个问题,而非6个:6个编号问题会引发6次调查,将一个12次调用的角色变为30次调用。
在当前正在执行的块中探测不熟悉的属性,绝不单独开启一轮任务:为该事件添加
JSONExtractKeys(assumeNotNull(properties))

The scope probe, and its three guards

范围探测及其三个防护措施

The Round 1a scope probe (one
per-product-usage
aggregate) knows which products carry volume. Pass it into each product brief as known state, each line carrying its own proof (
billable_feature_flag_requests_count_in_period = 0 in billing_usage_by_org_date over the window
, never a bare "flags are zero"). It changes what a brief says, never whether it runs. Three guards, and it is unsafe without all three:
  1. Write
    unmeasurable, no source
    for every product the probe has no column for. Experiment volume and web analytics volume have no source anywhere; heatmap volume lives only on the usage report's
    teams
    map;
    ff_count
    lives on the org usage report.
  2. Check
    realm
    on
    org-snapshot
    before trusting a flat zero: the table is Cloud only, so a self-hosted org reads zero everywhere while emitting usage daily, and it carries no row for a day with no usage.
  3. Each brief says "verify against the product's own source and report what you found", never "confirm and move on".
Never use the probe to skip a role the mode requires. Skipping is where a zero and an unmeasurable become the same thing, which is the failure this skill exists to prevent. An agent that ran and confirmed a zero is not a skip.
第1a轮的范围探测(一个
per-product-usage
聚合查询)可了解哪些产品承载业务量。将探测结果作为已知状态传递给每个产品简报,每行都附带证明(例如
billing_usage_by_org_date
窗口内
billable_feature_flag_requests_count_in_period = 0
,绝不能仅写“标志数量为零”)。它会改变简报的内容,而非决定是否运行简报。需同时启用三个防护措施,否则探测结果不可靠:
  1. 对于探测中无对应列的产品,写入
    无法测量,无数据源
    。实验业务量和Web分析业务量无任何数据源;热图业务量仅存在于使用报告的
    teams
    映射中;
    ff_count
    存在于组织使用报告中。
  2. 在信任零值之前,检查
    org-snapshot
    realm
    字段:该表格仅适用于Cloud环境,因此自托管组织的所有字段都会读取为零,但实际每天都会产生使用量,且无使用量的日期不会有对应行。
  3. 每个简报需说明“对照产品自身数据源验证并报告结果”,绝不能写“确认后继续”。
绝不能使用探测结果跳过模式所需的角色。跳过操作会导致零值和无法测量的情况被视为同一回事,而这正是本技能要避免的失败。运行并确认零值的代理不属于跳过操作。

Round 3: roll call, hedge sweep, named pairs

第3轮:角色点名、不确定性扫描、成对验证

Three checks before any re-run, because each catches a whole missing input rather than a wrong digit.
The roll call. List the roles the mode requires against the roles launched, and the Round 1 slugs required against the ones that returned. Every gatherer justifies its own skipped reads, but nothing checks the level above it, so a role dropped under time pressure disappears silently while the output reads complete. Launch the gap, or name it in the closing note. "Short of time" is a reason to state, never a reason to omit. This matters most on an ask that is neither an email nor a brief, where there is no expected section for the reader to notice missing.
The hedge sweep, and it produces a list, not an intention. Read every digest for the gatherers' own uncertainty ("probable not proven", "unmeasurable", "needs confirming", "worth reconciling", "the biggest skip is", "ask on the call") and write every hit into a numbered list before closing any of them. Each line gets a disposition: closed with the number, or carried into the output as a named open question. A hedging gatherer has almost always named the read that would close it, so each is cheap and pre-scoped.
Doing this in your head is what fails. A sweep held as an intention closes the easy hedges, and the one it drops is reliably the one sitting under the top recommendation, because that is the hedge whose answer takes work. An unresolved hedge is how a finding dies: the agent did its job, the signal sits in the digest, and it never becomes a sentence. Two specific shapes to catch, both of which have shipped as assertions: a hedge that would have SIZED a lever you are already recommending (you keep the recommendation and lose the number that makes it land), and a hedge naming a cause that another gatherer was holding the data to confirm.
The named pairs, sources that must agree:
  • A site-scan absence against the customer's own event stream. The scan is authoritative for what it found, never for what it did not. If their team receives events carrying that host in
    $current_url
    , the tool is installed there whatever the scan said. One query, before any absence claim reaches the output.
  • The header's money figures against the invoice.
  • Two gatherers against each other, wherever they touched the same object. Reachability claims are the ones that collide: one role probes a table, gets
    Unknown table
    and reports unmeasurable, while another reads a near-identical name successfully and publishes a finding from it. Both digests are then true and the output carries a contradiction. Before writing, list every object more than one role touched and confirm they agree on whether it exists and what it holds.
在重新运行任何操作前进行三项检查,因为每项检查都能发现整个缺失的输入,而非单个错误数字。
角色点名。列出模式所需的角色与已启动的角色,以及所需的第1轮标识与已返回的标识。每个收集器会自行说明跳过的读取操作,但无人检查更高层级的内容,因此若因时间压力放弃某个角色,它会静默消失,而输出看起来仍然完整。启动缺失的角色,或在结束说明中提及。“时间不足”是需说明的原因,而非省略的理由。这在既非邮件也非简报的请求中尤为重要,因为读者无法通过预期的章节发现缺失内容。
不确定性扫描,需生成列表而非仅记录意图。读取每个收集器摘要中的不确定性表述(如“可能但未证实”、“无法测量”、“需确认”、“值得协调”、“最大的跳过项是”、“通话时询问”),在关闭任何收集器前将所有命中项写入编号列表。每行需标注处理结果:已用数字确认,或作为未解决问题带入输出。提出不确定性的收集器通常已指出可解决该问题的读取操作,因此每个问题的解决成本低且范围明确。
仅在脑中进行扫描会导致失败。若仅将扫描作为意图,会解决容易的不确定性问题,而被遗漏的往往是核心建议背后的不确定性,因为解决该问题需要付出努力。未解决的不确定性会导致发现失效:代理已完成任务,信号存在于摘要中,但从未转化为句子。需特别注意两种情况,这两种情况曾被作为断言输出:一种是会影响已推荐方案效果的不确定性(保留建议但失去使其有说服力的数字),另一种是指出另一个收集器已掌握数据可确认原因的不确定性。
成对验证,必须一致的数据源:
  • 站点扫描未发现的内容与客户自身事件流的对比。扫描仅对已发现的内容具有权威性,对未发现的内容不具有权威性。若客户团队接收的事件中
    $current_url
    包含该主机,则无论扫描结果如何,该工具已安装在该主机上。在输出任何“未发现”声明前,执行一次查询。
  • 标题中的资金数据与发票的对比
  • 两个收集器对同一对象的分析结果对比。可达性声明最容易冲突:一个角色探测某张表,得到
    Unknown table
    并报告无法测量,而另一个角色读取名称几乎相同的表成功并发布发现。两个摘要都是真实的,但输出会存在矛盾。在撰写输出前,列出所有被多个角色处理的对象,确认它们对对象是否存在以及内容的判断一致。

Round 4 verification, and the docs cache

第4轮验证与文档缓存

Verification runs against the finished claim list; Round 2 pre-warming is additive and may never shrink it. Where the cache covers a claim, verify in main; fan out only for the claims it missed. The invariant holds either way: every claim gets its own live search, and the count of searches against claims is what proves it happened. Searching from
paidProducts
before the findings exist verifies levers you predicted and misses the ones the data produced, so build the claim list first (the pre-draft check in
voice.md
), then verify every lever and product fact it states.
Three lines govern the cache, and every Round 4 brief states them verbatim: a cached page is evidence only for a claim whose OWN live search returned that URL; the cache supplies a page body, never a verdict and never a citation; the cache is per run, never a page a previous run fetched. A verifier returns the verdict, the canonical URL and the verbatim sentence.
not found
is permitted, and a claim that comes back that way is cut or rewritten as a question, never shipped with the nearest-looking link. Cite
posthog.com/docs
first, a PostHog-authored
posthog.com/tutorials
page when no docs page covers it, never Community Questions or GitHub issues. If
docs-search
is unreachable, verify each URL with a plain HTTPS fetch.
验证针对已完成的声明列表;第2轮预加载是补充操作,可能不会缩小声明列表。若缓存覆盖了声明,在主程序中验证;仅对缓存未覆盖的声明分散验证。无论哪种情况,都需遵守不变原则:每个声明都需进行实时搜索,搜索次数与声明数量一致可证明验证已执行。在发现结果生成前从
paidProducts
进行搜索,会验证预测的方案而遗漏数据产生的方案,因此需先构建声明列表(
voice.md
中的预草稿检查),然后验证声明中提及的所有方案和产品事实。
缓存需遵守三条规则,每个第4轮简报需逐字说明:缓存页面仅能作为对应声明的实时搜索返回该URL时的证据;缓存仅提供页面内容,绝不提供结论或引用;缓存为每次运行专属,绝不使用之前运行获取的页面。验证器需返回结论、规范URL和原文句子。允许返回
未找到
,若声明返回此结果,需删除该声明或改写为问题,绝不附带看似相关的链接。优先引用
posthog.com/docs
,若无对应文档页面,引用PostHog官方的
posthog.com/tutorials
页面,绝不引用社区问题或GitHub议题。若
docs-search
不可用,通过普通HTTPS请求验证每个URL。

The closing note

结束说明

Nothing accumulates across runs, so this note is the only record. At Step 5, after the output, add a short note in chat: which agents ran long, any brief that overran its cap, and any fix this run revealed that belongs in the skill. Suggestion only. Two figures belong in it because both are correctness checks, not telemetry:
  • Claims whose own citation URL you fetched, against total claims. Count it from a list you append to as each verification happens, never from memory at the end of the run: a number reconstructed at the end is a number you will inflate, and an inflated count is worse than none, because the ratio is the only thing standing between a real Round 4 and a warm cache. Count fetched citations rather than searches run, because a search that returned a confident, adjacent page is exactly the failure this is watching for and it looks identical to a search that answered the question.
  • Roles launched against roles required, and Round 1 slugs returned against Round 1 slugs required. Print both halves. A dropped role is the loud half and gets noticed anyway; a Round 1 slug that quietly never ran is the silent half, and it is the one that reaches the output as an absence nobody questioned.
  • Hedges found, closed, and carried, as three numbers from the Round 3 list.
运行间无数据累积,因此本说明是唯一记录。在步骤5完成输出后,在聊天窗口添加简短说明:哪些代理运行时间过长、哪些简报超出调用上限、本次运行发现的需修复的技能问题。仅作为建议。以下两个数据需包含在说明中,因为它们是正确性检查而非遥测数据:
  • 已获取引用URL的声明数量与总声明数量的对比。在每次验证时将声明添加到列表中进行统计,绝不依赖记忆在运行结束时统计:运行结束时重构的数字会被夸大,而夸大的计数比无计数更糟,因为该比例是区分真实第4轮验证与缓存复用的唯一依据。统计已获取的引用URL数量而非运行的搜索次数,因为返回看似相关页面的搜索正是本检查要防范的失败,且与正确回答问题的搜索无法区分。
  • 已启动的角色与所需角色的对比,以及已返回的第1轮标识与所需第1轮标识的对比。打印两部分内容。缺失的角色是明显的部分,容易被注意到;而静默未运行的第1轮标识是隐藏的部分,会导致输出中出现无人质疑的缺失内容。
  • 发现的不确定性问题数量、已解决数量和带入输出的数量,来自第3轮列表的三个数字。

What main covers beyond the roles

主程序超出角色职责的处理内容

Round 2 subagents own the reads in the table; main does not re-run them. Beyond it: Vitally's
get_account_conversations
(limit 20; metadata only,
source: "google"
is Gmail or calendar,
zendesk
is support),
get_account_feature_requests
,
get_account_health
. Conversation bodies, notes and tasks come from the warehouse (
vitally.messages
,
vitally.notes
,
vitally.tasks
), avoiding the MCP calls that overflow on large accounts. Every email with the account syncs to Vitally, so that pair of reads is the whole comms record and a mailbox search adds nothing. Do not reach for one: a
gog gmail search
on a contact's first name matched a different customer's personal address at another domain and nearly put that account's failed payment into this brief. If you search a mailbox anyway, read the recipient domain before treating a message as this account's. Keep both the Vitally traits and the postgres reads where they overlap and cross-check them. The calendar runs every run with an explicit start/end window (unbounded text searches return nothing), by attendee name from the
Accepted:
subject, else by account name and admin emails.
Four names main needs by hand, because main verifies numbers without opening a query file. The event saying a limit actually cut a product off is
org_quota_limited_until
; who changed a limit is
billing limits updated
; the live forecast is
billing_customer
joined to
billing_upcominginvoice
on
customer_id
; current limits are
custom_limits_map
. A guessed column here returns an empty result that reads exactly like "nothing was limited", the inverse of the truth. These are pointers, not recipes: open
quota-limits
and
in-flight-period
in
queries-money.md
before writing either query
, because both carry filters and aliases that decide whether the result means anything, and writing SQL from this paragraph alone has produced a column of nulls and a hard 400.
Call prep runs every role. An email run drops the roles whose product the account does not implicate, and names each one dropped. In every mode, skip an individual read only when it cannot apply, and name each skip. Lookback 30 days; re-run a single query at 14 days (follow-up) or 7 (same-week call prep) only where the shorter window changes the conclusion.
第2轮子代理负责表格中的读取操作;主程序绝不重新运行这些操作。除此之外,主程序还需处理: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:
主题中的参会者姓名查询,否则按账户名称和管理员邮箱查询。
主程序需手动记住四个名称,因为主程序无需打开查询文件即可验证数字。表示限制实际影响产品的事件是**
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会生成全空列和400错误。
通话准备需运行所有角色。邮件运行可跳过账户未涉及的产品对应的角色,并列出每个被跳过的角色。在任何模式下,仅当读取操作不适用时才跳过,并列出每个跳过的操作。回溯30天的数据;仅当更短的窗口会改变结论时,才重新运行单个查询(跟进邮件回溯14天,当周通话准备回溯7天)。

Step 3. Mode detection

步骤3:模式检测

This step flags, it never blocks; any mode or request runs on any account. The skill only drafts, so a human weighs every output; raise contradictions loudly, never withhold work.
Flag at the top when the account looks already-handled:
onboardingMinimumEligibility
false, a terminal stage (
3. Onboarding Completed
,
6c. Sales Handoff
,
7. Paid Call Purchased
), or a teammate's recent message on the same topic. An assignment is never a flag; only an active thread is.
The user's explicit ask always wins: "draft outreach for X" produces the draft whatever the account state, flag leading, never a refusal. The signals below decide only when the ask is open. A pasted inbound is a reply; a named call is call prep.
SignalRead fromWatch for
stagePipeline stage
vitally.custom.onboardingPipeline
The only field carrying the stage, and every live value carries a numeric prefix:
1. New Account
,
2. Onboarding Initiated
,
3. Onboarding Completed
,
6c. Sales Handoff
,
7. Paid Call Purchased
. Match the full string including the prefix; a bare
New Account
matches nothing, and neither does the unprefixed trait key. Retired values still in the wild (
3. Customer Engaged
,
3a.
/
3b. Engaged
,
6a.
/
6b. Onboarded
,
6d. Churned
) get flagged rather than routed on. Absent means never entered the pipeline: it routes like
1. New Account
only when the account is eligible with no AE or CSM. Absent beside an assigned AE or CSM means sales has it: note it, still draft, put the loop-in in the framing. An assigned OS says nothing here
outreachInitial outreach sent
vitally.custom.onboardingUsageOutreachSentDate
Fall back to
vitally.custom.initialOutreach
, often null even when outreach went out
threadsOnboarding-team conversations
vitally.messages.from
admin ids
AE and support threads do not count
meetingBooked FUTURE meetingconversation
source: "google"
with an acceptance subject (
Accepted:
, localized, e.g.
Angenommen:
)
A hint only. Confirm the date on the calendar and fall through if the meeting is past, declined or not found, or a stale acceptance routes to call prep forever
inboundInbound after outbound
lastInboundMessageTimestamp
vs
lastOutboundMessageTimestamp
Counts AE, CSM and support threads and fires on system messages, so it is often true off a calendar acceptance. A prompt to read the actual latest inbound body, never a reason to route alone. If the latest inbound is a calendar or system message, or is addressed to another owner, the signal is not satisfied
Read the stage from the trait, not from
crm_segments
/ Vitally
segments
. Those are a different field with a different vocabulary (
Onboarding Lead
,
Onboarding referral
,
CSM Managed
,
TAM
,
Top 20
,
Annual Plan
), carry no
New Account
and no
Sales Handoff
, and no value matches the trait's exactly. Segments corroborate the stage and hold the durable sales-referral record; they never define the stage.
Routing, for an open ask only:
  1. Terminal stage, or a live thread on the same topic held by a teammate OR by the sender: the flag leads, the research is still full, and the recommendation is a note or a loop-in rather than cold outreach. This sets the framing and never the mode, so carry on down the list; a run that stops here has a flag and no output. The sender's own recent thread is a collision exactly like a teammate's: not a reason to withhold a draft, but the thing the draft must not repeat.
  2. inbound true and the latest inbound is a real customer question: email, reply state
  3. else meeting present: call prep
  4. else stage is
    1. New Account
    , or absent with
    onboardingMinimumEligibility
    true and no AE or CSM, outreach null, threads 0: email, first-touch state. An absent stage beside an assigned AE or CSM falls through to 4
  5. else email, follow-up state
Read the conversation history before finalizing; thread context outranks any single signal. Flag and keep going on: outreach exists but stage still
1. New Account
; stage
2. Onboarding Initiated
with no outreach trace; terminal timestamps on a non-terminal stage, the signature of a sales-disqualification reset.
此步骤仅标记,绝不阻塞;任何模式或请求都可在任何账户上运行。本技能仅负责起草内容,因此所有输出都需由人工审核;需大声指出矛盾之处,绝不停止工作。
当账户看起来已被处理时,在顶部标记:
onboardingMinimumEligibility
为false、处于终端阶段(
3. Onboarding Completed
6c. Sales Handoff
7. Paid Call Purchased
),或队友近期就同一主题发送过消息。分配任务不属于标记;仅活跃线程属于标记。
用户的明确请求始终优先:“为X起草外展邮件”会生成草稿,无论账户状态如何,标记放在开头,绝不拒绝。仅当请求不明确时,以下信号才会起作用。粘贴的入站消息属于回复;指定的通话属于通话准备。
信号读取来源注意事项
阶段管道阶段
vitally.custom.onboardingPipeline
这是唯一携带阶段信息的字段,所有有效值都带有数字前缀:
1. New Account
2. Onboarding Initiated
3. Onboarding Completed
6c. Sales Handoff
7. Paid Call Purchased
。需匹配包含前缀的完整字符串;仅
New Account
无法匹配任何内容,无前缀的属性键也无法匹配。仍在使用的已废弃值(
3. Customer Engaged
3a.
/
3b. Engaged
6a.
/
6b. Onboarded
6d. Churned
)需标记而非用于路由。缺失表示从未进入管道:仅当账户符合条件且无AE或CSM时,才按
1. New Account
路由。若缺失阶段且已分配AE或CSM,则表示销售团队负责该账户:需注明此情况,仍需起草内容,并在框架中添加抄送信息。已分配OS不影响此处的判断
外展首次外展已发送
vitally.custom.onboardingUsageOutreachSentDate
回退到
vitally.custom.initialOutreach
,该字段即使外展已发送也常为空
线程入职团队对话
vitally.messages.from
管理员ID
AE和支持线程不计入
会议已预订的未来会议来源为
google
且主题包含接受信息(
Accepted:
,本地化版本如
Angenommen:
)的对话
仅作为提示。需在日历上确认日期,若会议已过去、被拒绝或未找到,或接受信息已过期,则不路由到通话准备
入站外展后的入站消息
lastInboundMessageTimestamp
lastOutboundMessageTimestamp
对比
计入AE、CSM和支持线程,且会因系统消息触发,因此常因日历接受消息而返回true。这仅提示需读取最新的入站消息内容,绝不能单独作为路由依据。若最新入站消息是日历或系统消息,或发送给其他负责人,则该信号不满足
从属性读取阶段,而非从
crm_segments
/Vitally
segments
读取。这些是不同的字段,使用不同的词汇(
Onboarding Lead
Onboarding referral
CSM Managed
TAM
Top 20
Annual Plan
),不包含
New Account
Sales Handoff
,且无值与属性中的值完全匹配。Segments可佐证阶段,并保留持久的销售推荐记录;但绝不定义阶段。
仅当请求不明确时进行路由: 0. 处于终端阶段,或队友/发送者就同一主题存在活跃线程:标记放在开头,调研内容仍完整,建议为备注或抄送而非冷外展。这仅设置框架而非模式,因此继续执行后续步骤;若运行在此处停止,仅会有标记而无输出。发送者自身的近期线程与队友的线程冲突情况完全相同:不是拒绝起草的理由,但草稿必须避免重复内容。
  1. 入站信号为true且最新入站消息是客户的真实问题:邮件,回复状态
  2. 否则存在会议通话准备
  3. 否则阶段为
    1. New Account
    ,或阶段缺失且
    onboardingMinimumEligibility
    为true、无AE或CSM、外展为空、线程数为0:邮件,首次联系状态。若阶段缺失且已分配AE或CSM,则进入步骤4
  4. 否则邮件,跟进状态
在最终确定前读取对话历史;线程上下文优先级高于任何单个信号。标记并继续执行以下情况:外展已发送但阶段仍为
1. New Account
;阶段为
2. Onboarding Initiated
但无外展痕迹;非终端阶段存在终端时间戳(销售取消资格重置的特征)。

Step 4. People on the account (report it, do not route on it)

步骤4:账户相关人员(仅报告,不用于路由)

Report the roster in the header every run, assigned or not:
OS <name or NONE> · CSM <name or NONE> · AE <name or NONE>
. Middle dots, never pipes, which break the table cell.
An assignment is roster data, never a warning; an active thread earns the flag. An OS stays assigned after onboarding completes and an AE or CSM is often simply unassigned, so
NONE
is common and correct. Never turn either into "this belongs to X, check first": it reads as a blocker, is usually wrong, and buries the research. A teammate who recently messaged the account on the topic at hand IS a collision: read the conversation history for that, not the ownership fields.
Sources in order: the Vitally
key_roles
array (the
keyrole
rows in
account-context
) carries role label, email and name together;
accounts_replacement_v2.csm_name
/
ae_name
and Vitally
csmId
/
accountExecutiveId
corroborate;
billing_customer.sales_info.owner_*
and
crm_segments
are the fallback.
Top 20
is an account tier, not a person. A set
sfdc.last_task_disqualified__c
with no AE or CSM means sales passed and onboarding has the account again: note the date.
To answer "was this account ever referred to sales", read the
Onboarding referral
segment, not the stage.
Adding that segment fires the handoff playbook and it persists after the stage moves on, so a referred account routinely shows another stage. Read it with
sfdc.last_task_disqualified__c
: segment present and disqualified means it came back and is yours; segment present and not disqualified means sales still has it.
On annual plans read
purchasedCreditAmount
,
stripe.accountBalance
,
creditRunwayDays
,
projectedCreditExpiryDate
,
creditExpiryVsAnnualPlanExpiry
. Credits projected to run out before the plan ends go in the header, since they change the conversation rather than the draft.
每次运行都需在标题中报告人员名单,无论是否已分配:
OS <姓名或NONE> · CSM <姓名或NONE> · AE <姓名或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
segment,而非阶段
。添加该segment会触发转交流程,且阶段变更后仍会保留,因此已推荐的账户通常会显示其他阶段。结合
sfdc.last_task_disqualified__c
读取:存在segment且已取消资格,表示账户已返回并由你负责;存在segment且未取消资格,表示销售团队仍负责该账户。
对于年度计划,需读取
purchasedCreditAmount
stripe.accountBalance
creditRunwayDays
projectedCreditExpiryDate
creditExpiryVsAnnualPlanExpiry
。若预计信用额度在计划到期前耗尽,需在标题中说明,因为这会改变对话内容而非草稿内容。

Step 5. Output

步骤5:输出

No em dashes, en dashes or double hyphens in any output, the internal brief included.
Read
references/voice.md
before drafting anything a customer will see,
references/levers.md
before stating any recommendation, and the one mode file for the artifact.
Common header for every email and every call-prep brief, and for any other ask whose artifact is an account brief. Render it as a real markdown table, never inside a code fence (the fence below is only the template's source). Drop a row that does not apply rather than writing
n/a
.
undefined
任何输出(包括内部简报)中不得使用破折号、短破折号或双连字符。
在起草任何客户可见内容前,读取
references/voice.md
;在提出任何建议前,读取
references/levers.md
;并读取对应模式的文件。
所有邮件和通话准备简报,以及任何其他生成账户简报的请求,都需使用通用标题。将其渲染为真实的Markdown表格,绝不放在代码块中(以下代码块仅为模板来源)。若某行不适用则删除,绝不写
n/a
undefined

<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 bitingWhich 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
PeopleOS <name or NONE> · CSM <name or NONE> · AE <name or NONE>
Users<total> listed, <n> with seats, <n> with real activity
Org<link>

Header field rules, since several traits collide:

| Field | Rule |
|---|---|
| Renewal | On an annual plan `nextRenewalDate` is the monthly billing-cycle end, not the contract renewal: use `contractRenewalDate` (or `numberOfDaysUntilEndOfContract`). On a monthly account they are the same |
| MRR | `usage_mrr` is actual, `forecasted_mrr` is forecast, both net of any credit discount (`forecasted_usage_mrr` is the gross figure, do not mix them). Delta is `forecasted_mrr - usage_mrr`; a negative delta on an annual account is usually the annual-vs-usage artifact, not churn. `diff_dollars` is a different comparison |
| Health | `healthScore` from `get_user_details`. Two warehouse columns share the name and neither belongs here: `vitally.accounts.health_score` is a stale copy, `account_health_scores.health_score` is PostHog's own engagement model |
| Users total | `usersCount` from `get_user_details`; per-query counts can differ |
| Org link | `https://us.posthog.com/project/2/groups/0/<ORG_ID>` (organization is group type 0) |

Before handing the output over, confirm all eight and send anything unchecked back to the round that owns it: every claim traces to this run's queries or live searches; every skip is named per subagent with its reason; every trap hit is reported, and a run reporting none was interrogated; every source disagreement shows both figures unresolved; the header is a real table with the People row present; **no section shipped as settled while an agent owning its subject was still running**; the Round 3 roll call ran and any shortfall is named; the closing note states both figures.

**The write gate is the one traded away under time pressure, so it is explicit: a still-running agent owns its subject until it returns.** Wait for it, or write the section and mark it provisional in the output. Marked provisional satisfies the checklist; shipped as settled does not. A confident section a pending agent then contradicts costs the user a correction on work they have already read, and may have already acted on.
标记: <仅在适用时填写:队友就该主题的活跃线程、阶段不匹配、关联组织。绝不填写名单姓名。>
管道阶段<阶段内容>
健康评分<X>/10
续约日期<日期>(剩余<n>天,月度周期或合同)
MRR已入账$<实际值>,预测$<预测值>(<±差值>)
已购产品<产品列表>
已设置限制<产品 $每个>。标记任何接近预测值的限制;若最大项目无限制则写NONE
生效限制哪些产品实际被限制影响,来自
quota-limits
,每个限制需包含开始日期和当前是否仍在生效。NONE是有效答案,需完整写出。已设置限制和正在影响数据的限制是不同的事实
相关人员OS <姓名或NONE> · CSM <姓名或NONE> · AE <姓名或NONE>
用户数量共<总数量>,<n>个拥有席位,<n>个有真实活动
组织链接<链接>

标题字段规则,因多个属性存在冲突:

| 字段 | 规则 |
|---|---|
| 续约日期 | 对于年度计划,`nextRenewalDate`是月度计费周期结束日期,而非合同续约日期:需使用`contractRenewalDate`(或`numberOfDaysUntilEndOfContract`)。对于月度账户,两者相同 |
| MRR | `usage_mrr`是实际值,`forecasted_mrr`是预测值,均扣除信用折扣(`forecasted_usage_mrr`是总值,请勿混淆)。差值为`forecasted_mrr - usage_mrr`;年度账户的负差值通常是年度计费与使用量的差异,而非流失。`diff_dollars`是另一种比较方式 |
| 健康评分 | 来自`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) |

在交付输出前,确认以下八项内容,将未检查的内容返回给负责的轮次:所有声明都可追溯到本次运行的查询或实时搜索;每个跳过操作都由子代理注明原因;每个陷阱触发情况都已报告,未报告任何陷阱的运行需重新检查;所有来源分歧都展示了两个未解决的数据;标题为真实表格且包含相关人员行;**无任何部分在负责该内容的代理仍在运行时被标记为已完成**;第3轮角色点名已执行,任何缺口都已注明;结束说明包含两个统计数字。

**写入闸门是时间压力下最容易被忽略的部分,因此需明确:仍在运行的代理对其负责的内容拥有所有权,直到返回结果**。等待代理返回,或撰写该部分并在输出中标记为临时内容。标记为临时内容符合检查清单要求;标记为已完成则不符合。若已完成的部分被后续返回的代理结果矛盾,会导致用户需要更正已阅读甚至已执行的工作。

Pipeline reminder

管道提醒

A reminder to raise with the user, never a write to perform unless they ask for that write in so many words. Once they say they acted: outreach sent means
Onboarding Pipeline
moves to
2. Onboarding Initiated
; passed to sales means
6c. Sales Handoff
. Suggest a Vitally note only when the research concluded "do not reach out", recording why. A draft-only run needs no write.
此提醒需告知用户,绝不自行执行写入操作,除非用户明确要求。当用户表示已执行操作时:外展邮件已发送表示
Onboarding Pipeline
需改为
2. Onboarding Initiated
;转交销售团队表示
6c. Sales Handoff
。仅当调研结论为“请勿联系”时,才建议在Vitally中添加备注,记录原因。仅起草内容的运行无需执行写入操作。

Improving the skill

技能改进

When a run reveals a fix, ask whether to fold it in. Write the rule into the file that owns it, in the fewest words, and fix the rule that failed rather than adding an exception. Two rules keep these files honest. Every line changes what the model does; provenance, measurements and orientation go to
README.md
, which is never loaded at runtime, or get cut. The shape of the data is durable and gets written down (a join key, a column that does not exist, an id that collides across regions); the state of the product is perishable and belongs nowhere (which SDKs support a feature, what a tier costs, where a setting lives), so search for it every time and never record the answer.
当运行发现需修复的问题时,询问用户是否将修复整合到技能中。将规则写入负责该内容的文件,用最少的文字表述,修复失败的规则而非添加例外。两条规则可保持文件的准确性。每一行内容都需改变模型的行为;来源、测量和说明性内容需放入
README.md
(该文件不会在运行时加载)或删除。数据的结构是持久的,需记录下来(关联键、不存在的列、跨区域冲突的ID);产品的状态是易变的,绝不记录(哪些SDK支持某功能、某层级的价格、设置位置),因此每次都需搜索,绝不记录答案。