Readiness scoring
Keep a sales readiness score on every account that rises with each fresh signal and falls on its own as signals age: rounds, job postings, demo requests and any other account event land in Unify Account Events, each event decays with a half-life per signal type, the sum lands as a score on Unify Accounts every hour, and accounts crossing your line are posted to Slack once.
Set up the readiness-scoring 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 project add cookbook/readiness-scoring
3. Then follow .claude/skills/readiness-scoring/SKILL.md. Stop for my approval before any paid call, and stop at "cargo-ai project plan" before deploying anything.$cargo-ai project add cookbook/readiness-scoringSay this to your agent
Score our accounts by recent engagement so older signals count for less, and ping #sales-ready when one heats up.
Illustrative output #
Fictional records, to show the shape of what comes back.
northwind.example is ready (readiness score 14.2)
Its recent signals crossed the ready line this hour.
Unify Accounts, with the two columns this adds:
| domain | readiness_score | readiness_stage | its weighed events |
|---|---|---|---|
| northwind.example | 14.2 | ready_now | fundraising 12d ago, demo_requests 2d ago, job_offers 9d ago |
| fabrikam.example | 6.1 | warming | job_offers 3d ago, job_offers 17d ago |
| tailspin.example | 2.4 | cold | fundraising 290d ago |
Northwind is posted once, the hour it crosses the line. Tailspin’s round is old enough that it no longer counts for much, and nobody had to lower its score.
What you will be asked #
| Input | Why | |
|---|---|---|
| signals | Asked | It is the one input that is a decision about the business: what a round is worth against a demo request, and for how long |
| readyThreshold | Looked up | Too low and the channel hears about every account; too high and it never fires |
| warmingThreshold | Looked up | It is the nurture segment’s edge |
Done when #
- the inventory query was run and every key in
signalsmatches a type or model it returned, or a feed this deploy added - the operator approved the weights and half-lives in
infra/settings.ts - the plan showed
account_eventsandaccountsadopted with their schedules and the score columns added, no live column removed, the feeds on a watchlist, two segments and the play disabled, and the operator approved the deploy - after the first rebuilds, every account with a weighed event inside the horizon has a
readiness_scoreand areadiness_stage, and an account with only old events scores below one with the same events fresh - the thresholds were set from the pilot’s distribution, and the top ten accounts were read by hand and agreed with
- the play was enabled, and the first account to cross the line was posted once, with its signals
node --import tsx evals/contract.mjspasses against the adapted resources
What it costs #
The score itself bills nothing in credits. It is three columns recomputed hourly in the workspace’s own warehouse, with no LLM and no connector call. The Slack post is one action per account that crosses the line.
Cargo Signals bills per event delivered, per feed, on the watched accounts only. The first
delivery on a new watchlist looks back months, so the first days cost the most; each feed’s
dailyLeadLimit caps them. Read the current per-event price with
cargo-ai connection integration get cargoSignals before the deploy, multiply by the watched
accounts and the events the pilot week delivered, and give the operator that estimate. First-party
events the workspace already has cost nothing to add.
Score every account on how ready it is right now, from its recent signals, with older signals counting for less. This pipeline deploys the whole loop: signals in, a score rebuilt every hour, and a Slack message when an account crosses the line.
cargo-ai project add cookbook/readiness-scoring
- Signals in. Two Cargo Signals feeds watch the accounts in
gtm_accountsfor rounds raised and jobs posted. Every event lands in Unify Account Events, next to whatever else already unifies there: demo requests, signups, CRM meetings, web sessions. - Scored, on the account. A computed column on Account Events,
readiness_contribution, gives each event its weight times one half per half-life of age, both set per signal type ininfra/settings.ts. A metric column on Unify Accounts,readiness_score, sums it, andreadiness_stageturns the sum intoready_now,warmingorcold. Both models rebuild hourly, so the age is always today’s. - Routed. Two segments on Unify Accounts read the stage, and a play posts each account that
enters
ready_nowto Slack, once.
// infra/settings.ts
export const signals = {
fundraising: { weight: 10, halfLifeDays: 120 },
job_offers: { weight: 4, halfLifeDays: 21 },
demo_requests: { weight: 20, halfLifeDays: 30 }, // a first-party model, by slug
};
export const readyThreshold = 8;
export const warmingThreshold = 4;
Why the decay lives in columns #
A score written onto a row by a play is right only on the day it is written. The obvious fix, a play that rewrites every score every hour, runs a workflow per account per hour to do arithmetic. Here the events are never touched. A computed column is recomputed every time its model rebuilds, and it can read the clock, so with Account Events on an hourly schedule each event’s contribution is always today’s. The sum is an ordinary metric column, so the score sits on the account row every other pipeline and every rep already reads, on any warehouse, with no SQL to maintain.
Why everything goes through Account Events #
The score reads one model. Any source that unifies as an account event (a Cargo Signals feed, a
webhook model of demo requests, a CRM’s meetings) reaches it with no change to the query. The key
for a weight is the event’s Cargo Signals signal_type, or the source model’s slug when it has
none. A key with no weight counts for nothing, so a new source never moves scores until someone
has decided what it is worth.
How it works #
flowchart TD
feeds["Cargo Signals feeds<br/>fundraising · job offers<br/>on gtm_accounts"]
first["first-party models<br/>demo requests · signups · meetings"]
events["Unify Account Events · :00<br/>readiness_contribution = weight × 0.5^(age / half-life)"]
score["Unify Accounts · :10<br/>readiness_score = SUM(contribution)<br/>readiness_stage"]
segments["ready_now / warming segments"]
play["route_ready_accounts play<br/>stage = ready_now · added only · :20"]
slack["one Slack message per climb"]
feeds --> events
first --> events
events --> score --> segments
score --> play --> slack
| File | Resource | Role |
|---|---|---|
infra/settings.ts | (not a resource) | weights and half-lives per signal type, the two thresholds, the horizon |
infra/models/account-events.ts | defineModel | Unify Account Events, adopted: hourly schedule, readiness_contribution |
infra/models/accounts.ts | defineModel | Unify Accounts, adopted: readiness_score, readiness_stage, at :10 |
infra/models/signal-feeds.ts | defineModel ×2 | Cargo Signals fundraising and job offers, on a watchlist, as account events |
infra/models/gtm-accounts.ts | defineModel | the watched accounts, the shared native accounts model |
infra/segments/readiness.ts | defineSegment ×2 | ready_now_accounts, warming_accounts |
infra/plays/route-ready-accounts.ts | definePlay | the Slack post on entering ready_now, ships disabled |
infra/connectors/*.ts | defineConnector | Cargo Signals and Slack, bound |
infra/folders/index.ts | defineFolder ×2 | models and plays, named after the skill |
Placeholders (edit before deploy) #
- The live columns of Unify Accounts and Account Events, copied into the two declarations:
their
additionalColumnsreplace the live list, and a column left out is removed. signalsininfra/settings.ts, from the inventory query inreferences/run.mdandreferences/weights.md.- The thresholds, after the pilot.
- The job title filter on the job offers feed.
- The Slack channel id in
infra/plays/route-ready-accounts.ts.
Cost #
The score bills nothing: three columns recomputed in the workspace’s own warehouse, with no LLM. Cargo Signals
bills per event delivered on the watched accounts, capped per day by each feed’s
dailyLeadLimit. The Slack post is one action per account that crosses the line.
Verification #
node --import tsx evals/contract.mjs # the graph boundaries, from the compiled registry
cargo-ai project types && cargo-ai project check && cargo-ai project plan
evals/acceptance.md is the line-by-line acceptance test.
Composes into #
account-scoring for the fit gate, contact-sourcing and agentic-engagement on
ready_now_accounts. Works best after tam-building or a CRM sync has put the accounts in Unify
Accounts.