Skip to content
kedarvartakPublic

About

A signal-driven AI SDR agent built on Crustdata + Claude

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Repository files navigation

Tripwire

Tripwire watches for buying signals — a new VP of Sales, a funding round, a hiring surge — and the moment one fires, Claude autonomously researches the account, finds the right decision maker, and drafts outreach tied to that exact event.

Tripwire demo

ICP: "B2B SaaS, 50-200 employees, US"
         │
         ▼
[tripwire] ▶ find_buying_signals(icp={"industry":"B2B SaaS",...})
               — 3 signals, top: exec_change(85) @ Acme Corp

[tripwire] ▶ research_account(acme.com)
               — contact=Sarah Chen (VP of Sales), sales_jobs=5, posts=2

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
  Acme Corp  ·  120 ppl  ·  B2B SaaS  ·  SF
  Why now (85/100): New VP of Sales 1mo ago,
  Series A 3wks ago, 5 open AE roles.
  Contact: Sarah Chen — VP of Sales
  Signals: exec_change(85) · funding(75) · hiring_surge(70)

  > Hi Sarah — saw you joined Acme as VP of Sales
  > 1 month ago, right as the Series A closed and
  > the team started scaling. That 0→1 outbound
  > phase with 5 AEs ramping is exactly when data
  > quality decides whether the motion holds...
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Purpose

Crustdata's moat is real-time, event-driven data — not just "having data." But every MCP toolkit on top of Crustdata (Composio, Zapier, Merge) is purely request/response: search, enrich, fetch. None has a trigger primitive.

Tripwire is the missing layer: it diffs Crustdata's live data against a stored snapshot to detect change — and change is what buying intent actually looks like. Their data is the eyes; this is the reflexes.

Zero keys required to run

Leave CRUSTDATA_API_TOKEN unset and Tripwire serves realistic sample data from tripwire/fixtures/. Set the token (Crustdata free/self-serve tier) to run on live data — nothing else changes.

Quickstart (60 seconds)

git clone <repo> && cd crustdata
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt

# See the full pipeline output — no keys needed:
python -m tripwire.server --demo

That prints a ranked prospect card with signal scores, a qualified decision maker, and a personalized email draft. The [tripwire] ▶ lines on stderr are the reasoning trace — you can see exactly which tools fired and why.

Re-run anytime by deleting the working snapshot first (re-arms the demo):

rm -f tripwire/fixtures/snapshot_state.json && python -m tripwire.server --demo

Use it from Claude (MCP)

Claude Code — a project-scoped .mcp.json is already committed. Approve the tripwire server when prompted (or restart Claude Code), then:

prospect B2B SaaS companies, 50-200 employees, US — find the buying signals and
draft outreach for the top account

Claude will call find_buying_signals → research_account → emit a prospect card with the reasoning trace visible in the terminal.

Claude Desktop — add to ~/Library/Application Support/Claude/claude_desktop_config.json:

{
  "mcpServers": {
    "tripwire": {
      "command": "/path/to/crustdata/.venv/bin/python",
      "args": ["-m", "tripwire.server"],
      "env": { "PYTHONPATH": "/path/to/crustdata" }
    }
  }
}

Go live (optional)

# Get a self-serve token at crustdata.com → add it:
cp .env.example .env
# set CRUSTDATA_API_TOKEN=<your_token> in .env

export $(cat .env | xargs)
python -m tripwire.server --demo   # same command, real data

How it works

tripwire/
  crustdata.py    REST client — serves fixtures/ when no token is set (mock mode)
  models.py       Signal + CompanyState contracts
  signals.py      find_buying_signals: diffs live data vs. snapshot → ranked Signals
  research.py     research_account: composes 4 Crustdata calls into one account brief
  server.py       FastMCP server — 6 tools + reasoning trace on stderr
  fixtures/       sample data, seeded prior snapshot, and saved example outputs
.claude/
  skills/tripwire/SKILL.md    the SDR playbook Claude follows when prospecting

Signal scoring (priority order)

Signal Score Why
exec_change — new sales leader 85 Authority + mandate + 90-day re-tool window — all at once
funding 75 Budget unlocked; no named buyer yet
hiring_surge 50 + 4/role (cap 74) Scaling pain; magnitude-scaled

Stacking is the agent's job: when a company fires multiple signals, Claude sees the cluster and reasons about the compounding — the per-signal score stays honest and testable.

The demo snapshot trick

fixtures/snapshot_prev.json is a committed "last time we looked" state where Acme has zero sales roles, no funding, no VP Sales. The live fixtures show 5 roles, a Series A, and Sarah Chen. The diff fires all three signals on every fresh run — predictable, explainable, impressive. Delete fixtures/snapshot_state.json to re-arm between demos.

Saved example output

tripwire/fixtures/examples/prospect_card_acme.txt — a pre-rendered prospect card from a mock-mode run. Attach it to the outreach email so the reviewer sees output quality before they run anything.

Automated monitoring (polling)

find_buying_signals is one "tick" — pull, diff, emit. To run it unattended on a schedule:

python -m tripwire.run_polling                          # default: every 12h
POLL_INTERVAL_SECONDS=60 python -m tripwire.run_polling # fast, for testing
python -m tripwire.run_polling --once                   # single tick, then exit

Each tick diffs against the persisted snapshot, so a signal fires once — the first tick after the change appears — then goes quiet. 12h is deliberate: funding/exec/hiring events are daily-scale, so polling faster just burns credits detecting nothing. For true "the-moment-it-happens" latency, use the Watcher webhook below instead — polling is the zero-infra stand-in.

Real-time path (Phase 5 — Watcher webhook)

Tripwire also runs as a webhook receiver. Crustdata's Watcher API pushes a payload the moment a subscribed condition fires; we parse it into the same Signal and run the same loop — no polling, no cron.

# Start the receiver
uvicorn tripwire.webhook:app --port 8765

# Expose it publicly so Crustdata can reach it
ngrok http 8765

# Fire a synthetic event locally (no token needed)
curl -X POST http://localhost:8765/webhook/test?event_type=exec_change

To wire a real Watcher subscription (once you have a CRUSTDATA_API_TOKEN):

curl -X POST https://api.crustdata.com/watcher/subscribe \
  -H "Authorization: Bearer $CRUSTDATA_API_TOKEN" \
  -H "x-api-version: 2025-11-01" \
  -H "content-type: application/json" \
  -d '{
    "event_types": ["hiring_surge", "funding", "exec_change"],
    "filters": {"industry": "B2B SaaS"},
    "webhook_url": "https://<ngrok-id>.ngrok.io/webhook/crustdata"
  }'

Schema note: Crustdata's webhook payload fields aren't publicly documented. The schema in tripwire/webhook.py (WatcherPayload) is inferred from their marketing docs and verified against our test suite. Adjust _parse_payload() once you observe a live payload.

Tests

python -m pytest tests/ -v   # 47 tests, ~0.1s, no keys needed

The test suite encodes the product decisions, not just mechanics:

  • test_scoring.py — priority order (exec_change > funding > hiring_surge), magnitude cap, bounds
  • test_signals.py — diff fires on change, silent on same state, cold-start behaviour, full cluster
  • test_research.py — decision maker ranking, sales job filtering, post fallback logic
  • test_client.py — mock/live mode switch, fixture shapes, title filtering

Documentaion

Full research, architecture, phases, and outreach plan in docs/.

About

A signal-driven AI SDR agent built on Crustdata + Claude

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages