Initial commit: prospecting agent loop MVP

Closed-loop account-intelligence system: harvest -> relevance filter vs.
Capability Library -> synthesize -> score -> deliver -> feedback.

- 40-capability Broadcom + VMware library
- Live Exa.ai news connector with hard free-plan request ceilings
- Provider-agnostic LLM layer (Gemini/Anthropic) with offline mock fallback
- LLM confidence wired into composite scoring + low-confidence floor
- 7-account pilot list

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-23 11:39:59 -04:00
commit 39a338990f
35 changed files with 4020 additions and 0 deletions

View File

@@ -0,0 +1,61 @@
"""Capability Library loader + lightweight keyword pre-filter.
The design uses a vector ANN pre-filter to cut LLM calls ~60%. For the MVP we
approximate that with keyword overlap scoring (no embedding service required).
The interface is the same: given a signal, return candidate capabilities.
"""
from __future__ import annotations
import json
import re
from functools import lru_cache
from . import config
@lru_cache(maxsize=1)
def load_library() -> dict:
return json.loads((config.DATA_DIR / "capabilities.json").read_text())
def capability_by_id(cap_id: str) -> dict | None:
for cap in load_library()["capabilities"]:
if cap["id"] == cap_id:
return cap
return None
def _tokens(text: str) -> set[str]:
return set(re.findall(r"[a-z]{3,}", text.lower()))
def prefilter(signal_content: str, top_k: int = 3) -> list[dict]:
"""Return capabilities whose keywords overlap the signal, best first.
Stand-in for the vector ANN pre-filter in the design (section 5.2).
"""
sig_tokens = _tokens(signal_content)
scored: list[tuple[float, dict]] = []
for cap in load_library()["capabilities"]:
kw = set(k.lower() for k in cap.get("keywords", []))
kw_tokens: set[str] = set()
for k in kw:
kw_tokens |= _tokens(k)
overlap = len(sig_tokens & kw_tokens)
if overlap:
scored.append((overlap, cap))
scored.sort(key=lambda x: x[0], reverse=True)
return [cap for _, cap in scored[:top_k]]
def library_summary_for_prompt(candidates: list[dict]) -> str:
"""Compact capability descriptions for the relevance-filter prompt."""
lines = []
for c in candidates:
lines.append(
f"- {c['id']} | {c['name']}: {c['description']}\n"
f" Buying signals: {'; '.join(c['buyingSignals'])}\n"
f" Anti-signals: {'; '.join(c['antiSignals'])}\n"
f" Target buyer: {', '.join(c['targetBuyer']['titles'])}"
)
return "\n".join(lines)