provenance-grade · git:20260915.62b4ef6 · 2026-09-15 · sha256 00084c558dc44c78
provenance-grade git:20260915.62b4ef6A
Immutable. This exact content is served forever at /api/v1/blob/00084c558dc44c78.
---
name: provenance-grade
description: 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.
---
# provenance-grade
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
| Grade | Meaning |
|---|---|
| `verified` | The 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. |
| `asserted` | Answered directly and cleanly, but nothing established where the value came from. |
| `assumed` | Hedged, deferred, misaligned with the question, or a bare round number. The call did not establish this value. |
| `unstated` | The 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](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.
```ts
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.
```ts
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`):
```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](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](references/ethics.md).
## Limitations
Stated, not hidden — the full list with reasoning is
[references/limitations.md](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.*