Skip to content

About

๐Ÿฎ Porchlight โ€” an exit-interview Mind that remembers why members left and autonomously wins them back when their reason is fixed

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

ย 

History

83 Commits

Folders and files

Repository files navigation

Porchlight logo โ€” a porch lantern left on

Porchlight ๐Ÿฎ

An exit-interview Mind that remembers why each member left โ€” and autonomously wins them back the moment their reason is actually fixed

Porchlight โ€” remembers why members leave, wins them back when it's fixed


Live site Try it live Demo video Pitch Deck DoraHacks BUIDL Powered by Minds by Animoca Brands Creative Minds Jam Track Minds Builder Hub SDK Feedback


Minds SDK TypeScript Node.js Express zod Playwright Tests Coverage License CI/CD Pipeline CodeQL Release

๐Ÿค Sponsor

Minds by Animoca Brands

Porchlight is built on Minds by Animoca Brands โ€” the sponsor platform for the Creative Minds Jam. The Minds agent is the engine, not a wrapper: per-member long-term memory, semantic reasoning, and native-channel outreach all come from the Mind via @animocabrands/minds-client-lib. Builder console: build.hellominds.ai.

Track: Audience growth & community engagement.


โš ๏ธ The problem

For any membership creator, churn is the direct revenue wound. When a member cancels, the reason vanishes โ€” nobody records it. And when the creator later fixes the very thing that drove people away (resumes a series, drops the price, ends the drama), the people who left for exactly that reason are never told. Today's "win-back" is a generic "we miss you" blast that ignores why each person actually left.

๐Ÿฎ What Porchlight does

  1. Exit interview (memory). A member cancels โ†’ the Mind runs a short, warm interview and files a structured return-condition per member โ€” the reason, in their own words.
  2. Churn board (continuity). Every departure compounds into a live board: "37% left over the pivot away from deep-dives."
  3. Conditional win-back (autonomous follow-up). The creator announces what changed โ†’ the Mind semantically matches it against every stored reason and, unprompted, messages only the members whose reason is now resolved โ€” quoting their own words from weeks ago. Do-not-contact is always honored. Recovered MRR is tracked.

๐ŸŽฌ See it work

The money moment โ€” the Mind wins a member back, unprompted, quoting their own words. The creator posts "deep-dives are back"; the Mind semantically matches it to everyone who left over the pivot and reaches out to only them.

The Mind autonomously wins members back, quoting each member's own words
Porchlight churn dashboard
The dashboard โ€” every departure, remembered with its reason.
Churn intelligence board
The churn board โ€” "37% left over the pivot away from deep-dives."
Recovered MRR tracked automatically
Recovered MRR โ€” every conditional win-back, tracked to the dollar.

๐Ÿง  Why the Mind is the product (not a wrapper)

  • The exit interview is an adaptive conversation, not a form.
  • Condition matching is semantic judgement โ€” "the pivot to short clips" is resolved by "deep-dives are back" even with zero shared keywords. That is the Mind reasoning, not a filter.
  • The win-back recalls the member's own words from months earlier โ€” the Mind's long-term memory.
  • Remove the Mind and there is neither the memory nor the trigger. A stateless tool can send "we miss you"; it cannot know this person left for that reason and that reason is now fixed.

Uses @animocabrands/minds-client-lib across 7 methods: listMinds, getMind, ensureConversation, sendMessage, waitForReply, getHistory, getLatestHistoryFingerprint.

๐Ÿ•น๏ธ Try it without installing anything

try.porchlight.edycu.dev โ€” no signup, loads with data.

  • Announce a curated change โ†’ instant. Those verdicts are real Mind output, captured ahead of time (npm run precompute) because judging one change against all 18 departures takes minutes of live Mind time. Each is stamped with when it was captured.
  • Put the live Mind on the spot โ†’ pick someone who left, describe a change in your own words, and a real call runs there and then, with its reasoning, confidence, and what a keyword rule would have scored. Rate-limited to 3 per 15 minutes.
  • The interesting test is trying to fool it. Describe a fix that should not win someone back. It is supposed to stay quiet.

There is no keyword fallback in the deployed app. Without credentials it returns 503 and says so, rather than quietly answering with the very mechanism this project exists to beat.

๐Ÿ“Š About the numbers you'll see

Recovered-MRR figures are per run, not constants. Mind verdicts are non-deterministic โ€” the same departure judged against the same change can resolve on one run and not the next, and we have the traces both ways (FEEDBACK.md ยง7). The demo video shows a 19-departure arc that recovered $105/mo vs $35/mo for the keyword baseline; the deployed app starts from the 18-departure seed and shows whatever its own dated capture produced. Both are real; neither is a fixed marketing number. The shape is the claim: a large slice of recovered revenue that keyword matching provably cannot reach.

โšก Run it

npm install
npm run demo  # the full arc, headless (works offline in MOCK mode)
npm start     # dashboard at http://localhost:5173 (serves the pinned src/demoSeed.json)
npm run precompute  # capture REAL Mind verdicts + latency into src/liveCache.json (needs a key)
npm test      # 58 tests (interview parse, win-back, semantic edge, do-not-contact, live SDK, sessions, edges)

Offline by default, real Mind when you want it

With no API key, Porchlight runs in MOCK mode so the whole flow is demoable offline. To drive a real Mind:

  1. Create a Mind at hellominds.ai.
  2. Author the Porchlight Skill by describing it to your Mind:

    "When I forward a cancellation, run a short warm exit interview and reply with a JSON return-condition {reasonCategory, detail, verbatimQuote, doNotContact}. Later, when I tell you something changed, tell me which past cancellers that resolves and draft a personal win-back quoting their own words."

  3. Create a Builder API key in the Builder console.
  4. export MINDS_BUILDER_API_KEY=โ€ฆ (optionally MIND_ID=โ€ฆ), then npm run spike.

๐Ÿ—๏ธ Architecture

flowchart TD
    subgraph client["๐Ÿ–ฅ๏ธ Browser"]
        direction LR
        SF["Membership storefront<br/>cancel / rejoin"]
        DASH["Dashboard<br/>churn board ยท recovered MRR ยท audit"]
    end

    subgraph backend["โš™๏ธ Porchlight โ€” Node/TS ยท Express"]
        SRV["server.ts<br/>REST API ยท per-visitor sessions"]
        subgraph logic["engine/ โ€” the three behaviours that need a Mind"]
            direction LR
            EI["exitInterview.ts<br/>runs the interview,<br/>files the condition"]
            CM["conditionMatcher.ts<br/>does this change<br/>resolve that reason?"]
            TE["triggerEngine.ts<br/>wins back only<br/>the resolved"]
        end
        MW["minds.ts ยท client-lib wrapper"]
        KB["keywordBaseline.ts<br/>the control we measure against"]
        DB[("db.ts โ€” JSON store<br/>reason ยท verbatim quote ยท do-not-contact")]
    end

    subgraph minds["๐Ÿง  Minds ยท @animocabrands/minds-client-lib"]
        direction LR
        MIND["The Mind<br/>long-term memory ยท semantic reasoning ยท Skill"]
        CH["Native channel<br/>email"]
    end

    SF -- "cancel" --> SRV
    SRV --> EI
    SRV -- "creator posts what changed" --> TE
    TE --> CM
    TE -- "same change, keyword rule" --> KB

    EI -- "interview" --> MW
    CM -- "semantic judgement" --> MW
    TE -- "resolved ยท not do-not-contact" --> MW
    MW <--> MIND
    MIND --> CH

    EI -- "condition + quote" --> DB
    TE -- "recovered MRR" --> DB
    KB -- "what keywords would have caught" --> DB
    DB --> SRV
    MW -- "getHistory โ†’ audit" --> SRV
    SRV --> DASH

    %% Amber = the Mind, and the code that cannot work without it.
    %% Every amber edge is a real round-trip to the agent.
    classDef agent fill:#ffcf6b,stroke:#b8761a,stroke-width:2px,color:#20160a
    classDef brainwork fill:#fff1cc,stroke:#e89f45,stroke-width:1.5px,color:#20160a
    classDef plumbing fill:#e7ebf8,stroke:#6b76a3,stroke-width:1.5px,color:#171b2e
    classDef store fill:#2a3050,stroke:#575f8a,stroke-width:1.5px,color:#e9ecff
    classDef ui fill:#dcf3e5,stroke:#3fa96a,stroke-width:1.5px,color:#0b1a10
    classDef control fill:#f2e3e3,stroke:#a87878,stroke-width:1.5px,color:#3a2020

    class MIND,CH agent
    class EI,CM,TE brainwork
    class SRV,MW plumbing
    class DB store
    class SF,DASH ui
    class KB control

    style client fill:#f4fbf7,stroke:#bfe3cd,color:#0b1a10
    style backend fill:#f7f8fd,stroke:#ccd3ea,color:#171b2e
    style logic fill:#fffaf0,stroke:#edc98a,color:#20160a
    style minds fill:#fff8e8,stroke:#e2b666,color:#20160a

    linkStyle 5,6,7,8,9 stroke:#e89f45,stroke-width:2.5px
Loading
  • src/minds.ts โ€” the Minds integration (LIVE client-lib + MOCK brain).
  • src/engine/ โ€” exit interview, condition matcher, trigger engine.
  • src/board.ts โ€” the churn-intelligence board. src/server.ts + public/ โ€” dashboard.

Node 22+ ยท TypeScript ยท Express ยท zod. A JSON store mirrors the Mind's memory for the board and audit trail; the Mind remains the semantic brain.

๐Ÿงช Testing & CI

A production-grade harness runs on every push and PR โ€” a 7-stage GitHub Actions pipeline (ci.yml): Quality โ†’ Security โ†’ Build & Smoke โ†’ E2E โ†’ Performance โ†’ Deploy Gate โ†’ Fly.io deploy (main only, app files only, and it fails the build if the deployed app comes up in MOCK mode). Releases run in a dedicated release.yml workflow (see below).

npm run ci          # audit + strict typecheck + unit tests
npm test            # 58 unit tests (node:test)
npm run test:coverage  # 100% lines / branches / functions on every source file
npm run e2e         # Playwright E2E against the live dashboard (demo mode, no key)
npm run lighthouse  # Lighthouse CI (performance / a11y / SEO)
make security-scan  # npm audit + license check

Automatic semantic versioning

Version numbers are never bumped by hand. On every push to main, the release.yml workflow runs semantic-release: it reads the Conventional Commits since the last tag, computes the next SemVer, then โ€” with no manual release PR โ€” writes CHANGELOG.md, bumps package.json, tags the commit, and publishes a GitHub Release automatically.

Commit prefix Release
fix: patch โ€” 0.1.0 โ†’ 0.1.1
feat: minor โ€” 0.1.0 โ†’ 0.2.0
feat!: / BREAKING CHANGE: footer major โ€” 0.1.0 โ†’ 1.0.0
chore: docs: test: ci: refactor: no release

Commit conventionally and the version takes care of itself.

Layer Tool Status
Code Quality TypeScript strict (tsc --noEmit) โœ…
Unit Testing node:test โ€” core Mind flow + engine, DB, server, live-SDK & edge cases โœ… (51 ยท 100% coverage)
E2E Testing Playwright โ€” demo-mode smoke, win-back flow, responsive โœ… (3 specs ร— 2 browsers)
Security (SAST) CodeQL โœ…
Security (SCA) Dependabot + npm audit โœ…
Secret Scanning TruffleHog (CI) โœ…
Performance Lighthouse CI โœ… (advisory)
Versioning semantic-release (Conventional Commits โ†’ SemVer) โœ…

๐Ÿ“„ License

MIT ยฉ 2026 Edy Cu

๐Ÿ™ Acknowledgments

Built for the Creative Minds Jam (Minds by Animoca Brands). Thank you to the Minds team for the @animocabrands/minds-client-lib builder surface.

About

๐Ÿฎ Porchlight โ€” an exit-interview Mind that remembers why members left and autonomously wins them back when their reason is fixed

Topics

Resources

Code of conduct

Contributing

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Used by

Contributors

Languages