The operating system I built for my own agency, in production at crm.korlane.com: a CRM and money ledger where eleven automations post atomically, and a five-stage AI pipeline where Claude writes, GPT grades, and monthly recalibration re-weights the grader from real outcomes.
Six views — Today, Hunt, Craft, Deliver, Money, Learn — replace the spreadsheet that used to run an Upwork agency. Mine.
Underneath the views sits a real operating system: a Postgres data spine where eleven automation rules fire on every entity change — connects debited when a proposal goes out, refunds credited on outbid, income mirrored to transactions — each rule atomic inside a single prisma.$transaction, every mutation writing an append-only audit row. Above it sits the machine this dossier is really about: a five-stage AI pipeline that turns a scraped job posting into a scored, gated, human-reviewed proposal.
One honesty note before any drawing: KorCRM was built for one agency — my own. The multi-tenant access layer exists so my team can share it safely, not as evidence of paying customers. What the daily use buys instead is something rarer on a portfolio page: every claim here survived contact with production, because I am the production.
Somewhere tonight a freelancer is pasting proposal number forty into a text box, and the connects are spent whether or not anyone reads it.
The feed refreshed an hour ago. The job looks winnable, so the draft starts — from a blank page, again, because nothing recorded what the winning proposals had in common. The connects ledger lives in one spreadsheet tab, income in another, and the client who hired us in March is a row nobody updated. Most replies never come, and the ones that do arrive carry no lesson: the loop has no memory. Every evening ends the same way — money spent on attention, words spent on instinct, and nothing learned that survives until morning.
FIG. 1 shows the loop as lived. It runs; it never learns; it has no exit.
Gmail polling catches Upwork's notification emails; a WXT Chrome extension reads the live job page through three dedicated DOM extractors. BullMQ workers dedupe and normalize both streams into one job record. The pipeline starts from data, never from a paste.
GPT-5.4 Nano reads the posting for the client's real problem, red flags, and a job-quality score — cheap intelligence before any expensive prose. Claude Opus then decides how to win: hook type, framing, proof point, problem-agitate-solve structure. Strategy is its own stage so a bad plan never gets beautifully written.
Claude Sonnet writes three candidates in parallel — same strategy, different execution. Single-shot generation is a coin flip; best-of-three means you pick a winner instead of hoping for one.
GPT-5.4 scores every candidate on eight weighted rubric dimensions plus five deterministic checks — deliberately a different provider from the writer, because a model grading its own prose inflates it. The gate is arithmetic, not opinion: all eight dimensions ≥ 4, slop score 0, composite ≥ 3.5. Miss any one and nothing ships.
A failing winner gets at most two rewrites aimed at the flagged dimensions, each re-judged from scratch. Still failing, it lands in the human review queue flagged below-threshold — the machine never quietly ships its own rejects, and a human sends everything that leaves.
Every shipped proposal is tracked — viewed, replied, interviewed, hired. Monthly recalibration re-weights the rubric from those outcomes, and every historical evaluation keeps its own rubricWeightsSnapshot, so the past stays reproducible while the judge improves. This dashed arc is the exit FIG. 1 never had.
165K lines and one database, but 22 packages with hard frontiers: nine domain packages that never import each other, cross-domain reads through ports, writes only through named orchestration flows like submitProposal.
OrchestrationService facade deleted. Revisit when any domain needs an independent deploy cadence. VERIFIEDThe automation service still carries its legacy package name. Promoting it into the @korcrm/* convention mid-wave meant touching the deploy pipeline for a naming win.
Four months of production use leaves marks. Here they are, dated and traceable — including the one a portfolio would normally hide.
max-lines: 20 — which flagged half of all API routes, because Next.js route files are a framework convention, not a smell. A gate that cries wolf gets ignored.max-lines-per-function: 20. The false positives vanished; the rule caught the seventeen real violators.0.0.0.0:4000 and internet-reachable. The firewall said the box was closed; Docker's publish rules had opened it anyway, ahead of ufw.127.0.0.1, moved internal traffic onto the Docker network, and wrote the incident down. No evidence of compromise was found; the exposure was real regardless.Read REV D twice; I did. A proxy whose one job is holding secrets sat reachable from the internet because two correct systems — Docker publishing a port, ufw filtering INPUT — composed into a wrong one. The fix took a day. The lesson cost more: on a solo product, you are also the security team, and the only honest response to that is the habit this table demonstrates — find it, root-cause it, bind it, write it down where clients can read it.
prisma.$transaction.VERIFIEDCustomer count. One agency runs on KorCRM — mine. The multi-tenant access layer exists so my own team works safely inside it; it is not evidence of paying customers, and this page will not dress it up as traction.
Win-rate. The feedback loop exists and recalibrates monthly; outcome events are in the schema doing their job. The win-rate itself is my agency's business data, not a portfolio claim — a number I act on is not automatically a number I advertise.
EVALUATION_RUNS CONTRACTWhat the plate means for anyone running a pipeline of their own: the expensive part of AI writing isn't the writing — it's knowing, mechanically, when the output is allowed to exist, and feeding what happened next back into the grader. That whole apparatus is what you saw in FIG. 2, and it runs for the price of a coffee per proposal.
Discuss a system like this →The plain version, without the drafting frame: I built this because my own evenings looked like FIG. 1, and I was tired of paying for attention with no memory of what earned it.
The pipeline gets the headlines, but the part I'd defend in any interview is duller: 194 recorded decisions, 274 logged gotchas, a revision table that keeps its security incident on the page, and a rework wave where I stopped shipping features for a month to make the architecture tell the truth. A proposal machine without outcome memory is just a faster typewriter — the loop was always the product.
And the honest scope line, one more time: this is a one-agency system, run daily by the person who built it. That's a smaller claim than "SaaS with customers" and a stronger one than most demos — every screw in this drawing has been turned under load, by me, this quarter.
KorCRM filed proposals this week and will file more tomorrow — the machine on these sheets is how my agency eats. If your business still runs on a spreadsheet and somebody's memory, bring it over. The first conversation costs you nothing.