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

115
sales-agent/run.py Normal file
View File

@@ -0,0 +1,115 @@
#!/usr/bin/env python3
"""CLI entrypoint for the Sales Prospecting Agent Loop (MVP).
Commands:
python run.py loop [--account ID] [--tier strategic|enterprise|growth]
python run.py digest [--rep rep_sarah]
python run.py feedback --brief ID --action act_on|snooze|reject|won|lost [--note "..."]
python run.py recalibrate
python run.py accounts
python run.py briefs
"""
from __future__ import annotations
import argparse
import sys
from src import config, pipeline
from src.connectors import crm, exa
from src.delivery import digest as digest_mod
from src.memory import store, feedback
def cmd_loop(args):
if args.no_domain_filter:
config.EXA_DOMAIN_FILTER = False # per-run override
pipeline.run_all(tier=args.tier, account_id=args.account)
print("\n--- digest preview ---")
print(digest_mod.write_digest())
def cmd_digest(args):
print(digest_mod.write_digest(rep_id=args.rep))
def cmd_feedback(args):
entry = feedback.record_feedback(args.brief, args.action, args.note or "")
print(f"Recorded '{args.action}' on brief {args.brief}. "
f"New status: {store.get_brief(args.brief)['status']}")
def cmd_recalibrate(args):
weights = feedback.recalibrate()
if not weights:
print("Not enough feedback yet (need >=3 samples per capability/tier/signal combo).")
return
print("Updated scoring weights:")
for k, v in weights.items():
print(f" {k}: weight={v['weight']} (n={v['samples']})")
def cmd_accounts(args):
for a in crm.load_accounts():
print(f" {a['accountId']:18} {a['accountTier']:10} {a['accountName']:28} "
f"rep={a['assignedRep']['name']} has={a.get('currentProducts') or '[]'}")
def cmd_briefs(args):
briefs = sorted(store.load_briefs(), key=lambda b: b["composite_score"], reverse=True)
if not briefs:
print("No briefs yet. Run: python run.py loop")
return
for b in briefs:
print(f" {b['id']} [{b['priority']:8}] score={b['composite_score']:<5} "
f"status={b['status']:9} {b['account_name']}{b['capability_name']}")
def cmd_exa_usage(args):
st = exa.usage_status()
print("Exa.ai request budget")
print(f" enabled: {st['enabled']}")
print(f" month: {st['month']}")
print(f" used this month: {st['used_this_month']}")
print(f" monthly cap: {st['monthly_cap']}")
print(f" monthly remaining: {st['monthly_remaining']}")
print(f" per-run cap: {st['per_run_cap']}")
if not st["enabled"]:
print(" (Exa disabled — set EXA_API_KEY in .env to enable; using fixtures)")
def main(argv=None):
parser = argparse.ArgumentParser(description="Sales Prospecting Agent Loop (MVP)")
sub = parser.add_subparsers(dest="command", required=True)
p_loop = sub.add_parser("loop", help="Run the harvest->deliver loop")
p_loop.add_argument("--account", help="single account id")
p_loop.add_argument("--tier", choices=["strategic", "enterprise", "growth"])
p_loop.add_argument("--no-domain-filter", action="store_true",
help="include broad third-party news instead of scoping Exa to the account domain")
p_loop.set_defaults(func=cmd_loop)
p_dig = sub.add_parser("digest", help="Print the daily intelligence digest")
p_dig.add_argument("--rep", help="filter to a rep id")
p_dig.set_defaults(func=cmd_digest)
p_fb = sub.add_parser("feedback", help="Record rep feedback on a brief")
p_fb.add_argument("--brief", required=True)
p_fb.add_argument("--action", required=True,
choices=["act_on", "snooze", "reject", "won", "lost"])
p_fb.add_argument("--note", default="")
p_fb.set_defaults(func=cmd_feedback)
p_re = sub.add_parser("recalibrate", help="Recompute feedback weights (weekly job)")
p_re.set_defaults(func=cmd_recalibrate)
sub.add_parser("accounts", help="List target accounts").set_defaults(func=cmd_accounts)
sub.add_parser("briefs", help="List all generated briefs").set_defaults(func=cmd_briefs)
sub.add_parser("exa-usage", help="Show Exa.ai request budget").set_defaults(func=cmd_exa_usage)
args = parser.parse_args(argv)
config.ensure_dirs()
args.func(args)
if __name__ == "__main__":
sys.exit(main())