Skip to content

AntoineF23/leonardo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

22 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Linkup GTM Copilot

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...

Conversational memory (20-message window)

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

Slack Bot

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 -d

Requires SLACK_BOT_TOKEN and SLACK_APP_TOKEN env vars. See slack/ for setup.


Why This Matters for Linkup

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:

  1. Identifying them — "building with LLMs" isn't in any firmographic database
  2. Scoring fit — not every AI company needs a search API
  3. Crafting relevant outreach — generic "we're an API" emails don't work on technical buyers
  4. 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.


ICP Scoring

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

Competitor Detection & Displacement

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:

  1. The ICP score gets a boost on the "RAG / search APIs" criterion (they're already buying search)
  2. The outreach generation receives a competitor flag, so Claude crafts a displacement angle — referencing Linkup's SimpleQA benchmark advantage, structured output support, or pricing
  3. 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.


Setup

pip install -r requirements.txt

Create .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 model
python main.py

Architecture

                         ┌──────────────────────────┐
                         │        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   │
              └──────────────────────────────┘

Core Principle: Probabilistic vs. Deterministic

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.

Progressive Skill Loading

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.


Skills

Each skill is a folder in skills/ with a SKILL.md (YAML frontmatter + step-by-step instructions) and optional references/ docs containing domain knowledge.

Prospect Outreach

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

Lookalike Finder

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

Inbound Enrichment

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

CRM Scan

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>&1

Attio Lookalike Scan

Triggers — "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.


Deploying for Your Team

Three strategies to make the Slack bot available to your whole team, from simplest to most robust.

Option A: Single VM with Docker Compose

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.

Option B: Cloud Container Service (managed)

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 512Mi

Note: 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.

Option C: Kubernetes (self-managed or hosted)

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.

Recommendation

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.


What's Next

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.


Project Structure

├── 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

Dependencies

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

About

Linkup sells a search API to AI-native companies building agents and RAG pipelines. This copilot automates the prospecting workflow.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors