Repository navigation
One box, on autopilot, with a funnel to watch it - #31
Merged
Merged
Conversation
Four things were true of this product before this change: the front door
asked for a GitHub handle, there was no way to describe a market, nothing
it ever wrote could actually be sent, and there was no view of where any
lead stood. This addresses all four.
**The front door.** `POST /api/v1/campaigns` takes one line of text and
works out what it got. `acme.com` is crawled directly; "dental practices
in Austin" goes to a new `discover_domains` job, which asks the model for
real companies and queues a crawl for each. Nothing the model names is
trusted — every domain has to survive being fetched, so an invented
company simply drops out. The GitHub-handle form is gone from the UI.
GitHub remains a signal source, which is where it belonged.
**Sending.** `POST /actions/:id/execute` only ever recorded that a send
had happened somewhere else; nothing left the building. `runAutopilot`
is the executor. It re-checks policy from live state rather than from the
snapshot on the recommendation, holds to `trusted_automation` being
opt-in per campaign, refuses a draft that failed its quality gates, obeys
a daily cap, and sets reply-to so answers reach the customer — the one
part a human still does.
Three gaps had to close before any of that could work, each found by
running it against real sites rather than by reading the code:
- Nothing anywhere stored an address. The site provider now reads
`mailto:` links and prose, tells a personal address from a role one,
and keeps them apart: a shared inbox belongs to the company and is
marked as such at send time.
- The recommendation engine refuses an outbound action with no trigger
signal, and only GitHub produced signals — so everyone found by
crawling reached "scored" and stopped. A published team page stating
someone's role is grounded, citable evidence, and is now stored.
- Crawled people had identity confidence 0 against a 0.85 threshold,
so policy denied every send. A company naming its own staff on its
own site is first-party evidence and is now recorded as an identity.
**Notifications.** A lead alert the moment someone qualifies, one per
person, and a daily digest — sent even on a quiet day, because silence is
indistinguishable from the product being broken, which is the failure
this whole change exists to fix. Both are idempotent through a unique
index rather than through careful bookkeeping.
**The funnel.** `campaign_people.status` keeps only the present, so it
could answer where a lead is and nothing else. `lead_stage_events` records
every transition, and the sixteen engineering states collapse into six
stages a salesperson recognises. `/funnel` draws the stage bars and a
sideways per-lead timeline where width is time spent, so a stall is
visibly wide.
Verified end to end against real sites: "dental practices in Austin"
produced 25 real practices, crawled them, found named dentists, captured
their published inboxes, wrote grounded drafts, and attempted sends that
failed only on a deliberately invalid API key.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What was wrong
Four things were true of OutreachGraph before this:
POST /actions/:id/executerecorded that a send had happened elsewhere; it never sent anything. Every run ended at a card a human had to copy out by hand.What this does
One front door.
POST /api/v1/campaignstakes one line and classifies it deterministically. A domain is crawled; a phrase goes to a newdiscover_domainsjob that asks the model for real companies and queues a crawl each. Nothing the model says is trusted — every domain must survive being fetched, so invented companies drop out on their own. The GitHub intake is removed from the UI; GitHub stays a signal source, per the non-negotiable.Autopilot.
runAutopilotis the executor that actually puts mail on the wire. It rides the existingtrusted_automationapproval mode, so no non-negotiable is bent:trusted_automationstays opt-in per campaign, andisExecutable(decision, false)means only a plainallowsendsreply-topoints at the customer, so replies — the one thing a human does — reach themThree gaps found by running it, not reading it. Each independently made the whole thing produce nothing:
mailto:/prose, separates personal from role addressesNotifications. An instant lead alert (one per person, ever) and a daily digest — sent even on a quiet day, because silence is indistinguishable from a broken product. Both idempotent via a unique index.
The funnel.
lead_stage_eventsrecords every transition; the 16 engineering states collapse to 6 stages a salesperson recognises./funnelshows stage bars with conversion and a sideways per-lead timeline where width is time spent, so a stall is visibly wide.Verification
bun run check— format, typecheck, 624 tests pass, 0 fail (61 new).Run live against real sites: "dental practices in Austin Texas" → 25 real practices discovered → crawled → named dentists found at confidence 0.9 → published inboxes captured → grounded drafts written ("Hi Dr. Mulgrew, I saw 'Dr. Blake Mulgrew — Dentist' listed for Circle C Dental…") → sends attempted, failing only on a deliberately invalid Resend key. Nothing was delivered to anyone.
Note before merging
Migrations apply at boot on merge to
main.0009_autopilot.sqladdsworkspace_settings,notifications,lead_stage_events, and columns oncampaigns/companies.Autopilot only sends where a mailer is configured;
RESEND_API_KEY/EMAIL_FROMare already set in Railway. Campaigns created withautopilot: truewill send real email to real people once merged — the daily cap (default 25) and the per-campaign toggle are the controls.🤖 Generated with Claude Code