Overview
A placement-to-bind pipeline for insurance agencies. A quote PDF goes in, Claude extracts the policy fields, an operator verifies every one, the insured e-signs, and a bind request goes to the underwriter. The rule underneath is structural: AI drafts, but a human verification step is mechanically required before anything binds coverage, so the model can never commit an agency on its own. 1,129 commits across 328 PRs over 26 active days, now live with a pilot agency.
Project Design
Claude Code agents built it across roughly 35 parallel git worktrees: one branch and one PR per agent, enforced by a pre-tool hook that blocks branch-switching. Two things were load-bearing from day one: monitoring, and an automated check suite that blocks known failure classes in CI rather than trusting a rule written in a doc. That bias toward structural gates over advisory rules is the throughline of the whole build.
Key modules
Extraction
Claude reads the quote PDF and returns structured policy fields plus the anchor text printed beside every signable field.
Anchor resolver
Deterministically matches detected anchor text to the PDF's text layer and computes each field's coordinates.
Form fingerprinting
Keys operator-confirmed placements to a form's number/edition (plus a MinHash for numberless forms) so recurring templates come back pre-placed.
PDF kit
pdf.js text extraction, zoom-independent coordinate math, and pdf-lib assembly of the final bind package.
Bind dispatch
Assembles the bind package and routes the request to the underwriter once a human has verified every field.
Email relay
Fail-closed outbound over M365 Graph / Gmail behind a classified-status gate — unknown outcomes count as failures.
Key features
Form fingerprinting — placement that learns
Signature placement was the agents' biggest friction point, so it got the most attention. Insurance runs on standardized forms — the same ACORD and carrier templates recur constantly — so ReadySetBind treats placement as a learning problem, not a per-document chore. On upload, Claude detects each signable field and returns the verbatim anchor text printed beside it; a deterministic resolver matches that text against the PDF's text layer and computes the field's coordinates. When an agent nudges a field in the drag-and-drop editor, the corrected placement is saved against a fingerprint of the form — its printed form number and edition, plus a MinHash (a compact similarity signature) of the static label text for forms that carry no number. The next time a matching form comes through — confirmed by a similarity check so a genuinely revised edition is never reused blindly — the operator-approved placement is applied automatically and the document arrives pre-placed. One correction pays off on every future document built from that template.
Security & ops decisions
- Every sensitive database function is deny-by-default: execute rights are revoked from public, anonymous, and authenticated roles — and the tests assert all three stay revoked.
- Email failures route through a fail-closed classifier — anything unknown counts as failed — and a CI rule blocks any new email call that skips the classification.
- One audited function is the single source of tenant access for every row-level security policy — the database filters rows per user — with explicit deny-all policies on tables users should never touch.
- Telemetry pseudonymizes user IDs with a vaulted key, session replay is consent-gated, surveys are choice-only, and error reports are scrubbed before they leave the building.
- Every new API route, edge function, or policy change passes a standing authorization-review gate plus a dedicated security-reviewer agent before merge.
Builder notes
- ~35 parallel agent worktrees, with a pre-tool hook that blocks branch switching — one branch, one worktree, one PR per agent.
- The first 17 days produced 19 edge functions, 81 migrations, ~60k lines of app TypeScript plus ~20k of edge-function code, and 127 test files.
- The telemetry schema is fail-closed: a no-free-string type union means unregistered analytics properties cannot be sent at all.
- Restoring typed database clients surfaced 18 latent type bugs that had accumulated in eight days of untyped operation — the old workaround is now documented as an anti-pattern.
Lessons learned
- The same email bug shipped three times past written rules and zero times past an automated check. Prose documents intent; a gate enforces it.
- A platform's defaults are part of your attack surface. My own lockdown left a verification function callable by anonymous clients for three days, because revoking execute from PUBLIC doesn't touch the separate anon role Supabase grants by default. For those three days the lockdown was strictly worse than the hole it had closed.
- “Works locally” says almost nothing about production. One feature failed four separate ways that existed only on the server, each invisible until the previous one was fixed. Compile-green, deploy-broken.
- Don't rebuild infrastructure you could rent. The worst bug of the build lived entirely inside a custom email layer that shouldn't have existed.
What carried forward
Every third-party wrapper now returns an explicit status the caller has to branch on, never a bare boolean. Recurring defects earn an automated gate, not a third paragraph of documentation. And every sensitive database function is denied by default and tested against all three Postgres roles, not just the one that bit me.
Posts from this project
Compile-green, deploy-broken
Every test passed and it worked on my laptop. In production, placing PDF signature fields failed four different ways — pdf.js in a serverless runtime — each one invisible until the previous fix.
The email that shipped three times
I let AI agents build my own email plumbing on top of Resend. The same bug — marking undelivered mail as “sent” — shipped three times before a build gate finally stopped it.
The REVOKE that didn't
I locked a sensitive database function, verified the lock, and a Supabase default grant to the anonymous role left it open to the internet for three days.