Expansion signals
Every week the customers coming up on renewal are judged for an expansion moment (purchase cadence and last price from won deals, and dated outside events such as funding, hiring or a new leader); the signal and reason are written onto the account and posted as one digest for the customer team.
Set up the expansion-signals cookbook in this project.
1. If the Cargo CLI is not installed yet, follow every step in https://api.getcargo.io/INSTALL.md
2. From the Cargo project, run: cargo-ai cdk add cookbook/expansion-signals
3. Then follow .claude/skills/expansion-signals/SKILL.md. Stop for my approval before any paid call, and stop at "cargo-ai project plan" before deploying anything.$cargo-ai cdk add cookbook/expansion-signalsSay this to your agent
Every Monday, flag which customers near renewal are ready to expand and post the digest to #cs-weekly.
Illustrative output #
Fictional records, to show the shape of what comes back.
:seedling: *Expansion signals, week of 2026-10-05*
_6 accounts in their renewal window: 1 at risk, 2 ready to expand._
*Tailspin Logistics* (tailspin.example) · at_risk · New CDO joined Sep 22 from a
competitor's customer; renewed every 12 months, last at 24,000. Play: executive
check-in. Sources: tailspin.example/news/new-cdo · owner 4411
*Fabrikam* (fabrikam.example) · expansion · Raised a Series B on Sep 9 and posted
four data-engineer roles; renewed every 12 months, last at 38,000. Play: annual
prepay offer. Sources: fabrikam.example/press/series-b · owner 4402
*Northwind Traders* (northwind.example) · expansion · Hired a Head of Compliance on
Sep 30. Play: governance add-on demo. Sources: northwind.example/team · owner 4402
*Contoso* (contoso.example) · repeat_purchase · Ordered every 6 months, last at
9,500 on Apr 2. Play: renewal call at the last price. · owner 4419
*Litware* (litware.example) · renewal · No outside event. · owner 4411
Six won deals entered their renewal window this week, so six accounts carry a signal and a reason; a
seventh account had already renewed early and was written none.
What you will be asked #
| Input | Why | |
|---|---|---|
channelId on the digest | Asked | The digest names customers and their renewal risk. Locked so it never lands in a customer shared channel. |
Done when #
node --import tsx evals/contract.mjspassescargo-ai cdk planreports thegtm_accounts,gtm_opportunitiesandexpansion_digestsmodels, the two agents, the play, two connectors and three folders, with the play disabled and no CRM connector- seeded accounts and won deals, with two deals inside the window, produced two analyst runs and two accounts carrying a signal, a reason and a stamp; every event named in a reason has a dated source in it
- an account whose newer win already renewed it (closed more than two months after the trigger) was written
nonewith that reason, and an account with a second win within two months of the trigger was judged on its whole purchase history instead - a second play execution the same week judged neither deal again
- the digest posted once in the locked channel, listed exactly the accounts stamped that week with a signal other than
none, and a re-run that week posted nothing - no deal or contact record changed
What it costs #
The model writes are native and the research is web search inside the analyst, at most three per account. Immediately before the plan, read the live price of the one connector action:
cargo-ai orchestration action list postMessage --kind connector --integration-slug slack
Say it out loud. The recurring cost is one analyst run per won deal entering its window each week,
plus one digest run, billed as LLM tokens through the Anthropic connector. It scales with how many
customers are near renewal, not with the size of the book. Count the window before enabling: a
SELECT count(*) over gtm_opportunities with is_won true and close_date ten to twelve months back.
Every Monday the customers coming up on renewal are judged for an expansion moment, the judgment is
written onto the account, and one digest lands in Slack for the customer team. The worked example
runs on Cargo’s native accounts and deals models, so it deploys with no CRM connector; HubSpot,
Salesforce and Attio are the crm-backed variation in SKILL.md.
What it does #
- A weekly play picks up each won deal whose close date entered the ten-to-twelve-month window. A deal enters once, so each renewal is judged once.
- An agent judges the account from its won deals in SQL (a newer win, the cadence, the last price),
your expansion plays in the workspace context, and dated public events:
renewal,expansion,repeat_purchase,at_riskornone, with a reason, a play and its sources. - The play writes
expansion_signal,expansion_reasonandexpansion_signal_atonto the account record by id, and nothing else. - A digest agent posts one Slack message that afternoon, at-risk first, and records the week in a ledger so a re-run posts nothing.
What’s inside #
Adds 11 resources.
| File | Resource | Role |
|---|---|---|
infra/models/gtm-accounts.ts | defineModel (native account) | the customer book, plus the three expansion columns |
infra/models/gtm-opportunities.ts | defineModel (native deal) | every deal; the play’s model and the purchase history |
infra/models/expansion-digests.ts | defineModel (native custom) | the digest ledger: one row per week posted |
infra/plays/flag-expansion.ts | definePlay + defineWorkflow | who is judged, the judgment, the one account write |
infra/agents/analyst.ts | defineAgent | the judgment, as JSON, read-only |
infra/agents/digest.ts | defineAgent | the weekly Slack post, channel locked |
infra/connectors/slack.ts | defineConnector (slack) | the digest’s post path |
infra/connectors/anthropic.ts | defineConnector (anthropic) | the model both agents run on |
infra/folders/index.ts | defineFolder ×3 | where the models, agents and play are filed |
references/expansion-plays.md | (not a resource) | the example to copy into context/expansion-plays.md |
Why native models in the example #
Fewest dependencies: the example deploys with a Slack and an Anthropic connector and nothing else.
The judgment does not care where the deals come from. A team whose deals live in a CRM swaps the two
models for connector-backed ones and the write for the CRM’s updateRecords on the record id; the
play, the analyst and the digest stay as they are.
Why a play writes and the agent does not #
The agent hands back a judgment; the play persists it on the account by id. That keeps the write in one reviewable place, limited to three columns, and makes a missing signal mean one thing: the run failed.
Placeholders (edit before deploy) #
- The renewal window in
infra/plays/flag-expansion.ts, if contracts are not annual. channelIdininfra/agents/digest.ts.languageModelon both agents.context/expansion-plays.mdin your project, fromreferences/expansion-plays.md.
What it does not do #
It does not change a deal, a contact or an owner, contact a customer, or post anywhere but the locked channel.