---
title: "Cookbook: Intelliprompter"
type: cookbook
tags: [cookbook, score, transcripts, real-time, checklist]
created: 2026-10-27
updated: 2026-10-27
confidence: high
sources:
  - raw/docs/cookbooks__intelliprompter.md
  - raw/docs/cookbooks.md
jev_version: "jev-1.14.0"
sdk_python: "0.7.5"   # the cookbook installs cooksafe>=0.2.0,<0.3.0 and pins model jev-1.13.0
summary: "Tick off a speaker's talking points live: each unchecked point is one five-level Score over the whole transcript so far; code checks a point at 2.5 of 5, keeps it checked and stops asking about it."
---

# Cookbook: Intelliprompter

> **TL;DR** Send the full transcript so far as `state` and one `Score` question per talking point not yet covered, all in one request. The SDK returns a 0–4 score (the probability-weighted level, so it can fall between levels); the cookbook adds 1 for a 1–5 display, and code marks a point covered at 2.5, keeps it covered and drops its question from later requests. Jev only scores; the order, the 2.5 rule and the one-way checklist live in your code.

## Goal

A teleprompter tells a speaker what to say. Intelliprompter does the reverse: it listens to a talk given from bullet-point notes and, in near real time, ticks each planned point off once what the speaker has already said covers it (raw/docs/cookbooks__intelliprompter.md). The live app takes microphone audio through ElevenLabs speech-to-text and a local app; the cookbook skips that integration and replays six hard-coded transcript chunks to show only the TypeSafe part.

Upstream lists it under "Search and matching", level Beginner (raw/docs/cookbooks.md; [[cookbooks/overview]]). It is the first official cookbook that runs the same `Score` questions again and again over a growing text, with state kept in code between calls.

When to use it: you have a fixed list of things that should come up in a talk, meeting, call or lecture, and you want to know which have been covered, as the text grows. When not to: you need to know *when* or *how well* a point was made in one passage (each tick scores the whole transcript, not the latest line), or a point should be able to become uncovered again (this design never unchecks).

## Inputs / state shape

The `state` is the whole transcript so far under one key, not just the latest line:

```python
{"transcript": "Hello. This is Intelliprompter. It is a smart teleprompter. It listens while I present and marks points after I cover them."}
```

The checklist is three talking points with five criteria in all. Each criterion has short `display` text for the screen and a longer `description` that tells Jev what to look for, for example "The speaker said the name Intelliprompter." A talking point completes when all its criteria are covered. The six replayed chunks grow the transcript from 4 to 84 words.

Numbers below came from `jev-1.13.0` on 2026-09-29 (the cookbook pins `TYPESAFE_MODEL = "jev-1.13.0"`; since the 2026-10-24 alias move the default `jev-latest` is `jev-1.14.0`, [[reference/models-and-pricing]]). Every call is cached in `json_cache.json` in the typesafe-ai/typesafe-public-examples repo, so the published numbers can be replayed without a key.

## Questions asked

One `Score` per unchecked criterion, keyed `c_<point>_<criterion>`, all in one request:

| Field | Value |
|---|---|
| `type` | `score` |
| `instructions` | `` How completely has the speaker covered this criterion in `transcript`: "<description>" `` |
| `criteria` (ordered, levels 0–4) | "Not mentioned at all"; "Briefly or indirectly mentioned"; "Mentioned directly"; "Mostly covered"; "Covered well and with useful detail" |

```python
import os
from pathlib import Path

from cooksafe import JsonCache
from typesafe_sdk import Score, TypeSafeClient

TYPESAFE_MODEL = "jev-1.13.0"
COVERED_AT = 2.5  # the live app's default threshold on its 1–5 scale

client = TypeSafeClient(
    api_key=os.environ.get("TYPESAFE_API_KEY", "cache-only"),  # cached re-renders need no key
    model=TYPESAFE_MODEL,
    timeout=120.0,
)
json_cache = JsonCache(Path("json_cache.json"))

SCORE_LEVELS = [
    "Not mentioned at all",
    "Briefly or indirectly mentioned",
    "Mentioned directly",
    "Mostly covered",
    "Covered well and with useful detail",
]


def score_question(description: str) -> Score:
    return Score(
        instructions=(
            "How completely has the speaker covered this criterion in `transcript`: "
            f'"{description}"'
        ),
        criteria=SCORE_LEVELS,
    )


@json_cache
def score_tick(transcript: str, pending: tuple[tuple[str, str], ...]) -> dict:
    response = client.system_one(
        state={"transcript": transcript},
        questions={key: score_question(description) for key, description in pending},
    )
    return {
        "answers": {
            key: {"score": answer.score + 1} for key, answer in response.scores.items()
        }
    }
```

(Upstream verbatim except the display imports. Install: `pip install ipython 'cooksafe>=0.2.0,<0.3.0'`, then set `TYPESAFE_API_KEY`.)

## Combining logic in code

Jev returns scores and keeps no checklist state. The app keeps a set of covered keys. On each tick it appends the new chunk to the transcript, asks only about criteria not yet covered, and adds any key whose score reaches `COVERED_AT`. A covered key never leaves the set, so later requests omit it.

```python
def pending_criteria(covered: set[str]) -> tuple[tuple[str, str], ...]:
    return tuple(
        (f"c_{point_index}_{criterion_index}", criterion["description"])
        for point_index, point in enumerate(CHECKLIST)
        for criterion_index, criterion in enumerate(point["criteria"])
        if f"c_{point_index}_{criterion_index}" not in covered
    )


def run_loop(chunks: list[str]) -> tuple[set[str], dict[str, dict]]:
    transcript = ""
    covered: set[str] = set()
    latest: dict[str, dict] = {}
    for chunk in chunks:
        transcript = f"{transcript} {chunk}".strip()
        pending = pending_criteria(covered)
        if not pending:
            break
        for key, answer in score_tick(transcript, pending)["answers"].items():
            latest[key] = answer
            if answer["score"] >= COVERED_AT:
                covered.add(key)
    return covered, latest
```

(Condensed from upstream, which also records each tick's request for a playground link and prints a line per tick; `CHECKLIST` is the three-point list described above.)

The live app starts a new tick every half second when the transcript has changed and no request is in progress; the cookbook's loop uses the six chunks as its ticks. In the docs' words, 2.5 is "the demo's local coverage threshold, not a TypeSafe confidence value".

## Results / what the cookbook reports

Scores on the 1–5 display scale, `jev-1.13.0`, 2026-09-29 (raw/docs/cookbooks__intelliprompter.md):

| Tick | Words | Scores sent back | New checks |
|---|---|---|---|
| 1 | 4 | c_0_0 = 3.02, c_0_1 = 1.11, c_1_0 = 1.00, c_1_1 = 1.00, c_2_0 = 1.00 | 1 |
| 2 | 21 | c_0_1 = 3.74, c_1_0 = 1.00, c_1_1 = 1.10, c_2_0 = 1.09 | 1 |
| 3 | 40 | c_1_0 = 3.76, c_1_1 = 1.15, c_2_0 = 1.06 | 1 |
| 4 | 60 | c_1_1 = 3.67, c_2_0 = 1.10 | 1 |
| 5 | 70 | c_2_0 = 2.06 | 0 |
| 6 | 84 | c_2_0 = 3.93 | 1 |

All five criteria end checked. Tick 5 shows progress without a check: after one use case ("on stage at an industry event") the "give at least two uses" criterion scored 2.06, below 2.5; the second and third uses in tick 6 took it to 3.93. Not-yet-mentioned criteria stayed near 1.00 throughout, and each request shrank as criteria were checked (five questions at tick 1, one at ticks 5 and 6).

## Adapting it to a new domain

- **Replace the replayed chunks** with your transcription provider's accumulated text. Send the whole transcript each tick, not the newest line: a point may be made across several sentences.
- **Write descriptions Jev can check against the text**, as the cookbook does ("named at least two suitable uses, such as conference talks, lectures, or product demos"). A vague description ("explain the product") is hard to score.
- **Keep the threshold in code** and set it on your own transcripts; 2.5 is the demo's choice. Keep the one-way rule in code too, so a later, weaker paraphrase cannot uncheck a point.
- **Other uses of the same loop** (inferred): a sales-call or support-call script checklist, interview question coverage, meeting agenda tracking, a lecture's learning objectives.
- **Long sessions:** the transcript grows every tick and is sent in full, so it counts against the context limits on every request ([[reference/models-and-pricing]]); for a long call, plan for that before the limit, for example by checking whether trimming earlier text changes your scores (inferred; the cookbook's transcript stays at 84 words).

## Gotchas

- **The score is a weighted level, not a probability.** The SDK's Score answer is each level number times its probability, summed (0–4 here, so it can land between levels); the cookbook adds 1 for display ([[concepts/score]]). Do not read 2.5 as 50%.
- **Jev does not remember.** Each tick is independent; the covered set, the order and the one-way rule are application state ([[concepts/system-one]]).
- **Cost and latency grow with the transcript.** Each tick sends the whole transcript plus one question per unchecked point, so input tokens rise as the talk goes on while the question count falls ([[reference/models-and-pricing]]).
- **The cookbook pins `jev-1.13.0`.** Its numbers were recorded on 1.13; re-check the 2.5 cut if you run it on the default alias, which is now 1.14 ([[guides/production-operations]]).

## Related

- [[concepts/score]] — Score questions, levels and the weighted score
- [[patterns/fan-out]] — all of a tick's questions in one request
- [[cookbooks/entity-alignment]] — another one-Score-per-item cookbook
- [[cookbooks/overview]] — the cookbook catalog
- [[ideas/patterns-data]] — community patterns for transcripts and real-time streams

## Sources

- raw/docs/cookbooks__intelliprompter.md (https://docs.typesafe.ai/cookbooks/intelliprompter)
- raw/docs/cookbooks.md (https://docs.typesafe.ai/cookbooks) — section and level
