Skip to content
OPENVZ*AI
OPENVZ / LEADS

You write one sentence. It runs the rest — and stops in the middle for you.

Prospecting tools sell you a list. The AI ones sell you a list that emails itself. Both skip the part that decides whether any of it works: what this company is actually about, whether they are worth talking to, what the first sentence should say — and what to do once they answer. This runs the whole chain. Find, research, qualify, personalise, reach, follow up, manage. And it stops at the one step that should have a person in it.

MIT · v1.3.0 · macOS 23.1 MB (Apple silicon) · Windows 20.3 MB (x64) · runs on your Claude subscription · or swap in OpenAI / DeepSeek

It starts with a sentence

No form to fill in first. Say who you are after the way you would say it to a colleague, and it becomes a structured ICP — along with a list of everything it filled in that you never said. Below is literal terminal output, unedited.

$ openvz-leads target "Find dental clinics in California
                       with outdated websites and 5-50 employees"

  Dental practices in California with 5-50 staff whose website
  looks neglected.

  Industries       dental clinic
  Company size     5-50 employees
  Geography        California
  Titles           Owner, Practice Manager, Office Manager
  Must also match  outdated website

  Confidence: high (parsed by model)

  Filled in for you — you did not say these:
    · You did not name job titles — I inferred who decides at a
      practice this size.
    · "Outdated website" is checked during the account analysis,
      not during search: no search engine can filter on it.

  Save this as your ICP? [Y/n]

Those two "you did not say" lines are the only valuable part of this feature. A location is never guessed: a country invented on your behalf sends the whole run somewhere you never asked about, and nothing in the results would reveal it. Titles are the one field it may infer, because almost nobody names them and searching without any finds receptionists — and even then it has to say that it did. A qualifier like "outdated website", "recently funded" or "hiring" is neither an industry nor a size, so a four-field parse drops it and hands back clinics perfectly happy with their website. Here it survives: it loosens the search queries, and it becomes a criterion the account analysis has to check. The dashboard has the same thing with a text box.

One pipeline, eight steps

Not "here are some email addresses". A sentence in, and out the other end a record you can mark won or lost. Who does each step is on the right — one of them is you.

  1. 01

    Parse the ask

    A sentence becomes a structured ICP: industry, size, geography, titles, and the qualifiers those four fields cannot hold. With no model reachable a rule-based parser takes over — rougher, and it says so, but the box still does something when you type in it.

    ICP parser
  2. 02

    Find the companies

    Searches against the ICP, finds their sites, digs out decision-makers, infers the email pattern and verifies it over SMTP. No Apollo, no ZoomInfo — those databases were built this way too, with a subscription added in the middle.

    Scout
  3. 03

    Research | read their site

    Three tiers, cheapest first. Plain HTTP always works. With Crawl4AI installed, JavaScript renders and the page comes back as Markdown, so the model reads a document instead of de-tagged soup. Only a consent wall or a page that exists behind a click is worth waking Browser Use and a real browser for.

    Crawler
  4. 04

    Qualify | are they worth it

    One brief per account: what they do, why they fit (1–10 with reasons), buying signals, likely pains, who signs and who blocks — and each of your extra criteria checked against the evidence: confirmed, contradicted, or unknown. Contradicted caps the score. Unknown is written down as a gap rather than guessed either way.

    Profiler
  5. 05

    Personalise | draft the outreach

    A three-email sequence built on that brief, following proven cold-email frameworks under one hard rule: invent nothing. The brief's "do not say" list is handed to the writer, which is told to respect it.

    Writer
  6. 06

    Approve | you say yes

    The finished campaign lands in a review queue, and nothing leaves before you approve it. This is not a switch you turn off in passing — turning it off means deliberately editing a setting called require_approval.

    You
  7. 07

    Reach & follow up

    Sent from your own Gmail — no platform in the middle, and nobody else holding the list. Follow-ups are real threads (In-Reply-To points at the previous message), so they arrive as one conversation rather than three unrelated cold emails. Under a daily cap and quiet hours. It stops the moment they speak: a clear no is recorded as lost, and opt-out wording of any kind is treated as immediate and permanent.

    Sender / Handler
  8. 08

    Manage | to won or lost

    Contacted → replied → meeting → won or lost. Every move carries its time, its reason and who made it, and goes to your CRM as an event. Only a person can call a win: nothing in an inbox proves a deal closed.

    Stage machine / CRM sync

Installing it

macOS: open the dmg, drag the icon into Applications, and right-click → Open the first time. Windows: run setup.exe and click through, then More info → Run anyway on first launch. Both stops have the same cause — we have no code-signing certificate — and that is how each system treats every unsigned program, not a judgement about this one.

# double-click the icon; it opens in a window of its own
# no browser, no tab

# everything stays on your machine
macOS    ~/Library/Application Support/OpenVZ Leads/
Windows  %APPDATA%\OpenVZ Leads\

You also need the Claude Code CLI installed and logged in — that is the brain, running on the subscription you already have, with no second model bill; the app checks for it on the first screen and links you there. Python is bundled, so that is not your problem. Prompts and the sales knowledge base are placed in the directory above on first launch: plain Markdown, and changing how it sells needs no code. Uninstalling leaves your data directory alone: the prospects and briefs are in there.

What a brief looks like

This is exported Markdown, unedited. The last two blocks are the valuable part.

## Northwind Logistics

**Lena Fischer** — Head of Procurement

Fit **6/10** · confidence **low**

**What they do**
Regional freight forwarding across the Benelux

**Buying signals**
- Opened a second warehouse *(medium)* — homepage news item

**Decision chain**
- This contact: champion
- Likely economic buyer: Managing Director
- Likely blocker: IT

**Opening angles**
- Second warehouse means new carrier contracts
  - Why it lands: Timing

**Do not say**
- Do not claim they are struggling — no evidence of that

**Evidence gaps**
- No pricing or headcount data
- Website has no team page

A fit of 6, a confidence of low, and two gaps admitted outright — this brief is not pretending to know things. The language it is written in is a setting, independent of the emails: the analysis can be Chinese while the outreach stays English.

What makes it different

01

Nothing is sent by default

Sending is an optional plugin, not a prerequisite. With no outbound channel at all the first six steps still run: parsed, found, read, qualified, drafted, queued — then exported as CSV for your CRM or Markdown for a human to read. No sending account, no extra bill. Connect a channel if you want it to send — human review still stays on, and turning that off means deliberately flipping a setting called require_approval.

02

Every claim carries its source

A buying signal has to point at something that actually appeared in the evidence, and it has to say where. The brief carries a confidence level and a section listing what is still missing. A confidently wrong fact about a prospect is far worse than a blank field: a rep will repeat it in the first email, the prospect will spot it immediately, and the thread is dead.

03

What not to say matters as much as what to say

Every brief carries an avoid list — assumptions the evidence actually contradicts, claims that would land badly, subjects to stay off. The agent that writes the emails reads that list and is told to respect it. Most tools tell you how to open. Almost none tell you which sentence kills the deal.

04

No extra model cost — and the model is swappable

By default the brain is a headless call to the Claude Code CLI on your machine, running on the subscription you already pay for, which is what makes "no extra model cost" a true sentence. Swapping it is one config line: OpenAI, DeepSeek, or anything speaking the same API — vLLM, OpenRouter, a local Ollama. Every agent talks to the Brain and never to a vendor, so one line really is all of it.

05

Sending no longer means handing over your list

Connect a sending platform, or send straight from your own Gmail — which means the mail comes from a real person at a real address, follow-ups land in the same conversation, and the list never leaves you. Dropping the platform also means the four things it used to do become this product's job, and each one is a way to send a genuinely bad email: scheduling, merge-variable substitution, the opt-out footer, and checking for a reply before every follow-up. So each is handled explicitly. A `{{first_name}}` with no value does not become "Hi ," — that message is cancelled with the reason recorded. Nothing sends at all until a real postal address is filled in. And when the mailbox cannot be read, the follow-up is deferred rather than sent, because not knowing whether they replied is not the same as knowing they did not.

06

Stage changes reach the CRM you already run

What happens after a relationship exists belongs in the system that already holds your customers. So every stage change is an event, pushed there in a documented shape. A failed sync does not lose it: the event is written locally and marked unsynced, and the next heartbeat retries. Events go out in order, too — a failed push replayed later would leave a record reading "won" before "contacted".

07

The data never leaves your machine

One SQLite file on your own disk, openable with anything. No tenant, no upload, no console. Run several workspaces — different products, different ICPs — by pointing one environment variable at different paths.

08

The compliant defaults are the tight ones

Opt-out wording of any kind is treated as immediate and permanent, sends are capped per day, quiet hours are on out of the box, and if a prospect asks whether they are talking to an AI it is required to say yes — some jurisdictions mandate that, and lying about it never helped anyway. On the Gmail path the opt-out line and a real postal address are appended to every message; that address has no default and nothing sends until it is filled in, because a placeholder would go out as a fake address in real mail. LinkedIn automation breaks their terms of service, so it ships off.

The stack

Brain
The one place a model is called: headless Claude Code CLI by default, or OpenAI, DeepSeek, or any OpenAI-compatible endpoint. Retries, timeouts and skill injection.
Reading pages
Three tiers by cost: httpx + BeautifulSoup (included) → Crawl4AI (renders JS, returns Markdown) → Browser Use (a real browser). The upper two are optional; with neither installed it still reads the web.
Sending
Your own Gmail (OAuth; only a refresh token is stored, locally), a sending platform, or nothing at all — draft and export. Scheduling, substitution, the footer and stop-on-reply all happen on your machine.
Shape
A local Python heartbeat loop: quiet hours → CRM sync → usage budget → decide → act → log → sleep. Or run it always-on under Docker.
Decisions
Deterministic rules, not model calls. What to do next falls out of the pipeline counts — and a campaign awaiting review is not an action, because it is waiting on a person.
Data
SQLite in WAL mode with linear schema migrations. Stage history is its own event table, not a field that gets overwritten.
Interface
A desktop app in a window of its own, drawn by the system web view rather than a bundled browser engine. Bilingual. The sidebar is grouped in the order the work happens, and a banner at the top names the one thing to do next.
What you can change
Prompts and the sales knowledge base are plain Markdown — including the prompt that turns a sentence into an ICP. Changing how it sells needs no code.
Licence
MIT. Rename it, sell it, close it.

Leads works before a relationship exists: who is this person, are they worth talking to, and what should the first sentence say. What happens after — recording it, following up, moving it along — is a different job, and that is OPENVZ CRM. Use either on its own, or feed the list Leads exports straight into it.

See the CRM

Where it came from

OpenVZ Leads is a derivative of Harvey, an MIT-licensed project. The work went past renaming: upstream closes the loop automatically — written, then deployed — and this version demotes sending from a prerequisite to an optional plugin, adds a full human-review state machine, and builds out both ends the product did not have. At the front, a sentence becomes an ICP and says what it inferred. At the back, a stage machine carries the record from contacted to won or lost and tells your CRM about it. The account analysis in the middle is this version too. The original copyright notice stays in LICENSE, as MIT requires, and the full list of changes ships with the source.

Want one tuned to your market?

The source is complete, and installing it is a clone and a pip install. If you want it reshaped around your industry, your language and your qualification bar — or wired into the CRM you already run — say so.

Get in touch