provenance-grade

SkillAI & models

Attach an advisory knowledge grade, verified / asserted / assumed, to fields a CALL-E phone agent extracts, using heuristic signals in the transcript turns the API returns.

Available today. Use it from your connected AI after setup.

Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.

Then ask your AI: use the provenance-grade skill

What this skill tells your AI

The instructions your AI receives, as published by calle-ai/awesome-phone-call-agents in skills/provenance-grade/SKILL.md and read by ahel’s review.

Every phone agent extracts what was said and throws away how the speaker knew it.

Speech carries the speaker's epistemic state; text hides it. Structured extraction throws that signal away. This skill puts it back: it reads recipients[i].attempts[j].transcript_turns from a completed CALL-E call and attaches a knowledge grade to every extracted field, with the exact transcript span that supports it.

This skill never places calls: it lints a task before a host dispatches it and grades a transcript after a call has completed. It contains no dialing code, no phone numbers, and no network calls of any kind.

Two calls can both answer "Tuesday." In one, the person said "hold on, let me check", went quiet for eleven seconds, and came back with "Tuesday — we're holding eleven units at the Bhiwandi warehouse." In the other, they said "should be Tuesday" half a second after the question. Same extracted value. Completely different knowledge. The first is verified; the second is assumed — and if your workflow auto-commits on the second one, the extraction was correct and the answer was still wrong.

Why this is not a duplicate of anything in this repo

CALL-E already returns completion_confidence — but by its own documentation that is confidence that the task reached a clear end state, explicitly not confidence in the quality of the business answer. That is a documented, self-acknowledged gap, and this skill fills exactly it.

Against the other skills here: call-summarizer summarises after the fact. voice-preflight checks audio before the call. linecanary monitors line health. Nothing grades the epistemic basis of a spoken claim.

The grades

GradeMeaning
verifiedThe heuristic detects a lookup pause or explicit check language, plus an unrequested corroborating specific, plus read-back compliance where requested. This label is not proof that a lookup occurred or that the answer is true.
assertedAnswered directly and cleanly, but nothing established where the value came from.
assumedHedged, deferred, misaligned with the question, or a bare round number. The call did not establish this value.
unstatedThe field never appeared in the transcript (voicemail, IVR, unanswered question). Never a grade — there is nothing to grade.

Consumer rule: grades are advisory inputs to the host's own validation, not independent authorization to act. Keep a human in the loop for asserted, and never auto-act on assumed or unstated. The high-stakes prohibitions in references/safety.md apply to every grade, including verified.

Fail-closed: the default is assumed. asserted must be earned, verified must be earned twice. Absence of signal never upgrades a field. This is the same discipline this repo rewards everywhere else, applied one level up.

Two halves

Pre-call — scripts/lint-task.ts. Amends the caller's task text and result_schema so the signals are actually elicitable: forces a read-back of critical values, rewrites "do you know?" into "can you check?", asks for one corroborating specific, and gives the agent permission to wait while the person looks something up. You improve the signal you will later measure.

import { lintTask } from './scripts/lint-task.ts';

const { task, result_schema, amendments } = lintTask({
  task: 'Call the supplier. Do you know the unit price and delivery date?',
  result_schema: mySchema,
  critical_fields: ['unit_price', 'delivery_day'],
});
// -> task now asks "can you check", requests a read-back, asks for one specific

Post-call — scripts/grade.ts. Reads the transcript turns and emits a grade plus the supporting span per field.

import { gradeCall } from './scripts/grade.ts';

const provenance = gradeCall({
  callId: call.id,
  recipientId: recipient.id, // an ORGANISATION id, never a person
  turns: attempt.transcript_turns, // { speaker, text, offset_seconds }
  fields: [
    {
      field: 'delivery_day',
      value: extracted.delivery_day, // from CALL-E's structured output
      expects: 'weekday',            // duration | weekday | date | price | count | text
      questionTurn: 2,               // where the bot asked
      answerTurns: [4],              // where the person answered ([] if never answered)
      readbackRequested: true,
      critical: true,
    },
  ],
});
// provenance.fields[0] ->
// { field: 'delivery_day', value: 'Tuesday', grade: 'verified',
//   signals: ['A:gap=12.6s', 'B:let me check', "C:stock_count 'eleven units'", "C:place 'Bhiwandi'"],
//   span: 'It ships Tuesday. We are holding eleven units at the Bhiwandi warehouse for you.',
//   turnOffset: 26, unstable: false, gapSeconds: 12.6 }

Output contract (scripts/types.ts):

type Grade = 'verified' | 'asserted' | 'assumed' | 'unstated';

interface FieldProvenance {
  field: string;              // "eta_days"
  value: unknown;             // 5
  grade: Grade;
  signals: string[];          // ["B:let me check", "C:place 'Bhiwandi'"]
  span: string;               // exact transcript text supporting the value
  turnOffset: number | null;
  unstable: boolean;          // value was stated then revised (signal I)
  gapSeconds: number | null;
}

interface CallProvenance {
  callId: string;
  recipientId: string;        // organisation-level, never a person
  fields: FieldProvenance[];
  weakestGrade: Grade;        // the call is only as good as its worst critical field
  gradedAt: string;
}

How the grade is computed

Nine signals, described with examples in references/signals.md: retrieval gap (A), explicit check language (B), corroborating specific (C), hedging lexicon (D), deferral (E), read-back compliance (F), round-number shape (G), answer alignment (H), self-correction (I). They feed a fixed rule table:

if field not present in transcript      -> 'unstated'   (never a grade)
if E or H                               -> 'assumed'
if D present                            -> 'assumed'
if (B or A) and C and (F if requested)  -> 'verified'   (never when unstable)
if answered directly, no D/E/H          -> 'asserted'   (unless G with no C)
otherwise                               -> 'assumed'

Deterministic vs model-assisted — honestly. Signals A, B, D, E, F, G and I are regex and arithmetic. Signals C and H need semantics; in this build they run on a deterministic entity heuristic, and each exposes a provider interface (CorroborationProvider, AlignmentProvider) where a model can be plugged in — a model is constrained to returning spans, never a grade. The grade itself is always computed by the rule table above, never by a model's opinion. That is what makes it testable: 125 unit tests and a confusion matrix over 24 labelled fixtures, zero calls, zero network.

truth \ pred   verified  asserted  assumed  unstated
verified          4         0         0        0
asserted          0         6         0        0
assumed           0         1         9        0
unstated          0         0         0        4      -> 23/24 (95.8%)

The one miss is deliberate and shipped as a fixture: a code-switched answer whose hedge is the Hindi "shayad", which the English lexicon cannot see (assets/fixtures/23-codeswitch-hindi-hedge-missed.json). A named limitation beats a silent one. Reproduce with npm run eval; run tests with npm test.

Ethics boundary

This skill grades behaviour, not people.

  • No emotion detection, no stress or deception scoring, no voice biometrics. (Also impossible here by construction: CALL-E exposes no audio to grade — only transcript text and integer offsets.)
  • Hosts should associate grades with an organisation, not an individual. This pure grader does not enforce identifier contents or anonymize input: values, transcript spans and caller-supplied IDs may identify people. The caller must minimize inputs and redact display/export copies before sharing them.
  • A low grade means the call did not establish this, never this person lied.
  • Transcript spans are retained only as the minimum quote supporting one field.

Full statement: references/ethics.md.

Limitations

Stated, not hidden — the full list with reasoning is references/limitations.md. Headlines: offset_seconds marks turn start at integer resolution, so the latency signal is weak and weighted accordingly; ASR errors corrupt hedge detection; the lexicon is English-centric and swappable (scripts/lexicon/en.json), with the Hindi file (scripts/lexicon/hi.json) shipped unvalidated; directness is cultural — a terse answer is not necessarily a guess; and the grades are not yet validated against outcomes.

Roadmap (stated, not built)

The grade is a prediction. The validation is: record the grade, wait for the promised event, record whether it held, and measure grade-vs-outcome accuracy per organisation. Over enough calls this produces a calibration curve and a per-supplier reliability score — a supplier whose asserted Tuesdays actually arrive on Tuesday earns trust; one whose verified claims fail flags a broken lookup process. None of that is built and none of it is tested. It is named here because naming the unvalidated claim is what separates a measure from a demo.


CALL-E tells you the call finished. This tells you whether to believe it.

Signals

GitHub stars
104
Forks
527
Last commit
Sep 2026
Advanced
Item type
skill
Key
provenance-grade
Source
github.com/calle-ai/awesome-phone-call-agents