Install
openclaw skills install @cargo-ai/next-step-trackerEvery weekday morning each promise made on a recorded call, by your team or the customer, is read from the call log, recorded once, closed when the activity log shows it was kept, and flagged in Slack once it falls due or goes overdue. Triggers: "track the next steps from our calls", "remind me when I promised something on a call", "what did we commit to and not do", "chase customer follow-ups we agreed on", "commitment tracker for sales calls", "flag overdue next steps in slack". Cargo CDK, defineAgent, harness claudeCode, call-capture entries, native models, Slack postMessage, ledger model; HubSpot, Salesforce or Attio as an adaptation. Skip when: you want calls written into the repository, which is call-capture; or a digest of everything that moved, which is standup.
openclaw skills install @cargo-ai/next-step-trackerState: to-be-approved. Deploy-verified against a live workspace: yes, on 2026-10-08. The
scheduled run read two call entries, recorded three commitments (two ours, one theirs), posted one
nudge for the two overdue ones and marked only those nudged; a manual re-run the same day added no
row and 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.
"We'll send that over by Thursday" stops being the sentence nobody remembers saying. Every weekday morning one agent runs, and three things happen:
cadence/log/calls/ (the
ones call-capture writes) and records every specific commitment either side made, quoted, with
a due date, in the commitments model. Same entry, same promise, one row forever.gtm_activities model for an
email, meeting, note or task on the account after the call that plainly matches the promise, or a
later call entry that says it happened. It closes the row only on a match, and cites the record.The repository is read and never written: no pull request to review every morning. The evidence
models (gtm_activities, and gtm_accounts for who owns what) are read-only. Both are native, so the
example deploys with no CRM; a team on HubSpot, Salesforce or Attio swaps them for connector-backed
models (crm-backed below) and the prompt does not change. The only writes are the ledger and one
post a day to a channel locked on the action.
Every weekday morning, tell #deal-desk which promises from our calls are due or overdue.
Illustrative output, fictional records:
:hourglass: *Next steps due — Thu Oct 8*
*Dana Ruiz*
• Fabrikam: "Send the Globex migration one-pager to Priya" — you promised on Oct 1,
due Oct 6 (2 days overdue). An email to Priya with the one-pager closes it.
• Northwind: "Legal will review the MSA by Friday" — they promised on Oct 2, due today.
Follow up with Sam; nothing to do for them.
*Lee Park*
• Contoso: "Share the SOC 2 report" — you promised on Oct 6, due today. An email with
the report attached closes it.
_Sources: cadence/log/calls/2026-10-01-fabrikam.md · cadence/log/calls/2026-10-02-northwind.md ·
cadence/log/calls/2026-10-06-contoso.md_
Eleven commitments were open; four closed overnight on logged activity, three were due and posted, and a second run that morning posted nothing.
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/next-step-tracker writes this example to infra/next-step-tracker/
and this procedure to .claude/skills/next-step-tracker/. No project yet?
cargo-ai cdk init <dir> --cookbook next-step-tracker && 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. On a CLI too old to have add, copy this folder in as a sibling of what is
there by hand; everything below is unchanged.gtm_accounts model, rewire the imports to the existing one and drop the copy; two
resources with one slug is a collision at deploy.ls cadence/log/calls/ on the default branch. Empty means
install call-capture first, or the tracker runs clean and finds nothing every morning.cargo-ai cdk add connector/slack). Set channelId in infra/agents/tracker.ts to a channel
id (C…) from that connector's channel autocomplete, and invite the bot.
references/nudge.md (installed beside this file) is the message shape.## Decisions section 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. Confirm cargo-ai cdk check prints
the agent bound to your repository with no trailing subdirectory. Show the diff, and deploy only
on an explicit yes: cargo-ai cdk deploy.cargo-ai ai message create), and walk
Done when line by line with evidence. Then send it again: the second run must add no row and
post nothing.Derive before you ask. An input with a lookup is looked up, not asked. Only the ones marked asked genuinely live in the operator's head.
| Input | Kind | How it is answered | Why it matters |
|---|---|---|---|
repository binding (infra/agents/tracker.ts) | value | derived: leave repository, defaultBranch and connector unset; plan fills them from the checkout's git origin. cargo-ai cdk check prints what it resolved. | It is the only place the call log is read from. A hand-written owner/name is the value nobody notices is wrong until every morning comes back empty. |
| call log path | value | derived: ls cadence/log/calls/ and read two entries. The prompt expects call-capture's shape: frontmatter with a date, and an Actions section of checkboxes. | A log in another folder or shape is read as empty, and an empty morning looks exactly like a team that kept every promise. |
evidence source (infra/models/gtm-activities.ts, gtm-accounts.ts) | value | derived: cargo-ai storage model list. A project with a CRM-backed activities or gtm_accounts model uses crm-backed; otherwise the native models stay and something must fill gtm_activities (a sequencer webhook, a play, cargo-ai storage record create-bulk). | It is the only evidence a promise was kept besides a later call. An empty gtm_activities closes nothing, and the post grows every day until it is muted. |
channelId (infra/agents/tracker.ts) | asked | the Slack channel id (C…) the nudges may land in, read from the connector's channel autocomplete. Invite the bot. | Nudges quote what customers said on calls. Locked so they never land in a customer shared channel. |
TRACKER_TIMEZONE and the cron (infra/agents/tracker.ts) | asked | the team's morning. Keep the cron after call-capture's, so yesterday's calls are in the log when the tracker reads it. | A timezone that disagrees with the cron makes "due today" yesterday's list. |
LLM connector and model (infra/connectors/anthropic.ts) | value | derived: cargo-ai connection connector list. Any Anthropic model pairs with claudeCode; languageModel is a placeholder to set. | The harness runs against Cargo's LLM proxy, so this is what each morning is billed against. An openAi connector here typechecks and fails at deploy. |
Checked before moving on, not after the deploy:
cadence/log/calls/ on the default branch holds at least one entry with an unchecked actioncargo-ai cdk check prints agent:commitment_tracker bound to <your repo>#<branch> with no
trailing subdirectorychannelId is a C… id the Slack connector can see, and the bot is in that channelnode --import tsx evals/contract.mjs passes| Variation | When it is right | How | What it costs |
|---|---|---|---|
ours-only | The team only wants to see what it promised | In §1 of infra/agents/tracker.prompt.ts, record only commitments with promised_by "us" | A customer's "legal will review by Friday" is the commitment most often lost, and it is the one a rep has to chase. Dropping it hides stalled deals. |
default-windows | Your team's follow-up rhythm is not seven days for us, fourteen for them | Change the two fallbacks in §1 of the prompt | Only promises with no date said on the call use them. Too short and the post fills with things that were never late. |
quiet-day-line | Silence on a clear day reads as a missed run | In §3 of the prompt, post one "Nothing due today" line instead of nothing | One more message a day in the channel. The default is silence because a nudge channel nobody needs to read is the one that gets muted. |
per-rep-dm | Each rep wants their own list | One postMessage use per rep with its channel locked, and a map from owner to use in §3 | Every rep added is a deploy. Never unlock the channel to let the agent find a DM itself. |
crm-backed | The emails, meetings, notes and tasks already live in HubSpot, Salesforce or Attio | Replace infra/models/gtm-activities.ts and gtm-accounts.ts with connector-backed models (fetchRecords, every column, scheduled sync) of the CRM's engagements and companies, keep the slugs, and map the CRM's columns onto account_id, opportunity_id, contact_id, occurred_at, kind, subject, body and owner_email in the prompt's §2 query. Keep both read-only on uses | One more connector and a sync on a schedule. Evidence is as fresh as the last sync, so schedule it before the tracker's cron or a promise kept yesterday afternoon is nudged this morning. Never add CRM actions to the agent instead |
reach-back | The log has more than 30 days of open promises worth chasing | Widen the window in §1 | Backfilled promises arrive overdue all at once. Prefer letting call-capture's STALE-CHECK prefix keep old ones out. |
infra/agents/tracker.prompt.ts §2) A row
closed on a guess vanishes from the post and from everyone's mind, and nobody finds out until the
customer asks. "Probably sent" stays open.infra/agents/tracker.prompt.ts
§1 and §3) commitment_id is the entry path plus the promise, so an entry is never re-extracted;
last_nudged_on is set after the post succeeds, so a failed post is retried and a re-run posts
nothing.infra/agents/tracker.prompt.ts, Rules) Ticking
checkboxes in the call entries would rewrite call-capture's record and open a pull request every
morning that someone has to merge for the tracker's own state. State that changes daily belongs in
a model.infra/agents/tracker.ts) gtm_activities and gtm_accounts are on
uses with readOnly: true. A tracker that can write the evidence can close its own commitments,
and a CRM write action added for "convenience" creates tasks a rep spends the morning deleting.channelId is locked on postMessage. (infra/agents/tracker.ts) The post quotes customers.
A channel the agent picks is how one quote lands in front of the customer who said it.infra/agents/tracker.prompt.ts §1) A paraphrase is
how a rep gets chased for something nobody promised.node --import tsx evals/contract.mjs passescargo-ai cdk plan reports the agent, the commitments, gtm_activities and gtm_accounts models, the
three connectors and the two foldersgtm_activities (and a gtm_accounts row per test account) with
cargo-ai storage record create-bulk --model-uuid <uuid> --records '[{"data":{…}}]', the model
uuid read from cargo-ai storage model listgtm_activities row or a later call entry that plainly matches the
promise, and a seeded activity that does not match closed nothinggit log on the default branch shows no commit and no pull request from the trackerThe evidence reads are SQL over workspace models and bill nothing. The post is slack.postMessage
on the bound connector; immediately before the plan, run
cargo-ai orchestration action list postMessage --kind connector --integration-slug slack and read
the current cost out loud. The recurring cost is the harness run itself, once each weekday, billed as
LLM tokens through the Anthropic connector. It scales with the number of call entries in the window
and open commitments to check, not with the size of the activity log, and the run posts at most once.
call-capture (the call log it reads), and standup (the evening recap can cite what closed today).