English · 繁體中文
Approval-gated automation for temple (宮廟) affairs. Registrations flow in, an AI collates them into offering/lamp lists and drafts the purchase orders and notices — but nothing money-facing or member-facing ships until a real person approves it. Every step is recorded in an immutable audit trail.
Runs on the Node.js 24 standard library with zero runtime dependencies — no
native builds, no external services. git clone → pnpm dashboard.
Inspired by Morning Operator's human-in-the-loop model (approval gates · SQLite job queue · provider-neutral adapters · industry packs). This project applies that model to verticals Morning Operator doesn't cover — starting with Taiwanese temple ceremony operations — as a focused, runnable reference implementation.
It ships two packs on one engine (
temple-affairsandvolunteer-shifts) to demonstrate the point of the architecture: the domain lives in the packs, and the queue / gates / audit / adapters are generic.
Temple ceremonies (法會) generate a mountain of low-stakes-but-error-sensitive paperwork: totalling everyone's lamps and offerings, drafting purchase orders, sending payment notices. It's automatable — but you cannot let it run fully autonomous, because a wrong total or a notice sent to someone who never consented is a real-world trust failure.
So the design rule is: automation prepares, a human authorizes. The interesting engineering is everything that makes that safe and reliable — the gates, the queue, the audit trail, the escalation rules.
registrations
│ collate (AI adapter)
▼
┌─────────────────── GATE ① review-collation ───────────────────┐
│ reviewer sees item totals, per-registrant breakdown, anomalies │
└────────────────────────────┬──────────────────────────────────┘
approved │ fulfill
▼
┌─────────────────── GATE ② approve-delivery ───────────────────┐
│ purchase order + notice preview + recipient count │
│ ⚠ auto-escalated if a restricted-action rule fires │
└────────────────────────────┬──────────────────────────────────┘
approved │ deliver (notify adapter)
▼
notices sent · event done
pnpm install # only dev deps (typescript); runtime is dependency-free
cp .env.example .env # optional — sensible defaults built in
pnpm dashboard # open http://127.0.0.1:4173The dashboard is the interactive showcase: click 重新載入示範 to create one event from each pack, inspect what the automation prepared, then approve or reject each gate as a named reviewer. An approval resumes the queue automatically and stops again at the next human gate. Dedicated volunteer-roster and financial-control views preserve the operational details after approval; queue health, event progress, and the immutable audit trail update on the same screen.
For a terminal-only walkthrough, run pnpm demo. It drives both packs through
the same approval flow using a scripted reviewer.
pnpm demo runs two different packs through the same engine: a 中元普渡法會
(temple-affairs) and a 志工排班 (volunteer-shifts). For each it drives the worker
through both approval gates (auto-approving as a scripted reviewer) and sends
notices via the console adapter. In between it enqueues a deliberately failing job
to show retry-with-backoff → dead-letter, and finally prints each event's audit
trail.
Other scripts:
| command | what it does |
|---|---|
pnpm dashboard |
start the interactive human-review dashboard on port 4173 |
pnpm seed |
reset the DB and queue one ceremony's first job |
pnpm worker |
run the long-lived worker loop (Ctrl-C to stop) |
pnpm test |
run the queue/gate unit tests |
pnpm typecheck |
tsc --noEmit |
| Piece | File | Role |
|---|---|---|
| Job queue | src/queue.ts |
enqueue / lease / complete / fail, with attempt leases (crash recovery), exponential backoff, and a dead-letter state |
| Approval gates | src/approvals.ts |
open a gate → pipeline pauses → decide() resumes it |
| Audit log | src/audit.ts |
append-only record of every state change, replayable per event |
| Web dashboard | src/dashboard.ts + web/* |
local review queue, decisions, event progress, and audit activity |
| Engine (generic) | src/engine.ts |
routes a job to the owning pack's handler; knows nothing about any domain |
| Pack registry | src/registry.ts |
maps pack_id → handlers + advance logic |
| Pack — data | packs/*.json |
the domain as reviewable data: gates, metrics, restricted & prohibited actions |
| Pack — code | src/packs/*.ts |
that pack's job handlers and post-approval steps |
| Adapters | src/adapters/* |
provider-neutral notify and ai — swap console→LINE or mock→Claude without touching business logic |
Everything persists to a single SQLite file via Node 24's built-in node:sqlite.
A pack has two halves. The declarative half (packs/<id>.json) is static,
reviewable data — a non-engineer can read it and see exactly what the system will
and won't do:
gates— where a human must approve, and what they get to seerestricted_actions— tiny rules (purchase_order.total > 30000) that force an extra escalation instead of running automaticallyprohibited_actions— things the system must never do (e.g. store full national ID numbers, send to non-consenting registrants)
The runtime half (src/packs/<id>.ts) registers that pack's job handlers.
Two packs ship as reference implementations:
| pack | domain | pipeline |
|---|---|---|
temple-affairs |
temple ceremony paperwork | registrations → collate → gate → fulfill → gate → deliver |
volunteer-shifts |
volunteer scheduling | availability → roster → gate → dispatch → gate → notify |
Adding a third vertical = write packs/<id>.json + src/packs/<id>.ts
(calling registerPack) + one line in src/packs/index.ts. The engine, queue,
gates, audit and adapters are untouched.
The showcase is complete but intentionally offline: the console notify
adapter prints instead of sending, and the mock AI adapter is deterministic with
no network calls. Wiring NOTIFY_ADAPTER=line / AI_ADAPTER=anthropic (Claude
claude-opus-4-8) behind the same interfaces is where real deployment begins.
Apache-2.0 — same as the project that inspired it.