Call capture
Turn your recorded calls into a go-to-market knowledge base: each recorded call is collected every morning, scribed into a log entry, and, once a claim repeats, promoted into context, as one pull request against your go-to-market repo. Nine recorders ship; a tenth is one adapter file.
Set up the call-capture 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/call-capture
3. Then follow .claude/skills/call-capture/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/call-captureSay this to your agent
Every morning, pull yesterday's Gong calls into this repo as log entries and open one pull request with anything worth adding to our context.
Illustrative output #
Fictional records, to show the shape of what comes back.
[call-capture] scribe 2026-10-06
Captured 5 · scribed fresh 5 · scribed from backfill 0 · pending 0
+ cadence/log/raw/calls/2026-10-05-northwind.md
+ cadence/log/calls/2026-10-05-northwind.md
+ ## Objections
+ - "We already pay for Globex, I can't justify a second tool" (Priya Raman, VP RevOps)
+ ## Actions
+ - [ ] Send the Globex migration one-pager to Priya
+ context/objection/already-have-globex.md
+ Heard on 2 calls: cadence/log/calls/2026-09-22-fabrikam.md,
+ cadence/log/calls/2026-10-05-northwind.md
Five calls became five log entries, and the Globex objection reached context/ only because a second
account raised it; a single mention would have stayed in its log entry.
What you will be asked #
| Input | Why | |
|---|---|---|
| CALL_RECORDER_API_KEY | Asked | It is the collector’s only credential, and the workspace is the only copy of it that outlives the session that set it. In the catalog it is read server-side on every run, so a rotation needs no deploy; as a secret() in the agent’s env it is the deploying shell’s value, so every deploy needs the key again and a rotation lands only on a re-apply. |
INTERNAL_DOMAIN (scripts/collect/config.ts) | Looked up | It is how an internal call is told from a customer one. Avoma’s is_internal is false on every meeting in some workspaces, so it cannot be used; leave the placeholder and every standup is captured as an account. |
Done when #
--dry-runlisted the calls it would take, and the run without it wrote those files intocadence/log/raw/calls/with asource:id in each; running it twice wrote nothing the second timeRECORDERinconfig.tsis the recorder the team records on, and the capturedsource:lines carry that slugcargo-ai workspaceManagement envVar listshowsCALL_RECORDER_API_KEYas a secret entry, andgrep -r CALL_RECORDER_API_KEYfinds its name in the collector and the docs and its value nowhere — the deployed run reads the catalog, so no local shell has to hold the key again- on an adapter marked
docsrather thanlive:--dry-runlisted real calls with the right dates and account slugs through it, and one captured file was opened and read — that is what moves it tolive, and until someone does it a clean run proves nothing - on a recorder that does not ship: the new adapter compiles (a half-written one does not typecheck, and the error names the missing field), it is registered under a key equal to its own
provider,--dry-runlists real calls through it, and nothing inscripts/collect/recorder.tshad to change to make it fit cargo-ai cdk planreports three resources and does not hit the recorder’s API while planning- the first scheduled run opened one pull request whose body reports four numbers: captured, scribed fresh, scribed from backfill, and pending remaining
- every scribed call has exactly one entry under
cadence/log/calls/, and no raw file was edited, moved or deleted - at least one
context/file in the diff cites two different calls as its occurrences, and no file in the diff cites only one - a call where your company was the buyer, not the seller, is filed as vendor intel and appears nowhere as pipeline
- after merging and
cargo-ai cdk deploy, the changedcontext/file is readable from the workspace context repository, and an agent with thecontextcapability quotes it back
What it costs #
There are no connector actions in this skill, so it consumes no per-record credits: the collector
talks to your recorder directly from the harness environment, under whatever API limits your
recording plan already gives you. Those limits differ enough between recorders to be worth checking
once — a daily three-day window is nothing against Grain’s per-minute allowance and is real against
Fireflies’ Free tier of a few dozen requests a day, and references/recorder-apis.md records
each. The collector paces itself and backs off on a 429 regardless, so the failure is a slow run
rather than a half-captured one.
The recurring cost is the harness run itself, once a day, and it scales with how much the agent reads — which is the raw captures it scribes. The per-run cap is the cost control as well as the review control. A lower cap spreads the same backlog over more days at the same daily cost rather than paying for it all at once, and the pending remainder in the pull request body is how you watch whether it is keeping up.
To make what is said on calls reusable, collect every recorded call each morning, scribe it into a log entry, and promote a claim into your context only once it has been heard twice. This cookbook does that with a committed collector script and a Claude Code harness agent, delivered as one reviewable pull request against your go-to-market repository. Nine call recorders ship; a tenth is one adapter file.
cargo-ai cdk add cookbook/call-capture
What it does #
- Collects.
scripts/collect/calls.tspulls every call in a three-day rolling window whose transcript the recorder has finished processing, and writes one raw file per call intocadence/log/raw/calls/. Deterministic, idempotent, no LLM anywhere near it. - Scribes. The agent reads each pending raw capture and writes
cadence/log/calls/<date>-<account>.md: what was said, the GTM intel under fixed headings, the actions as checkboxes. Capped at 12 per run, fresh-first then oldest-backfill. - Promotes. A claim reaches
context/— the knowledge layer at the repository root — only on its second independent occurrence, citing both calls. - Stops. One pull request, never merged. The agent has repository write access and no other write path: no email, no CRM, no merge.
How it works #
- The cron trigger fires at 07:00 UTC, after the recorder has finished processing yesterday.
- The agent clones the repository — the project’s own, resolved from the checkout’s git origin at deploy rather than written down. The harness’s working tree is the GTM repo itself, which is what lets its output be a diff rather than a column value.
- It runs the collector —
npx tsx scripts/call-capture/collect/calls.ts— which takes its recorder and internal domain fromcollect/config.tsin the tree it just cloned, andCALL_RECORDER_API_KEYfrom the workspace environment variables a harness agent inherits in full. The agent is told not to fetch calls itself and not to edit the script’s collection rules. - It scribes the pending captures, then promotes what repeats.
- It opens one pull request whose body reports four numbers: captured, scribed fresh, scribed from backfill, pending remaining. The remainder is the drain gauge; a number that never falls means the cap is too low or a class of call is failing to parse.
- A human merges, and the next
cargo-ai cdk deploysyncscontext/into the workspace context repository, where the scorer, the researcher and every other agent read it.
Adds 4 resources plus a script bundle.
| File | Resource | Role |
|---|---|---|
infra/agents/call-scribe.ts | defineAgent (claudeCode) | schedule, model, folder — and no env of its own |
infra/agents/call-scribe.prompt.ts | (not a resource) | the scribe’s contract: window, cap, repetition bar, limits |
infra/connectors/git.ts | defineConnector (github) | the clone, branch, push and PR path, resolved by binding |
infra/connectors/anthropic.ts | defineConnector (anthropic) | the model the harness runs on, billed and metered |
infra/folders/index.ts | defineFolder | the workspace folder this cookbook’s resources are filed in |
scripts/collect/recorder.ts | (not a resource) | the Recorder contract and the provider-agnostic pipeline |
scripts/collect/recorders/ | (not a resource) | nine recorder adapters and the registry that names them |
scripts/collect/config.ts | (not a resource) | the two project choices: which recorder, which domain |
scripts/collect/calls.ts | (not a resource) | the entrypoint: resolve a slug, then capture |
The two halves, and where they land #
This cookbook has one directory per layer it touches, and the install mirrors each into its namesake in the project:
call-capture/infra/ -> infra/call-capture/ what is declared and deployed
call-capture/scripts/ -> scripts/call-capture/ what the agent runs
Those are the layers cargo-ai cdk init already scaffolds — infra/ is the CDK project, scripts/
is “imperative glue for runtime surfaces the CDK cannot declare yet” — so a cookbook that needs both
contributes to both under its own name rather than inventing a third place.
The collect/ subdirectory is a namespace, not decoration: call recordings are the first sensor,
and the next one (Slack threads, reply metadata, product usage) lands beside it as its own
entrypoint rather than growing this one. The agent’s step 1 names the collectors it runs, so adding
a sensor is a new file plus a line in the system prompt.
scripts/package.json is belt and braces. In a Manifest repo the CDK project root is infra/, so
nothing under scripts/ is ever imported as a resource. In a project whose CDK root is the repo
root, the loader imports every .ts it finds except directories carrying a package.json —
without that file, cargo-ai cdk plan would import the collector and run it against the live API on
every plan.
Why the split #
The collection and the judgement are different jobs, and the failure modes for mixing them are not symmetric.
A fetch loop an agent re-derives every morning is a fetch loop that silently changes shape: a window that drifts, a filter that quietly widens, a field read differently on a day the model was less careful. Nothing downstream can tell, because the archive is what everything downstream is diffed against. So the fetch is a committed script and the agent is told not to improvise it.
The scribing is the opposite. It is judgement — what was actually agreed, whether this is the same
objection as last week, whether a vendor call is being misread as pipeline — and it produces a diff
across a dozen markdown files. That is what harnessSlug: "claudeCode" buys: a working tree, the
git history to read before writing, and a pull request. It does not buy its own model: the harness
runs against Cargo’s LLM proxy, so connector and languageModel are required here exactly as
they are on a streamText agent, and they are what the run is billed and metered against.
Whoever records your calls #
The collector is a contract, nine implementations of it, and a slug that picks one.
recorder.ts defines Recorder — a provider slug and three methods (listReady, transcript,
notes) — and holds everything that is true whoever records your calls: deduplication, account
slugging, the internal-domain filter, file layout, the rolling window, --dry-run. recorders/
holds one adapter per vendor — Avoma, Granola, Fathom, Gong, Fireflies, Grain, tl;dv, Modjo and
Clari Copilot — plus the registry. config.ts names the one that runs, typed against the
registry’s keys, and calls.ts resolves it and calls capture.
npx tsx scripts/call-capture/collect/calls.ts --list
npx tsx scripts/call-capture/collect/calls.ts --recorder=granola --dry-run
A recorder that is not there is one new file beside the others and one entry in recorders/index.ts.
The compiler is what keeps that honest: a half-written adapter does not typecheck, and the error
names the field that is missing. Auth deliberately stays in the adapter — Bearer, Basic, two custom
headers and a signed GraphQL POST are four different things — while the 429 backoff is shared
through fetchJson(url, init), which takes the whole request.
Two rules keep a registry from becoming a graveyard of code nobody runs. Every entry declares
whether it was verified against a live workspace (Avoma) or written from the vendor’s own
specification (the other eight), and one written from docs says so on every run. And the five
recorders in references/recorder-apis.md that do not ship each carry the reason — a list
endpoint with no date filter, a transcript that is not JSON, an OAuth token that expires in ten
minutes — rather than a stub.
Why the context is not in this folder #
defineContext is a per-workspace singleton, and the knowledge layer belongs at the repository root
where humans edit it — which is exactly where cargo-ai cdk init already declares it. So this folder
ships none.
Placeholders (edit before deploy) #
Two of them, both in scripts/collect/config.ts, because they are choices rather than secrets: in
code the compiler checks one of them, a reviewer sees both, and an edit reaches the next run as soon
as it merges — the harness re-clones this repository every morning, while a value in the agent’s
spec would need a redeploy. The agent declares no environment of its own at all.
RECORDER— which of the nine slugs records your calls, typed against the registry, so a slug that is not a recorder failsnpm run typecheck. A recorder that does not ship is one new file satisfyingRecorderplus an entry inrecorders/index.ts, andreferences/recorder-apis.mdcarries the endpoints for five more.INTERNAL_DOMAIN— your own email domain, or every internal standup is captured as a customer call. Matched against the domain of each attendee’s address, or a subdomain of it.
And one credential, which is not in any file: create it once in the workspace with
cargo-ai workspaceManagement envVar create --key CALL_RECORDER_API_KEY --secret and the harness
inherits it. Deliberately not a secret() in the agent’s env, which would need the key in the
deploying shell on every deploy and a re-apply to pick up a rotation.
What it does not do #
It does not contact anyone, write to a CRM, merge its own pull request, edit or delete a raw capture
or an existing log entry, or touch plan/ and infra/. It reports what the field said; it does not
change the strategy or the deployed engine.