oll.am · oll.in v2 Roadmap · concrete, on top of the MVP · 2026-07-08

oll.in — the v2 plan

Now that v1 exists (a Next.js app on the frozen spines: paste a job → grounded dossier → take it), v2 stops being a wishlist and becomes a set of small additions that reuse the MVP. Every v2 feature leans on something v1 already built — the dossier loop, the memory corpus, preferences + applications CRUD, auth, Stripe. This is the concrete build order, each with its frontend route, backend delta, the one input it needs, and its effort.
Thesis: v2 = additions, not rebuilds Order: revenue-ready + low-dependency first Each: a route + a small backend delta + one input

What's inside

  1. The MVP is the platform
  2. v2.1 · Guest headshot — the fastest second franc
  3. v2.2 · Career Radar — the always-on hook
  4. v2.3 · Get-found — inbound presence
  5. v2.4 · Ollie — the autonomous agent
  6. v2.5 · Tracker+ & German
  7. The money model

The MVP is the platform the leverage

The reason v2 is cheap: v1 didn't just ship a feature, it stood up the reusable surface every later feature needs. Read the right column — that's why each v2 item is an addition, not a project.

v2 featureWhat it fundamentally isReuses from the MVP
Guest headshota no-login photo salefoto-service (live) · Core billing · the headshot UI
Career Radarjobs fed into the dossier loop, on a schedulethe whole dossier route · preferences CRUD · oll-memory profile · applications CRUD
Get-founda draft-post variant of generation + a scheduleroll-write generation · the memory corpus · auth
Ollie (agent)the dossier verbs driven autonomouslythe exact dossier verbs (via MCP) · applications CRUD (the review inbox)
This is the frozen-spines thesis paying off — with one honest exception. Radar is "point the existing dossier route at a scheduled feed"; Get-found is "a draft-post variant + a scheduler." Those are routes + deltas, no new infrastructure. Ollie (v2.4) is the exception — a per-user autonomous loop is a capacity model, not a route (see its section). Everything up to v2.3 is new code on the frozen spines; v2.4 is the one item that may need new infra. Naming that up front is the point.

v2.1 · Guest headshot the fastest second franc · effort S · frontend BUILT

A standalone, no-login page that sells the CHF 29 Bewerbungsfoto — the lowest-commitment on-ramp, and the quickest new revenue after v1. Status (2026-07-08): the frontend funnel is built and verified by running on branch feat/v2-guest-photo; both backend halves are green PRs awaiting merge.

Built + verified (frontend)
  • The /photo funnel — no-login: upload 4–8 snaps (client-validated) → Swiss style pick → CHF 29 pay-once (price on screen one) → Stripe return at /photo/fulfill (real foto FulfillResponse state machine → delivered image URLs).
  • BFF routes (/api/photo/checkout·fulfill·health) mirror services/foto/openapi.yaml exactly, over FOTO_BASE + the v1 retry lib. Built on v1's design tokens — same product.
  • Verified running: tsc + build clean (25 routes) · foto-service booted local :5001 · Playwright walk upload→pay hit the real BFF→foto hop; the honest error path (CORE_UNAVAILABLE) surfaces — no mock.
The inputs (to go end-to-end)
  • Merge PR #84 (Core guest-checkout pair) + #85 (foto front door) — both all-green; unblocks foto→Core→Stripe.
  • Create the Stripe CHF 29 headshot price (STRIPE_PRICE_HEADSHOT_CHF29_ONE_TIME, test→live).
  • ⚠️ Real gap — selfie upload/storage: foto's contract has no upload endpoint (input_photo_urls are references; storage is a foto TODO). The funnel captures + validates snaps client-side but sends [] until a storage endpoint exists — flagged in the UI, not faked. This is the one genuine backend addition v2.1 needs before real Replicate training.
  • Set FOTO_PUBLIC_BASE_URL={web-origin}/photo; a .ch domain (nice-to-have).

v2.2 · Career Radar the always-on subscription · effort M · BUILT + TESTED

The recurring hook: oll.in watches the market for you daily and keeps an application half-ready. This is where the product stops being a one-shot tool and becomes a service — and it reuses the entire dossier route.

Status (2026-07-08): oll-scout promoted to a real service → PR #93 (50 tests, ruff clean, docker build clean; saved-search CRUD keyed off the Core JWT + per-user tick + daily Core-email digest + ownership isolation, all proven by curl). The /radar frontend renders real scored match cards off a live tick (pulled 7 → 4 strong → digest sent), with a live min-score filter and a "Build dossier" hand-off into the v1 composer. The review-fix caught + fixed a real broken hop (the BFF hit the now-auth-required scout with no Bearer → 401 → 0 matches); now forwards the real Core JWT. The CHF 19/mo tier is wired to a real Core subscription-mode session (no fake charge) — the one product that legitimately is a subscription. Anon preview — SOLID: a public, no-auth GET /api/scout/sample-matches (PR #93, fixture-scored in-memory — no PII, no Core round-trip) now backs the logged-out /radar preview, so a stranger sees real scored cards with zero 401s in prod; signed-in still forwards the real JWT to the ownership-scoped /matches (verified: prod-mode sample-matches → 200 with no Core hop, while the old anon path correctly 401'd). Sam-gated: AUTH_PROVIDER=core in prod · a live CHF 19/mo recurring price + RADAR_PLAN_GATE · a Jooble key for live CH coverage (Arbeitnow works keyless today).
The build
  • Frontend: a `/radar` route + nav item — daily scored matches; each has a "Prepare application" button that opens the existing v1 dossier flow pre-filled with that job.
  • Backend delta: the oll-scout service (already built — real Arbeitnow jobs + model scoring) gets a per-user scheduled tick → score vs the memory profile → store → a daily digest email (Core).
  • Reuses: the dossier route, preferences CRUD, the memory profile, applications CRUD (all from v1).
The inputs
  • A Jooble API key (free) for real Swiss coverage (Arbeitnow covers DACH today).
  • A scheduler (a simple cron/tick; Dramatiq only if load demands).
  • Subscription billing (~CHF 19/mo) — Core already supports Stripe subscription mode.

v2.3 · Get-found inbound presence · effort M · BUILT + TESTED

The second engine: instead of only applying out, help recruiters find you. oll.in drafts credible LinkedIn posts in your voice and schedules them — official OAuth only, you approve every one.

Status (2026-07-08): the /get-found route is built and honest. The draft is real oll-write/Groq generation grounded on the corpus (4 grounded passages; honest abstain on a miss). Postiz connections honestly report "no key"; the schedule action returns a real 501/409 ("no LinkedIn channel connected") — never a fake "scheduled." Sam-gated: a POSTIZ_API_KEY + a LinkedIn OAuth app to actually schedule (sanctioned OAuth only — never scraping).
The build
  • Frontend: a `/presence` route — draft posts (grounded in your CV / wins) → edit → schedule.
  • Backend delta: an oll-write draft-post prompt (small); wire Postiz (already deployed) for scheduling.
  • Reuses: oll-write generation + the memory corpus.
The one input
  • A LinkedIn OAuth app (your dev app) + the user's consent — the only real blocker; posting is sanctioned OAuth, never scraping.

v2.4 · Ollie — the autonomous agent the vision · effort L + infra · prototype BUILT + TESTED

The flagship: "Ollie" runs your search — scouts, matches, and prepares each application while you sleep, then hands you a ready dossier to send. You always hit send. The magic is that most of it is the v1 verbs driven autonomously — but this is the one v2 item that isn't purely a route + delta.

Status (2026-07-08): the review-queue is built and proven end-to-end (after the shared Radar bearer-fix): a real match (score 100) → real dossier compose (oll-memory grounding + oll-write generation) → a draft with citations, prepared_by=ollie, status=draft, nothing sent. UI is honest: "drafts for you to review, never auto-sent"; "Approve & mark sent" is a status marker, not an employer submission (hard ethics/ToS line — no auto-apply, human-on-send always). The always-on per-user runtime is deferred, infra-gated — "Prepare now" is an honest manual trigger today.
⚠️ The hidden cost — this is a capacity model, not a feature. Everything else in v2 is a frontend route + a small backend delta. Ollie is a per-user agent runtime: an OpenCLAW loop running on a schedule, per subscriber. On a shared €5 VPS that is a real capacity question (concurrent loops, memory, rate-limits, cost-per-user) — new infrastructure, not new code. That's the honest reason it's last, alongside the ToS care. Don't let the "effort L" label hide that this rung needs a capacity plan before a line of it ships.
The build
  • Runtime: OpenCLAW → the MCP seam (built + round-trip proven) drives the exact v1 dossier verbs + Radar, per user, on a cron.
  • Frontend: an "Ollie prepared these" queue inside /applications — the human reviews and sends.
  • Reuses: the dossier verbs (via MCP), applications CRUD as the review inbox, Radar as the source.
The inputs / care
  • A per-user agent runtime (hosting the OpenCLAW loop).
  • Highest ToS care — human-on-send always; no auto-apply. This is why it's last.

v2.5 · Tracker+ & German polish · effort S each · BUILT

Applications tracker+: extend v1's applications CRUD with a richer status pipeline, RAV-deadline reminders (Core email), and interview-reply nudges — a natural addition to the list that already exists. Full German: next-intl over the Next.js app; v1 output already adapts language to the job, so this is UI-string localization, not logic.

Status (2026-07-08): Tracker+ status pipeline built; the RAV reminder emails the user's own account address via Core (recipient-scoped, 401 if not signed in — real error surfaced), correctly framed "Swiss-format Bewerbung." German is in via next-intl (de.json, working language toggle; build passes). German — SOLID, proven by a Playwright walk across every surface (home/compose, /photo, /radar, applications, Ollie): a real defect was found + fixed (/photo and /radar were never wired to next-intl — English content under a German shell); now every visible string is routed through useTranslations (rich markup + interpolation), full EN/DE key parity, zero missing-key fallbacks or unresolved {vars}, no layout overflow on the longer German strings.

The money model how v2 earns

Pay-once (own the tool)
  • The Photo — CHF 29 (guest, v2.1) · add-on inside the flow, never standalone
  • One Application — CHF 9 (v1, live) — the trial rung: get this important one right
  • Active Search Pass — CHF 99 flat, unlimited applications for the search (bundles the dossier engine)
Subscribe (the service works for you)
  • Career Radar — ~CHF 19/mo (v2.2) — jobs found + dossiers, the honest LinkedIn-Premium alternative
  • Ollie (v2.4) folds into the Radar subscription as the autonomous tier
⚠️ The ladder has to add up out loud — or it confuses the exact everyday-professional we built for. A RAV job-seeker owes 10–12 applications/month. At CHF 9 each that's CHF 90–108/mo — which makes a CHF 99 pass look like mere break-even and Radar (CHF 19/mo) look strangely cheap next to per-dossier pricing. The fix is not the price, it's capping the CHF 9 unit visibly: frame it as "first application CHF 9 · the Pass pays for itself by application #11", so the page does the arithmetic for the buyer. CHF 9 is a trial rung, not the everyday unit — the Pass (unlimited) or Radar (found-for-you) is the home for anyone applying at volume. Each rung must be the obviously-right choice for one kind of user; per-application pricing that silently out-costs the Pass is a ladder that fights itself.

The honest line holds: you own the tool (pay once); you subscribe to the work. The dossier engine is yours; Radar/Ollie is a service that never sleeps.

Gate order: v1 deploys + earns the first stranger franc → v2.1 guest photo (merge Core guest-checkout) → then a data-gated fork: v2.2 Radar ⇄ v2.3 Get-found → v2.4 Ollie → v2.5 polish. Radar is architecturally next (it reuses the whole dossier route + oll-scout), but which ships first is a data call, not a now call. Get-found needs only a LinkedIn OAuth app + a prompt, and it's what employed-and-curious users — the broader audience — want first; Radar is what active seekers want. Let the waitlist + the first francs vote: if the first ten buyers skew active-seeker, ship Radar; if they skew employed-and-curious, flip to Get-found. This is exactly what the attribution nights are for. Companion: the feature scope · architecture.