The product is built and locally verified across three rungs — the code is committed on two feature branches, not yet pushed / deployed. Going live is a short, ordered sequence: fill two blanks → deploy the RAG backend → deploy the front-end to the domain → wire live Stripe → launch. Everything below marked SAM is a human-only, often irreversible act; everything else Claude can prepare to one click.
What's already done — the deploy is the remaining inch
feat/oll-memory (PR #80) and feat/memory-sidecar, not pushed to prod. Deploy is what's left.Rung 0 — the product front, real in the browser
Landing + three-pane editor (draft · memory sidecar · chat), the 9-op ✨ menu, redline diff with Accept/Reject/Retry, the Ink-In sweep, the persisted Groq↔Claude toggle. Next.js standalone build green, Playwright-verified.
✓ built + verified locally · committed onfeat/memory-sidecar
Rung 1 — correctness plumbing
Caller-supplied stable document_id (edit-safe upsert, kills stale chunks) · raw-cosine vector_score so a relevance floor is possible · hybrid dense + keyword retrieval · per-user mem:user:{id} scoping enforced server-side.
Rung 2 — onboarding & export
First-corpus "what I found" over /api/extract · pypdf PDF ingest · export-to-Markdown-with-citations. The chargeable hook (gate the 70b / Claude dial behind Pro) is wired to the plan from /api/auth/me — pending the live Stripe step below.
Needs Sam — the human-only / irreversible list
| Item | Stage | Why only Sam |
|---|---|---|
| Pick the dedicated domain | 0 | A purchase + DNS change on Sam's registrar; sets the URL every later step points at. |
| Set the price + free/paid split | 0 | A pricing decision — the number and what's free vs Pro. Blocks the Stripe product. |
| Create the Coolify apps + Neon DB + Ollama host | 1–2 | Console provisioning on the VPS / Neon that touches shared prod infra. |
| Trigger the deploys | 1–2 | Deploy is a deliberate act (tag / deploy-all.yml dispatch) — never automatic on merge. |
| Create the live Stripe product + flip test→live keys | 3 | Irreversible money wiring — real cards, real webhook. Only Sam. |
| Run the real-card payment drill | 3 | A live charge on a real card to prove the plan flips to Pro end-to-end. |
The two decisions
⛔ blocks the money step SamPick the dedicated domain
Why: ollwrite launches on its own name, not a subdomain — it's the URL every later step (Coolify FQDN, Core CORS_ORIGINS, the Stripe success URL, the GTM posts) points at. Locking it now avoids re-pointing everything later.
Do: buy the domain, then point DNS at the VPS. Coolify + Traefik issue the Let's Encrypt TLS automatically once the record resolves.
# DNS (Hostinger) — both records → the Hetzner VPS A @ 72.62.150.237 A * 72.62.150.237 # then set this FQDN as the Coolify domain for the ollwrite app (Stage 2)
Set the price + the free / paid split
Why: the number and the free/paid boundary define the Stripe product (Stage 3) and what the app gates behind Pro. Feed the value to feed the corpus — meter generously; the free tier must be genuinely usable so people bring their library.
Do: decide the split, e.g. Free = Groq-8b generation + a capped corpus; Pro = the Claude / 70b dial + unlimited corpus. Then set PRICE in src/lib/pricing.ts so the paywall + upgrade copy read the single source.
// src/lib/pricing.ts export const PRICE = { amount: 9_00, // TBD — the chosen number, in minor units currency: 'chf', interval: 'once', // pay-once, not subscription free: { model: 'groq-8b', corpusDocs: 25 }, // TBD split pro: { model: 'claude|70b-dial', corpusDocs: Infinity }, };
Deploy oll-memory — the RAG backend the sidecar needs
Claude preps Sam triggersMerge PR #80 (feat/oll-memory) through green CI
Why: #80 now carries the launch-critical commits — caller-supplied stable document_id (edit-safe upsert), raw-cosine vector_score (enables the relevance floor), and pypdf PDF extraction. Merging is the graduation to main; needs green CI + Sam's go per the merge gate.
Create the oll-memory Coolify app
Why: one Coolify app per service (Base Directory + Watch Paths) so only oll-memory redeploys on its changes, reachable Core-side by its network alias.
Base Directory /services/oll-memory Watch Paths services/oll-memory/** Port $PORT # 12-factor, non-root Network alias oll-memory # shared docker network `ollam` Auto-deploy OFF # deploy is a deliberate tag/dispatch
Provision the private Neon oll_memory DB with pgvector
Why: database-per-service (ADR-008) — oll-memory owns its own store, no other service touches it. The vector extension is what makes similarity search possible.
-- once, on the fresh Neon oll_memory database
CREATE EXTENSION IF NOT EXISTS vector;
Stand up Ollama running nomic-embed-text, then set the env
Why: prod embeds with real nomic (768-dim) reachable from oll-memory; the keyless local-hash provider stays the CI / dev default so the pipeline needs no model download. /api/extract generation also needs an oll-model token.
EMBED_PROVIDER=ollama EMBED_DIM=768 OLLAMA_EMBED_BASE_URL=http://ollama:11434 DATABASE_URL=postgresql://…@…/oll_memory # the Neon oll_memory DB OLL_MEMORY_SERVICE_TOKEN=<generate> # the caller (BFF) presents this OLL_MODEL_BASE_URL=https://model.oll.am OLL_MODEL_SERVICE_TOKEN=<model token> # for /api/extract generation
Verify oll-memory is healthy before moving on
Why: a green backend is the precondition for Stage 2 — the front-end will call these paths on first load.
# health + db (expect vector: present) + a real round-trip curl -s https://<oll-memory>/api/health curl -s https://<oll-memory>/api/health/db # → { vector: "present", … } # then one real ingest → query and confirm a scored, cited chunk comes back
Deploy ollwrite — the product front-end
Claude preps Sam triggersDeploy the feat/memory-sidecar branch to the dedicated domain
Why: this is the verified product front. Merge it to its main / point Coolify at it, set the Stage-0 domain as the app FQDN → Traefik issues TLS.
Set the ollwrite env — service tokens are server-only
Why: the service tokens must never be NEXT_PUBLIC_ — that would ship them to the browser. They live only in the BFF (server) layer. AUTH_ENABLED=true turns prod gating on.
OLL_MEMORY_BASE=https://<oll-memory> OLL_MEMORY_SERVICE_TOKEN=<from step 6> # server-only — NOT NEXT_PUBLIC_ OLL_MODEL_BASE=https://model.oll.am OLL_MODEL_SERVICE_TOKEN=<model token> # server-only — NOT NEXT_PUBLIC_ CORE_API_BASE=https://core.oll.am AUTH_ENABLED=true # prod gating ON
Decide the generation path — flag for Sam
Why: the demo uses WRITE_DIRECT_MODEL=true — editor ops + chat call oll-model directly. That ships, but for prod the cleaner path routes generation through write-service, the one server that owns prompts + entitlement + metering, so the model dial and usage caps apply uniformly.
Two options, Sam picks: (A) keep WRITE_DIRECT_MODEL=true — fastest to live, dial gated in the BFF. (B) route chat + ops through write-service /grounded-chat — entitlement + metering + retry in one place (the keystone from the build-out plan). Groq stays the default generation provider either way.
Verify the product end-to-end on the domain
Why: prove the whole path works on real prod services before touching money.
- Landing + editor load on the domain (TLS green).
- Magic-link login works against
core.oll.am. - Ingest → proactive sidecar card → chat → citation chip works end-to-end.
Wire the money step — live Stripe
⛔ irreversible SamCreate the ollwrite product + price in Stripe, wire Core checkout
Why: the product's /api/checkout is a 501 stub today — it must call Core's real checkout-session endpoint at the Stage-0 price. Then the Pro dial (Claude / 70b) gates on the plan read live from /api/auth/me.
Do: create the product + price in Stripe → replace the 501 stub with a Core checkout-session call → confirm the Pro dial is gated on plan==='pro'.
Flip test→live keys, add the live webhook
Why: real cards need the live Stripe keys and a live webhook so a completed payment flips the plan to Pro. Without the webhook + secret, payment succeeds but the plan never upgrades.
# Core (or the ollwrite billing env) — swap test → live STRIPE_SECRET_KEY=sk_live_… STRIPE_WEBHOOK_SECRET=whsec_… # from the LIVE webhook endpoint # create the live webhook in the Stripe console → the billing/webhook path
Run the live-payment e2e drill
Why: the only proof that matters — a real card, a real charge, the plan flips to Pro and the Claude / 70b dial unlocks. This is the first-stranger-dollar gate.
Do: pay with a real card end-to-end → confirm /api/auth/me returns plan: pro → confirm the Pro dial is now usable. (Refund your own test charge after.)
Launch — distribution
SamRun the first-week distribution push
Why: a live product with no audience earns nothing. The GTM plan has the exact order and honest angles — lead "private + real editor + you own it," never "AI writing."
Do (order from the plan): Show HN (honest plain title) → free lasting boards (Uneed · MicroLaunch · Fazier · Peerlist) → pay-once directories (NoSubscription · Buy Once Software — our home turf) → AI directories → GitHub awesome-lists → authentic community posts in the ranked rooms → newsletter pitches → Product Hunt last, after banking social proof. Full segment/persona/room detail: the build-out & GTM plan.
ollwrite — Launch Runbook · irreversible clicks only · 2026-07-05. Executes the build-out & GTM plan; the ordered rung checklist lives in the Backlog. Grounded in the feature docs + integration plan. Deploy model + env matrix: CI/CD & Environments · Service Env Matrix.
Everything is built and verified locally. These are the deliberate, irreversible steps Sam takes to go live — in dependency order. Two blanks to fill first: the dedicated domain, and the price + free/paid split.