Local-first apps built on the SAFE framework. No ports. No servers. No subscriptions. Yours to keep, yours to delete.
SAFE = Session-Authorized, Fully Explicit
App Store = browse, install, run — without giving anyone your data
41 apps in the catalog: 14 gated, 18 building, 6 stalled, 3 archived
(make list prints the live count and won't drift from this table the way
prose does). The gated ones below have a real, CI-verified test suite —
not a claim that they're feature-complete or polished:
| App | Description |
|---|---|
| ask-jeles | SAFE search engine — local stacks first, verified sources when you need the world |
| band-camp-arcade | Five small local browser toys for marching band kids — no accounts, no network calls |
| bureau | A Bureaucracy homage at UTETY — a closed requirement graph and a deadlock proved by fixpoint |
| civics-check | America's 250th civics quiz — naturalization test, timeline sort, quote matching, offline |
| field-acoustics | Design-time acoustic model for marching drill — what the audience hears when the form turns |
| jarvis | Voice-first browser assistant with an indexed, provenance-tracked fact store |
| law-gazelle | Legal case management and document analysis |
| marching-arts | Authorization core for a marching-program platform — sealed grants, one compiled SQL predicate |
| private-ledger | Local-first private financial ledger — no cloud, no subscriptions |
| source-trail | Track and audit data sources |
| story-timeline | Track events, characters, and locations across a narrative timeline |
| terpsi-chat | Schema and gates for messaging between under-18s in a youth-arts org |
| the-squirrel | Genealogy companion — local-first, your tree stays in your tree |
| utety-chat | Chat with 11 AI faculty members, including Gerald (a headless rotisserie chicken) |
building, stalled, and archived apps (field-notes, njord, ratatosk,
the-forge, and 23 more) aren't listed here — they're real work, just not yet
past the CI-verified bar. See
.willow/store/catalog.json for the full
catalog — the root catalog.json is a pointer to it, not a
copy (docs/store_refit_plan.md P3) — or run make list. docs/store_refit_plan.md's
status-vocabulary migration note has the full five-word enum
(seeded · building · gated · stalled · archived) and what each means.
Two tiers, and the difference isn't "tested" vs. "untested" — it's who's answerable for the code:
- Playground — where every app above actually lives right now:
apps/<app>/. Contested by default (CLAUDE.md§5), not yet a standing app. The house's only record of it is a keeping record atstores/<major>/stored/<app_id>.json—majors,relation(if it spans more than one craft),location,maker,lane, andstate(the same five-word enum used for catalogstatus, above). The record is not a second copy of the code: the code is not duplicated, the record is whatstores/stores. - Promoted — a full SAFE app, in its own repo, held to the bar in
stores/promote_check.py: every gate passing and verified by a hand other than the author's (proposing and ratifying never rest in the same hand). A promotion writes a record atstores/<major>/promoted/<app_id>.json— the verdict, every individual gate result, who verified it, and when. Nothing inapps/has cleared this bar yet.
stores/ is organized per major — one home per craft: python, node,
rust, go, cpp, obsidian, browser. See
stores/README.md for what each covers. A build that
spans more than one craft names all of them plus the relation holding them
together, so two implementations of one thing are never mistaken for an
unrelated pair (or the reverse).
Full reasoning, the migration history, and every open question:
docs/store_refit_plan.md.
make run app=story-timelinemake run does cd apps/<app> && python app.py — it works for any app whose
entry point is a top-level app.py (most of them). A few declare a different
entry_point in their manifest (e.g. utety-chat, private-ledger); check
safe-app-manifest.json for those and run them accordingly.
For local development with the existing dev environment:
cd apps/story-timeline
../../.venv-dev/bin/python3 app.py- Create
apps/<your-app>/withapp.py,requirements.txt,safe-app-manifest.json— this is the playground (CLAUDE.md§5), a contested tier, not yet a standing app. - Add a catalog entry:
id,name,description,status,path,tagsin.willow/store/catalog.json. Don't hand-writetier,majors, orstate— those are generated from step 3 andcatalog_lint.py --strictwill reject them if they disagree with it. - Add a keeping record at
stores/<major>/stored/<your-app>.json(docs/store_refit_plan.mdP1) —app_id,majors,location,maker,lane,state. See any existing record understores/*/stored/for the shape. - Run
python tools/catalog_lint.py --strictbefore pushing — it checks steps 2 and 3 agree with each other and withapps/. - PR or push
Each app is a self-contained SAFE app — portless, local-first, and explicit about its permissions. Apps keep user data local by default; for example, Story Timeline stores nodes in ~/.willow/store/story-timeline/timeline.db and only source code/docs belong in this repo.
The store is a monorepo. Each app deploys independently.
ΔΣ=42