Cadence

The Khaeli CRM, one system of record for the agency, with an assistant that never sends.

Head of Engineering · Product design · Sep 2026 to Present · Web

  • Product design
  • Full-stack engineering
  • AI engineering
Cadence overview
  • Product design
  • Full-stack engineering
  • AI engineering

Overview

Cadence is the CRM Khaeli runs on: who our clients are, what we have proposed, what we have invoiced, what has actually been paid, and the follow-ups we owe people. Inside it lives Kairo, an assistant that briefs you before a call, researches companies, drafts costings, quotations and follow-up emails, and finds new leads on a schedule.

It is an internal product with a product-grade bar, because the brief had a second clause: one day Khaeli should be able to install it for other businesses. I lead it as Head of Engineering and designed the product, building alongside coding agents whose work ships only after review.

The agency in a pocket, installed from the home screen

The Cadence home screen on a phone: due and overdue counts, proposals and active clients tiles, and a getting started checklist over an iOS style tab bar

The Problem

Before this build, Khaeli's commercial life was split across tools that each held a piece of the story. A self-hosted Twenty CRM kept contacts and deals. A separate customer portal app kept projects, invoices and payment chasing, joined to the CRM by nothing stronger than a stored ID. Before those, there was a Frappe CRM fork that never grew past its rebrand. Every system was someone else's shape: deal pipelines and seat pricing language for a business that actually sells engagements, retainers and invoices.

Split data has a compounding cost. You cannot ask "what is going on with this client" and get one answer, you cannot compute cash questions across systems, and you cannot package any of it for a buyer. The deciding constraint, written into the first architecture decision record, was exactly that: a product whose data lives half in a self-hosted CRM and half in a separate app cannot be installed for someone else. Ours can only be worth selling if it is one thing.

Goals and Constraints

  • One system of record. Cadence absorbs the CRM and the portal, and both get decommissioned. Until cutover, migration follows one rule: new work starts here, in-flight projects finish where they are, because clients are long-lived and projects end on their own.
  • Sellable without SaaS plumbing. Each future buyer gets their own instance and database. The schema carries no tenant column at all, and a CI script fails the build if one ever appears. The investment moves from row isolation to provisioning.
  • An assistant that cannot act alone. Kairo proposes, a person applies. It never sends email, never writes a commercial record on its own, and never computes money: figures come from the server with their currency, and the system prompt forbids arithmetic.
  • The team's own language. A glossary defines Client, Proposal, Quotation, Touchpoint, Chase, each with the words we deliberately avoid. Stage is a human judgement; what actually happened is derived from the records.
  • A small team's stack. One Turborepo: Next.js web app, Hono API on Bun, Postgres with Drizzle, deployed on Railway with the deployment graph kept as code.

The Solution

The data model is a short commercial spine. A Client holds Touchpoints, the communications a person logs with the next step they owe. A Proposal links one Google Doc, because proposals are written in Docs and the CRM should hold the link and the value, not a worse word processor. A Costing prices it internally from the Rate Card; a Quotation is the client-facing price; an Invoice tracks what is owed; a Payment records what arrived, confirmed by a reviewer against imported bank evidence, and is never edited, only reversed with a reason.

Kairo sits beside the spine, not on it. Every skill it works to is server-owned and role-gated, every tool call lands in the audit log, and nothing it drafts becomes a record or an email until a named person applies it through the same validated endpoints a form uses.

Cadence data model: the commercial spine from Client to Payment, and Kairo's lane, where every draft passes code checks and a person before it lands on the spineClient: one record, one stage. Proposal: links one Google Doc. Quotation: the client-facing price. Invoice: issued and tracked. Payment: reversed, never edited. Touchpoint: logged by a person. Costing: from the Rate Card. Chase: drafted, sent by a person. Bank transaction: imported evidence. Grounding: CRM records via tools, approved documents, register and web; all treated as untrusted. Kairo drafts: briefs, costings, quotations, emails, background checks; one per answer. Checks in code: cites numbered sources only; money from the server; fit scored by code, not the model. A person reviews: edits, applies or dismisses; a named staff member acts. Audited endpoints: the same validated writes a form makes; every tool call logged. Chase drafting: an overdue invoice becomes a drafted chase in the review queue. Prospecting: sources → dedupe → rule gate → screen → research → rank → draft. THE COMMERCIAL SPINEKAIRO, THE ASSISTANT · PROPOSES, NEVER APPLIESsets the valueoverdueconfirmedby a reviewerwrites land on the spineread toolslead suggestions:accept or dismissClientone record, one stageProposallinks one Google DocQuotationthe client-facing priceInvoiceissued and trackedPaymentreversed, never editedTouchpointlogged by a personCostingfrom the Rate CardChasedrafted, sent by a personBank transactionimported evidenceGroundingReadsCRM records via tools,approved documents,register and web; alltreated as untrustedKairo draftsLLMbriefs, costings,quotations, emails,background checks;one per answerChecks in codecites numbered sourcesonly; money from theserver; fit scored bycode, not the modelA person reviewsedits, applies ordismisses; a namedstaff member actsAudited endpointsthe same validatedwrites a form makes;every tool call loggedChase draftingScheduledan overdue invoice becomes a drafted chase in the review queueProspectingScheduledsources → dedupe → rule gate → screen → research → rank → draftTHE COMMERCIAL SPINE
The commercial spine, and the one gate everything Kairo drafts must pass through.

One page answers: what is going on with this client?

A client record in Cadence with highlights for win probability and outstanding balance, recent activity, proposals and invoices, and a details panel with contacts

Process

I wrote the decisions down instead of settling them in chat: the repo carries more than forty architecture decision records, from "invoices are documents, not integrations" to which AI framework runs which workload. The ADRs that got reversed stay published with their reasoning, including the component library we vendored on a Friday and deleted the following Monday once it started fighting our token system.

The workspace design is measured, not eyeballed. I captured a best-in-class CRM's computed styles and bounding boxes from an authenticated session, wrote the measurements up as a reference system, and built an A/B harness that signs into our running app and scores every contracted element against that reference within two pixels and exact color. Composition and density come from the benchmark; the type, the indigo and the domain language stay ours. A motion audit followed the same discipline: 111 findings, each one traced to a numbered plan or an explicit rejection.

The team runs the product while it is built. Staging carries a UAT feedback button that files bugs with the page and device attached, and every release ships an in-app changelog note in plain language.

The What's new dialog in Cadence listing recent changes with New and Improved tags, over a dimmed dashboard
Release notes live in the product, written for the people who use it.

Key Features

Kairo researches like a careful junior, and shows its work. Ask for a background check and it reads the client record, checks the company register, searches the web, reads touchpoints and payment history, and writes a cited snapshot with a fit verdict computed by code, not vibes. When the evidence is thin, it says so instead of inventing a score. Model vendors hide behind a ghost name, so swapping providers never becomes a user-facing event.

A Kairo chat running a background check on a seeded demo client, with its nine tool steps expanded and a background check card concluding the evidence is insufficient to score
A background check on seeded demo data: nine tool steps, cited findings, and an honest 'insufficient' verdict.
Anatomy of a Kairo answer: a gated request becomes a durable run, a budgeted agent loop calls typed tools including full-text retrieval, code checks the answer, and a person applies the draft; evals replay the world against live modelsRequest gate: staff only; skill chosen, role-gated: refused, never downgraded; rate-limited. Durable run: queued job, one per person; steps streamed to the page; cancellable and retried. Model behind a ghost tier: vendor never exposed; chat and agent tiers; steps, time and tokens capped by the skill. Retrieval (RAG): only approved documents, full-text ranked, top 8 chunks in a context budget; no embeddings or vectors. CRM read tools: clients, touchpoints, proposals, payment history, rate card. Research tools: company register, web search, page fetch: SSRF-guarded, capped. Pricing tools: costing and quotation totals from domain code, one currency, never mixed. Propose tools: capture one draft artifact per answer; never save, never send. Answer checks: citations must use backend-numbered sources; unknown → rejected cited docs rechecked for approval and revision; changed → blocked fit score and band computed by code from per-criterion evidence. Persist and audit: run steps, token usage and counters on the run row; every tool call audited. A person applies: through the same validated endpoints a form uses; emails land in their drafts. Cases: background checks, costings, discovery, email, screening. Replayed world: register, websites and search served from fixtures. Live model calls: metered tokens and cost; three trials per case. Graders: expected band, an artifact or not, amounts grounded. Model choice: picked per tier on pass rate and cost; not yet wired to CI. No embeddings and no fine-tuning: approved documents are retrieved by full-text search and cited by number, never trained on.AGENT LOOP · BUDGETED PER SKILLEVALS · LIVE MODELS, REPLAYED WORLDtyped calls, strict schemasRequest gateCodestaff only; skill chosen,role-gated: refused, neverdowngraded; rate-limitedDurable runJobqueued job, one per person;steps streamed to the page;cancellable and retriedModel behind a ghost tierThird-partyvendor never exposed; chat and agent tiers;steps, time and tokens capped by the skillRetrieval (RAG)Full-textonly approved documents,full-text ranked, top 8chunks in a context budget;no embeddings or vectorsCRM read toolsclients, touchpoints,proposals, paymenthistory, rate cardResearch toolscompany register, websearch, page fetch:SSRF-guarded, cappedPricing toolsCodecosting and quotationtotals from domain code,one currency, never mixedPropose toolscapture one draft artifact per answer; never save, never sendAnswer checksCodecitations must use backend-numbered sources; unknown → rejectedcited docs rechecked for approval and revision; changed → blockedfit score and band computed by code from per-criterion evidencePersist and auditrun steps, token usage andcounters on the run row;every tool call auditedA person appliesthrough the same validatedendpoints a form uses;emails land in their draftsCasesbackground checks,costings, discovery,email, screeningReplayed worldregister, websitesand search servedfrom fixturesLive model callsmetered tokens andcost; three trialsper caseGradersCodeexpected band, anartifact or not,amounts groundedModel choicepicked per tier onpass rate and cost;not yet wired to CINo embeddings and no fine-tuning: approved documents are retrieved by full-text search and cited by number, never trained on.
One Kairo answer: a gated, durable run; a budgeted agent loop over typed tools; code checks before a person applies anything. Evals replay the outside world against live models.

Reports are saved questions. Any staff member assembles a page of Insights: a data source, filters, a metric, a group-by, drawn as numbers, bars, pies or lines. Money is always one stated currency per figure, never silently combined.

A Sales health report in Cadence with a proposal pipeline bar chart by status and a cash collected by month chart
A report: saved Insights over live records.
The insight editor with payments as the data source, sum of amount grouped by month, filters for reversed and currency, and a live bar chart preview
The insight editor, filters to chart in one screen.

Chasing money is drafted, never fired. An overdue invoice drafts its follow-up into a review queue. A person edits, sends from their own mail, and records it; nothing in the queue emails anyone by itself.

A drafted chase in Cadence for an overdue invoice on a fictional client, with options to edit the draft, dismiss it, or record it as already sent
A drafted chase waits for a person. The demo client and invoice are seeded data.

The phone build is a product, not a squeeze. Cadence installs as a PWA, opens full screen with an iOS style tab bar and liquid-glass chrome, and puts today's due follow-ups as a badge on the icon.

Records built for thumbs

The clients list on a phone with stage chips and win probability meters on fictional demo companies

Log the call before you forget it

A client record on a phone with quick actions to log a touchpoint, create a proposal or invoice, and ask Kairo

What you owe people, in one list

The follow-ups list on a phone showing the next step owed to each fictional client

Outcome

Cadence is real and in internal use at Khaeli: staging and production environments deploy from main on Railway, and the team runs structured UAT on staging with the in-app feedback channel. The Twenty instance and the customer portal it replaces are still being wound down under the migration rule, so I will not claim the cutover is done. No external customer runs it yet; that is the ambition the architecture is holding a seat for, not a result.

What I can say is that the properties it was designed around have held: every record a client's money depends on lives in one Postgres schema, and nothing Kairo drafts, costings, quotations, briefs or chases, becomes a sent email or a saved record without a person's name on the action. That gate is code, not policy.

The launch film, cut from the product running on a seeded demo workspace.

Tools used

Next.js, Hono, Bun, Postgres, Drizzle, Railway