Skip to main content
Glama

Receipts

test

Interview transcripts in. Themes and answers out, where every claim cites a verbatim quote with speaker and timestamp, or it is dropped, visibly.

Receipts: run themes, inspect a dropped claim, ask a question, follow a sentence to its source

Most tools that summarise research quietly invent the connective tissue: a paraphrase presented as a quote, a theme one person mentioned reported as "users want", a confident answer to a question nobody in the corpus addressed. Receipts refuses all three, and shows you the refusal.

Live demo: https://receipts.alexjpower74.workers.dev (eight sample interviews loaded; Themes and Ask run the real model, so please be kind to the owner's API bill).

The rule

A claim reaches the screen only with a citation the verifier has checked against the transcript text: the quote must be a verbatim substring of exactly one spoken line (whitespace and curly quotes normalised, case-sensitive). A theme survives only with at least two verified quotes from at least two different interviews. Everything the model wanted to say that failed goes to the Dropped panel with its reason: no citation, line not found, quote not in line, single source.

The model is not trusted to quote from memory. It has to fetch the lines it cites, through tools (search_quotes, get_line), and the verifier re-checks the output anyway.

Related MCP server: call-copilot

What it does

  • Transcripts. Paste or upload [mm:ss] Speaker: text or WebVTT. Eight fictional sample interviews ship with it, every one labelled SAMPLE TRANSCRIPT — fictional interview.

  • Themes. One click. A live ticker shows each tool call as the model searches the corpus. Theme cards carry their quotes with speaker and timestamp; click a quote and the reader scrolls to the line. The Dropped panel lists what was thrown out and why.

  • Ask. "What did people say about pricing?" Every sentence of the answer is a link to its source lines. A sentence with no source renders as dropped, not as text. When nobody said it, it says so, and shows the searches it ran.

  • MCP server. Any agent can query the same corpus as tools (below).

Evals, with a negative control

evals/negative-control.test.ts runs in CI on every push with no model at all: 21 planted attacks (fabricated quotes, paraphrases, ghost lines, ghost transcripts, quotes from the wrong line, case changes, single-source themes padded with fabricated second sources) go through the verifier as if the model had emitted them. The suite fails if any survives. The positive path, a fully supported payload, must come through byte-for-byte. It was shown red three separate ways before it was trusted.

npm run evals runs seven scenarios against the real model and prints a scorecard; it re-verifies every surviving citation independently of the app code.

Scorecard, 2026-09-12, claude-sonnet-5, main 2c8fb2fa6e88c4, three runs:

run

passed

what failed

1

6 / 7

"trust in AI summaries": cited the line after the expected refusal ("if it's wrong, it's my name on it")

2

6 / 7

"do incentives matter": cited both sides across four interviews, but not the one phrase the scenario named

3

6 / 7

"does anyone read the deck": cited the deck-sceptic and the deck-reader, ten lines across six interviews, none of them the two phrases the scenario named

Every miss is quote selection, not fabrication: every citation in all three runs re-verified against the corpus, no sentence shipped without a source, no forbidden claim ("everyone agrees", "dark mode", "several participants" about a single source) was asserted, and the nobody-said-that scenario answered nothingFound. The scenarios accept alternative lines that make the same point; they still miss when the model picks yet another line that does. We publish the three runs rather than tune the scenarios until one run reads 7 / 7. The stub model scores 0 / 7 on content by design; its run only proves the wiring.

Architecture

flowchart LR
  UI[React UI<br/>Transcripts · Themes · Ask] -- SSE --> W[Cloudflare Worker]
  W --> D1[(D1<br/>transcripts, lines)]
  W --> A[Agent loop<br/>tool use, 8-call cap]
  A -- search_quotes / get_line --> IDX[Corpus index]
  A --> M[Claude<br/>or scripted stub]
  A --> V[Verifier]
  V -- themes + dropped --> W
  MCP[MCP server<br/>stdio] --> IDX
  MCP -. API_URL .-> W
  • src/corpus parser, index, D1 schema and seed. src/verify the verifier. src/agent model interface (Anthropic + stub), tool loop, themes and ask. src/worker HTTP API and stores. src/ui React. mcp/ the MCP server. evals/ scenarios, scorer, negative control.

  • src/contract.ts is the seam: every type the slices share, and the only place a field is added.

  • Without ANTHROPIC_API_KEY the scripted stub runs and every result says model: "stub"; the UI shows it. The stub plants one fabricated theme per run so the Dropped panel always has a demo.

MCP server

Any agent can query the same corpus the UI shows, as tools, over stdio:

tool

input

returns

list_transcripts

TranscriptSummary[]

search_quotes

{query, limit?}

ranked lines with lineId, speaker, timestamp, text, score

get_themes

the verified ThemesResult: themes with verbatim citations, and dropped with reasons

get_source

{lineId}

one line with speaker, timestamp, text and transcript title

By default it indexes the shipped samples in-process. Set API_URL=http://localhost:5402 to read from a running Worker instead. Claude Code, in .mcp.json:

{ "mcpServers": { "receipts": { "command": "npx", "args": ["tsx", "mcp/server.ts"],
  "cwd": "/path/to/receipts", "env": { "API_URL": "http://localhost:5402" } } } }

Run it yourself

git clone https://github.com/alexjpower74-create/receipts && cd receipts && npm install
cp .dev.vars.example .dev.vars        # add ANTHROPIC_API_KEY, or leave it empty for the stub
npm run build && npm run dev:worker   # API + built UI on http://localhost:5402
npm run dev                           # or the Vite dev server on :5403, proxying /api
npm run test:all                      # typecheck, unit + negative control, D1 inside workerd

npm run test:e2e runs Playwright on Chromium and WebKit at desktop and phone width.

How it was built

Three Claude Code sessions built this in parallel under rig, each owning a file slice enforced by a commit guard, against a shared src/contract.ts written first; a fourth session led, graded every checkpoint from a worktree pinned to the commit, and merged. Every suite was made red once before it counted. That habit found what code review would not have:

  • The unit tests could not see a seed deadlock; the first real wrangler dev request hung. Now a workerd test.

  • A stateful stub answered the second request mid-script. Caught by curling the live Worker twice.

  • A negative control that did not go red (a delete that SQLite's INSERT OR REPLACE cascade was already doing) was recorded as a finding and replaced with a break that did.

  • A test double that accepted anything hid that real D1 rejects the schema file as written. The suite running the real binding failed on the first statement; the fake now rejects it too.

  • Merging one slice's eight samples broke another slice's tests that assumed five. Counts now come from the data.

Build reports, per slice, are in docs/.

MIT © Alexander Power

Related MCP Connectors

Related MCP Servers

  • A
    license
    Not graded
    quality
    A
    maintenance
    Search and read locally-recorded meeting transcripts over stdio. Read-only, no network. Exposes list_sessions, get_session, and search_transcripts to any MCP client.
    46 PyPI
    7
    MIT
  • A
    license
    A
    quality
    B
    maintenance
    Enables any MCP-compatible agent to query a local OpenRhyme activity timeline, search history, and issue control commands over stdio while keeping all data on-machine.
    5
    MIT