Built by Sharon Paul · github.com/SpSharon
A Streamlit + LangGraph app that helps a small church post consistently and warmly to Facebook and Instagram — with a human approving every word before anything is published.
This repository is a template. It ships configured for a fictional parish, "St. Example's Episcopal Church", with clearly-labeled placeholder config, voice samples, and artwork. A real parish adopts it by replacing those placeholders with its own facts, voice, and images — see Make it yours. It began life as a Kaggle Gen AI capstone (How this was built), then became a private production app built for one real parish (still running); this public version has been de-identified, but the engineering is the same code that serves real volunteers.
This is unpaid personal work, contributed on a volunteer basis to a local church (a non-profit organisation), in my own time and separate from my employment.
One measured result up front: the post-draft honesty guard cut bait
fabrication from 0.9333 (14/15) to 0.0667 (1/15), both runs judged
by the same strict judge on the same dataset — the full before/after is in
evals/README.md.
A small parish has no communications staff. Social media falls to a volunteer who is busy, unpaid, and not a marketer. The failure mode isn't bad posts — it's silence: someone looking for a church finds a page last updated in March and quietly decides the place is dormant. ChurchReach targets that gap. Not creativity — consistency and warmth at low effort, aimed at newcomers rather than the existing congregation.
A volunteer answers a few gentle questions and gets a Facebook draft and an Instagram draft in the church's own voice, image ideas (generated via FLUX, with strict no-people rules), and short vertical video assembled locally from the church's own clips and photos. Then a person reads every word and posts it by hand.
These rules were fixed in the build brief before implementation began, and the test suite holds them in place:
Nothing posts automatically. Ever. There is no posting integration in the codebase at all — no Meta/Graph API calls, no scheduled publishing, nothing to misconfigure. The only outbound calls are to Anthropic (drafting), FLUX (image generation), and Supabase (the church's own history archive). This is easy to verify: search the code for outbound HTTP and you will find exactly those three clients.
Absolute honesty — silence over invention. The app may only state facts
present in config/church_config.yaml. If the accessibility details aren't
filled in, it says it doesn't know and invites a human to add them, rather
than producing a plausible sentence. Enforced in churchreach/guardrails.py
and in every prompt — and, because the eval harness measured that prompt
rules alone don't stop a drafter from repeating a fact the volunteer's
request presupposes ("promote our Wednesday service"), a second layer now
audits every draft after writing: churchreach/honesty.py checks each
concrete claim against the config facts (deterministic patterns + one
model audit call, merged conservatively), repairs a violation once, and
flags what it removed or couldn't verify on the review screen — warn,
don't hide. The same harness that found the gap re-measures it
(evals/README.md).
Consent gates before people. If a photo or clip may show identifiable
people, the app asks whether they agreed to appear — and nothing touches disk
until that's answered. Stricter rules apply to minors: a named child is
blocked on keywords alone, and an LLM classification re-scans every human edit
so a bare name with no keyword can't slip through. Uploads reach storage
through exactly one consent-gated function (persist_upload), so the rule
can't be bypassed by a new code path — and a source-level test fails if anyone
adds one.
Commenter privacy. The Reply helper never persists a commenter's words — metadata only.
Cost discipline. Every LLM call counts against a rolling 24-hour cap that clears itself. A volunteer can't run up a bill, and can't be locked out by a cap only the owner could reset.
Fail soft. A missing font degrades to the default; a bad logo upload falls back to the bundled one; a storage outage downgrades History to session-only instead of blocking a post. Branding never takes the app down.
ChurchReach is built on LangGraph and is deliberately a workflow, not an
agent — LLM calls orchestrated through predefined code paths, not a model
directing its own process. The graph in churchreach/graph/build.py is fixed
and linear: plan → draft → consent → human_review → record. The only
conditional edge is a deterministic error check. The model is never given
tools to call; the image and video tools are invoked by the volunteer clicking
buttons. human_review is a LangGraph interrupt — the literal opposite of
autonomy.
That restraint is the point. The cost of an autonomous mistake here — a church's public page publishing something untrue about itself — is unrecoverable, and no efficiency gain justifies it. LangGraph supplies state, checkpointing, and the human-in-the-loop interrupt; the autonomy was declined on purpose. The guardrail engine is a hybrid for the same reason: a deterministic keyword/pattern layer plus an LLM classification, merged so that any layer can raise a flag and none can lower one. The model gets a vote on caution and no veto over it.
Create post is the core loop: answer a few questions (or bring your own idea) and review a Facebook and an Instagram draft. Sermon studio turns Sunday's sermon into posts with a deterministic verbatim-quote gate (a quote that isn't actually in the transcript is demoted to unattributed) plus a five-day devotional email. Month planner drafts a month of ideas so nobody faces a blank screen on a Saturday night. Welcome kit answers what newcomers actually search for — times, parking, what to wear, children, communion — built only from the config file. Video kit assembles a phone clip or 3–5 photos into a vertical video for Reels/Stories, locally with OpenCV/ffmpeg; it edits footage, it does not generate video, and it costs nothing to run. Reply helper drafts a kind, honest reply to a comment you're unsure about. Log performance feeds likes/comments back into the planner. History keeps everything approved, including older posts worth resharing. Usage shows spend and how much of the day's cap is left.
Supporting behavior: a photo studio (overlay text, stylize, brighten,
announcement cards), automatic alt text for screen-reader users, a
brand-conformant typeface picker, and a logo library scanned from
assets/logos/ at runtime.
Python 3.12 · Streamlit (UI) · LangGraph (the approval workflow, with a
compatibility fallback engine so the full loop is integration-tested offline)
· Supabase — PostgREST for post history, Storage for a private image archive,
with row-level security enabled and no policies by design so anon keys can
do nothing · Anthropic API for drafting and FLUX for images, both called as
plain REST via requests (no SDKs — deliberate, to keep cloud installs
reliable) · Pillow/OpenCV/NumPy for imaging and video, ffmpeg via
packages.txt.
app.py is a thin rendering layer; business rules live in tested modules —
the consent rules were deliberately extracted into churchreach/ui_policy.py
so they could be tested. Roughly 16,000 lines of Python; the test suite (400+
tests, all offline, no network) is nearly the size of the engine, which is a
deliberate consequence of the safety model: honesty and consent rules are only
real if they're tested. Runtime dependencies are seven, each version-pinned on
purpose, with the reasoning written into requirements.txt.
A repo tour: churchreach/ is the engine (guardrails, prompts, graph, image
and video tools, storage), app.py the UI, tests/ the suite, config/ the
church's editable facts and voice, skills/ the posting-arc's human-readable
statement (the arc itself is engine data), assets/ the replaceable artwork,
docs/ the build journal, briefs,
audits, and deploy checklist, and scripts/ the volunteer-guide generator and
a sandbox test runner.
You need Python 3.12 (what CI and Streamlit Cloud run — nearby versions generally work too) and about ten minutes.
git clone <your-fork-url> churchreach && cd churchreach
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
cp .env.example .env # then edit .env
streamlit run app.pyFill in .env (never committed; .gitignore covers it):
ANTHROPIC_API_KEY— required. The app will not start without it.APP_PASSWORD— required. The shared password your volunteers use to sign in; pick your own.FLUX_API_KEY— optional. Without it, image generation is unavailable but everything else works.SUPABASE_URL+SUPABASE_KEY— optional. Without them the app runs with session-local history and tells you so in the sidebar; with them, approved posts and generated images persist. Create a free project at supabase.com, then runscripts/supabase_schema.sqlin its SQL editor once.
To try the drafting workflow with no keys at all, run the offline demo, which
drives the real graph with a scripted fake LLM: python cli_demo.py --offline
(add --auto approve for a non-interactive run). One optional extra: install
ffmpeg locally if you want sound and H.264 output in the Video kit — without
it the app degrades to silent video, by design.
pip install -r requirements-dev.txt
python -m pytest tests/ -qThe whole suite is offline — no keys, no network. CI
(.github/workflows/tests.yml) runs it on every push to main and every pull
request with Python 3.12, plus a compile check of the UI layer. verify_setup.py, verify_supabase.py, and
verify_images.py are separate live checks that spend a few cents against
your real keys — run them once after setup.
The template is adopted by editing data, not code — two config files and your own artwork. No Python file needs to be opened, and the test suite stays green after you've made it yours:
config/church_config.yaml— everything named about your parish, in two labeled groups. The facts (name, service times, location, parking, contacts, welcome-kit topics) are the only factual claims the app may make, so keep them true and current. The branding anchors below them (short_namefor the page title and footer, yourhashtags, yoursignature_imagery, theimage_settingline that describes your actual building to the image generator, and the twofallback_scenes) steer the prompts and the UI — they are deliberately kept out of the facts fence, and fallback scenes that mention people are dropped automatically, so editing them can re-anchor the app but never weaken a guardrail. Every value ships as an obvious placeholder; replace each one.config/voice_samples.md— replace the fictional samples with real passages from your website, bulletins, and past posts. This is what makes drafts sound like you.assets/— drop in your logo (logo.png, white background), your marks inassets/logos/(the dropdown picks up any PNG/JPG you add), and your own people-free photos. Every bundled image is an obvious placeholder.- Optional: once deployed, set
app_urlin the config and rerunpython scripts/make_vestry_guide.py(needsrequirements-dev.txt) to regenerate the printable volunteer guide — the script reads the config, so it needs no editing either.
One thing is deliberately not config: the four-week posting arc
(Belonging → Growth → Service → Family). It ships as engine data in
churchreach/arc.py, works for any parish as-is, and
skills/church_social_skill.md is its human-readable statement (a test
keeps the two in sync). Changing the arc is a maintainer edit to both
files, not part of adopting the template.
.streamlit/config.toml ships with the national Episcopal palette
(PMS 193 C #BF0D3E) as an example of brand-disciplined theming — swap in
your own tradition's colors freely. The brand-color citations in
churchreach/images/tools.py are illustrative for the same reason.
The original runs on Streamlit Community Cloud's free tier. Push your
customized fork to your own GitHub repo, create a Streamlit Cloud app from it,
and set the same five secret names in the app's Secrets dashboard instead of
.env. Two hard-won warnings, both documented at length in
docs/DEPLOY_CHECKLIST.md: packages.txt is passed line-by-line to
apt-get install, so a comment in that file will take your app down; and
Supabase pauses free-tier projects after ~7 days without database activity and
deletes them 90 days later — a parish posting weekly does not clear that bar,
so read the checklist's section on keeping the database awake before you rely
on it.
ChurchReach began as a hands-on build — a Kaggle Gen AI capstone, written
with Google Agent Development Kit (ADK) 2.0 for one small parish — and then
became the private production app described at the top of this README
(still running). This repository is a later,
substantially enhanced and hardened iteration (now on the Claude and FLUX
APIs): Sharon Paul set the requirements, safety guardrails, architecture,
and acceptance criteria, then directed Claude to implement them under
a written brief (docs/BUILD_BRIEF.md) whose opening line fixes the division
of labor — "Implementation approach is yours to determine; the requirements and
guardrails below are not." The safety rules above were hard invariants from day
one, held across sessions and models.
What made that work is documented in this repo rather than claimed:
docs/BUILD_MEMORY.md is the engineering journal (one lesson per entry,
including the embarrassing ones), and docs/AUDIT*.md are independent
fresh-context audits of the codebase — which she commissioned against her
own project — whose findings were fixed one at a time, each with tests. The
method's track record includes the audits catching real bugs the working
context couldn't see, a live outage caused by a one-line change and traced
through the build log, and a fabricated feature in a draft document caught by
a plain question she asked. The journal keeps those stories because
they are the method: nothing is "done" until a fresh pair of eyes re-derives
it.
The first version was Sharon's own hands-on build; this hardened iteration was produced by directing Claude against her requirements, guardrails, and tests. She set the rules, verified the behavior, and runs the app in production. This README tries to hold the same standard the app does — every claim above is checkable in this repository.
MIT — see LICENSE. The Episcopal shield and related marks are trademarks of
The Episcopal Church; the placeholder art here deliberately avoids them, and
your own diocese's marks come with their own usage rules — check before you
ship them.