docs(plan): revise Phase 11 — per-campaign notes as FACTS/POLICY/LEAVES trees
Drop the unified 🌹 Pastebin in favor of one self-contained Apple Note per
campaign, each structured top-to-bottom as FACTS (per-note invariants) →
POLICY/CoT (decision tree: state ∧ signal-predicate → leaf) → LEAVES (message
bodies). Tree/leaf model: state.ts becomes a tree evaluator (signals computed
in code, policy+leaves+facts-text in the note) and the NextAction.reason chain
is persisted per send as the CoT path for the teach-loop. Self-contained leaves
let copy diverge per campaign (incall vs outcall vs leaving-NY urgency).
Campaign set, naming convention, priority-based selection, 2-way multi-note
sync, and voice-corpus split all updated to match.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
cccd06d5b7
commit
684c87bc8a
1 changed files with 36 additions and 19 deletions
55
PLAN.md
55
PLAN.md
|
|
@ -232,28 +232,43 @@ Must-haves (port from designs + replicate quinn-inbox-ops + wire real LP):
|
|||
- Post-MVP (next waves): full auto-runner UI, advanced tour/backfill, GPU UI helper, deeper iOS sharing of web components if wanted, performance, more real-time (if SSE available).
|
||||
- Maintain: keep designs/ as living spec (update HTMLs if UI evolves); run /pm for joker or other cross todos.
|
||||
|
||||
**Phase 11: Pastebin → Campaigns-as-CoT + Voice Assets (design wave — see below)**:
|
||||
**Phase 11: Campaign-notes as trees (FACTS · POLICY/CoT · LEAVES) + Voice Assets (design wave)**:
|
||||
|
||||
_Insight (2026-06-29 session):_ the single Apple Note **`🌹 Pastebin`** is not a flat snippet bag — it is the **playbook / chain-of-thought** the drafter reasons through. The current parser (`src/pastebin/pastebin.service.ts`) flattens it to `Map<key, body>` and discards the structure that carries the reasoning (INBOUND vs FOLLOWUP sections, the `━━━` dividers, the per-entry situation headers, and the sequence/branch ordering ②→③→⑫c, ⑫→⑫b→⑫c, Ⓓ→Ⓔ, ⑨→Ⓐ). Stop flattening it.
|
||||
_Insight (2026-06-29 session):_ a pastebin/campaign note is the **playbook / chain-of-thought** the drafter reasons through — not a flat snippet bag. The current parser (`src/pastebin/pastebin.service.ts`) flattens `🌹 Pastebin` to `Map<key, body>`, keeping only the leaf strings and discarding the reasoning. Worse, the reasoning is *recomputed and thrown away again* in `src/engine/state.ts`: the decision tree is hardcoded there (`inferState` → node, `decideNextAction` → branch → leaf key), and the **CoT path is the `NextAction.reason` string** (e.g. "ghost-post-rate bargain → OF kind pitch", state.ts:125) which evaporates the moment the key is rendered. Stop discarding the path; lift the branches out of code.
|
||||
|
||||
- **Pastebin = message library + per-step CoT.** Promote entries from `{key, body}` to section-aware `PastebinStep { key, body, section: 'facts'|'inbound'|'followup'|'ad', label, next? }`. `section`/`label` come from dividers + bold headers already in the note; `next` encodes the sequence/branch (the reasoning).
|
||||
- **Campaigns = clusters that compose pastebin steps**, defined by audience + lifecycle-moment, NOT one-template-per-campaign. The real campaign set (operator's words):
|
||||
- **Brooklyn** — local-to-Williamsburg audience, **incall** offer; steps ②→③→⑤/⑫→⑫c.
|
||||
- **Manhattan** — Manhattan audience, **outcall** offer; steps ② (outcall framing)→⑤→⑫→⑫b→⑫c. (Brooklyn/Manhattan are largely the same audience, different **offer** + locality.)
|
||||
- **Leaving NY** — lifecycle/exit; all warm NYC (fence + ghosts); urgency "NYC through July 1"; steps Ⓑ→Ⓐ→⑨/Ⓐ; **hands off** non-converters to OF. Trigger = days-until-tour-window-end.
|
||||
- **OF** — out-of-budget / cold / non-converting → onlyfans.com/transquinnftw; steps ⑨→Ⓐ→OF.
|
||||
- Model: `CampaignDef { id, kind: 'bulk'|'lifecycle', audience: CampaignFilter, trigger?: TourWindowTrigger, steps: PastebinStep[], handoff? }`.
|
||||
- **Three additions the current code cannot express:**
|
||||
1. **`locality` on the prospect** (Manhattan / Brooklyn / unknown) — new **classifier output inferred from conversation content** (decision: from the thread, not area code or manual tag). Feeds a new `CampaignFilter.locality`. Touches `src/engine-svc/classifier.service.ts` + `src/classify/` + prospect summary + `src/campaigns/match.ts`.
|
||||
**Structural change — no more unified `pastebin`; one note PER CAMPAIGN.** Each campaign note is a self-contained tree, in this top-to-bottom order:
|
||||
- **FACTS** — the invariants this campaign's leaves draw on (rate/geo/dates/OF), per-note (not one global block), so each note is an independently editable + syncable unit.
|
||||
- **POLICY / CoT** — the decision tree: `(state ∧ condition over named signals) → leaf`. This is the reasoning, made explicit and editable.
|
||||
- **LEAVES** — the actual message bodies.
|
||||
|
||||
**Tree/leaf model.** Internal nodes = a `ProspectState` (from `inferState`) + a branch predicate over **named signals** (`isBargainHunter`, `hasCallSignal`, `rateQuoted`, `lastInboundDays`). Leaves = message bodies. A root→leaf **traversal IS the CoT** — persist it (the `reason` chain) per send so the teach-loop / `src/corrections` corrects a *branch*, not just a string. **Policy/facts split:** facts (signal/state computation) stay in code (engine atoms/classifier/`inferState`); policy (facts→leaf routing) + facts-text + leaves move into the note. `state.ts` stops owning the policy and becomes the **tree evaluator + signal computer**, evaluating whichever campaign's tree applies.
|
||||
|
||||
**Self-contained leaves are a feature, not duplication.** The same logical step gets campaign-specific copy: Brooklyn's rate leaf = *incall Williamsburg*; Manhattan's = *outcall to Manhattan*; Leaving-NY's = *"gone after Jul 1"* urgency. Today that's one shared `②`; per-note leaves let copy diverge to match each note's FACTS.
|
||||
|
||||
**The campaign set (operator's words)** — each its own note:
|
||||
- `🌹 💬 Qualify · NYC` — reactive inbound (the current ①–⑫ live-convo tree).
|
||||
- `🌹 🌉 Brooklyn · NYC` — BK-local audience, **incall** offer.
|
||||
- `🌹 🏙 Manhattan · NYC` — Manhattan audience, **outcall** offer. (Brooklyn/Manhattan = same audience, different offer + locality.)
|
||||
- `🌹 🗽 Leaving NY · NYC` — lifecycle/exit; all warm NYC (fence + ghosts); "NYC through Jul 1" urgency; **hands off** non-converters to OF; trigger = days-until-tour-window-end.
|
||||
- `🌹 💋 OF · NYC` — out-of-budget / cold / non-converting → onlyfans.com/transquinnftw.
|
||||
- Model: `CampaignDef { id, emoji, regionalMarket, kind:'reactive'|'bulk'|'lifecycle', audience: CampaignFilter, trigger?: TourWindowTrigger, facts, policy: PolicyTree, leaves: Leaf[], handoff? }`.
|
||||
|
||||
**Campaign selection.** A prospect can match >1 campaign (a BK lead who's also gone-after-Jul-1). Default: **one active campaign per prospect, by priority**, with Leaving-NY/OF as lifecycle overrides near the window end (decided this session; revisit only if composition is needed).
|
||||
|
||||
**Naming convention (discovery + 2-way sync):** `<🌹> <campaign-emoji> <campaign-name> <regional-market>`. The `🌹` prefix is the prospector marker (app scans for it); `<regional-market>` = tour-stop market (currently **NYC**, from `src/markets/registry.ts`) — campaigns are scoped per regional market.
|
||||
|
||||
**2-way Notes sync (NEW requirement).** Today pastebin is one-way read-only (Notes→app via macsync). Make it **bidirectional + multi-note**: app discovers all `🌹`-prefixed notes, parses each into a `CampaignDef`, and writes edits back to the Note; edits in Notes flow into the app. `src/pastebin` grows from read-only `list()` to read/write + multi-note discovery + FACTS/POLICY/LEAVES section parser. Conflict policy TBD (last-write-wins vs note-as-source-of-truth with app-edit confirmation). Write path needs macsync/quinn-messenger (mesh dep — surface in UI like other macsync features).
|
||||
|
||||
**Migration.** Decompose the single `🌹 Pastebin` note into the per-campaign notes above; peel the voice corpus out to `assets/voice/`.
|
||||
|
||||
**Three additions the current code cannot express:**
|
||||
1. **`locality` on the prospect** (Manhattan / Brooklyn / unknown) — new **classifier output inferred from conversation content** (decided: from the thread, not area code or manual tag). Feeds a new `CampaignFilter.locality`. Touches `src/engine-svc/classifier.service.ts` + `src/classify/` + prospect summary + `src/campaigns/match.ts`.
|
||||
2. **`TourWindowTrigger`** — "days until tour-window end," sourced from `src/markets/registry.ts` (NYC through July 1). Makes *Leaving-NY* fire; ties the tour-stop **market** notion to campaigns (resolves the two-market split noted in CLAUDE.md).
|
||||
3. **`handoff`** — Leaving-NY's non-converters flow into OF; makes the exit funnel explicit.
|
||||
- **Voice assets (NOT campaigns).** The Eros AD block, plus **`TS4Rent — Profile copy (source of truth)`** (profile body + 22 interview answers) and **`Tryst Bio`** Apple Notes, are **voice/style corpus** — exemplars for conditioning the ML draft generation (Quinn's voice: pink-haired brainy-bimbo gamer girl, sweet sub good-girl, "hun 💗", concrete nerd detail). Bring this copy into a prospector **`assets/voice/`** directory in this repo, used as voice reference when generating/training response output (the drafter; cf. `src/engine/state.ts` persona keys + `src/corrections` `voice`/`tone` categories). Source of truth stays the Notes; `assets/voice/` is the curated, checked-in corpus the runner conditions on.
|
||||
- **2-way Notes sync — per-campaign notes (NEW requirement).** Today pastebin is one-way read-only (Notes → app, macsync). Make it **bidirectional**, and give each campaign its own Apple Note (not just the single `🌹 Pastebin`). Naming convention so the app can discover + round-trip them:
|
||||
`<🌹 rose-emoji> <campaign-emoji> <campaign-name> <regional-market>` — e.g. `🌹 🗽 Leaving NY · NYC`, `🌹 🌉 Brooklyn · NYC`, `🌹 🏙 Manhattan · NYC`, `🌹 💋 OF · NYC`.
|
||||
- `🌹` prefix = the prospector marker (same convention as `🌹 Pastebin`), so the app finds all its notes by scanning for the rose.
|
||||
- `<regional-market>` = the tour-stop market (currently **NYC**; from `src/markets/registry.ts`) — campaigns are scoped per regional market.
|
||||
- Sync is **2-way**: edits in the app write back to the Note and edits in Notes flow into the app (conflict policy TBD — last-write-wins or note-as-source-of-truth with app-edit confirmation). Requires a write path through macsync/quinn-messenger (mesh dep — note in UI like other macsync features).
|
||||
- `src/pastebin` grows from read-only `list()` to read/write + multi-note discovery; the campaign↔note mapping is the naming convention above.
|
||||
|
||||
**Voice assets (NOT campaigns).** The Eros AD block, plus **`TS4Rent — Profile copy (source of truth)`** (profile body + 22 interview answers) and **`Tryst Bio`** Apple Notes, are **voice/style corpus** — exemplars for conditioning the ML draft generation (Quinn's voice: pink-haired brainy-bimbo gamer girl, sweet sub good-girl, "hun 💗", concrete nerd detail). Bring this copy into a prospector **`assets/voice/`** directory, used as voice reference when generating/training response output (cf. `src/engine/state.ts` persona keys + `src/corrections` `voice`/`tone` categories). Notes stay source of truth; `assets/voice/` is the curated, checked-in corpus the runner conditions on.
|
||||
|
||||
**Build order:** per-note FACTS/POLICY/LEAVES parser + `🌹`-discovery → 2-way write-back via macsync → `state.ts` refactor to tree-evaluator (policy from note, signals in code) + persist `reason`-chain CoT per send → `locality` classifier field → `CampaignDef` + `TourWindowTrigger` + priority selection → derive Campaigns UI from defs (facets remain the override path) → `assets/voice/` corpus + drafter conditioning. Add/refresh `src/pastebin/README.md` + `src/campaigns/README.md` with this model.
|
||||
- **Build order:** section-aware parser → 2-way multi-note sync (discover by 🌹 prefix, write-back via macsync) → `locality` classifier field → `CampaignDef` + `TourWindowTrigger` → derive Campaigns UI from defs (facets remain the override path) → `assets/voice/` corpus + wire into drafter conditioning. Add/refresh `src/pastebin/README.md` + `src/campaigns/README.md` with this model.
|
||||
|
||||
**Risks/Mitigation**:
|
||||
|
|
@ -277,4 +292,6 @@ _Insight (2026-06-29 session):_ the single Apple Note **`🌹 Pastebin`** is not
|
|||
|
||||
This PLAN + the designs/ + current shell code + platform prospector bits + mcp-prospector + handoffs = everything needed for complete takeover. Work preserved, no loss.
|
||||
|
||||
(End of plan. Update this file as phases complete. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> for the handoff structure.)
|
||||
(End of plan. Update this file as phases complete. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> for the handoff structure.)
|
||||
|
||||
**2026-06-29 update (migration to unified app)**: Thorough plan for migrating *all* prospector from LP to here (UI-first unification of designs + inbox-ops + LP tabs into the 7-surface PWA + full backend/MCP/runner port). Session plan file has the complete document. Started Phase 1: bilingual (migration 0006 + entity/audit/prospects/inbound + types) + Triage (toggle+dual render + toolbar Classify/MR/Refresh Pastebin wired + handlers per designs/main-view). tsc clean, relevant tests green, 2 scoped commits+pushes. See MIGRATION-PLAN in ~/.grok/sessions/.../plan.md + todos. Continue per that plan (read designs/ + this + CLAUDE first).
|
||||
Loading…
Add table
Reference in a new issue