Natural language in, pipeline out.
Linkup sells a search API to AI-native companies building agents and RAG pipelines. This copilot automates the prospecting workflow: identify companies building with LLMs, score their fit against Linkup's ICP, detect competitors like Tavily or Exa in their stack, and generate outreach that speaks to their technical reality — including displacement angles when relevant.
> Research Mistral AI and write outreach to their CTO
🔍 research_company(company_name="Mistral AI") ✓ 3.2s
🔍 research_prospect(person_name="Arthur Mensch") ✓ 2.8s
💾 save_report(report_type="outreach") ✓ 0.1s
📋 attio_upsert(name="Mistral AI", domain="mistral.ai") ✓ 0.8s
📝 attio_create_note(title="Outreach — Arthur Mensch") ✓ 0.6s
ICP Score: 88/100 — Hot 🔥
├─ AI/LLM applications: 25/25 (foundation models, Le Chat, fine-tuning API)
├─ Needs real-time web: 18/20 (Le Chat web search, live data grounding)
├─ RAG / search APIs: 20/20 (web retrieval, grounding, factuality)
├─ Funding: 15/15 (Series B — $468M from Andreessen Horowitz)
├─ B2B: 5/10 (mixed B2B/B2C — API + consumer chat)
├─ Team size: 5/5 (600 employees)
└─ Geography: 0/5 (Paris — flagged EU, not US)
📧 Cold Email — Subject: "Real-time web grounding for Le Chat"
Arthur — Le Chat's web search currently relies on internal crawling.
Linkup's API would give you #1 SimpleQA-ranked results with 3 lines
of code. Mistral customers like [X] saw 40% fewer hallucinations...
💬 LinkedIn — "Congrats on the $468M raise. Linkup could help Le Chat
ground responses in real-time web data without maintaining your own
search infra. Happy to show a 15-min demo?"
📧 Follow-up — Subject: "Factuality benchmark data — Linkup vs. alternatives"
Attaching our SimpleQA results vs. Tavily and Exa...
The agent resolves references across turns — this is how a real prospecting session flows:
> Research Langfuse
→ deep research, ICP score 82 (Hot), report saved
> Now write outreach to their CTO
→ resolves "their" → Langfuse, researches Marc Klingen, generates 3 messages
> Find companies similar to that one
→ resolves "that one" → Langfuse, runs full lookalike pipeline
The copilot runs as a Slack bot with live progress updates — tool calls appear in real-time as the agent works, then the final response replaces the progress message.
Features: Live tool progress, markdown→Slack formatting, per-user conversation memory, thread support.
# Run with Docker
docker compose -f docker/docker-compose.yml up -dRequires SLACK_BOT_TOKEN and SLACK_APP_TOKEN env vars. See slack/ for setup.
Linkup's sales motion targets AI-native teams — companies building agents, RAG pipelines, and LLM-powered products. These teams evaluate search APIs on benchmarks (SimpleQA), latency, and integration ease. The prospecting challenge is:
- Identifying them — "building with LLMs" isn't in any firmographic database
- Scoring fit — not every AI company needs a search API
- Crafting relevant outreach — generic "we're an API" emails don't work on technical buyers
- Detecting displacement opportunities — prospects already using Tavily, Exa, or SerpAPI are warmer leads with a clear switching angle
This copilot automates all four. It uses Linkup's own API to research targets, scores them on signals that matter for Linkup specifically, and generates outreach that references their actual tech stack — including competitive displacement angles when it detects a rival in their tooling.
Fully deterministic — no LLM. Defined in tools/icp_scorer.py with config/icp_weights.py.
These criteria are a v1 hypothesis. They're based on Linkup's public positioning (#1 on SimpleQA, integrations with LangChain/CrewAI/n8n, AI search for agents) and observable patterns in the AI tooling market. In production, I'd calibrate against closed-won data — which signals actually predicted conversion, and what weights should shift.
| Criterion | Max | Method | Why it matters for Linkup |
|---|---|---|---|
| Building AI/LLM applications | 25 | Keyword matching (40+ signals) | Core market — if they're not building with AI, they don't need a search API |
| Needs real-time web data | 20 | Keyword matching | Linkup's differentiator is live web results, not static data |
| Using RAG or search APIs | 20 | Keyword matching (includes competitor detection) | Already buying what Linkup sells — or something close to it |
| Has raised funding | 15 | Funding stage lookup | Budget signal — Series A/B companies are actively building |
| B2B company | 10 | Indicator detection | B2B = higher contract values, longer retention |
| Team size 10-500 | 5 | Numeric range check | Sweet spot — big enough to pay, small enough to decide fast |
| Based in US/EU | 5 | Geography matching | Linkup's primary markets |
Total: 100 points — Hot >= 75 | Warm >= 50 | Cold < 50
The agent tracks Linkup competitors (Tavily, Exa, SerpAPI, Perplexity, Brave Search, You.com, Google Custom Search, Bing Search API, SearxNG) via config/competitors.py. When research reveals a competitor in a prospect's stack:
- The ICP score gets a boost on the "RAG / search APIs" criterion (they're already buying search)
- The outreach generation receives a competitor flag, so Claude crafts a displacement angle — referencing Linkup's SimpleQA benchmark advantage, structured output support, or pricing
- The sales brief (inbound enrichment) flags the competitor explicitly with recommended talking points
This turns competitive intelligence from a manual research step into an automated part of every prospect interaction.
pip install -r requirements.txtCreate .env:
# Required
LINKUP_API_KEY=lk-...
ANTHROPIC_API_KEY=sk-ant-...
# Optional
ATTIO_API_KEY=... # Enables CRM sync
DAILY_SCAN_EMAIL=you@co.com # Recipient for daily scan emails
CLAUDE_MODEL=claude-sonnet-4-5-20250929 # Default modelpython main.py ┌──────────────────────────┐
│ main.py │
│ Rich CLI loop │
└────────────┬─────────────┘
│ user message
▼
┌──────────────────────────┐
│ agent/orchestrator.py │
│ │
│ 1. Match skill from │
│ trigger keywords │
│ 2. Inject SKILL.md + │
│ references into │
│ system prompt │
│ 3. Send to Claude API │
│ with 15 tools │
│ 4. Loop on tool_use │
│ 5. Return final text │
└─────┬──────────┬─────────┘
│ │
tool calls │ │ tool results
▼ │
┌─────────────────────┐│
│ agent/tools.py ││
│ Tool Registry ││
│ ││
│ 15 definitions + │◄┘
│ Python executors │
└────────┬────────────┘
│ wraps
▼
┌──────────────────────────────┐
│ tools/ │
│ linkup_search.py │
│ company_research.py │
│ prospect_research.py │
│ icp_scorer.py │
│ attio_client.py │
│ attio_scanner.py │
│ gmail_client.py │
│ report_formatter.py │
│ daily_report_formatter.py │
└──────────────────────────────┘
| Layer | Role | Example |
|---|---|---|
| Claude (probabilistic) | Routing, generation, analysis | Pick workflow, write outreach, analyze traits |
| Tools (deterministic) | API calls, scoring, formatting | Linkup search, ICP scoring, Attio upsert |
| Skills (declarative) | Workflow definitions | Step-by-step instructions, domain knowledge |
Claude handles reasoning. Deterministic Python handles execution. This separation is intentional — ICP scoring doesn't need an LLM, and outreach generation doesn't need a rule engine.
The system prompt starts light (~500 tokens of skill descriptions). When Claude identifies a workflow, the orchestrator injects the full SKILL.md + reference docs into context. This keeps each turn focused and avoids overwhelming the model with irrelevant instructions.
Each skill is a folder in skills/ with a SKILL.md (YAML frontmatter + step-by-step instructions) and optional references/ docs containing domain knowledge.
Triggers — "research", "outreach", "email", "write to", "reach out"
Deep-researches a company (and optionally a person), then generates 3 personalized outreach messages with competitor-aware angles.
| Step | Tool | Output |
|---|---|---|
| 1. Research company | research_company |
CompanyBrief (industry, tech stack, funding, AI signals) |
| 2. Research person | research_prospect |
ProspectProfile (role, background, hooks) — optional |
| 3. Generate outreach | Claude | Cold email + LinkedIn message + follow-up (with displacement angles if competitor detected) |
| 4. Save report | save_report |
reports/<company>_outreach.md |
| 5. CRM sync | attio_upsert + attio_create_note |
Company + outreach note in Attio |
Triggers — "similar", "lookalike", "companies like"
Analyzes seed companies, builds an ideal customer profile, finds similar companies, and scores them.
| Step | Tool | Output |
|---|---|---|
| 1. Research seeds | research_company x N |
CompanyBrief per seed |
| 2. Analyze traits | Claude | IdealProfile + 3-5 search queries |
| 3. Search lookalikes | search_linkup x N |
Candidate companies |
| 4. Score | score_icp x N |
ICPScore per candidate (Hot/Warm/Cold) |
| 5. Enrich | Claude | Competitor detection, displacement, outreach hooks |
| 6. Save + CRM sync | save_report + attio_upsert x N |
Report + all results in Attio |
Triggers — "new signup", "enrich", "sales brief", "inbound"
Researches an inbound lead, scores them, and generates an actionable sales brief with urgency-based follow-up.
| Step | Tool | Output |
|---|---|---|
| 1. Research company | research_company |
CompanyBrief |
| 2. Score ICP | score_icp |
ICPScore (0-100, tier) |
| 3. Competitor check | Claude | Warning flag if competitor detected |
| 4. Generate brief | Claude | SalesBrief (use case, talking points, urgency, action) |
| 5. Save + CRM sync | save_report + attio_upsert + attio_create_task |
Report + company + follow-up task |
Follow-up task deadlines: Hot (score >= 75) → 2 days | Warm (score >= 50) → 7 days
Triggers — "daily scan", "scan my CRM", "what changed in CRM"
Snapshots the Attio CRM, diffs against yesterday, presents a structured summary. Also runs as a standalone cron job (daily_scan.py) — no LLM required.
# Every day at 8 AM
0 8 * * * cd /path/to/project && .venv/bin/python daily_scan.py >> .tmp/daily_scan.log 2>&1Triggers — "scan top customers", "scan Attio", "find lookalikes from CRM"
Reads top customers from Attio, runs the full lookalike pipeline, writes scored results back to the CRM.
Three strategies to make the Slack bot available to your whole team, from simplest to most robust.
Run the existing docker-compose.yml on a cloud VM (e.g. a $5-10/month DigitalOcean droplet, AWS Lightsail, or a small EC2).
# On the VM
git clone <repo> && cd Leonardo
cp .env.example .env # fill in API keys
# Generate Gmail token locally first, then scp token.json to the VM
docker compose -f docker/docker-compose.yml up -d| Pros | Cons |
|---|---|
| Simplest to set up — one command | Single point of failure (VM goes down = bot goes down) |
| Cheap ($5-10/month for the VM) | No auto-scaling — one container handles all users |
| Same Docker setup you already have | API keys live on the VM (need to secure access) |
Easy to update: git pull && docker compose up -d --build |
Gmail OAuth token must be refreshed locally and redeployed |
Best for: Small team (< 10 people), getting started fast, low message volume.
Deploy the Docker image to a managed container platform: Google Cloud Run, AWS ECS Fargate, or Azure Container Apps. These handle restarts, scaling, and monitoring out of the box.
# Example with Google Cloud Run
gcloud builds submit --tag gcr.io/PROJECT/gtm-copilot docker/
gcloud run deploy gtm-copilot \
--image gcr.io/PROJECT/gtm-copilot \
--set-env-vars "SLACK_BOT_TOKEN=xoxb-...,SLACK_APP_TOKEN=xapp-...,LINKUP_API_KEY=lk-...,ANTHROPIC_API_KEY=sk-ant-..." \
--min-instances 1 \
--memory 512MiNote: Slack Socket Mode requires a persistent WebSocket connection, so set
--min-instances 1(Cloud Run) or use an always-on task (ECS). Serverless cold starts will drop the Slack connection.
| Pros | Cons |
|---|---|
| Auto-restarts on crash, built-in health checks | More setup than a simple VM |
| Scales if message volume grows | Slightly more expensive (~$15-30/month with always-on) |
| Secrets managed via platform (no keys on disk) | Socket Mode needs a persistent instance (no scale-to-zero) |
| Logs, metrics, alerts included | Gmail OAuth token needs a mounted secret or volume |
| Easy CI/CD: push to main → auto-deploy | Vendor-specific config to learn |
Best for: Growing team (10-50 people), need reliability without managing infrastructure.
Deploy to a Kubernetes cluster (EKS, GKE, or self-managed). Full control over scaling, secrets, networking, and multi-environment setups.
# k8s/deployment.yaml (simplified)
apiVersion: apps/v1
kind: Deployment
metadata:
name: gtm-copilot
spec:
replicas: 1 # Socket Mode = single replica
selector:
matchLabels:
app: gtm-copilot
template:
spec:
containers:
- name: bot
image: ghcr.io/your-org/gtm-copilot:latest
envFrom:
- secretRef:
name: gtm-copilot-secrets
volumeMounts:
- name: gmail-token
mountPath: /app/token.json
subPath: token.json
volumes:
- name: gmail-token
secret:
secretName: gmail-token| Pros | Cons |
|---|---|
| Full control: networking, secrets, resource limits | Overkill for a single bot |
| Native secret management (K8s Secrets, Vault) | Requires K8s expertise to maintain |
| Easy to add sidecars (monitoring, log shipping) | Higher baseline cost (cluster overhead) |
| Multi-env (staging/prod) with namespaces | More moving parts to debug |
| GitOps-friendly (ArgoCD, Flux) | Cluster management is ongoing work |
Best for: Large team (50+), existing K8s infrastructure, strict security/compliance requirements.
For most teams getting started: go with Option A. It takes 10 minutes, costs almost nothing, and you can always migrate to B or C later. If your team already uses a cloud platform with container services, jump straight to Option B — the reliability gains are worth the extra 30 minutes of setup.
This is a working v1. Here's what I'd prioritize to turn it into a real pipeline machine:
Measurement & iteration — Track email open rates, reply rates, and booked meetings per outreach variant. Feed results back into prompt tuning: which subject lines work, which displacement angles convert, which ICP tiers actually close. Right now the agent generates outreach; next step is learning which outreach works.
ICP calibration — Validate the 7 scoring criteria against Linkup's actual closed-won data. Which signals predicted conversion? Should "RAG / search APIs" be weighted higher than "AI/LLM applications"? The deterministic scorer makes this easy to iterate — change weights, re-score, compare.
Displacement playbooks — Expand competitor detection into structured playbooks per competitor (Tavily: latency + SimpleQA advantage; Exa: breadth of index; SerpAPI: structured output + AI-native design). The agent already detects competitors; the next step is arming it with battle-tested angles.
Pipeline integration — Connect outreach delivery (currently draft-only) to email sending with tracking pixels, and close the loop with CRM stage updates so the agent can report on its own conversion funnel.
├── main.py # CLI entry point + conversation loop
├── daily_scan.py # Standalone cron (no LLM, deterministic only)
│
├── agent/
│ ├── orchestrator.py # Claude API loop: tool_use + skill injection
│ ├── tools.py # 15 tool definitions + Python executors
│ └── skill_loader.py # Parse SKILL.md frontmatter, load references
│
├── skills/ # 5 self-contained workflows
│ ├── gtm-prospect-outreach/ # Research → outreach messages
│ ├── gtm-lookalike-finder/ # Seeds → ICP profile → scored lookalikes
│ ├── gtm-inbound-enrichment/ # Research → ICP score → sales brief
│ ├── gtm-crm-scan/ # Snapshot → diff → summary
│ └── gtm-attio-lookalike/ # CRM seeds → lookalike pipeline
│
├── slack/
│ └── bot.py # Slack bot with live progress updates
│
├── tools/ # Deterministic Python (no LLM)
│ ├── linkup_search.py # Linkup API: search + structured output
│ ├── company_research.py # 3 Linkup searches → CompanyBrief
│ ├── prospect_research.py # LinkedIn + activity → ProspectProfile
│ ├── icp_scorer.py # Weighted scoring (7 criteria, 100 pts)
│ ├── attio_client.py # Attio REST API (CRUD + notes)
│ ├── attio_scanner.py # CRM snapshot + diff engine
│ ├── gmail_client.py # Gmail OAuth send-only
│ ├── report_formatter.py # Rich CLI panels + markdown files
│ └── daily_report_formatter.py # CRMDiff → HTML email + markdown
│
├── models/ # Pydantic v2 data models
├── config/ # ICP weights, competitors, known customers
└── docker/ # Docker Compose for Slack bot deployment
| Package | Role |
|---|---|
anthropic |
Claude API — routing, generation, tool orchestration |
linkup-sdk |
Linkup Search API — web research + structured output |
pydantic |
Data models and validation |
rich |
Terminal UI — panels, tables, spinners |
requests |
HTTP client for Attio REST API |
slack-bolt |
Slack bot framework |
google-api-python-client |
Gmail API for email delivery |