Files
recap/docs/path-2b-and-path-1-interweave.md
Keysat 0ae59f3550 Add multi-tenant cloud mode: self-serve purchase, credit metering, core-decoupling
Introduces RECAP_MODE=multi alongside single-mode self-host:
- Tenant auth + accounts (magic-link via System SMTP), per-tenant credit pool,
  anonymous trial minting with per-IP/-64 caps
- Self-serve Pro/Max purchase: inline Lightning (BTCPay) + card (Zaprite),
  prepaid 30-day periods, expiry-reminder emails
- Core-decoupling: relay owns cloud tier/expiry keyed by Recaps user-id
- SQLite (better-sqlite3) schema for multi-mode; filesystem unchanged for single
- StartOS actions/versions through 0.2.155
2026-06-13 14:25:05 -05:00

12 KiB

Path 2B + Path 1 Interweave Plan

Companion doc to architecture-simplification-plan.md (Path 1) and the chat thread that proposed Path 2A (relay-only upload, ship first).

This doc covers:

  1. Path 2B — bringing internal-meeting analysis into the Recaps cloud frontend as a first-class feature alongside YouTube/podcast summaries.
  2. How Path 2B depends on Path 1 — what Path 1 unlocks vs. what could be partially built without it.
  3. Migration path — how Path 2A's relay-only upload data flows forward into Path 2B's cloud-side library.

Context

Recap Relay (the operator-side backend) is now generic enough to analyze ANY audio — not just YouTube/podcasts. The download step is the only YouTube-specific code; everything downstream (transcribe → diarize → cluster → analyze → polish) applies cleanly to arbitrary audio. Path 2A exposes this via a relay-admin-only upload UI so operator Grant can run internal meeting analysis on his own hardware TODAY without waiting on Recaps multi-tenant work.

Path 2B is the longer arc: same capability surfaced in the cloud Recaps app, so signed-in users can submit private meeting audio, manage it alongside their other content, and (optionally) share with colleagues.


Path 1 (recap) state

architecture-simplification-plan.md defines:

  • One Recaps binary, two modes via RECAP_MODE=single|multi env var
  • Magic-link + optional password auth via StartOS SMTP
  • Per-user library at /data/history/<userId>/<sessionId>.json
  • Per-user keysat license (mintable via Keysat admin API)
  • BTCPay subscription + one-time credit-purchase flows
  • Lite-settings UI for non-operator cloud users
  • Self-hosted operator stays single-tenant by default; the .s9pk ships free + open

Status: written but not built. The relay-side work has continued in parallel (FIFO queue, clustering suppression, polish pass) which strengthens the case for Path 1 — the cloud user experience benefits from all of it, and the keysat-license layer is increasingly friction-without-value for cloud users who already have email-verified accounts.


Path 2B — internal meetings in cloud Recaps

What it looks like

A signed-in Recaps user gets a second submission affordance alongside the existing "Paste a YouTube/podcast link" input:

┌─────────────────────────────────────────────────────────┐
│ Submit content                                          │
│  ○ Paste a YouTube/podcast link                         │
│  ○ Upload audio file (private)        [Choose file…]    │
│                                                         │
│  Title:        [_____________________________]          │
│  Participants: [_____________________________]  (opt)   │
│  Meeting type: [▼ default / 1:1 / all-hands / …]        │
│                                                         │
│             [ Summarize ]                               │
└─────────────────────────────────────────────────────────┘

The submission flows the same way YouTube submissions do: Recaps-app → Relay (/relay/v1/summarize-upload for files, existing /relay/v1/summarize-url for URLs). The relay handles both via the same pipeline — only the input step differs (download vs. multipart receive).

Library + rendering

Each saved session in the cloud user's library has a type field:

  • "youtube" — existing rendering (video player + topics + transcript chips)
  • "podcast" — existing podcast rendering (audio player + topics + transcript)
  • "meeting" — new rendering: no media player; topics + transcript chips expandable below each topic card; PLUS a "Meeting analysis" block at the top with Decisions / Action Items / Open Questions / Key Quotes (the structured-extras pass from Phase 2 of Path 2A)

Library list view shows a small icon distinguishing meeting items so the user can filter at a glance.

Privacy + sharing

Meetings are PRIVATE by default — visible only to the submitting user. Two later features:

  • Share with team — a meeting can be optionally shared with N named cloud-Recaps users (each looks up by email). Other users see it in a "Shared with me" library section.
  • Export to markdown / PDF — already exists for YouTube content; add the meeting-extras blocks to the export.

Audio handling

  • Uploaded audio goes from browser → Recaps-app (Node Express, multipart middleware) → Recap Relay (forward as multipart to /relay/v1/summarize-upload).
  • Recaps-app's tmp file is deleted immediately after the relay acknowledges receipt.
  • Relay's tmp file is deleted after the pipeline completes.
  • NEITHER side keeps the audio. The TRANSCRIPT + analysis are saved. If the user wants to re-process (different prompt set), they'd re-upload the audio.
  • The transcript stays in the user's per-user library at /data/history/<userId>/<sessionId>.json, scoped + isolated.

Path 2B's hard prerequisite: Path 1

Path 2B fundamentally requires multi-tenant auth in Recaps. Without Path 1:

  • No per-user library separation
  • No way to know whether the meeting audio is private to a user vs. visible to everyone running this Recaps instance
  • No way to share with named other users
  • No way to bill upload-heavy users differently from URL-only users (uploads might warrant a different price tier given the storage cost)

You COULD build a stripped-down Path 2B on single-tenant Recaps — operator uploads audio, it's saved to the operator's library, no sharing. But that's roughly equivalent to Path 2A with a fancier UI, just on the wrong side of the codebase split (Recaps-app vs. relay). Not worth the duplication.

So: ship Path 2A as the immediate beachhead, do Path 1 next, then Path 2B on top.

What Path 1 unlocks (relevant to 2B)

The Path 1 doc already covers most of what 2B needs:

  • Per-user library at /data/history/<userId>/*.json
  • Auth-aware request scoping (req.userId)
  • Per-user keysat license OR the simplified user-id + tier headers
  • BTCPay subscription tracking (for billing upload-heavy use)

Path 1's "Lite-settings panel for cloud users" already imagines a post-auth Recaps UI without operator config noise. The submission input would extend in 2B to add the upload option.

The relay-side header migration Path 1 proposes (X-Recap-User-Id + X-Recap-User-Tier replacing the bearer license token) is also beneficial for 2B — uploads from a single user with N concurrent browsers all carry the same user-id, so credit accounting is per-user not per-install.


How Path 2A's data flows into Path 2B

When Path 2A ships (relay-only upload, results saved to /data/internal-meetings/<id>.json on the relay), those summaries are tied to the OPERATOR (Grant) — there's no cloud-user concept yet.

When Path 2B lands:

  1. Migration script — walks /data/internal-meetings/*.json and re-homes each entry under the operator's cloud user account (/data/history/owner/*.json initially, then /data/history/ <operator-user-id>/*.json after Path 1's owner→admin rename).
  2. Same JSON shape — Path 2A should save in a shape compatible with Path 2B's expected library shape (chunks + entries + speakers
    • meeting-extras). One way to guarantee this: design the Phase 1 save shape now to match what Recaps' saveToHistory produces for type=meeting, even though no Recaps UI consumes it yet.
  3. No re-processing needed — transcripts and analysis transfer verbatim. The user just sees them appear in their cloud library.

The relay-side upload endpoint (/relay/v1/summarize-upload) is the same in both worlds. Path 2A calls it via the operator dashboard's admin auth; Path 2B calls it via Recaps-app server proxying a signed-in cloud user's POST.

So the relay code path is built once and serves both.


Operator-editable prompt sets (Phase 3 in 2A; carries to 2B)

The "meeting type" dropdown is operator-editable. Each set has:

  • Name (e.g. "1:1 with direct report")
  • Topic-analysis prompt template (replaces the YouTube/podcast version)
  • Meeting-extras prompt template (Decisions / Action Items / ...)
  • Optional metadata schema overrides (e.g. "this meeting type always expects 2 participants")

Stored in relay_meeting_prompt_sets_json config field. Operator edits via the dashboard (similar to existing prompts panel). Cloud Recaps users pick a set at submission time; the relay applies it during analyze + polish.

Default sets ship built-in:

  • default — neutral meeting prompt, all sections enabled
  • 1on1 — emphasizes Action Items + Open Questions; light on Decisions
  • all-hands — emphasizes Decisions + Key Quotes; less actionable
  • customer-interview — emphasizes Key Quotes + Open Questions; light on Decisions
  • standup — short-form; Action Items + Open Questions only

Suggested order of operations

  1. Path 2A Phase 1 — relay-only upload, no extras, basic topic+transcript rendering. ~2-3 days.
  2. Path 2A Phase 2 — meeting-extras analysis pass (Decisions / Action Items / Open Questions / Key Quotes). ~1-2 days.
  3. Path 2A Phase 3 — prompt sets dropdown. ~1 day.
  4. (Use Path 2A in production for some weeks; gather feedback on prompts, output quality, UX.)
  5. Path 1 — multi-tenant Recaps. ~3-4 weeks per the existing architecture-simplification doc, modulo amendments.
  6. Path 2B — surface internal meetings in cloud Recaps. ~1.5-2 weeks given Path 1 has shipped. Migrates the Path 2A artifacts into the new per-user library.

Total wall time: ~6-8 weeks for the full arc. Path 2A capability available to operator after step 1 (~2-3 days). Cloud users get meetings after step 6.


Open questions

These should be settled BEFORE Path 2B build starts:

  1. Pricing for uploads. Does an upload count the same as a URL submission against a user's monthly credit cap? Or is it priced differently to reflect the upload bandwidth + storage cost? My default: same price (1 credit per submission) — bandwidth cost is trivial, storage is just JSON.

  2. Audio retention. Default: never retain. Optional per-user setting "keep audio for 7/30/90 days so I can re-process with a different prompt set"? Adds operator storage cost; only worth it if users actually want it.

  3. Sharing model. Path 2B Phase 1 = no sharing, private only. Phase 2 = shared with N named users. Phase 3 = optional public share URL. Each phase adds auth/permission complexity. Worth designing the data model now ({ ownerId, sharedWith: [userId] }) even if only ownerId is populated in Phase 1.

  4. Speaker name persistence. If a user identifies "Speaker_A" as "Matt Hill" in one meeting, should that name auto-suggest in the next meeting if a fingerprint match is found? Requires storing fingerprints per-user across meetings. Big privacy + product decision. My instinct: opt-in toggle in user settings, default off.

  5. Meeting type defaults. Should Recaps' submission flow have a default meeting type, or force the user to pick? My instinct: default to "default" set; let users pick if they want.


Decision points for Grant

  • Confirm the phasing above (2A → 1 → 2B) is the right order
  • Pre-commit to the "uploaded audio is never retained" default — it's the privacy-safer choice and aligns with how YouTube downloads already work
  • Pick a side on the open questions above before Path 2B starts, OR defer them as Phase 2/3 of Path 2B