To spot expansion moments before renewal, judge each customer once, when its won deal turns ten months old: read its purchase history and any dated public event (a funding round, a hiring push, a new leader), label it expansion, at_risk, repeat_purchase, renewal or none with a reason, and send the go-to-market team one list a week. The expansion signals cookbook deploys exactly that on Cargo: a weekly play, a read-only analyst agent, three columns on the account, and one Slack digest.
The problem it solves is that the renewal call is usually the first time anyone looks at a customer since it signed. By then the budget is set. We run the same idea on Cargo’s own customer book: the signal shows up weeks before the renewal, in data you already hold, as long as something reads it on a schedule.
How it works, end to end #
- Monday 06:00 UTC, a play picks up won deals entering the window. The play runs on a deals model (
gtm_opportunities) and enrolls a deal when it is won, has anaccount_id, and closed between ten and twelve months ago. It useschangeKinds: ["added"], so a deal enters once and each renewal is judged once, not every Monday for two months. - An analyst agent judges the account. It queries the deals model in SQL for every won deal on that account: if a newer win closed more than two months after the trigger, the account already renewed and gets
none. Otherwise it states the cadence across wins and the last price paid. It reads your expansion plays from the workspace context, then runs at most three web searches for public events in the last 120 days, keeping only events with a dated source. - The play writes three columns onto the account, by id.
expansion_signal,expansion_reason(the reason, the suggested play and the source URLs) andexpansion_signal_at. Nothing else is written: no deal, no contact, no owner. The agent itself has read-only access to both models. - Monday 15:00 UTC, a digest agent posts once. It reads the accounts stamped in the last seven days with a signal other than
none, posts one Slack message withat_riskfirst, thenexpansion,repeat_purchaseandrenewal, each line with the reason as written on the account and its owner. A ledger model (expansion_digests) keeps it to one post per ISO week, so a re-run posts nothing.
The five labels mean:
| Signal | What it means |
|---|---|
expansion | An outside event or usage pattern says they need more |
at_risk | An event puts the renewal itself in doubt, such as layoffs or a new leader |
repeat_purchase | The cadence across won deals says the next order is due |
renewal | The anniversary is the moment, nothing more |
none | Nothing to act on, including an account a newer win already renewed |
Run it #
npm install -g @cargo-ai/cli
cargo-ai login --email you@company.com # emailed code, no browser, 100 free credits, no card
cargo-ai project cookbook view expansion-signals # what it asks you for, before anything is written
cargo-ai project init my-gtm --cookbook expansion-signals && cd my-gtm && npm install
# already have a Cargo project? from its root:
cargo-ai project add cookbook/expansion-signals
Then, before the first deploy:
- Fill the models. The example ships native accounts and deals models, so it deploys with no CRM connector. A native model starts empty: load accounts, then won deals with their
account_id, withcargo-ai storage record create-bulk --model-uuid <uuid> --records '[{"data":{...}}]'. A won deal with no account is never judged. - Write your expansion plays into the workspace context (
context/expansion-plays.md, from the example in the cookbook). Without it, the suggested play is generic advice. - Set the Slack channel id (
C...) on the digest and thelanguageModelon both agents. - Plan, then deploy.
cargo-ai project planprints the diff: three models, two agents, the play, two connectors and three folders.cargo-ai project deployapplies it. The play ships disabled: run it by hand on five deals, read each reason against its sources, then enable it and execute it once, because anaddedplay does not backfill deals that entered while it was off.
If your customers and deals live in HubSpot, Salesforce or Attio, the cookbook’s crm-backed variation swaps in connector-backed models and writes the three properties back to the CRM company record by id. Create the three custom properties first: a write to a property that does not exist fails every row.
What it needs and what it costs #
| Needs | Why |
|---|---|
A Cargo workspace and @cargo-ai/cli | The project is deployed with cargo-ai project |
| An authorized Anthropic connector | Both agents run on it |
| An authorized Slack connector | The digest’s only post path |
| Won deals with an account and a close date | The trigger and the purchase history |
Cost, from the code: the native model writes and Slack’s postMessage carry no Cargo credit price in their action definitions, so the recurring cost is model usage on your Anthropic connector: one analyst run per won deal entering its window each week (with up to three web searches inside it) and one digest run. It scales with how many customers are near renewal, not with the size of the book. Count the window before you enable the play: a SELECT count(*) over the deals model with is_won true and close_date ten to twelve months back.
What it will not do #
- It does not change a deal, a contact or an owner. The only write is three columns on the account.
- It does not contact a customer or post anywhere but the one locked Slack channel. Locking the channel on the action is deliberate: the digest names customers and their renewal risk.
- It does not read product usage. The judgment comes from won deals, your written expansion plays and public events. If usage against plan is your strongest signal (it is ours), that is a column you add, not something the example reads.
- It does not re-judge in Slack. The digest repeats the reason on the account, so the record and the channel never disagree.
- It judges an annual book by default. Monthly or two-year contracts change the two
close_datevalues in the play.
Related: Customer expansion revenue strategies covers the playbook side, and track job changes catches a champion who left, the first at-risk signal. To verify who expands in the first place, pair it with a monthly win-loss review.
Frequently asked questions #
No. The example runs on native accounts and deals models you load with the CLI or a sync, so it deploys with only Slack and Anthropic connectors. The crm-backed variation reads HubSpot, Salesforce or Attio instead and writes the signal back to the company record.
Once per renewal. The play enrolls a won deal when its close date enters the ten-to-twelve-month window and uses changeKinds added, so the same deal is not judged again the next Monday. The whole-book variation judges every account on a cadence instead, at one agent run each.
It is told never to invent a deal, amount, event or URL, to keep only events with a dated source, and to return every URL it relied on. The play writes those URLs into the reason on the account, and the agent’s evaluator rubric scores whether any signal other than renewal or none names a dated event with a URL. Pilot on five deals and read each reason against its sources before enabling.