Skip to content

Designing an AI agent that can't misquote the Bible

How the Sunday slides agent proves it never changed a verse, why Claude only gets to propose design changes, and how the design pass went from 50 cents to 8 cents a build.

, 6 min read

Illustration: the Scripture on a slide locked inside a dashed boundary; design changes (style, place, band, split) are accepted and "rewrite verse" is refused

In Part 1 I told the story: the same volunteers spent two hours or more every week building the Sunday slides, so I built an agent that does it on Thursday evening. This part is for the engineers: how it works, and how it earns enough trust to go in front of a congregation.

The shape of it

Diagram: the church Google Doc feeds a code-built deck, then a Claude design pass, then human review on WhatsApp, with every verse checked after each step
How the weekly slides are made

Four stages, and only one of them involves a model:

  1. Read the plan. Parse the church's Google Doc: the roster, three Bible references, hymn numbers, the activity of the week.
  2. Build the deck, in plain code. Copy last Sunday's deck, fetch every verse in English and Dutch, lay them out, swap in names, songs, dates, events and the roster table.
  3. Design pass (Claude). Fix anything that's hard to read from the back of the hall.
  4. People review, on WhatsApp, before anything goes on screen.

The most important design decision wasn't which model or which framework. It was where the model is not allowed. Everything below follows from that.

Rule 1: Scripture comes from a source, never from a model

Verse text is fetched from published translations and copied byte for byte. No model is ever asked to write, translate, shorten or tidy it.

The edge cases are where "just use AI" would be tempting, and where the agent deliberately doesn't guess:

  • Half verses. Philippians 3:4b–14 starts halfway through verse 4. The agent includes the whole verse and flags it for the reviewer.
  • Dutch Psalm numbering sometimes runs one verse ahead of the English, because the title counts as verse 1. The agent aligns the two and flags that too.

Rule 2: trust, but read it back

After the deck is built, and again after every design pass, the agent reads every verse back from the finished slides and compares it with the source. Any difference stops everything and nothing is published. Here's the end of the design pass:

python
after = scripture_texts(now, labels_of(now))
changed = [k for k, v in before.items() if k in after and after[k] != v]
if changed:  # can't happen with the allowed operations; checked anyway
    raise ScriptureChanged(f"Scripture text changed in {changed} — stop and check the deck")

My favourite line in the codebase is that comment. With the allowed operations, this can't happen. I check it anyway. A guard that "can't fire" costs nothing, and it's the only one that matters on the day it does.

Rule 3: the model proposes, the code decides

Claude never edits the slides directly. It looks at the deck the way a presentation designer would, then answers with a list of proposed changes in a fixed shape: restyle this box, move it, put a soft band behind text on a busy photo, split this long text over two slides. Each proposal must pass our checks before it touches the deck:

python
def check(op: Op, pres: dict, labels: list[Label], split_done=frozenset()) -> DesignOp | str:
    """The typed operation for a proposed change, or why it is refused."""
    if not 1 <= op.slide <= len(pres["slides"]):
        return "unknown slide"
    t = target(op, pres, labels, split_done)
    if op.op in NEEDS_ELEMENT and t.element is None:
        return "unknown element"
    if op.color is not None and (_hex_rgb(op.color) is None or loud(op.color)):
        return "colour missing, invalid or loud"
    return KINDS[op.op].parse(op, t)

The list of operations is the guardrail:

  • There is no "rewrite this reading" operation. Small text fixes (typos, capitals, spacing) exist only for titles and announcements, never readings, references or names.
  • A change that makes text run off the slide is undone automatically.
  • Even colours are fenced: loud() refuses neon. It's a church, not a nightclub.

The same idea runs through the WhatsApp side. Claude reads "swap the Epistle for Philippians 4" and turns it into one of a handful of actions (approve, change reading, remove slide, add slides, redesign, answer a question). The code carries the action out. If the request is unclear, it asks back with tap-to-answer buttons instead of guessing. It also refuses to remove the skeleton of the service, and new slides are accepted only as PowerPoint files.

Rule 4: boring operations

  • Nothing is overwritten. If a deck for that Sunday already exists, the build stops.
  • Idempotent runs. The Friday retry does nothing if Thursday succeeded.
  • Slides are found by label, never by position. Labels live in the speaker notes, so a person can move slides around without confusing the agent.
  • A watchdog (Cronitor) emails the coordinator when a build fails or doesn't run at all.

Sermon slides: a layout problem, solved in code

Preachers' slides are often dense. You could hand that to the model ("make this readable"), but splitting text is a deterministic problem, so it's done in plain code before Claude ever looks:

  • a slide holds at most nine lines of body text, never below 28 pt;
  • breaks fall between "blocks" (a numbered point, a paragraph, a bold sub-heading with its paragraph), never inside one;
  • parts are balanced, so no slide ends up holding one lonely "Amen.";
  • the heading repeats on every part with "(cont.)";
  • every sermon slide gets one body size: the largest that doesn't need more than 30% more slides.
Mock-up: one crowded sermon slide with four points spread over two readable slides, the heading repeated with (cont.)
A crowded sermon slide spread over two readable slides

That leaves Claude the parts that need taste, like contrast and balance, and keeps the parts that need rules in code that's tested offline.

Songs: show your uncertainty

Hymns come from the best source available: the church's own PowerPoint folder, then 217 public-domain hymns, then word slides made from the scanned hymnal for the other 510. That last group was read by OCR, which makes mistakes. So those slides go into the deck hidden, and the review says so, until a person has proofread them.

A tool that says "I'm not sure about these three" earns more trust than one that is always confident.

What it costs (short version)

About $0.07 per weekly build and €3.40–4.40 a month in total. Getting the design pass from $0.50 to $0.08 a build took two decisions: Sonnet instead of Opus, and only designing the slides that changed since last week. The full breakdown, the monitoring and the night it failed are in Part 3.

What I'd tell another engineer

  • Draw the line around the model first. Decide what it must never touch, then make that impossible in code and check it anyway.
  • Let the model propose and the code apply. A small set of typed, validated operations is far easier to trust than "here's the deck, improve it".
  • Use code for rules and the model for taste. Sermon paging is rules. Contrast is taste.
  • Meet people where they are. Google Doc in, WhatsApp out. Nobody learned a new tool.
  • Make uncertainty visible. Hidden hymn slides, flagged half verses, and a review page that says either Ready for review or exactly what to check.

Next, in Part 3: how it's run. Monitoring, latency, the Thursday it failed, and what every piece costs.

Filed under

  • Agents
  • AI architecture
  • Python
  • Resilience

Related

  1. AI, , 6 min read

    I built an AI agent that makes our church's Sunday slides

    Every week the same few volunteers spent two hours or more chasing sermon slides, copying Bible verses and fixing line breaks. So I built them an agent that does it on Thursday evening, for about seven cents, and still lets a person say no.

    • Agents
    • AI
    • Building

Next

Have a system worth arguing about?

If this note raised a question, or you would have designed it differently, I would like to hear it.

Start a conversation