Valentine
A tiny, read-only open-source agent for venture funds. You give it a founder or a company, and before you get on the call it tells you whether anyone at your fund has already talked to them — who, when, and what came of it.
MIT licensed · runs on your machine · your keys, your data. The whole agent is a few hundred lines you can read in a sitting.
valentine acme.com → "⚠ Sarah emailed Acme's founder 3 weeks ago — logged 'passed, too early.'" or a clean ✅. That single verdict, delivered the moment before a call, is the product. Everything else on this page is a way to trigger it.Install
Valentine is published to npm as valentine-agent. There's nothing to install ahead of time — npx fetches and runs it:
npx valentine-agent init # connect your CRM (read-only token)
npx valentine-agent acme.com # one verdict before the call
Or install it globally so you can type just valentine:
npm install -g valentine-agent
valentine init
valentine acme.com
The package registers three binaries: valentine and valentine-agent (identical — the CLI) and valentine-mcp (the MCP server). Requires Node 18+.
Quickstart
Two steps. The first connects a CRM and picks a model; the second runs a sweep.
# 1. one-time setup — asks for a read-only CRM token + an Anthropic key
valentine init
# 2. sweep before any call — pass a domain, a name, or a LinkedIn URL
valentine acme.com
valentine "Jane Founder"
A run takes a couple of seconds and ends in one of three verdicts: prior contact, clear, or ambiguous. Nothing is ever written back to the CRM.
What it needs
Two credentials:
- A read-only CRM API token — Attio or Affinity.
- An Anthropic API key — this powers the agent. A sweep on the default model costs roughly a cent.
Both can be entered interactively with valentine init, or supplied as environment variables (see Configuration). Nothing leaves your machine except the model call to Anthropic.
CLI reference
The CLI dispatches on the first argument. If that argument isn't a known command, it's treated as a lookup target.
Commands
| Command | What it does |
|---|---|
valentine <domain|name> | Sweep the fund's CRM for prior contact and print a verdict. |
valentine init | Connect a CRM and choose a model. Interactive, or headless with flags / a non-TTY. |
valentine mcp | Run as a stdio MCP server (see MCP). |
valentine watch roadmap | Pre-meeting calendar heads-up. Prints the planned behaviour today. |
valentine help | Print usage. Same as --help or no arguments. |
valentine version | Print the version. Same as --version. |
Flags
| Flag | Meaning |
|---|---|
--json | Print the verdict as a JSON object instead of formatted text. Never prompts. |
--non-interactive, -y | Never prompt. Implied automatically when stdin isn't a TTY (most agent sandboxes). |
--crm <attio|affinity> | Which CRM, for headless init. |
--crm-key <key> (alias --key) | The CRM token, for headless init. |
--anthropic-key <key> | The Anthropic key, for headless init. |
--model <id> | Override the model for this init. |
--help / --version | Usage / version. |
Examples
# a normal pre-call sweep
valentine stripe.com
# machine-readable, for a script or another agent
valentine --json "Patrick Collison"
# configure without any prompts (CI, agents, dotfiles)
valentine init -y --crm attio --crm-key "$VALENTINE_ATTIO_KEY" \
--anthropic-key "$ANTHROPIC_API_KEY" --model claude-haiku-4-5
Configuration
Config resolves from two places. A saved file is read first; environment variables fill in anything missing (and VALENTINE_MODEL always wins for the model).
Config file
Written by valentine init to ~/.valentine/config.json:
{
"crm": "attio", // "attio" | "affinity"
"attioKey": "...", // or "affinityKey"
"provider": "anthropic",
"model": "claude-haiku-4-5",
"authMethod": "api_key",
"anthropicKey": "sk-ant-..."
}
Environment variables
| Variable | Purpose |
|---|---|
ANTHROPIC_API_KEY | Powers the agent loop. Required. |
VALENTINE_ATTIO_KEY | Read-only Attio token. Set this or the Affinity key. |
VALENTINE_AFFINITY_KEY | Read-only Affinity token. Use instead of the Attio key. |
VALENTINE_MODEL | Override the model. Defaults to claude-haiku-4-5. |
Set ANTHROPIC_API_KEY plus exactly one CRM key and you can skip init entirely — useful in agent sandboxes and CI.
The verdict
Every run resolves to exactly one verdict. This is the heart of the product — one line a partner can read in two seconds.
| Verdict | Meaning |
|---|---|
prior_contact ⚠ | Any real signal of prior contact exists — an interaction date, a list membership, a note, an owner, or a known linked person. When in doubt, Valentine errs toward this. |
clean ✅ | The record genuinely has no interactions, lists, notes, or owner — or there's no matching record at all. |
ambiguous ❓ | Multiple weak matches it couldn't confidently disambiguate. |
In text mode you get the verdict, a one-line summary, and (when known) owner, last touch, status, and the record IDs the verdict rests on. The --json shape:
{
"target": "acme.com",
"verdict": "prior_contact", // clean | prior_contact | ambiguous
"summary": "Sarah emailed Acme's founder 3 weeks ago — 'passed, too early'.",
"owner": "Sarah Lee", // optional
"lastTouch": "2026-05-12", // optional
"status": "passed, too early", // optional
"citations": ["rec_abc", "rec_def"] // record IDs used
}
Models & cost
Valentine runs on the Anthropic API. Two models are offered at setup:
| Model | Notes |
|---|---|
claude-haiku-4-5 | Fast and cheap. The default and recommended choice — roughly a cent per sweep. |
claude-sonnet-4-6 | Sharper reasoning on messy data, ~10× the cost. |
AWS Bedrock and local/Ollama providers are stubbed in the registry but not yet enabled. Authentication is via API key; using a Claude Pro/Max subscription isn't permitted for third-party tools under Anthropic's terms, so that path is deliberately disabled.
MCP server
Valentine ships as a Model Context Protocol server, so any MCP-capable host — Claude Desktop, Claude Code, Cursor, Hermes, openclaws — can call it mid-conversation: "anything on acme.com before my call?"
It exposes exactly one tool:
valentine_verdict(target: string)
// target: a company domain (e.g. acme.com) or a company/founder name
// returns the same JSON shape as --json; never writes to the CRM
Start it standalone over stdio with either binary:
valentine mcp # or: valentine-mcp
npx -y valentine-agent mcp # no install
Claude Desktop
Add to ~/Library/Application Support/Claude/claude_desktop_config.json (macOS), then restart Claude:
{
"mcpServers": {
"valentine": {
"command": "npx",
"args": ["-y", "valentine-agent", "mcp"],
"env": {
"ANTHROPIC_API_KEY": "sk-ant-...",
"VALENTINE_ATTIO_KEY": "..."
}
}
}
}
The same block works for Cursor and any other stdio MCP host. If you've already run valentine init, you can drop the env block — the server reads ~/.valentine/config.json too.
Headless / agents
Valentine is built to be driven by other agents, not only typed by hand. Two ways in:
- Shell out with
--json. It never prompts under--jsonor a non-TTY; it prints one JSON object and sets an exit code. Errors come back as a message on a non-zero exit, not a wizard. - Mount the MCP server for agents that speak MCP — one
valentine_verdicttool, same JSON.
export ANTHROPIC_API_KEY=sk-ant-...
export VALENTINE_ATTIO_KEY=... # or VALENTINE_AFFINITY_KEY
npx -y valentine-agent --json acme.com
echo $? # 0 / 10 / 20 / 1
See AGENTS.md in the repo for the full machine-readable contract.
Exit codes
The verdict is encoded in the process exit code so scripts and agents can branch without parsing output:
| Code | Meaning |
|---|---|
0 | clean — no prior contact |
10 | prior_contact |
20 | ambiguous |
1 | error (bad config, network, etc.) |
How it works
Three moving parts, each small enough to read end to end.
- A connector (
src/connectors/) — read-only CRM access behind a smallCRMConnectorinterface. Attio and Affinity ship in the box; the interface has no mutating methods at all. - An agent (
src/agent.ts) — the loop: the model thinks, calls a read tool, gets the result, repeats, then callssubmit_verdict. Hand-rolled on the Anthropic Messages API, capped at 10 steps, so it stays tiny and auditable. - A trigger (
src/cli.ts) — the CLI today; the MCP server;watchand a Slack command on the roadmap.
The rules the agent runs by live in one file, src/prompt.ts — its system prompt. In short: read-only always; search companies (and people if implied); pull context on a promising match; weigh every signal; emit exactly one verdict and no prose around it.
The agent's tools
The model is given three tools and nothing else. The first two read; the third ends the run.
| Tool | Purpose |
|---|---|
search_crm | Find a company or person by domain or name. Returns matches with owner, connection strength, and interaction dates. |
get_context | For a promising match, pull notes (where outcomes like "passed, too early" live), list memberships (e.g. a "Passed" or "Portfolio" list, with stage), and linked people. |
submit_verdict | Capture the final structured verdict and end the run. Called exactly once. |
There is no fourth tool. There is no write tool. The agent physically cannot mutate the CRM, because the capability isn't in the contract.
CRM connectors
Both connectors are read-only and data-model-agnostic: they rely only on standard features present on every workspace, and anything custom or missing is skipped gracefully rather than assumed. A bad term or an object the token can't see returns "no match" — it never crashes a sweep.
Attio
Uses Attio's standard system features: the name / domains attributes; the built-in interaction signals (first/last email and calendar interaction, strongest connection user and strength); the notes, list-entries, and workspace-members endpoints. So Valentine can surface the relationship owner, connection strength, last email, last meeting, list memberships with stage, notes, and linked people.
Affinity
Uses Affinity's standard V1 API: organization / person search with interaction dates and persons, plus the notes, single-record list-entries, and lists endpoints. Valentine maps companies → organizations and people → persons. Affinity's V1 has no standard org-level owner or connection-strength field, so those stay unresolved — but the verdict still fires on interaction dates, list membership, notes, and linked people.
Adding a CRM contributions welcome
HubSpot and Salesforce are each one new file: implement the CRMConnector interface (whoami, search, getContext) and add a case to the factory. Nothing else in the codebase changes — the agent and triggers depend only on the interface, never on a specific CRM.
Safety & privacy
- Read-only, structurally. The connector contract has no mutating method. Valentine never writes, sends a message, or moves a deal — it can't.
- Local. Runs on your machine with your token. Keys live at
~/.valentine/config.jsonor in your environment. Your pipeline never leaves the fund. - One thing leaves. Only the model call goes out, to the Anthropic API, using your own key.
- Auditable. MIT licensed and small enough to read every line — there's no black box between you and your founders.
Troubleshooting
- "Not configured" / it errors instead of asking. Under
--jsonor a non-TTY, Valentine fails loud rather than launching a wizard. SetANTHROPIC_API_KEY+ a CRM key, or runvalentine init. - Connection failed at
init. The token couldn't reach the workspace. Check it's a valid read token for the CRM you selected. - Always "clean" when you expect a hit. The token may lack access to the object, notes, lists, or members — those sub-reads degrade to empty silently. Confirm the token's scope.
- Subscription auth error. Pro/Max subscriptions aren't allowed for third-party tools; choose an API key in
init.
Roadmap
valentine watch— read your calendar, resolve each external attendee's domain, sweep, and DM a one-line heads-up ~30 minutes before the meeting. The interface is stubbed; the command prints its planned behaviour today.- Slack command —
/valentine acme.comfor the rest of the partnership. - More CRMs — HubSpot and Salesforce connectors.
Develop
git clone https://github.com/80x-djh/valentine
cd valentine
npm install
npm run dev -- acme.com # run from source (tsx)
npm run mcp # run the MCP server from source
npm run build # compile to dist/
The design is documented in SPEC.md; the agent contract in AGENTS.md. PRs — especially new connectors — welcome.
Valentine is MIT licensed. Built by Daniel Hull. Questions or a setup hand: daniel@80x.ai.