ethanplusai/harvey
Autonomous AI sales agent powered by Claude Code or Codex. Finds prospects, writes cold emails, sends campaigns, handles replies, books meetings.
About ethanplusai/harvey
ethanplusai/harvey is an open-source project on GitHub, mainly written in Python. Autonomous AI sales agent powered by Claude Code or Codex. Finds prospects, writes cold emails, sends campaigns, handles replies, books meetings. It currently holds 3,343 stars and 673 forks with 31 open issues, and was last pushed on 2026-10-27 (repository created 2026-03-12).
Project Overview
Git Homed tracks it on the Today's Trending board, currently at rank #35 with 248 new stars today.
GitHub Repository Details
README
Harvey
An autonomous sales agent that runs on your Claude Code or Codex subscription.
Harvey finds businesses worth selling to, learns something specific about each one, writes cold email that references it, sends it, reads the replies, and works the conversation toward a meeting. It runs on your machine, in a loop, on its own.
The unusual part: there is no model API bill. Harvey uses your Claude Pro or Max subscription through Claude Code, or your ChatGPT subscription through Codex. Model calls use the plan you already pay for, without an API key or per-token API billing. Claude is the default; Codex is an opt-in provider on macOS and Linux.
$ harvey run
============================================================
Harvey is online. Always Be Closing.
============================================================
Checking pipeline state...
Decision: handle_replies (2 new replies)
handler → Elena Park, Harbor Dental Studio interested, answer drafted
handler → Priya Nair, Highline Roofing objection, answer drafted
Decision: write_campaign (3 new prospects with no drafts)
writer → 2-email sequence: Phoenix HVAC hiring a dispatcher
outbox → 5 emails waiting for your approval
Cycle complete. Sleeping for 15 minutes.
And the dashboard it keeps, at localhost:5555. In this order: the Today screen, a
conversation Harvey handed back because a prospect asked for a contract, approving
a drafted reply from the outbox, confirming a signal, building a cohort from
confirmed signals, searching contacts, and the same screen in dark mode:
The demo data is invented: "Bookwell" selling online booking to roofers, dental practices and HVAC shops, on .test domains.
---
Table of contents
- Why the subscription thing matters
- What Harvey actually does
- Quick start
- You confirm what it looks for
- The prospecting pipeline
- The skills library
- The sub-agents
- Nothing sends without you
- The dashboard
- How the data is stored
- Configuration
- Using a ChatGPT plan instead (Codex)
- Legal and deliverability
- Troubleshooting
- Project structure
- Philosophy
- Roadmap
Why the subscription thing matters
Every other autonomous sales agent bills you per token. That is the whole reason they cost what they cost: an agent that thinks in a loop, all day, is an agent that burns API credits in a loop, all day. Vendors solve this by thinking less — shorter prompts, cheaper models, fewer passes.
Harvey uses the subscription-backed CLI for the provider you choose: claude for Claude Code, or an isolated codex app-server for Codex. Both return text with model tools disabled. Harvey handles the prospecting and mail operations itself.
Claude calls run with none of your own Claude Code setup: no CLAUDE.md (yours, the project's or any parent folder's), no hooks, skills, plugins, MCP servers or settings files, no tools, in an empty temporary folder, and nothing saved to your Claude transcripts. Your login (subscription or claude setup-token) and any admin-managed policy still apply. This needs a Claude CLI with --safe-mode; with an older one Harvey pauses instead of sending prompts unisolated and says to run claude update. To check this offline against your installed CLI, with no credentials or network: HARVEY_CLAUDE_CLI_TESTS=1 pytest tests/test_claude_cli_isolation.py.
Your selected subscription covers model calls, subject to its usage limits. That lets Harvey use its skills library for personalization and reply handling without a separate model API bill.
What this costs you:
| | | |---|---| | Claude Pro/Max or a ChatGPT plan with Codex access | your existing subscription | | Finding businesses | $0 on the default source, or ~$0.37 per 1,000 on the cheapest paid one | | Reading their websites | $0 — three HTTP requests each, no model call | | Writing and sending | your subscription + a mailbox (~$7/mo Google Workspace) | | Prospecting tools | none. No Apollo, no ZoomInfo, no Clearbit, no Clay. |
For comparison, the tools Harvey replaces start at $250–500/month — and they are weakest precisely where Harvey is strongest: small local businesses with 5–50 people, where single-provider contact coverage caps out around 30% and micro-businesses are frequently absent entirely.
Harvey also budgets itself. It reads the active provider's subscription quota and throttles to leave headroom for your own Claude or Codex work. Set max_daily_claude_percent: 80 for either provider—the setting's name predates Codex support. If quota cannot be read, Harvey reports it as unavailable and falls back to its daily call budget.
Harvey runs locally with your own login. Check the terms and usage limits of the provider you choose.
---
What Harvey actually does
Harvey runs a heartbeat: wake up, check the budget, decide what most needs doing, do it, log it, sleep. Every 15 minutes by default.
┌──────────────────────────────────────┐
↓ │
Wake → Quiet hours? → Budget OK? → Decide → Act → Log → Sleep
│ │
(sleep) (sleep)
It decides deterministically, not by asking a model what to do — that would burn a call on something derivable from four counts:
handle replies > send campaigns > write campaigns > prospect > idle (analyze)
Profiling and outbox draining ride along on every cycle regardless, because they cost nothing and have to happen on schedule.
---
Quick start
Prerequisites
- Python 3.11+ — check with
python3 --version. macOS ships 3.9; if that's what you have,brew install [email protected]first. - An active Claude Pro or Max subscription, with the CLI logged in (
claude login) — or, opt-in, a ChatGPT plan with the Codex CLI (see below) - A mailbox to send from — Gmail/Workspace recommended, on a dedicated secondary domain. Microsoft 365 works too (setup)
- A warmup service on that mailbox — we recommend TrulyInbox. It warms a new mailbox before cold email, keeps it out of spam after, and Harvey reads its inbox rate to tell you when you're ready (Warmup)
Easiest: let Claude set it up
git clone https://github.com/ethanplusai/harvey.git
cd harvey
claude
Then say: "set up Harvey for me". The repo ships a CLAUDE.md that turns Claude Code into the setup wizard — it checks what state you're in, installs what's missing, asks what it needs, and trains Harvey on your product.
Or do it yourself
# 1. Install — note python3.13, not python3. On macOS bare python3 is the
system 3.9, and a venv built from it can't run Harvey.
python3.13 -m venv .venv && source .venv/bin/activate && pip install -e .
2. Configure — every variable is documented inline with where to get it
cp .env.example .env && $EDITOR .env
3. Learn your product from your website (writes harvey.local.yaml; re-running
it updates the product, targeting and company and keeps your other settings)
harvey train https://your-company.com
4. Choose what makes a good prospect (see below — this one matters)
harvey signals --confirm free
5. Find businesses. Free source, no account needed.
harvey discover
6. Watch it work
harvey dashboard # prints a private link to http://127.0.0.1:5555
harvey run
Or deploy with Docker (Linux)
Docker Compose runs the dashboard; start and stop the heartbeat from its
Actions card. Host networking preserves the dashboard's loopback-only bind at
127.0.0.1:5555. This Compose setup supports native Linux; use the pip install
above on macOS or Windows. On a remote Linux host, reach the dashboard through
an SSH tunnel rather than exposing its port to the network.
git clone https://github.com/ethanplusai/harvey.git
cd harvey
Create the credentials file only if you do not already have one.
test -e .env || cp .env.example .env
Run as your Linux user, never root. Set these again when rebuilding.
export HARVEY_UID=$(id -u) HARVEY_GID=$(id -g)
Sign in to Claude on the host first; ~/.claude must already exist.
The image ships Claude only; Codex is not included.
docker compose build
docker compose run --rm harvey setup
docker compose up -d
docker compose logs harvey
Open the private unlock link printed in the logs.
Compose mounts the checkout directory at /app. Setup credentials, both YAML
config files, training output, knowledge files, and data/ remain in that
checkout when containers are recreated. Directory mounting also permits atomic
configuration saves; do not replace it with individual config-file mounts.
Harvey loads .env directly. Restart the container after editing credentials.
The checkout and Claude auth directory must be writable by the selected user.
To train later, use docker compose run --rm harvey train ;
harvey.local.yaml is saved directly to the checkout. Back up this file, .env,
skills/, and data/ before moving installations. The running code also comes
from the checkout: restart after updating it, and rebuild when dependencies or
the Dockerfile change. The build context excludes local data and credentials.
The dashboard listens on 5555. If you change the port in the Compose command,
change the Dockerfile health-check port too and rebuild. Stop the service with
docker compose down; its files remain in the checkout. Use a host install if
you need the Codex provider.
---
You confirm what it looks for
This is the part that makes Harvey different from a black box, and it is deliberately not skippable.
Harvey knows how to collect 29 signals about a business. It does not collect any of them until you say so. On first run it proposes the catalog; you confirm, skip, or reject each one in the dashboard or from the terminal:
harvey signals # review the catalog
harvey signals --confirm free # everything that costs nothing (26 of them)
harvey signals --confirm SERP_RANK # a paid one, opted into explicitly
Every signal shows what it is in plain language, what it costs, and how many companies already carry it.
| | Signal | What it tells you |
|---|---|---|
| Discovery | SERP_RANK | Where they rank for their own core search, stored with the search phrase. Positions 11–30 are the sweet spot: visible enough to be trying, and losing. |
| | IN_LOCAL_PACK / MAPS_RANK | Whether they hold one of the three map slots above the results, and where they sit in Google Maps. |
| | NO_WEBSITE | They have a listing and a phone and no site. If you sell websites, the highest-intent signal that exists. |
| | UNCLAIMED_LISTING | Nobody has claimed their Google Business Profile. |
| | REVIEW_RATING / REVIEW_COUNT | Good but invisible, or visible but struggling. |
| Profile | INCUMBENT_AGENCY | Who currently has the account, credited in their own footer. Every detection builds a roster you can target as a whole book of business. |
| | RUNNING_GOOGLE_ADS / RUNNING_META_ADS | They are spending on acquisition today. The strongest budget signal available for free. |
| | NO_ONLINE_BOOKING | Leads have to phone in, so after-hours demand is lost. |
| | NO_SCHEMA_MARKUP | A concrete, checkable SEO gap you can name in an email. |
| | BLOG_STALE | Content marketing started and abandoned. |
| | SITE_PAGE_COUNT, TECH_STACK, BLOCKS_AI_CRAWLERS | Size, platform, AI-search visibility. |
| | HIRING_ROLE / OPEN_ROLES | What they're hiring for, and how many roles are open. When their site links a Greenhouse, Lever, Ashby or Workable board, Harvey reads that board's public feed (one request): roles matching icp.hiring_signals (else icp.titles), with department, location, posting date and the posting's URL; when the board lists none, it records "not hiring" so a closed role stops counting. Otherwise it reads the careers page. Set hiring_signals to the roles that mean they're buying: the titles fallback names who you email, so "CEO" only matches a CEO opening (never "Assistant to the CEO"), which is rarely the signal you want. |
| | YC_BACKED, ACCELERATOR, FUNDING_ROUND | A startup, as its own site says: YC or another accelerator (with the batch or cohort), or a raise it announced (round, amount, date). |
| People | CONTACT_FOUND, DECISION_MAKER_TITLE, LIKELY_OWNER, REGISTRY_VERIFIED | Who decides, and whether they own the place. |
| Contact | EMAIL_PATTERN, EMAIL_STATUS, CONTACT_FORM_URL | Whether you can actually reach them. |
Then a cohort is a query, not a list
Once signals are confirmed, the dashboard's cohort builder turns them into a target set — pick what a good prospect must have, what disqualifies them, and a range for numeric signals (organic rank 11 to 30), and it counts the matches live:
Has a marketing agency AND running Google Ads AND NOT already has online booking
→ 23 companies
That is a campaign with a reason behind it. You know exactly why each of those 23 is on the list, and so does the email Harvey writes them.
---
The prospecting pipeline
Four stages. Each one runs independently, re-runs safely, and states its cost.
1. DISCOVER — who exists
The only stage that spends money, so it always estimates first and never calls anything until you press go.
harvey discover --providers # the menu, with real prices
harvey discover --estimate # projected spend, then exits
harvey discover # go
| Source | Cost | Free tier | Best for | |---|---|---|---| | OpenStreetMap (default) | free | unlimited, no account | Trying the whole pipeline before paying anyone. Coverage is thin for businesses without a storefront: OSM maps premises, so whole trades are barely present (under 2,000 roofers and under 2,300 plumbers in the entire US). Harvey says so in the UI rather than quietly under-delivering. | | TinyFish | free | 30 searches a minute, 500 an hour, no card | Finding businesses by phrase (including the startup and hiring presets) at no cost. Records no search rank: its ordering isn't Google's and it takes no location, so use Serper or DataForSEO SERP for rank signals. | | DataForSEO Business Listings | $0.372 / 1,000 | $1 credit | Local trades, clinics, contractors. Phone, domain, rating, claimed status — and it can filter for businesses with no website at all. | | DataForSEO SERP | $0.006 / search at depth 30 (Live, $0.002 per 10 results) | $1 credit + free sandbox | Organic rank as the buying signal, plus who holds the local 3-pack, searched from the city centre. | | DataForSEO Google Maps | $0.002 / search (up to 100 results) | $1 credit + free sandbox | Map rank, rating, category and claimed status, including businesses with no website. | | Serper | ~$0.30–1.00 / 1,000 | 2,500 free, no card | The easiest paid one to try. | | Scavio Google Maps | $0.01 / page (up to 20 listings) | 50 credits, no card | Google Maps listings around each city: phone, website, category, rating and reviews, including businesses with no website. | | Scavio Google Search | ~$0.03 / depth-30 search | same 50 credits | Rank as the signal, on the same key as Scavio Google Maps. |
Selling to startups? Harvey has a built-in startup skill, made of three parts:
- The searches (each a web search, so Serper or DataForSEO SERP):
yc (YC startups) | Companies whose own site says they're backed by Y Combinator | YC_BACKED, with the batch (W24, S23) |
| accelerator (Accelerator startups) | Ones backed by Techstars, 500 Global, Antler, Entrepreneur First, South Park Commons, Neo, AI Grant, a16z speedrun or Plug and Play (two searches per industry; the signal also reads Pioneer) | ACCELERATOR, with the program and cohort ("Techstars 2024") |
| funding (Recently funded) | Companies announcing their own raise ("we raised", "announces $X Series A") in the last 18 months, news sites and funding databases excluded | FUNDING_ROUND: round, amount and date |
harvey discover --preset accelerator --provider serper, or the Look for
menu on the Discover tab.
- The signals: turn them on in the Signals tab. They read the homepage and
- The writing: the writer and reply handler use
skills/selling_to_startups.md, a playbook for writing to founders with a
section on companies that just raised, for YC- and accelerator-backed
companies and for raises dated within the last 18 months. An older or
undated raise alone doesn't bring the playbook, and is never written to as
news.
Harvey never reads ycombinator.com, the accelerators' directories, Crunchbase, PitchBook or LinkedIn, whose terms forbid scraping them; everything comes from the companies' own sites and your search provider.
Looking for companies hiring a role? `harvey discover --preset hiring
--provider serper` (or Look for: Companies hiring for your roles) searches
your industries for companies whose own careers pages list one of your
icp.hiring_signals roles (else icp.titles). Hosted boards and job sites
are left out of the search, since a board page names a board, not a website
to profile. Confirm HIRING_ROLE and profiling reads the board their site links
to and records the role; the search hit alone records nothing.
Cities geocode themselves (cached forever, one lookup each). Directories, aggregators and institutions are filtered by pattern, not a blocklist — en.wikipedia.org and biz.yelp.com never make it in.
Depth 20–30, not 100. Google removed 100-results-per-page in September 2025, so depth 100 is now billed as ten pages nearly everywhere. Below rank 30 it is mostly directories anyway.
2. PROFILE — what they are
Free. Three HTTP requests per business. No browser, no model call. This is the highest value-per-effort stage in the whole system, because it produces the facts that make an email specific.
Website requests from Scout, Profile, and the local training crawler are limited to public HTTP(S) destinations. Every DNS answer and redirect is checked; local, private, link-local, and metadata addresses are rejected. Connections use a validated IP while preserving the website's TLS identity. These website clients ignore environment proxies, so proxy routing cannot bypass that boundary.
Homepage, robots.txt, sitemap.xml, plus the team and careers pages when they exist — then regex over HTML already in hand. Out comes the incumbent agency, the ad pixels, the missing schema, the abandoned blog, the open roles, the named humans.
Agency detection is the compounding one. Almost every agency credits itself in the footer, and Harvey scores the wording: "Powered by" / "Website by" → 0.9 confidence; a bare descriptive link → 0.75; an unlabelled link → 0.35 and not recorded. A wrong incumbent in an email is worse than saying nothing.
3. ENRICH — who decides
The owner, from the company's own site. Harvey reads the homepage and follows the site's own menu to the pages where people are named — team, about, our story, meet the dentists, attorneys, contact — six pages at most, skipping links that land back on the homepage. On those pages it reads:
- cards and lists: a name with a title under it, even when the title is only a
away inside a span; - sentences: "owned and operated by Gary Pruitt", "led today by President Ilan Brooks", "Ines Varga started Countwise";
- schema.org: the business's own
founderandemployeeentries (a blog post's author is not staff); - the name on the door: when two people both have owner titles, the one whose surname is in the business name comes first. A surname alone never makes anyone the owner: Linda Smith, Office Manager, at Smith & Sons isn't one.
LIKELY_OWNER confirmed, each owner is recorded as an observation with the evidence that says so.
When a company's site names nobody, or names an owner whose address is only a guess, and Harvey has no contact there yet — common for small trades — it also uses a general address the company prints on its own pages (only general mailboxes such as owner@, office@, info@ or hello@, never a person's own address). Harvey reads whole pages for it, including schema.org and addresses hidden from scrapers, and accepts an address on another domain only when that domain leads to the company's own site. It is never guessed, it is verified like any other address, and those emails greet the company's team instead of a first name.
A site behind a bot challenge (SiteGround's "sgcaptcha", Cloudflare's "Just a moment…") is reported as challenged, not unreachable, and isn't retried.
4. VERIFY — can you reach them
Harvey learns each company's address pattern and verifies one candidate, rather than brute-forcing name variations. Any address the site publishes can teach it: nvale@ next to Nora Vale is {first initial}{last}, and j.smith@ is an initial and surname. A lone dotless address no named person owns (kmorrow@: Kmorrow, or K. Morrow?) is ambiguous and teaches nothing. An address two named people could own (jsmith@ when John and Jane Smith both work there) is format evidence only, and Harvey never guesses or verifies an address another named person could own: a verifier would confirm their mailbox. It decodes the usual hiding tricks (Cloudflare email protection, a mail link's data-user/data-domain attributes, name [at] domain [dot] com) and never invents an address from ordinary text. A person's own published address is used as is. With no evidence at all, it guesses first.last@, or first@ when something besides the website says the company is small (your ICP's company size, or a local