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.

The MVP, in one line. 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:

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

CommandWhat it does
valentine <domain|name>Sweep the fund's CRM for prior contact and print a verdict.
valentine initConnect a CRM and choose a model. Interactive, or headless with flags / a non-TTY.
valentine mcpRun as a stdio MCP server (see MCP).
valentine watch roadmapPre-meeting calendar heads-up. Prints the planned behaviour today.
valentine helpPrint usage. Same as --help or no arguments.
valentine versionPrint the version. Same as --version.

Flags

FlagMeaning
--jsonPrint the verdict as a JSON object instead of formatted text. Never prompts.
--non-interactive, -yNever 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 / --versionUsage / 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

VariablePurpose
ANTHROPIC_API_KEYPowers the agent loop. Required.
VALENTINE_ATTIO_KEYRead-only Attio token. Set this or the Affinity key.
VALENTINE_AFFINITY_KEYRead-only Affinity token. Use instead of the Attio key.
VALENTINE_MODELOverride 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.

VerdictMeaning
prior_contactAny 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.
cleanThe record genuinely has no interactions, lists, notes, or owner — or there's no matching record at all.
ambiguousMultiple 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:

ModelNotes
claude-haiku-4-5Fast and cheap. The default and recommended choice — roughly a cent per sweep.
claude-sonnet-4-6Sharper 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:

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:

CodeMeaning
0clean — no prior contact
10prior_contact
20ambiguous
1error (bad config, network, etc.)

How it works

Three moving parts, each small enough to read end to end.

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.

ToolPurpose
search_crmFind a company or person by domain or name. Returns matches with owner, connection strength, and interaction dates.
get_contextFor 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_verdictCapture 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


Troubleshooting

Roadmap

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.