An end-to-end business-development pipeline built on Claude Managed Agents — lead sourcing, company research, outreach drafting, a weekly nudge, and a lightweight deployable CRM to run it from.
One rule holds everywhere: the system researches and drafts; a human always sends. Every outreach path stops at a draft. The only mail the system sends is the weekly digest and morning brief — to you, never to a prospect — and pipeline status is never machine-written.
No API key, no Vercel account, no cloud anything:
git clone https://github.com/iaj6/bd-desk && cd bd-desk/crm
npm install && npm run demo # http://localhost:3010
# port in use? PORT=3011 npm run demoThat seeds a fictional pipeline and boots the CRM. You can open a target, run "research", watch it attach a brief, draft outreach off the card, run a sponsor profile and watch its portfolio auto-add new targets to the board, and export the whole thing to Excel. The research and drafts are canned — no model is called and nothing leaves your machine — but every path around them is the real code. Wiring it to real agents is Setup, below.
All companies shown are fictional demo data from the example brand pack.
The demo isn't only the board. Start it with a throwaway bearer —
MCP_TOKEN=demo CRON_SECRET=demo npm run demo — and the CRM's MCP server and both
cron routes answer too, still on canned data with no cloud account. Point Claude Code
at /api/mcp with Bearer demo and drive the whole pipeline over MCP, or fire the
nightly run yourself:
curl -H 'authorization: Bearer demo' localhost:3010/api/cron/pipeline.
Most Managed Agents examples demo one primitive at a time. This repo composes all of them into one working system:
| Primitive | Where it's used here | Code |
|---|---|---|
| Agents / Environments / Sessions / Events | four research agents + the session-driving scripts | src/setup.ts, src/dossier.ts |
Graded outcomes (user.define_outcome) |
every research run ships with a rubric; the platform grades the deliverable and sends the agent back to revise | src/rubrics.ts |
| Offline evals with pinned baselines | npm run eval — platform grader + deterministic checks, deltas vs a pinned baseline |
src/eval.ts |
| Skills (incl. bundled executable) | output formats + sweep protocol load on demand; a bundled validate_blocks.py the agent runs on its own draft |
skills/, src/setup-skills.ts |
| MCP consumption | agents call the CRM's own MCP server with an allowlisted toolset | src/setup-mcp.ts |
| Vaults | the CRM MCP bearer injected at egress — agents never see the token | src/setup-vault.ts |
Multi-agent (multiagent coordinator) |
the Radar delegates per-sponsor mapping to a locked sub-agent in parallel threads | src/setup-multiagent.ts |
| Deployments (cron + run-on-demand) | weekly Radar sweep + weekly digest; fire any time with radar-fire / digest-fire |
src/deploy-radar.ts |
| Memory stores | the Radar's cross-run worklist (a BFS frontier over PE sponsors) | src/setup-radar.ts |
| Dreaming (gated research preview) | npm run dream consolidates the memory store + transcripts into a cleaner store |
src/dream.ts |
| Files API | deliverables written to /mnt/session/outputs/deliverable.md and fetched verbatim |
src/outputs.ts |
See docs/examples/ for what these produce — a sample dossier, a
sponsor profile (with the machine blocks the CRM ingests), and an eval scorecard.
┌──────────────────────────────────────────────┐
│ brand/brand-pack.yaml (YOUR positioning) │
│ compiled by `npm run brand-pack` into every │
│ agent prompt + the CRM's outreach drafter │
└──────────────────────────────────────────────┘
│
weekly cron ──► OPPORTUNITY RADAR ───────┼─────────► CRM (Next.js on Vercel)
(graded sweep) walks the PE-sponsor │ pipeline board · REST · MCP server
graph via a Sponsor │ Vercel Blob storage
Mapper sub-agent; │ ▲ │
pushes qualified targets─┘ │ │ nightly cron:
over MCP │ │ draft → finalize
│ │ → research → follow-ups
button / CLI ─► DOSSIER / SPONSOR PROFILE ─────────────┘ │
(graded runs) researches the live web, writes ▼
deliverable.md + machine-readable outreach DRAFTS
people/portcos blocks (a human sends them)
weekly cron ──► WEEKLY DIGEST — reads the pipeline over MCP, emails the founder
the 2–3 things to do this week (ungraded on purpose: a revision
cycle could re-send the email)
Four agents (agents/*.system.md): the Dossier writes a seven-section
pre-call brief; the Sponsor Profile maps a PE fund's portfolio (its → ADD
portcos auto-feed the pipeline); the Radar hunts your ICP as a graph walk over
PE sponsors, coordinating a locked Sponsor Mapper sub-agent; the Digest
nudges you weekly. Prompts stay short — identity + brand canon + pointers to
skills (skills/) that carry the procedure.
This is the real setup — to just look around, use the demo instead.
You need: an Anthropic API key with Managed Agents access, a Vercel account (CRM
hosting + Blob), and optionally a Resend key for email.
Cost. Research sessions run Claude Opus 5 for minutes at a time — a graded dossier is
27–34 minutes of agentic work (web search plus one or more grader iterations), so each
dossier / sweep / eval task costs on the order of a few dollars, not cents, at list
prices. The nightly cron caps itself (3 research + 4 draft sessions per run), which puts
a default install with both crons on in the low-tens-of-dollars-per-week range.
Draft-only actions (outreach, the digest) are a single short call and cost cents. These
are order-of-magnitude figures — meter your own first few runs before turning the
crons loose, and keep evals/tasks.json short while iterating.
npm install
# 1. Your brand — the config surface for everything
cp brand/brand-pack.example.yaml brand/brand-pack.yaml # gitignored; edit every field
cp .env.example .env # fill in as you go
# 2. The CRM (deploy first — agents need its URL + MCP endpoint)
cd crm && npm install
vercel link && vercel blob add # create the project + a Blob store
cp .env.example .env.local # fill in; mirror to Vercel env vars
vercel --prod
cd ..
# 3. Compile the canon, provision everything (order matters; all idempotent)
npm run brand-pack -- --local
npm run setup # environment + dossier agent
npm run setup-sponsor # sponsor-profile agent
npm run setup-radar # memory store + radar agent
npm run setup-digest # digest agent (needs CRM_URL/DIGEST_TO/EMAIL_FROM in .env)
npm run setup-vault # the vault (needs CRM_URL)
npm run setup-mcp # MCP bearer into the vault + point radar/digest at the CRM's MCP server
npm run setup-skills # upload skills/ + attach to agents
npm run setup-multiagent # Sponsor Mapper sub-agent + radar coordinator roster
npm run brand-pack # now push the canon to the CRM too
# 4. Crons
npm run deploy-radar # weekly sweep (Mondays 8am ET — edit in the script)
npm run deploy-digest # weekly digest cron (the CRM sends it via crm_send_digest — set RESEND_API_KEY + BRIEF_EMAIL in the CRM env)Add the agent/environment ids from .managed-agents.json to the CRM's Vercel env
(DOSSIER_AGENT_ID, SPONSOR_AGENT_ID, ENVIRONMENT_ID) and redeploy once.
The example is Ridgeline Data Co. — a fictional food & beverage consultancy. To point the whole pipeline at your own business:
- Edit
brand/brand-pack.yaml— it is the only file with your vertical in it. The four agent prompts and all three rubrics are deliberately vertical-neutral; they score and pitch against "the ICP in the brand canon", never a named industry. You should not need to touch them. Re-runnpm run brand-pack && npm run updateto push the new canon. sponsoris the hub concept. The data model, the board's grouping, and the whole Radar protocol assume your targets cluster under a PE sponsor. If yours cluster under something else — an agency, a platform, a parent company — rename it, or leave it blank and add targets by hand or over MCP.- If your ICP is not cluster-shaped at all, swap
skills/bd-radar-protocol/SKILL.mdfor your own sourcing loop and leave everything else. The Dossier, Sponsor Profile, and CRM do not care how a target reached the board. - Then check it with the evals — put 2–3 of your own companies in
evals/tasks.jsonandnpm run evalto see fit scoring on your real ICP before you trust a run.
- CRM — the pipeline board at your Vercel URL. A Research button starts a graded
dossier/sponsor session; the pipeline's nightly cron finalizes finished research,
researches promising untouched targets (capped), drafts missing outreach, and
surfaces due follow-ups. The grader's verdict lands on each target. Your data
stays yours: export an Excel workbook (Targets + People sheets), the full
pipeline as JSON or CSV, the people across it as a
rolodex CSV (one row per contact), or any single target as
a markdown brief (
/api/export, buttons in the UI). - CLI —
npm run dossier -- "Some Company",npm run radar,npm run radar-fire(fires the deployment now and tails it, grading verdicts included).npm run radar-runslists what the weekly cron has produced; after editing a prompt or the canon,npm run updatere-pushes it to the agents and repins the weekly cron. - MCP — the CRM is an MCP server (
/api/mcp, bearerMCP_TOKEN). Wire it into Claude Code and drive the pipeline conversationally (crm_list_targets,crm_research,crm_draft_outreach, …). The agents themselves use an allowlisted subset of the same server.crm_capture_seedis the reverse-flywheel hook — flag a post-worthy observation from a real account onto a target, to turn into content later.
Fill evals/tasks.json with 2–3 real companies from your ICP (one expected Strong,
one expected Skip, one sponsor), then:
npm run eval -- --baseline # pin your starting point
# ...change a prompt, a skill, the canon, or the model...
npm run eval # scorecard with deltas vs the PINNED baselineEvery task runs the real agent and is scored twice: by the platform grader against the same rubric production uses, and by deterministic checks (machine blocks parse, sources cited, banned words absent, expected fit). This is how you change prompts without vibing it.
Things we hit building this that the docs won't tell you loudly enough:
- Graded sessions idle transiently. With
user.define_outcome, a session goesidlearound evaluation cycles whileoutcome_evaluations[].resultis stillpending/evaluating. "Done" is:terminated, ORidleand every outcome evaluation in a terminal state (satisfied/max_iterations_reached/failed/interrupted). Naive idle-means-done grabs half-finished deliverables. - Graded agents write files, not messages. Under
define_outcome, agents put the deliverable in/mnt/session/outputs/and only narrate in messages. Pin a filename in the kickoff and fetch it viafiles.list({scope_id: sessionId})+download(allow ~1–3s indexing lag after idle). That's what this repo does. - The event stream is progress, not truth. It drops on session reschedules
(e.g. model-overload retries) while the session keeps running server-side. On
stream end, poll
sessions.retrieveto the done-condition above and rebuild fromevents.list. - Deployments update in place; there is no delete.
deployments.updatechangesinitial_events/ pinned agent version / resources on the live cron — run history intact. Deployments also auto-pause after repeated errors; if a scheduled run goes quiet, checkdeployment.statusfirst. agents.updatecan add but not remove amultiagentblock — the pre-roster version stays addressable, so provision coordinator rosters as a separate version bump. No-op updates don't bump versions.- Skills:
SKILL.mddescriptions reject XML/angle-bracket tags;display_titleis unique org-wide (on partial-run recovery, adopt the existing id viaskills.list()); agents pinningversion: "latest"pick up skill edits with no agent version bump. allow_mcp_serversonly gateslimitednetworking — unrestricted environments reach MCP servers with no environment change.- Dreaming is a gated research preview —
/v1/dreams404s until your org is opted in.npm run dreamis ready for when it is. - The first
events.sendblocks while the sandbox provisions — about 100s on a cold environment, measured, before the call returns and the session starts running. Print something before you await it. A runner that prints a session URL and then sits silent for a minute and a half looks hung, and the reflex is to kill it — which leaves an orphaned session that never registered its task (events.listreturns 0) and never runs. Budget it into wall-clock too: a graded dossier took 27-34 minutes end to end, provisioning included.
brand/ brand-pack.example.yaml — copy to brand-pack.yaml and make it yours
agents/ system prompts (+ rubrics/ used to grade every research run)
skills/ on-demand procedure: formats, sweep protocol, machine-block
contracts + the validate_blocks.py the agents run on their drafts
src/ provisioning + run scripts (setup*, deploy*, dossier, radar, eval, dream)
evals/ tasks.json (fill with YOUR companies) + pinned baseline + runs
docs/ architecture diagram, board screenshot, tour gif, and examples/ (sample deliverables)
tests/ one vitest suite over both projects — `npm test`
crm/ Next.js CRM: pipeline board, REST API, MCP server, nightly pipeline,
Vercel Blob storage, morning-brief email, demo mode
The repo is two npm projects: the root (agents + scripts) and crm/ (the app), each
with its own lockfile. Tests for both live in tests/ and run from the root.
npm run check # lint + typecheck + tests (root)
npm test # tests only — no network, no API key, ~0.4s
cd crm && npm run lint && npm run typecheck && npm run buildCI runs all of the above on every push, plus a job that boots demo mode with no credentials — the promise the quickstart above makes is the one most likely to break silently. See CONTRIBUTING.md.
Honest about the edges, so nothing surprises you after you deploy:
- Beta platform. Managed Agents is beta; the API can move. The SDK is pinned and the "hard-won notes" above are dated — expect to re-check them when the surface changes.
- Single-operator auth. The CRM is one shared Basic-Auth password — no accounts, roles, or audit log. Right for a solo BD desk, not a team; put it behind your own SSO if more than one person needs it.
- Cost is real and unmetered here. Research runs Claude Opus for minutes (a graded dossier is 27–34 min). There is no per-run spend cap beyond the pipeline's session caps — watch your usage.
- Dreaming is a gated research preview —
npm run dream404s until your org is opted in. - Beta grader / Files API. A deliverable that never writes its output file falls back to the message log; a graded verdict can lag a few seconds behind idle.
Managed Agents is a beta surface (anthropic-beta: managed-agents-2026-04-01); the
SDK is pinned and this repo reflects the API as of August 2026. Agents run on
Claude Opus 5 (claude-opus-5). Built as a series of
hands-on reps against the patterns in Anthropic's
cwc-workshops; this project is not
affiliated with Anthropic.
MIT — see LICENSE.

