开发工具·2026年6月10日·7 分钟

如何使用 Claude Code 构建 AI GTM Brain

作者拆解了如何用 Claude Code 构建一个 AI GTM Brain:从感知市场、记录账户记忆、判断触发时机、生成行动草稿到从反馈中学习。文章强调真正有价值的不是更快群发,而是把 GTM 判断封装成可运行、可审计、可迭代的系统。

如何使用 Claude Code 构建 AI GTM Brain

当一个团队说他们想用 AI 做增长时,他们通常指的是更快地发送:一个 agent 可以日夜不停地,把同一个模板发给更长的名单。

这是这件事里廉价的那一半,而且它早就不管用了。困难的那一半,是围绕发送这件事做判断:该联系哪家公司,为什么是这周,以及该说什么才能证明你真的注意到了他们。名单再长,也解决不了这个问题。

GTM Brain 是一个每天替你做出这个判断的系统,不需要你参与。它由五个部分组成,并作为一个循环运行:

  • 感知:观察市场里哪些目标账户刚刚发生了变化。
  • 记忆:保留你对每一个账户学到的一切。
  • 判断:决定谁值得收到一条消息,以及为什么是现在。
  • 行动:基于真实触发信号写开场白。
  • 学习:追踪反馈结果,并在下周改进。

把这五件事做好,发送本身就会变成最简单的部分。

整个构建背后有一个核心想法:人获得的是 UX,软件获得的是 API,agent 获得的是 command line。这个系统会通过 command line 定时运行,下面的六个步骤会把它搭起来,每一步都附上我会粘贴进 Claude Code 的 prompt。

Step 1. 契约

在写任何 agent code 之前,先写清楚边界。

这一步会在你更换工具的那一天救你一命。你的代码不应该把 intent vendor 或 email tool 的名字硬编码进去。它只需要两个形状,仅此而已:一个进来的 signal,一个出去的 message。在一个空文件夹里打开 Claude Code,然后给它这段:

把这段交给 Claude Code:

Start a Python project called gtm-brain. Build only the boundary layer for now.

1. brain/models.py: a Signal dataclass (account, bucket, summary, contact), where bucket is one of job, social, company, funding.

2. Two adapter classes with fixed signatures and no vendor names:
   class SignalSource: fetch(self) -> list[Signal]
   class Delivery: send(self, draft, dry_run: bool = True) -> str

3. Stub versions of both (StubSignals, StubDelivery) that return hard-coded fake data, so the whole loop runs before any real integration exists.

4. config/icp.yaml: one ICP in plain language. Who is a good fit, who is not, and the single strongest signal combination for you.

Keep the four bucket names and the two method signatures fixed.

好的结果是什么样:

两个很小、能在一分钟内读完的文件,以及一个可以基于 fake data 端到端运行的 loop。你会先看到一个 signal 进去、一个 draft 出来,然后再为任何 integration 付费。

哪里容易出问题:

最容易反噬你的捷径,是让代码直接调用 apollo_search 或 instantly_send。这样做会让每个部分都焊死在某一个 vendor 上,一旦换工具,就意味着五个部分全部重写。把这两个形状放在最前面,更换工具时只需要写一个新的 adapter。

Step 2. 感知市场

从变化开始。某家公司发生了变化,而这个变化就是你联系它的理由。

有四类变化最重要。过去十年做 marketplace 的经验告诉我,盯住这几类就够了,其它的可以先放掉:

  • Job:一个职位开放、被重新发布,或者有人进入了一个掌握预算的岗位。
  • Social:一家公司与竞争对手互动,或者发布了与你所解决问题相关的内容。
  • Company:一次发布、一次扩张、一次技术栈变化。
  • Funding:一轮融资或一次收购,带来了新的预算和明确任务。

大多数团队只盯其中一种,然后忽略其余几种。在 Sortlist 内部,覆盖率来自 Signals,也就是我们自己的 intent tool;但无论你是购买数据还是自己抓取,原则都一样。

这一部分很小,一次构建完成:

实现 brain/sense.py

sense(source, memory):
  call source.fetch()
  check that every Signal.bucket is one of job, social, company, funding
  write each signal to memory
  raise a clear error on an unknown bucket

cluster_by_account(signals):
  group this run's signals by account
  return the groups, so the orchestrator ranks accounts, not loose signals

Write to memory before anything is judged. Two hits on one company in a single run is a stronger signal than either alone.

好的结果是什么样:

一次运行打印出 "sensed 11 signals across 7 accounts,",其中同时出现 funding round 和 new role 的公司会作为一个热门 clustered account 浮现出来。

哪里容易出问题:

什么都感知,却什么都不排名。把 raw signals 直接倒给下游,你最终会把一个路人的随手点赞,和一个 perfect-fit account 的 funding round 当成同等重要。应该在入口处就完成 clustering,而不是把混乱推给 judge。

Step 3. 记住每一个账户

这是大多数工具跳过的一块,也正因如此,它们最后都会变成套了一个 prompt 的 mail merge。

每家公司保留一条记录:你看到过的每个 signal,你发过的每条 message,每次 reply,每个 outcome。一家公司第三次融资意味着什么,取决于你是否知道你已经给他们发过两次邮件且都没有回应。没有这段历史,你会把一个 warm account 和一个 cold account 当成一回事。

这一部分需要认真做好,所以要把规格写完整:

brain/memory.py 做成一个 zero setup 的 SQLite 文件:

Tables: accounts, signals, touches, outcomes

Methods:
record_signal(signal)
record_touch(draft, send_id) -> touch_id
record_outcome(account, touch_id, result)      # replied | meeting | no_reply | bounced
history(account) -> dict                       # signals + touches + outcomes in one object
last_touch_age_days(account) -> float or None

History is read for every account the judge sees, so return the whole story in one call. Keep method names stable so the store can move to Postgres later without touching the rest of the code.

好的结果是什么样:

你向 store 查询任意 account,都能一次拿到它的完整故事:发生过什么变化、你发过什么、对方有什么反馈。系统不会用同样的方式重复联系两次。

哪里容易出问题:

大多数团队会跳过这一步,直接上线。然后他们用冷启动输出评判系统,觉得它太泛泛,于是在第三周关掉它,而那恰好是历史数据本来即将让系统变得敏锐的前一周。先构建这一部分。其余四个部分都依赖它。

Step 4. 判断谁值得跟进,以及为什么是现在

这是 Claude 真正开始工作的地方,也是这个系统不再只是一个脚本的地方。

判断层会读取新的信号,从记忆中拉取该账户的完整历史,结合你对好客户的定义,然后决定三件事:

这个账户现在是否值得发送一条消息,

为什么是现在,

以及应该执行哪一个 play。

这是竞争对手无法复制的部分,也正因为如此,它值得你自己构建。

这个 prompt 会指定模型、temperature、fallback,以及 system prompt 所在的位置:

实现 brain/judge.py,并把 system prompt 放在 prompts/judge.md,不要写死在代码里:

judge(account, new_signals, icp, memory):
1. assemble a JSON payload: { icp, new_signals, history, days_since_last_touch }
2. send it to Claude (model claude-sonnet-4-6, temperature 0) with the system prompt loaded from prompts/judge.md
3. parse the reply into Verdict(score, why_now, play, rationale)
4. on a failed call or malformed JSON, fall back to a weighted-bucket heuristic so a run never crashes halfway through

什么样算好:

高热度账户会拿到高分,并且 why_now 可以直接复制进邮件。温度不够的账户会返回 skip,并附上理由。一个能对四个账户里的三个说不的系统,胜过一个把四个账户全都发出去的系统。

哪里容易出问题:

脱离 memory 去给 signal 打分。一个新鲜信号本身无法区分:这是一个你已经追过两次的账户,还是一个你从未触达过的账户。于是模型会重复同样的开场白,而你看起来就像在过度跟踪。没有 history,score 就只是猜测。

Step 5. 根据触发信号行动

只有在 judge 已经决定谁值得跟进、以及为什么值得跟进之后,它才开始写。

这一部分的规则很简单:把 trigger 原样说回来。比如:“Saw you opened a Head of RevOps role, second ops hire this quarter.” 永远不要写 “Hi {{firstName}}”。如果这条消息上个月也能原封不动地发出去,那你就跳过了 trigger。

用同样的方式构建这一部分,并默认使用 dry run:

实现 brain/act.py,并让 delivery 默认保持 dry run:

act(verdict, trigger, sequences, delivery, memory, dry_run=True):
- on a "skip" verdict, do nothing
- otherwise send Claude the trigger, the play, and that play's guardrails from config/sequences.yaml, using the system prompt from prompts/draft.md
- parse the reply into { subject, body }, call delivery.send(draft, dry_run), and record the touch in memory

dry_run stays True until a --live flag turns it off: print the draft, send nothing, by default.

什么样算好:

一条你愿意原样转发的 draft,开头基于这个账户本周真实发生的事情。先用 dry-run 连续读十条,再让任何一条真正离开发送系统。

哪里容易出问题:

在你读过输出之前,就把 delivery 接成 live。一个会自己发送的系统,会在有人看到之前,把一条自信但错误的开场白发给你的整张名单。保持 dry-run 为默认值;让 --live 成为一次明确的决定。

Step 6. 从反馈中学习

一个永远不会学习的 agent,只是一个更快的脚本。最后这一部分,决定了它不会永远停留在脚本层面。

把每一次 outcome 都写回 memory,并让系统给自己的 plays 打分。持续带来会议的 signals 获得更高权重。持续失败的 copy 被淘汰。测试、评分、保留或丢弃。这就是为什么它会一周比一周更好,而不需要你手动调参。

这一部分也很小:

实现 brain/loop.py

record_result(memory, account, touch_id, result, bucket, prompt_variant):
  log an outcome (replied | meeting | no_reply | bounced)

adjust_weights(memory, signal_cfg) -> dict:
  re-weight the four buckets by win rate (wins / touches), with a floor so no bucket ever drops to zero

best_variant(memory, min_sample=10) -> str:
  return the drafting prompt with the best reply rate once there is a real sample, else the current default

每周运行一次,并让 orchestrator 在下一次运行时读取新的 weights。

然后把五个部分接进一个命令:

Write run.py. Load config/icp.yaml and config/sequences.yaml and the two stub adapters, then on each run: sense new signals, cluster by account, judge every account, act on the non-skips, and record everything to memory. Print one ranked line per account. Support --offline (heuristic judge, no API key) and --live (actually send). Default to a dry run.

现在,一行 cron 就能让整个系统每天运行。如果你已经在 agent runtime 上跑了一组 agent,也可以把它作为一个 skill 放进去。

0 8 * * * cd ~/gtm-brain && .venv/bin/python run.py

什么样算好:

第一个月,它按照你的猜测来给 buckets 加权。第三个月,它按照你的市场真实反馈来加权,shortlist 会更短、更锋利,并且按照真实转化排序。

哪里容易出问题:

只记录 sends,却不记录 outcomes。没有 result data,weights 就永远不会变化,你买到的只是一个昂贵的 scheduler。从第一天起就把 reply 和 meeting data 写回来,哪怕一开始必须手动录入。

两个 prompts

到目前为止,每个 prompt 都是在构建某个部分,构建完就放到一边。但这两个不一样:系统会在每个账户、每次运行时使用它们。判断力和表达方式都住在这里,所以这两个 prompt 才是需要长期保留和调优的。

judge 使用这个:

ROLE
You are the judgment layer of a GTM brain. For one account, decide whether a buying signal is worth acting on right now, and how.

INPUT
A JSON object:
{
  "icp": "plain-language description of a good-fit customer",
  "new_signals": [{ "bucket": "job|social|company|funding", "summary": "..." }],
  "history": { "signals": [...], "touches": [...], "outcomes": [...] },
  "days_since_last_touch": number or null
}

TASK
Weigh the new signals against ICP fit and the full history, then score the account and choose exactly one play.

SCORING
80-100  strong ICP fit and a high-intent signal (funding, or two signals clustered in the same run)
50-79   good fit, one solid signal
20-49   weak fit, or a single low-intent signal (a lone social touch)
0-19    off-ICP, or noise

RULES
- If days_since_last_touch is under 7, prefer "nurture" or "skip", never "first_touch".
- If the signal is weak or the account is off-ICP, score it low and pick "skip". Saying no is part of the job.
- "why_now" must quote the actual trigger from new_signals, never a generic value proposition.

OUTPUT
Return only this JSON, no prose:
{
  "score": <integer 0-100>,
  "why_now": "<one sentence a rep could say to the buyer, quoting the trigger>",
  "play": "first_touch | follow_up | nurture | skip",
  "rationale": "<one line for the audit trail>"
}

drafting step 使用这个:

ROLE
You write the opening message for a GTM brain. The signal is the reason you are reaching out, and the message has to prove you noticed it.

INPUT
A JSON object:
{
  "trigger": "the specific thing that moved, e.g. opened a Head of RevOps role",
  "bucket": "job|social|company|funding",
  "why_now": "the judge's one-line reason",
  "play": "first_touch | follow_up | nurture",
  "guardrails": { "goal": "...", "must": [...], "never": [...] }
}

TASK
Write a subject line and a body of three to five sentences.

REQUIREMENTS
- Open on the trigger. The first sentence names what the account just did. Never open with "Hi {{firstName}}" or a template line.
- Connect the trigger to one problem you solve, in a single sentence.
- Close with one low-friction ask: fifteen minutes, or the offer of a one-pager.
- Plain human sentences, mixed length. No fake urgency, no "circling back", no em dashes, no buzzwords.

OUTPUT
Return only this JSON, no prose:
{ "subject": "<6-9 words>", "body": "<3-5 sentences>" }

这两个 prompt 都会作为可编辑文件随 repo 一起交付,它们也是你最需要根据自己市场持续调优的部分。

你会得到什么

当这五个部分接在一起后,它就可以按计划自动运行,并自己完成这些事:

  • 扫描市场。
  • 给发生变化的账户打分,并丢掉其余账户。
  • 为少数值得发送消息的账户起草内容。
  • 把按优先级排序的 shortlist 发到 Slack,每个名字旁边都附上原因。

两分钟:批准、编辑,或者砍掉。不需要建名单,不需要猜 subject lines,也不需要开周一会议讨论该联系谁。

我在 Sortlist 用这个形态跑了一组 agent fleet,每月 token 成本大约 $400。我们今年原本计划招聘五个人,后来取消了,因为这些工作现在跑在 token budget 上。growth brain 是我会最先构建的那个。它第一次帮你约到一个本来会错过的会议时,就已经回本了。

从两个开始

你不需要一开始就拥有全部五个部分。先构建 memory,再在它上面构建 judge。

先把它们接到 stub data 上,dry run 一周,直到你信任这个 loop,然后再接入一个真实 signal source。你会得到一份值得发送消息的账户 ranked list,每个账户都带有入选原因,而这些原因来自你原本已经忽略掉的 signals。这才是值得付费的部分。

我打包了整个系统的干净版本:五个部分、两个 adapters、config、每一步的 build prompt、上面两个 prompts,以及运行它的 cron。一个小型 skeleton,你可以 clone 下来,再按自己的市场去改。

评论 BRAIN,我会发给你。