Launch runbook · irreversible clicks only · 2026-07-05

ollwrite — Launch Runbook

Everything is built and verified locally. These are the deliberate, irreversible steps Sam takes to go live — in dependency order, each with its why and the exact env / commands where known. Two blanks to fill first: the dedicated domain, and the price + free/paid split. Executes the build-out & GTM plan; the ordered rung checklist lives in the Backlog.

"The deploy is the remaining inch — this is the map of the clicks only Sam can make."

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

All three rungs are built and verified locally; the work sits committed on 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 on feat/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.

✓ built + verified locally · in oll-memory PR #80

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.

✓ built + verified locally · committed on the two branches
The read: nothing here is a build task. Rungs 0–2 are done and green on the branches. What remains is deploy + the two decisions + live Stripe — the clicks below.

Needs Sam — the human-only / irreversible list

Everything else Claude prepares to one click. These are the acts only Sam can take (a domain purchase, a price, a deploy trigger, a live-key flip).
ItemStageWhy only Sam
Pick the dedicated domain0A purchase + DNS change on Sam's registrar; sets the URL every later step points at.
Set the price + free/paid split0A pricing decision — the number and what's free vs Pro. Blocks the Stripe product.
Create the Coolify apps + Neon DB + Ollama host1–2Console provisioning on the VPS / Neon that touches shared prod infra.
Trigger the deploys1–2Deploy is a deliberate act (tag / deploy-all.yml dispatch) — never automatic on merge.
Create the live Stripe product + flip test→live keys3Irreversible money wiring — real cards, real webhook. Only Sam.
Run the real-card payment drill3A live charge on a real card to prove the plan flips to Pro end-to-end.
0

The two decisions

⛔ blocks the money step Sam
Two blanks the rest of the runbook depends on. Fill both before Stage 1 finishes — Stage 2 needs the domain, Stage 3 needs the price.
1

Pick 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)
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 },
};
1

Deploy oll-memory — the RAG backend the sidecar needs

Claude preps Sam triggers
The memory sidecar and chat can't work in prod without the retrieval service. Stand it up first — it's the dependency the front-end calls.
3

Merge 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.

4

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
5

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;
6

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
7

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
2

Deploy ollwrite — the product front-end

Claude preps Sam triggers
With retrieval live, deploy the product itself to the dedicated domain from Stage 0.
8

Deploy 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.

9

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
10

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.

11

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.
3

Wire the money step — live Stripe

⛔ irreversible Sam
The platform already has the Stripe billing spine + a parallel-webhook pattern from Core. This step plugs ollwrite into it at the chosen price.
12

Create 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'.

13

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
14

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.)

4

Launch — distribution

Sam
Live + chargeable. Now the first-week GTM push — the ranked communities, personas, and submission order from the plan.
15

Run 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.

Honest sequencing. This is after-/alongside-first-franc work — the humaniz.me money path stays P0 and nothing here jumps that queue. Within ollwrite, the chargeable rung (Stage 3) is the goal; the exotic v2 features (gap-chips, voice profile, second-brain) sit after the first ollwrite dollar. The ethics line holds: ollwrite is an honest writing tool the author stays responsible for.

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.