Home / Ops / More in this area
Updated Jul 16, 2026 · Affirmology_MirrorDrip_Spec_v1.md
Goal. A few days after a person receives their Origin Soul Song (in the normal demo flow), the system automatically renders and delivers their Mirror reading - no manual batch, no one remembering to press a button. It is the built-in re-engagement beat: the first "how did it know that?" gift arrives, then a deeper one lands unprompted while they are still thinking about the first.
Applies to the DEMO process (every real demo-taker), not just beta.
On Origin Soul Song delivery → schedule the Mirror for ~3 days later → a scheduled worker renders it from the person's stored Profile and emails it (link to their page), once, idempotently, honoring holds and opt-outs.
demo_render.py, the delivered/job-complete path).due_at = now + DELAY_DAYS.MIRROR_DRIP_DELAY_DAYS in app_config (env-overridable). Default 3 days.A new table (or reuse the consumer DB), e.g. mirror_drip:
| field | meaning |
|---|---|
| id | pk |
| email / profile_id | who |
| origin_job_id | the Origin render it followed |
| created_at | when scheduled |
| due_at | created_at + DELAY_DAYS |
| status | pending → sent / held / skipped / error |
| mirror_job_id | the render, once made |
| attempts, last_error | for self-heal |
Durable so a "drip opened days later" survives restarts (mirrors the pattern already noted in
db_models.py). No in-memory scheduling.
A Render Cron Job (or a boot poll loop, like chant_runner) runs on a cadence (hourly is plenty):
1. Select status=pending AND due_at <= now.
2. For each, run the gate checks (§6). If gated → mark held/skipped with reason, move on.
3. Render the Mirror from the stored Profile (reuse build_mirror_brief + the existing Mirror
render path; length target 12 - 15 min / ~2200 words per the reading-render length rule). One at a
time / paced (Fish + Sonnet cost).
4. On success: attach to the person's page, send the Mirror email (§7), mark sent.
5. On failure: increment attempts, store last_error, leave pending for retry with backoff; after N
attempts mark error and surface it (no silent drops - self-healing loop principle).
.mirror_hold (the Mirror is globally held during its rebuild - the drip must respect this; while
held, drips just wait as pending, they do not fire)._has_mirror) - mark skipped (never double-send).MIRROR_DRIP_ENABLED (default OFF until Jeff flips it, since the Mirror is
mid-rebuild and each render costs real money).Affirmology_MirrorEmail_v1.html body.MIRROR_DRIP_ENABLED
gate, one-at-a-time pacing, a daily cap (MIRROR_DRIP_DAILY_MAX), and a per-run max_cost_usd like
the existing batch. Ask-credit-before-bulk still applies for any backfill of the already-waiting queue._has_mirror guard).MIRROR_DRIP_ENABLED=false.MIRROR_DRIP_ENABLED=true when the Mirror rebuild is approved and .mirror_hold is lifted.kinds.