The two master-gate lanes plus the RAG lane were built on feat branches with synthetic data, keyless, and verified by running — tests, a live tick, and (for oll-scout) Docker. All landed as draft PRs, nothing merged, nothing deployed.
| Branch / PR | What ran | Result |
|---|---|---|
| poc/ollin-dossier-verbs PR #87 | 3 verbs added to oll-write; pytest; ruff; offline poc_dossier.py against synthetic CV+JD. | 172 passed (+22), ruff clean, OpenAPI valid. All 3 verbs 200 offline. The CV emitted a literal gap marker [add: German level not stated — confirm C1] instead of inventing it — the groundedness rule works. |
| poc/oll-scout PR #88 | New service, fixture provider (7 synthetic CH/DACH roles); pytest; --once tick; live gunicorn + Docker. | 32 passed, ruff clean; Docker image healthy. Tick pulled 7 → scored 7 → 4 strong (≥60) → digest "sent": top match Senior Backend Engineer, Helvetia Fintech · Zürich [91]. Idempotency + SDK-boundary proven. |
| poc/ollin-rag-synthetic PR #86 | Synthetic candidate corpus → seed → retrieve → assemble cited context; abstain test. | 5 passed. 5 docs / 26 chunks ingested; job facets retrieved real passages (cos 0.10–0.36) → 15 numbered [n] evidence items; off-corpus query abstained (0.072 < floor, no fabricated citation). Sandboxed to a local stand-in using oll-memory's own default embedder; prod memory never touched. |
SCOUT_PROVIDER=jooble to confirm live CH coverage (the POC-C concentration-risk check).feedback-merge-gate).Before writing a line of the plan, we read the three services oll.in leans on. This is the technical picture — real modules, real function names, real boundaries. Everything downstream is a diff against this.
| Service | Location | Shape that matters |
|---|---|---|
| oll-write LIVE | services/oll-write/ | Flat ~14 modules. Verbs are thin @write_bp.post("/<verb>") in routes.py; each calls service.<verb>(). Shared gate _authorize_and_gate(text, token) → core_client.get_me → live plan → usage limit. Shared model call _run_model(messages) → text_client.complete_full. Prompts = named constants in llm_client.py. Boundaries: text_client.py (oll-model, X-Service-Token) + core_client.py (Core JWT). No memory/RAG anywhere. |
| foto-service | services/foto/ | Guest checkout (no auth). replicate_client.py = provider registry: mock (default, no spend) vs replicate (guarded two-stage flux-LoRA train→sample). Idempotency = jobs.py SQLite claim_for_generation atomic UPDATE. Generation inline. Calls Core's guest billing pair (PR #21). |
| Core billing LIVE | core/modules/billing/ | Routes all @require_auth except /webhook. service.create_checkout_session: mode = "payment" if plan=="one_time" else "subscription" — both modes exist. Webhook dispatch table handles checkout.session.completed, customer.subscription.*, invoice.payment_*. No event-id dedupe table — relies on naturally-idempotent upserts. |
plan=="one_time").customer.subscription.deleted + invoice.payment_succeeded.Copy the existing single-input verb pattern verbatim. Add tailor_cv, cover_letter, interview_prep. Each takes the BFF-assembled profile context (retrieved, cited) plus the job description. No new boundary, no memory client.
| File | Change |
|---|---|
| routes.py | 3 new @write_bp.post("/tailor-cv" | "/cover-letter" | "/interview-prep"), each mirroring the clarify handler: bearer → DTO validate → service.X() → _map_service_error. |
| dtos.py | 3 request/response pairs. Request = {profile: str, job_description: str, tone?: str}; response = the standard {text, provider, model} (CV/letter) or {questions: [...]} shape for interview prep. |
| service.py | 3 functions, each: _authorize_and_gate(profile+job, token) → llm_client.build_<x>_messages(profile, job, tone) → _run_model(messages). Same exceptions (AuthError/OverLimitError). |
| llm_client.py | New named constants: _TAILOR_CV_SYSTEM, _COVER_LETTER_SYSTEM, _INTERVIEW_PREP_SYSTEM + build_*_messages builders. Keep the honest-editor ethics block; add the Swiss-format + "ground on cited passages, abstain if thin" rules. Respect MAX_INPUT_CHARS. |
| openapi.yaml | 3 new paths + schemas (contract-first; Schemathesis gate stays green). |
| tests/ | test_tailor_cv.py, test_cover_letter.py, test_interview_prep.py mirroring an existing verb test (mock text_client + core_client). |
llm-call-contract): the profile passages arrive with ids; the system prompt instructs the model to write only from them and to leave a gap rather than invent. Citations are rendered by the BFF (below), not the model — the model just references passage ids. This is what makes the output "from real work, cited" and honest on a thin corpus.The oll.in web app is a thin Next.js client with a server-side BFF, lifted from ollwrite's proven pattern (proactive-memory-sidecar). The browser never names a collection or holds a service token — the BFF derives everything from the Core JWT.
collection: mem:user:{id} from JWT · inject memory service token · orchestrate{profile: assembled cited passages, job_description}.The scaffold, guest-checkout wiring, idempotent store and tests already exist and pass. Going live is configuration + one verification run, not a rewrite — the replicate provider is already implemented and guarded.
# services/foto/.env — flip provider + supply flux-LoRA config FOTO_PROVIDER=replicate REPLICATE_API_TOKEN=r8_… # burned if ever exposed FLUX_LORA_TRAINER=<owner/model:version> REPLICATE_USERNAME=<dest for trained versions> FOTO_PUBLIC_BASE_URL=https://<ch-domain>
The _require_replicate_ready guard refuses to run until all four are set, so a misconfigured deploy fails loud, not silently on the mock. The only open question is output quality for the Swiss Bewerbungsfoto — which is POC-B, a run, not a build.
guest-checkout-session + guest-verify-session; they're additive and never weaken the authed routes. Needs Sam's per-PR go + green CI (feedback-merge-gate).plan=="one_time".ProcessedEvent(event_id PK, processed_at) table; the webhook short-circuits a seen event.id. Small, but do it first — it's the one real gap the grounding found (idempotent-paid-fulfillment). Verify with adversarial-reviewer — this touches the money path.ProcessedEvent table is a new migration, not a change to existing handlers; the guest pair is new routes; the Radar price is config. Nothing existing is edited — that's the rule for touching a frozen service.The single genuinely-new backend. It copies the house service template (thin routes · sole-boundary clients · own Neon DB · contract-first · per-endpoint tests) and adds nothing exotic. POC-C settled its one hard unknown — the data source is Jooble (primary, only self-serve API with real Swiss coverage, GREEN ToS) + Arbeitnow (open, no-key supplement).
services/oll-scout/ app.py config.py routes.py service.py dtos.py errors.py jooble_client.py # SOLE data-source boundary (POST jooble.org/api/{key}) arbeitnow_client.py # supplement, no key scoring.py # calls oll-model via X-Service-Token (like text_client) store.py # own Neon oll_scout: jobs + user matches (ADR-008) digest.py # daily match digest -> Core /email/send scout_cron.py # the always-on tick (inline; Dramatiq only if load demands) openapi.yaml Dockerfile requirements.txt tests/
jooble_client.search(keywords, location="Switzerland") + Arbeitnow page; dedup on source id / updated.scoring.py asks oll-model to rank each role against the user's oll-memory profile (fit + gaps), via the internal service token.oll_scout.digest.py → Core /email/send. Alert, don't apply.feat/ollin-verbs branch, add the 3 verbs to dev oll-write (or, faster: hand-build the prompt and hit the existing /draft to validate value before writing the verb).POST jooble.org/api/{key} with {keywords, location:"Switzerland"}; also GET arbeitnow.com/api/job-board-api.Set FOTO_PROVIDER=replicate + flux-LoRA config; generate from Sam's phone snaps. Pass gate: output passes an eyeball test vs real Swiss Bewerbungsfoto standards, consistent across ≥3 subjects.
Subscription mode exists, so this shrank: add a test-mode Radar price + the ProcessedEvent dedupe table on feat/core-radar-billing; drive a subscription checkout → invoice.payment_succeeded webhook → /me reports the plan; replay the same event.id and assert it's a no-op. Pass gate: plan flips once, replay is inert, all existing billing tests stay green (adversarial-reviewer).
Add the 3 verbs to feat/oll-mcp-thin; apply services/oll-mcp/docs/openclaw-wiring.md; run one real application through OpenCLAW over the --http seam (human on send). Pass gate: OpenCLAW returns a review-ready dossier. (Seam already proven both transports.)
| Service | New vars | Default / note |
|---|---|---|
| foto | FOTO_PROVIDER · REPLICATE_API_TOKEN · FLUX_LORA_TRAINER · REPLICATE_USERNAME | Defaults to mock (keyless); real only when all set (guarded). |
| oll-scout | JOOBLE_API_KEY · SCOUT_DATABASE_URL · OLL_MODEL_BASE_URL · OLL_MODEL_SERVICE_TOKEN · CORE_BASE_URL | Own Neon oll_scout; scoring + digest reuse the service-token pattern. |
| Core | STRIPE_PRICE_RADAR (+ existing pack price) | Radar price id; subscription mode already on. |
| oll.in BFF | CORE_BASE_URL · MEMORY_BASE_URL · OLL_WRITE_BASE_URL · FOTO_BASE_URL · MEMORY_SERVICE_TOKEN | Server-side only; browser holds the Core JWT cookie, nothing else. |
The change-sets map onto independent lanes — different files, different services — so they parallelize cleanly, each behind its POC gate.
Prove grounded quality, then ship the 3 verbs + the BFF/frontend. The MVP core. Master gate.
Confirm Jooble field quality, then build oll-scout. Master gate for Radar. Runs fully parallel to α.
One config flip + a quality run. Independent; can finish first.
Guest pair + Radar price + event dedupe. Gated by merge-gate; smaller than first drawn.
MCP loop reuses the proven seam; Postiz presence deferred (highest ToS care, lowest near-term revenue).