Discovery detection behind a privacy gate
Vocion now detects which of your recorded calls were discovery calls — without ever letting an agent read a transcript that didn't first match a CRM record you own. Plus: automations become inspectable, testable, and owned.
Once Zoom and Granola are connected, recordings pile up fast: prospect intros, all-hands, 1
, candidate interviews, board calls. Somewhere in that pile are the discovery calls that should be turning into proposals — but finding them by having a model read every transcript wholesale is a privacy problem, not a feature. Your internal meetings are none of the sales agent's business. Releasesv2.5.0 through v2.14.0 (2026-08-18 to 2026-08-20) ship discovery-call detection built
the other way around: match first on metadata, read only after a match, and never hand the
transcript to the agent at all.
What's new
- Privacy-gated detection (
v2.5.0, 2026-08-18): a transcript is never read unless the meeting first matches — by attendee email or company domain, metadata only — a CRM party the seller owns (or a seller-hosted call with an external guest). One function,readMatchedTranscript, is the sole place the feature touches transcript content, and it refuses without a match row. - Agent-driven detection with
classify_call(v2.8.0, 2026-08-20): the RevOps Lead orchestrates via granted tools —match_meetings,classify_call,get_discovery_ledger,reconcile_discovery_window.classify_callreads the transcript server-side through the content gate, scores it with one fixed model call, and persists verdict + provenance before returning only structured scores. The transcript body is never returned to the agent. - A discovery ledger with a real audit trail: every match and assessment lands on
/dashboard/discoverywith transcript hash, thresholds, classifier version,workspace_sha, and who assessed it (agent + mission run). Every classification routes to the review queue — no trust rule can generate a proposal without a human. - The sweep is a scheduled automation, not a CLI (
v2.6.0), and automations themselves became inspectable, testable, and owned (v2.7.0, 2026-08-19): every fire persists a run row, every automation names an owner agent, and the card shows what it does, its parameters, its last outcome, and a Test run control with day replay and dry run. - Wider capture, honest gaps: Granola notes match alongside Zoom recordings (
v2.9.0) — so calls the prospect hosts on Teams or Meet are visible — with double-captures deduped. Meetings that fail closed are reported as unmatchable with the fix stated, never silently skipped (v2.13.0), and a zero-attendee recording whose title names a HubSpot contact by full name now matches anyway (v2.14.0). Tool payloads lead with counts, and those counts surface in the activity trace (v2.10.0–v2.12.0).
Why we built it
The privacy guarantee is structural, not a prompt. We didn't write "please don't read internal
meetings" into a system prompt and hope; we built a funnel where the unwanted state is unreachable.
Stage one sees only metadata — titles, hosts, attendee emails. Only a meeting that matched an owned
CRM record gets a candidate row, and only a candidate row opens the content gate. When detection
moved from a deterministic job to an agent-driven loop in v2.8.0, that guarantee had to survive
an agent choosing its own steps — so classify_call collapses read, classify, and record into a
single server-side function. The transcript never enters agent-steered context, and an
assessed-but-unlogged call is not a reachable state. Refusals are typed (no_candidate,
no_transcript), and the tools exist only for agents explicitly granted them.
Fail-closed is the right default, but silent fail-closed is a bug. Zoom↔calendar correlation is
id-based, never time-based, so a recording with no attendee metadata matches nothing — by design.
The trouble: on 2026-08-20 a real 31-minute prospect intro sat among 318 scanned recordings,
matched nothing, and reconcile reported zero gaps. Match and coverage were computed from the same
blind metadata, so the system was confidently wrong. Now every fail-closed meeting is named in an
unmatchable list (metadata only) with the reason and the actual fix — and name-in-title matching
rescues the common case outright. The same instinct drove v2.7.0: the sweep first shipped
scheduled but unobservable, where a run that scanned nothing, ran clean, or threw were
indistinguishable afterwards. Run rows, owners, and dry-run replays fixed that.
Where to find it
The ledger lives at Dashboard → Discovery; the sweep's automation card (with Test run) at
Dashboard → Team → Automation. Grant the tools to an agent via harness.grantTools, schedule
the check with a do.prompt automation, and see Automations and
Tools for authoring. Upgrading spans migrations 0049–0053. Full detail in
PRs #55, #56, #57, and #59 on vocion-core.