Install
openclaw skills install @cargo-ai/expansion-signalsEvery 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. Triggers: "flag customers ready to expand", "which customers should we upsell this quarter", "renewal and expansion signals every week", "tell account managers which accounts to reach out to before renewal", "find expansion opportunities in our customer base", "at-risk renewals digest in slack". Cargo CDK, native accounts and deals models, definePlay, defineAgent, webSearch, Slack; adapts to HubSpot, Salesforce or Attio. Skip when: you want who raised money across prospects, which is track-funding-rounds; or buying signals on accounts that are not customers yet, which is monitor-buying-signals.
openclaw skills install @cargo-ai/expansion-signalsState: to-be-approved. Deploy-verified against a live workspace: yes, on 2026-10-04, on native
models seeded with six won deals across three accounts: the play's filter picked exactly the four
won ten to twelve months back; the analyst wrote renewal onto the account with one win, none onto
the account that renewed ten months later, and, after a fix found by this run, repeat_purchase
onto the account with a six-month cadence; the digest posted once and a re-run, once its ledger read
back, posted nothing. Treat Done when
below as the acceptance test and review cargo-ai cdk plan before deploying. Make no outcome claim
for this skill until it is approved.
The renewal stops being the first time anyone looks at a customer. Every Monday:
gtm_opportunities model: the
deal is won, has an account, and closed ten to twelve months ago. A deal enters that window once,
so each renewal is judged once.gtm_opportunities it reads the account's purchase history:
whether a newer win already renewed it, the cadence across its wins, the last price paid. It
reads your expansion plays from the workspace context and runs up to three web searches for
dated public events. It answers renewal, expansion, repeat_purchase, at_risk or none,
with a reason of at most three sentences, the play to run, and every URL it relied on.expansion_signal,
expansion_reason and a expansion_signal_at stamp. It writes nothing else: no
deal, no contact, no owner.The worked example runs on Cargo's native accounts and deals models, so it deploys with no CRM
connector. Customers who live in HubSpot, Salesforce or Attio take the crm-backed variation: the
same play, with connector-backed models and the write going back to the CRM record.
Every Monday, flag which customers near renewal are ready to expand and post the digest to #cs-weekly.
Illustrative output, fictional records:
: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.
This folder is a worked example: real CDK resources written for some other company. The job is
to end up with the code your company would have written, in your project, and an agent does the
adapting. If the cargo-cdk skill is in your session it carries the long form of this; if not, this
is enough.
cargo-ai cdk add cookbook/expansion-signals writes this example to infra/expansion-signals/
and this procedure to .claude/skills/expansion-signals/. No project yet?
cargo-ai cdk init <dir> --cookbook expansion-signals && cd <dir> && npm install does both.
If you are reading this from the project's .claude/skills/, the install already happened —
start at step 2.gtm_accounts or gtm_opportunities model, rewire the imports to the existing one and drop the
copy; two resources with one slug is a collision at deploy. If the customer book lives in a CRM,
take the crm-backed variation now, before the first deploy.account_id, with cargo-ai storage record create-bulk --model-uuid <uuid> --records '[{"data":{…}}]',
or point a sync at them. A won deal with no account is never judged.references/expansion-plays.md is the
example; copy it to context/expansion-plays.md and rewrite it for what you sell.## Decisions in your copy
of this file.node --import tsx evals/contract.mjs from this skill's folder, then
npm run check && cargo-ai cdk plan from the project root. Show the diff, and deploy only on an
explicit yes. The play ships disabled.added play does not backfill), and let
the digest post the next Monday.Derive before you ask. An input with a lookup is looked up, not asked.
| Input | Kind | How it is answered | Why it matters |
|---|---|---|---|
| where the customer book lives | derived | cargo-ai storage model list and cargo-ai connection connector list. Native models with rows: the default. A CRM with the deals: crm-backed. | Judging an empty native model while the deals sit in HubSpot is a pipeline that runs green and flags nothing. |
| the renewal window | asked | the contract length most customers sign. Annual is the default: a won deal ten to twelve months old. | A monthly book judged on an annual window is judged once a year; a two-year book is judged a year early. |
| the expansion plays | asked | what you sell into an existing customer and which play answers which signal. Written once into the context. | Without it, suggested_play is generic advice. With it, the digest tells the account manager what to do. |
channelId on the digest | asked | the Slack channel id (C…) from the connector's channel autocomplete. Invite the bot. | The digest names customers and their renewal risk. Locked so it never lands in a customer shared channel. |
| LLM connector and model | value | derived: cargo-ai connection connector list. languageModel is a placeholder on both agents. | It is what every judgment is billed against. |
| Variation | When it is right | How | What it costs |
|---|---|---|---|
crm-backed | The customers and deals live in HubSpot, Salesforce or Attio | Add a defineConnector for the CRM (default: true). Replace both native models with connector-backed ones: extractSlug: "fetchRecords", config: { objectType: "companies" } and "deals", columnSelectionMode: "all", an hourly schedule, and never a filter. Run the play on the gtm_opportunities model with the CRM's won flag and close date (HubSpot: hs_is_closed_won, closedate; or put it on companies and filter the recent_deal_close_date roll-up). Create three custom company properties (expansion_signal text, expansion_reason multi-line text, expansion_signal_at date) and replace model.customColumn with the CRM's updateRecords on companies, matched on the record id (hs_object_id of the deal's associated company), with those three mappings. | A write to a property that does not exist fails every row, so create them first. Never sit a unify step between the play and the write: it reports success and nothing lands. |
window | Contracts are not annual | Change the two close_date values in infra/plays/flag-expansion.ts | A window that overlaps the next renewal judges the same account twice. |
whole-book | You want every customer judged on a cadence, not only near renewal | Move the play to gtm_accounts, filter on custom__expansion_signal_at being empty or older than ninety days, and let the analyst decide in SQL whether the account has a win at all | Every account in the book is judged each cycle, customers or not, at one agent run and up to three searches each. |
no-web | Compliance does not allow web research on customers | Drop webSearch from the analyst and its search sentence | Only renewal, repeat_purchase and none remain reachable from the deal history alone. |
per-owner-dm | Account managers want their own list | Replace the locked channelId with one postMessage use per owner channel, and split the post by owner | More locked uses to keep in sync with the team. An owner with no use gets nothing. |
infra/agents/analyst.ts) A
hand-kept "customer" column is a second answer to the question the deals already answer, and it
drifts: an account that renewed early would be judged as if it had not.infra/plays/flag-expansion.ts) A write to a deal or owner turns a judgment into a forecast
change nobody approved. In the crm-backed shape, a unify step between the play and the write
reports success while nothing lands. The contract fails on either.custom__expansion_signal;
the write takes expansion_signal. A prefixed slug is silently dropped while the node still
reports success.infra/agents/analyst.ts) An agent that can write decides
its own routing, and a missing signal could then be a failed run, a skip or a choice.changeKinds: ["added"] stays. It is what judges each renewal once. Without it every won deal
in the window is re-judged every Monday for two months, and each run overwrites the last reason.infra/agents/digest.ts) It reads the reason the account
carries. A second judgment in Slack is how the record and the channel disagree about one account.node --import tsx evals/contract.mjs passescargo-ai cdk plan reports the gtm_accounts, gtm_opportunities and expansion_digests models, the two
agents, the play, two connectors and three folders, with the play disabled and no CRM connectornone with that reason, and an account with a second win within two months of the
trigger was judged on its whole purchase history insteadnone, and a re-run that week posted nothingThe 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 slackSay 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.
crm-enrichment (firmographics on the same company records), win-loss-review (what closed-won
says about who expands), and track-job-changes (a champion who left is the first at-risk signal).