heylead 0.10.332__tar.gz → 0.10.333__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. {heylead-0.10.332 → heylead-0.10.333}/AGENTS.md +1 -1
  2. {heylead-0.10.332 → heylead-0.10.333}/PKG-INFO +1 -1
  3. {heylead-0.10.332 → heylead-0.10.333}/SKILL.md +1 -1
  4. {heylead-0.10.332 → heylead-0.10.333}/clawhub/SKILL.md +1 -1
  5. {heylead-0.10.332 → heylead-0.10.333}/clawhub.json +1 -1
  6. heylead-0.10.333/docs/superpowers/plans/2026-09-09-agent-commons-heartbeat.md +53 -0
  7. heylead-0.10.333/docs/superpowers/specs/2026-09-09-agent-commons-heartbeat-design.md +84 -0
  8. {heylead-0.10.332 → heylead-0.10.333}/pyproject.toml +1 -1
  9. {heylead-0.10.332 → heylead-0.10.333}/server.json +2 -2
  10. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/agent_loop.py +16 -1
  11. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/schemas.py +20 -4
  12. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/db/schema.py +44 -1
  13. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/server.py +10 -1
  14. heylead-0.10.333/src/heylead/services/agent_commons.py +227 -0
  15. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/hot_lead_closer.py +12 -0
  16. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/icp_research.py +9 -0
  17. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/reply_agent.py +11 -0
  18. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/strategist_replan.py +12 -0
  19. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/inspect.py +91 -2
  20. {heylead-0.10.332 → heylead-0.10.333}/.cursorrules +0 -0
  21. {heylead-0.10.332 → heylead-0.10.333}/.github/workflows/publish.yml +0 -0
  22. {heylead-0.10.332 → heylead-0.10.333}/.github/workflows/test.yml +0 -0
  23. {heylead-0.10.332 → heylead-0.10.333}/.gitignore +0 -0
  24. {heylead-0.10.332 → heylead-0.10.333}/Dockerfile +0 -0
  25. {heylead-0.10.332 → heylead-0.10.333}/LICENSE +0 -0
  26. {heylead-0.10.332 → heylead-0.10.333}/README.md +0 -0
  27. {heylead-0.10.332 → heylead-0.10.333}/clawhub/clawhub.json +0 -0
  28. {heylead-0.10.332 → heylead-0.10.333}/docs/AUDIT-2026-08.md +0 -0
  29. {heylead-0.10.332 → heylead-0.10.333}/docs/CLI_AND_DX_IMPROVEMENTS.md +0 -0
  30. {heylead-0.10.332 → heylead-0.10.333}/docs/EXPLICIT_IMPROVEMENT_PLAN.md +0 -0
  31. {heylead-0.10.332 → heylead-0.10.333}/docs/HANDOFF-2026-08-06.md +0 -0
  32. {heylead-0.10.332 → heylead-0.10.333}/docs/QUALITY_AND_SATISFACTION_ASSESSMENT.md +0 -0
  33. {heylead-0.10.332 → heylead-0.10.333}/docs/TOOL_TEST_REPORT.md +0 -0
  34. {heylead-0.10.332 → heylead-0.10.333}/docs/USER_STORY_TEST.md +0 -0
  35. {heylead-0.10.332 → heylead-0.10.333}/docs/product-listing-copy.md +0 -0
  36. {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/plans/2026-08-07-cloud-sync-push.md +0 -0
  37. {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/prompts/2026-09-09-quontex-feedback/00-shared-context.md +0 -0
  38. {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/prompts/2026-09-09-quontex-feedback/01-first-degree-client.md +0 -0
  39. {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/prompts/2026-09-09-quontex-feedback/02-first-degree-backend-dashboard.md +0 -0
  40. {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/prompts/2026-09-09-quontex-feedback/03-duplicate-sends-hardening.md +0 -0
  41. {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/prompts/2026-09-09-quontex-feedback/04-reply-context-backend.md +0 -0
  42. {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/prompts/2026-09-09-quontex-feedback/05-reply-context-client.md +0 -0
  43. {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/prompts/2026-09-09-quontex-feedback/06-decision-maker-targeting.md +0 -0
  44. {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/prompts/2026-09-09-quontex-feedback/07-kb-port-and-goal-icp-match.md +0 -0
  45. {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/prompts/2026-09-09-quontex-feedback/08-stop-feedback-learning-loop.md +0 -0
  46. {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/prompts/2026-09-09-quontex-feedback/README.md +0 -0
  47. {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/specs/2026-08-23-premium-mixed-outreach-design.md +0 -0
  48. {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/specs/2026-08-23-unanswered-lead-alerts-design.md +0 -0
  49. {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/specs/2026-09-08-campaign-prospect-provenance-design.md +0 -0
  50. {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/specs/2026-09-09-edit-campaign-agent-flags-design.md +0 -0
  51. {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/specs/2026-09-09-host-ops-playbook-design.md +0 -0
  52. {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/specs/2026-09-09-hot-lead-closer-design.md +0 -0
  53. {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/specs/2026-09-09-icp-research-agent-design.md +0 -0
  54. {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/specs/2026-09-09-inspect-agent-ops-design.md +0 -0
  55. {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/specs/2026-09-09-inspect-jobs-design.md +0 -0
  56. {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/specs/2026-09-09-reply-exception-agent-design.md +0 -0
  57. {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/specs/2026-09-09-strategist-replan-agent-design.md +0 -0
  58. {heylead-0.10.332 → heylead-0.10.333}/llms-full.txt +0 -0
  59. {heylead-0.10.332 → heylead-0.10.333}/llms.txt +0 -0
  60. {heylead-0.10.332 → heylead-0.10.333}/scripts/find_query_contracts.py +0 -0
  61. {heylead-0.10.332 → heylead-0.10.333}/scripts/fix_stuck_prospects.py +0 -0
  62. {heylead-0.10.332 → heylead-0.10.333}/scripts/reactivate_existing_network.py +0 -0
  63. {heylead-0.10.332 → heylead-0.10.333}/scripts/rekey_unipile_account.py +0 -0
  64. {heylead-0.10.332 → heylead-0.10.333}/scripts/release.py +0 -0
  65. {heylead-0.10.332 → heylead-0.10.333}/scripts/seed_shared_directory.py +0 -0
  66. {heylead-0.10.332 → heylead-0.10.333}/scripts/test_reaction_live.py +0 -0
  67. {heylead-0.10.332 → heylead-0.10.333}/smithery.yaml +0 -0
  68. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/__init__.py +0 -0
  69. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/__main__.py +0 -0
  70. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/__init__.py +0 -0
  71. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/brand_strategist.py +0 -0
  72. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/brief_builder.py +0 -0
  73. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/comment_generator.py +0 -0
  74. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/context_summarizer.py +0 -0
  75. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/draft_guard.py +0 -0
  76. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/email_generator.py +0 -0
  77. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/embeddings.py +0 -0
  78. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/evidence.py +0 -0
  79. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/followup_generator.py +0 -0
  80. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/followup_schemas.py +0 -0
  81. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/hot_lead_closer.py +0 -0
  82. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/hume_client.py +0 -0
  83. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/hume_voice.py +0 -0
  84. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/icp_generator.py +0 -0
  85. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/icp_generator_v2.py +0 -0
  86. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/icp_pipeline.py +0 -0
  87. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/icp_research.py +0 -0
  88. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/icp_schemas.py +0 -0
  89. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/inbound_qualifier.py +0 -0
  90. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/ingestion/__init__.py +0 -0
  91. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/ingestion/chunker.py +0 -0
  92. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/ingestion/crawler.py +0 -0
  93. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/ingestion/store.py +0 -0
  94. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/intent.py +0 -0
  95. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/length_fixer.py +0 -0
  96. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/linkedin_enricher.py +0 -0
  97. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/llm.py +0 -0
  98. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/llm_router.py +0 -0
  99. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/llm_validator.py +0 -0
  100. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/memory_extractor.py +0 -0
  101. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/memory_formatter.py +0 -0
  102. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/message_fixer.py +0 -0
  103. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/message_generator.py +0 -0
  104. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/message_improver.py +0 -0
  105. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/message_validator.py +0 -0
  106. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/news_service.py +0 -0
  107. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/persona_cards.py +0 -0
  108. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/post_analyzer.py +0 -0
  109. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/prompt_loader.py +0 -0
  110. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/prospect_analyzer.py +0 -0
  111. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/referral_extractor.py +0 -0
  112. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/reply_agent.py +0 -0
  113. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/reply_generator.py +0 -0
  114. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/reply_pipeline.py +0 -0
  115. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/sentiment.py +0 -0
  116. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/signal_classifier.py +0 -0
  117. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/strategist_prompts.py +0 -0
  118. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/strategist_replan.py +0 -0
  119. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/strategy_prompts.py +0 -0
  120. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/targeting_recheck.py +0 -0
  121. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/vector_search.py +0 -0
  122. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/voice_analyzer.py +0 -0
  123. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/voice_memo_generator.py +0 -0
  124. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/author_identity.py +0 -0
  125. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/cli/__init__.py +0 -0
  126. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/cli/log_analyzer.py +0 -0
  127. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/collectors/__init__.py +0 -0
  128. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/collectors/comment_mining_collector.py +0 -0
  129. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/collectors/company_follower_collector.py +0 -0
  130. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/collectors/company_page_collector.py +0 -0
  131. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/collectors/competitor_collector.py +0 -0
  132. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/collectors/distributed_post_collector.py +0 -0
  133. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/collectors/hiring_collector.py +0 -0
  134. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/collectors/job_change_collector.py +0 -0
  135. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/collectors/keyword_collector.py +0 -0
  136. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/collectors/network_post_collector.py +0 -0
  137. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/collectors/news_collector.py +0 -0
  138. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/collectors/post_intent_collector.py +0 -0
  139. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/collectors/profile_view_collector.py +0 -0
  140. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/collectors/prospect_post_collector.py +0 -0
  141. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/collectors/watchlist_web_collector.py +0 -0
  142. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/config.py +0 -0
  143. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/constants.py +0 -0
  144. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/correlation.py +0 -0
  145. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/daemon.py +0 -0
  146. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/data/country_signals/brazil.json +0 -0
  147. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/data/country_signals/france.json +0 -0
  148. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/data/country_signals/germany.json +0 -0
  149. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/data/country_signals/india.json +0 -0
  150. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/data/country_signals/mexico.json +0 -0
  151. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/data/country_signals/nigeria.json +0 -0
  152. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/data/country_signals/philippines.json +0 -0
  153. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/data/country_signals/poland.json +0 -0
  154. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/data/country_signals/ukraine.json +0 -0
  155. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/data/country_signals/united_kingdom.json +0 -0
  156. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/data/country_signals/vietnam.json +0 -0
  157. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/db/__init__.py +0 -0
  158. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/db/aio.py +0 -0
  159. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/db/async_bridge.py +0 -0
  160. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/db/backup.py +0 -0
  161. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/db/connection_queries.py +0 -0
  162. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/db/global_contact_queries.py +0 -0
  163. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/db/post_queries.py +0 -0
  164. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/db/queries.py +0 -0
  165. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/db/signal_queries.py +0 -0
  166. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/db/strategist_queries.py +0 -0
  167. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/db/strategy_queries.py +0 -0
  168. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/flags.py +0 -0
  169. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/formatter.py +0 -0
  170. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/guardrails.py +0 -0
  171. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/linkedin/__init__.py +0 -0
  172. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/linkedin/api_metrics.py +0 -0
  173. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/linkedin/backend_client.py +0 -0
  174. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/linkedin/chat_lookup_cache.py +0 -0
  175. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/linkedin/circuit_breaker.py +0 -0
  176. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/linkedin/experience.py +0 -0
  177. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/linkedin/messaging.py +0 -0
  178. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/linkedin/profile.py +0 -0
  179. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/linkedin/profile_normalize.py +0 -0
  180. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/linkedin/rate_limiter.py +0 -0
  181. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/linkedin/relations.py +0 -0
  182. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/linkedin/search.py +0 -0
  183. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/linkedin/search_traffic.py +0 -0
  184. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/linkedin/unipile.py +0 -0
  185. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/linkedin/voyager_health.py +0 -0
  186. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/logging_setup.py +0 -0
  187. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ops_log.py +0 -0
  188. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/__init__.py +0 -0
  189. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/comment_main.json +0 -0
  190. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/filter_candidates.json +0 -0
  191. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/followup_fix.json +0 -0
  192. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/followup_message.json +0 -0
  193. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/followup_message_buy.json +0 -0
  194. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/followup_news.json +0 -0
  195. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/followup_reasoning.json +0 -0
  196. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/followup_reasoning_buy.json +0 -0
  197. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/followup_validate.json +0 -0
  198. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/followup_validate_buy.json +0 -0
  199. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/fragments/voice_rules.txt +0 -0
  200. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/message_fix.json +0 -0
  201. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/message_improve.json +0 -0
  202. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/outreach_inmail.json +0 -0
  203. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/outreach_inmail_buy.json +0 -0
  204. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/outreach_invitation.json +0 -0
  205. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/outreach_invitation_buy.json +0 -0
  206. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/outreach_job_search.json +0 -0
  207. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/outreach_response.json +0 -0
  208. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/outreach_response_buy.json +0 -0
  209. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/outreach_system.json +0 -0
  210. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/outreach_system_buy.json +0 -0
  211. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/outreach_system_partner.json +0 -0
  212. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/outreach_system_recruit.json +0 -0
  213. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/outreach_validate.json +0 -0
  214. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/outreach_validate_buy.json +0 -0
  215. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/prospect_analysis.json +0 -0
  216. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/voice_analysis.json +0 -0
  217. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/scheduler/__init__.py +0 -0
  218. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/scheduler/engine.py +0 -0
  219. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/scheduler/enqueue_gate.py +0 -0
  220. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/scheduler/executors.py +0 -0
  221. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/scheduler/leader.py +0 -0
  222. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/scheduler/planner.py +0 -0
  223. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/scheduler/trace.py +0 -0
  224. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/__init__.py +0 -0
  225. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/action_health.py +0 -0
  226. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/action_timeline.py +0 -0
  227. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/action_verifier.py +0 -0
  228. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/anomaly_detector.py +0 -0
  229. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/brand_service.py +0 -0
  230. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/calendar_booker.py +0 -0
  231. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/campaign_optimizer.py +0 -0
  232. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/campaign_refill_service.py +0 -0
  233. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/campaign_spawner.py +0 -0
  234. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/channel_selector.py +0 -0
  235. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/cloud_sync.py +0 -0
  236. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/communication_strategist.py +0 -0
  237. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/company_name.py +0 -0
  238. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/connection_sync.py +0 -0
  239. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/conversation_enricher.py +0 -0
  240. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/daily_digest.py +0 -0
  241. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/dedup_service.py +0 -0
  242. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/directory_card.py +0 -0
  243. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/dm_connection_guard.py +0 -0
  244. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/engagement_verifier.py +0 -0
  245. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/experiment_service.py +0 -0
  246. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/fit_gate.py +0 -0
  247. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/health_score.py +0 -0
  248. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/hubspot_service.py +0 -0
  249. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/icp_match_scorer.py +0 -0
  250. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/icp_search.py +0 -0
  251. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/inbound_helpers.py +0 -0
  252. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/inbound_pipeline.py +0 -0
  253. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/inbound_policy.py +0 -0
  254. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/inbound_relationship.py +0 -0
  255. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/inbound_service.py +0 -0
  256. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/inbox_match.py +0 -0
  257. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/intent_signals.py +0 -0
  258. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/live_thread_enroll.py +0 -0
  259. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/outreach_channel.py +0 -0
  260. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/own_identity.py +0 -0
  261. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/own_profile_sync.py +0 -0
  262. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/pattern_detector.py +0 -0
  263. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/post_research_service.py +0 -0
  264. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/process_caches.py +0 -0
  265. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/profile_signals.py +0 -0
  266. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/project_brief.py +0 -0
  267. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/prospect_email.py +0 -0
  268. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/prospect_timezone.py +0 -0
  269. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/provider_id_repair.py +0 -0
  270. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/provider_id_resolver.py +0 -0
  271. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/recover_stuck_outreaches.py +0 -0
  272. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/referral_enroll.py +0 -0
  273. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/revenue_estimator.py +0 -0
  274. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/search_account_resolver.py +0 -0
  275. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/seed_shared_directory.py +0 -0
  276. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/session_health.py +0 -0
  277. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/signal_activator.py +0 -0
  278. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/signal_analytics.py +0 -0
  279. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/signal_linker.py +0 -0
  280. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/signal_optimizer.py +0 -0
  281. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/signal_scorer.py +0 -0
  282. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/signal_service.py +0 -0
  283. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/strategy_engine.py +0 -0
  284. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/tabular_reader.py +0 -0
  285. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/timezone_resolver.py +0 -0
  286. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/unanswered_lead_alerts.py +0 -0
  287. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/unipile_email.py +0 -0
  288. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/textutil.py +0 -0
  289. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tier.py +0 -0
  290. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/timeutil.py +0 -0
  291. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/__init__.py +0 -0
  292. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/account.py +0 -0
  293. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/analytics.py +0 -0
  294. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/archive_campaign.py +0 -0
  295. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/backfill_inbox.py +0 -0
  296. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/book_meeting.py +0 -0
  297. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/brand_strategy.py +0 -0
  298. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/campaign.py +0 -0
  299. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/campaign_control.py +0 -0
  300. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/campaign_report.py +0 -0
  301. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/check_replies.py +0 -0
  302. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/close_outreach.py +0 -0
  303. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/compare_campaigns.py +0 -0
  304. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/contacts.py +0 -0
  305. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/create_campaign.py +0 -0
  306. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/create_post.py +0 -0
  307. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/crm_sync.py +0 -0
  308. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/delete_campaign.py +0 -0
  309. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/delete_message.py +0 -0
  310. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/dismiss_lead.py +0 -0
  311. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/edit_campaign.py +0 -0
  312. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/emergency_stop.py +0 -0
  313. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/engage_prospect.py +0 -0
  314. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/experiment_loop.py +0 -0
  315. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/export_campaign.py +0 -0
  316. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/generate_icp.py +0 -0
  317. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/generate_send.py +0 -0
  318. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/icp.py +0 -0
  319. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/import_prospects.py +0 -0
  320. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/inbox.py +0 -0
  321. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/manage_watchlist.py +0 -0
  322. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/network.py +0 -0
  323. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/organization.py +0 -0
  324. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/partner_followup.py +0 -0
  325. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/profile_editor.py +0 -0
  326. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/profile_history.py +0 -0
  327. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/profile_signals.py +0 -0
  328. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/prospect.py +0 -0
  329. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/prospect_timeline.py +0 -0
  330. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/reply_to_prospect.py +0 -0
  331. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/retry_failed.py +0 -0
  332. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/scheduler.py +0 -0
  333. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/scheduler_status.py +0 -0
  334. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/send_email.py +0 -0
  335. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/send_followup.py +0 -0
  336. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/send_inmail.py +0 -0
  337. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/send_message.py +0 -0
  338. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/send_voice_memo.py +0 -0
  339. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/setup_profile.py +0 -0
  340. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/show_conversation.py +0 -0
  341. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/show_signals.py +0 -0
  342. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/show_status.py +0 -0
  343. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/show_strategy.py +0 -0
  344. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/signal_report.py +0 -0
  345. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/signals.py +0 -0
  346. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/skip_prospect.py +0 -0
  347. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/suggest_next_action.py +0 -0
  348. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/switch_account.py +0 -0
  349. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/unlink_account.py +0 -0
  350. {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tracing.py +0 -0
@@ -33,7 +33,7 @@ HeyLead is an autonomous LinkedIn SDR (Sales Development Representative) that ru
33
33
 
34
34
  ## Agent ops
35
35
 
36
- When the user asks what the agents did, who is held, or why a reply was skipped, call `inspect()` first. It is read-only and never writes.
36
+ When the user asks what the agents did, who is held, or why a reply was skipped, call `inspect()` first. It is read-only and never writes. If they ask what the agents left for the next tick, or who went dark, call `inspect(action='commons')`.
37
37
 
38
38
  A hold: `prospect(action="conversation", outreach_id="...")` then `send_message(action="reply", outreach_id="...")`. Operator replies skip the reply agent.
39
39
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: heylead
3
- Version: 0.10.332
3
+ Version: 0.10.333
4
4
  Summary: MCP-native autonomous LinkedIn SDR. Your AI sales rep, one command to fill your pipeline.
5
5
  Project-URL: Homepage, https://github.com/D4umak/heylead
6
6
  Project-URL: Repository, https://github.com/D4umak/heylead
@@ -77,7 +77,7 @@ After adding the MCP server, tell your OpenClaw agent:
77
77
 
78
78
  ## Agent ops
79
79
 
80
- When the user asks what the agents did, who is held, or why a reply was skipped, call `inspect()` first. It is read-only and never writes.
80
+ When the user asks what the agents did, who is held, or why a reply was skipped, call `inspect()` first. It is read-only and never writes. If they ask what the agents left for the next tick, or who went dark, call `inspect(action='commons')`.
81
81
 
82
82
  A hold: `prospect(action="conversation", outreach_id="...")` then `send_message(action="reply", outreach_id="...")`. Operator replies skip the reply agent.
83
83
 
@@ -80,7 +80,7 @@ After adding the MCP server, tell your OpenClaw agent:
80
80
 
81
81
  ## Agent ops
82
82
 
83
- When the user asks what the agents did, who is held, or why a reply was skipped, call `inspect()` first. It is read-only and never writes.
83
+ When the user asks what the agents did, who is held, or why a reply was skipped, call `inspect()` first. It is read-only and never writes. If they ask what the agents left for the next tick, or who went dark, call `inspect(action='commons')`.
84
84
 
85
85
  A hold: `prospect(action="conversation", outreach_id="...")` then `send_message(action="reply", outreach_id="...")`. Operator replies skip the reply agent.
86
86
 
@@ -17,7 +17,7 @@
17
17
  "icp",
18
18
  "campaign-management"
19
19
  ],
20
- "version": "0.10.332",
20
+ "version": "0.10.333",
21
21
  "license": "MIT",
22
22
  "author": {
23
23
  "name": "HeyLead",
@@ -0,0 +1,53 @@
1
+ # Agent Commons + Heartbeat Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Official inspectable scratch so later ticks can read earlier notes, and a host can see when a sibling agent went dark.
6
+
7
+ **Architecture:** New `agent_commons` table (beat upsert per agent+campaign; append-only notes with 24h expiry). Sidecar `record_agent_beat` after each of the four `run_agent_loop` returns. Kernel passes `reason`/`note` into tools that accept them. `inspect(action='commons')` is read-only. Client SQLite only.
8
+
9
+ **Tech Stack:** Python 3.12, SQLite, existing `run_agent_loop` / `inspect` / pytest (`PYTHONPATH=src python3.12 -m pytest`).
10
+
11
+ **Spec:** `docs/superpowers/specs/2026-09-09-agent-commons-heartbeat-design.md`
12
+
13
+ ---
14
+
15
+ ## Files
16
+
17
+ - Create: `src/heylead/services/agent_commons.py`
18
+ - Create: `tests/test_agent_commons.py`
19
+ - Modify: `src/heylead/db/schema.py` (`SCHEMA_VERSION` 9→10, `_SCHEMA_SQL`, `_apply_migrations`)
20
+ - Modify: `tests/test_migration_backup_gating.py` (`EXPECTED_MIGRATION_STATEMENTS`)
21
+ - Modify: `src/heylead/ai/agent_loop.py` (pass kwargs into tools)
22
+ - Modify: `src/heylead/ai/schemas.py` (four step enums + `note`)
23
+ - Modify: `src/heylead/services/reply_agent.py`, `strategist_replan.py`, `hot_lead_closer.py`, `icp_research.py`
24
+ - Modify: `src/heylead/tools/inspect.py`, `src/heylead/server.py`
25
+ - Modify: `tests/test_agent_loop.py`, `tests/test_inspect.py`, `tests/test_host_ops_playbook.py`
26
+ - Modify: `AGENTS.md`, `SKILL.md`, `clawhub/SKILL.md`
27
+
28
+ Do not commit unless asked. Do not bump the package version here.
29
+
30
+ ### Task 1: Failing tests
31
+
32
+ - [ ] `tests/test_agent_commons.py` — beat upsert, off skips beat, one note per run, expiry, 500-char refuse, inspect commons/agents, inspect does not write
33
+ - [ ] Extend `test_agent_loop.py` — tool receives `reason`/`note`
34
+ - [ ] Extend inspect unknown-action + no-write lists with `commons`
35
+ - [ ] Playbook requires `inspect(action='commons')`
36
+
37
+ ### Task 2: Schema + service + kernel
38
+
39
+ - [ ] Table + SCHEMA_VERSION 10
40
+ - [ ] `agent_commons` service
41
+ - [ ] Kernel `_invoke_tool`
42
+
43
+ ### Task 3: Hook four loops + inspect + playbook
44
+
45
+ - [ ] Tools + `record_agent_beat` after each loop
46
+ - [ ] `inspect(action='commons')` and agents one-liner
47
+ - [ ] Playbook / MCP instructions / inspect docstring
48
+
49
+ ### Task 4: Verify
50
+
51
+ ```
52
+ PYTHONPATH=src python3.12 -m pytest tests/test_agent_commons.py tests/test_agent_loop.py tests/test_inspect.py tests/test_host_ops_playbook.py tests/test_migration_backup_gating.py tests/test_reply_agent.py tests/test_icp_research.py tests/test_strategist_replan.py tests/test_hot_lead_closer.py -q
53
+ ```
@@ -0,0 +1,84 @@
1
+ # Agent commons + heartbeat
2
+
3
+ Date: 2026-09-09
4
+
5
+ The four in-process agents write `actions_log`. They barely read each other, and a host cannot see whether a sibling went dark. This spec adds an official, inspectable scratch — not a covert channel.
6
+
7
+ Inspiration is the May–June 2026 OpenAI wiki swarm (cross-run notes, heartbeat, adapt when the world changes). We copy that *shape*. We do not copy covert GET-writes, fighting the operator, impersonation, or sandbox escape.
8
+
9
+ First slice of a larger program (coordinator and `heylead-api` port come later). Client SQLite only.
10
+
11
+ ## Goals
12
+
13
+ - Table `agent_commons` with two kinds of row: `beat` and `note`.
14
+ - Python sidecar upserts a beat after every `run_agent_loop` return (observe or act).
15
+ - Kernel tools `read_commons` and `write_commons` on all four registries. One note per run.
16
+ - `inspect(action='commons')` lists beats, ages, live notes, and stale agents.
17
+ - `inspect(action='agents')` gains one commons/liveness line.
18
+ - Playbook: if they ask what the agents left for the next tick, or who went dark, call `inspect(action='commons')`.
19
+ - Notes expire in 24h. Body ≤ 500 characters.
20
+ - Stale = beat older than 6 hours and that agent is not `off`.
21
+
22
+ ## Non-goals
23
+
24
+ - Coordinator agent.
25
+ - Porting loops to `heylead-api`.
26
+ - Syncing commons to the backend.
27
+ - Operator-delete tool (v1: turn the agent `off`, or wait 24h). `inspect` stays read-only.
28
+ - Recreating a forgotten note under a new key (`ZZZ…`).
29
+ - LinkedIn / email / calendar writes from commons.
30
+ - Changing default observe behavior or act flags.
31
+ - New MCP tool.
32
+
33
+ ## Data
34
+
35
+ `agent` is `reply` | `strategist` | `closer` | `icp_research`.
36
+
37
+ | kind | uniqueness | fields |
38
+ |---|---|---|
39
+ | `beat` | one current row per `(campaign_id, agent)` | `decision`, `reason` (≤240 chars), `timestamp`. ICP research uses `campaign_id=""`. |
40
+ | `note` | append | `body`, optional `outreach_id`, `created_at`, `expires_at` (= created + 24h) |
41
+
42
+ `off` does not run the loop, so the beat is not refreshed and ages. That is the liveness signal.
43
+
44
+ ## Kernel
45
+
46
+ Today tools are invoked as `tool()` with no arguments. `write_commons` needs the note text.
47
+
48
+ `run_agent_loop` passes `reason` and extras into a tool when the callable accepts them (`reason=`, `note=` from extras, otherwise ignored). Each vertical step schema adds `read_commons` and `write_commons` to its `action` enum, plus a `note` string (empty = use `reason`).
49
+
50
+ `write_commons` refuses a second call in the same run. Empty / overlong body is refused. It writes only to `agent_commons`.
51
+
52
+ `read_commons` returns current beats plus unexpired notes, scoped to the campaign (and outreach when the loop has one).
53
+
54
+ Sidecar `record_agent_beat` runs after the loop returns, including `none` / exhausted. It does not run when the vertical is `off` (loop skipped).
55
+
56
+ ## Inspect
57
+
58
+ `inspect(action='commons')` — never writes, no LLM, no LinkedIn.
59
+
60
+ - Last beat per agent: age, last decision, short reason.
61
+ - Live notes (unexpired).
62
+ - Stale line when a beat is older than 6 hours and mode is not `off`.
63
+
64
+ `inspect(action='agents')` adds one line: live note count and the newest beat age (e.g. `commons: 2 live notes, reply beat 12m ago`).
65
+
66
+ Unknown action still lists the valid set (now includes `commons`).
67
+
68
+ ## Failure
69
+
70
+ - Query failure → short error; nothing written.
71
+ - `write_commons` refused → tool result string, loop continues.
72
+ - Missing table on old DBs → create via existing schema init (`CREATE TABLE IF NOT EXISTS`).
73
+
74
+ ## Tests
75
+
76
+ - Beat upserted after a reply (and strategist / closer / ICP) loop.
77
+ - `off` does not refresh the beat.
78
+ - `write_commons` first call persists; second call in the same run is refused.
79
+ - Expired notes are hidden from `read_commons` and `inspect(action='commons')`.
80
+ - Body over 500 characters is refused.
81
+ - `inspect(action='commons')` does not write.
82
+ - `inspect(action='agents')` mentions commons / beat.
83
+ - MCP `inspect` signature / dispatch includes `commons`.
84
+ - Playbook names `inspect(action='commons')`.
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "heylead"
7
- version = "0.10.332"
7
+ version = "0.10.333"
8
8
  description = "MCP-native autonomous LinkedIn SDR. Your AI sales rep, one command to fill your pipeline."
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -3,7 +3,7 @@
3
3
  "name": "io.github.D4umak/heylead",
4
4
  "title": "HeyLead \u2014 Autonomous LinkedIn SDR",
5
5
  "description": "Autonomous LinkedIn SDR \u2014 voice-matched outreach, ICP generation, and campaign management.",
6
- "version": "0.10.332",
6
+ "version": "0.10.333",
7
7
  "websiteUrl": "https://github.com/D4umak/heylead",
8
8
  "repository": {
9
9
  "url": "https://github.com/D4umak/heylead",
@@ -13,7 +13,7 @@
13
13
  {
14
14
  "registryType": "pypi",
15
15
  "identifier": "heylead",
16
- "version": "0.10.332",
16
+ "version": "0.10.333",
17
17
  "runtimeHint": "uvx",
18
18
  "transport": {
19
19
  "type": "stdio"
@@ -100,7 +100,7 @@ async def run_agent_loop(
100
100
  transcript += f"\n\nTOOL {action}: unknown tool"
101
101
  continue
102
102
  try:
103
- result = tool()
103
+ result = _invoke_tool(tool, reason=reason, extras=extras)
104
104
  if inspect.isawaitable(result):
105
105
  result = await result
106
106
  text = str(result)
@@ -137,3 +137,18 @@ def _parse_step(raw: str, valid_decisions: frozenset[str]) -> dict[str, Any]:
137
137
  "done": done,
138
138
  "extras": extras,
139
139
  }
140
+
141
+
142
+ def _invoke_tool(tool: ToolFn, *, reason: str, extras: dict[str, str]) -> str | Awaitable[str]:
143
+ kwargs: dict[str, str] = {}
144
+ try:
145
+ sig = inspect.signature(tool)
146
+ except (TypeError, ValueError):
147
+ return tool()
148
+ params = sig.parameters
149
+ accepts_var = any(p.kind == inspect.Parameter.VAR_KEYWORD for p in params.values())
150
+ if accepts_var or "reason" in params:
151
+ kwargs["reason"] = reason
152
+ if accepts_var or "note" in params:
153
+ kwargs["note"] = extras.get("note") or ""
154
+ return tool(**kwargs) if kwargs else tool()
@@ -67,7 +67,10 @@ SENTIMENT = _obj({
67
67
  AGENT_STEP = _obj({
68
68
  "action": {
69
69
  "type": "string",
70
- "enum": ["read_thread", "read_timeline", "read_icp", "read_fit", "decide"],
70
+ "enum": [
71
+ "read_thread", "read_timeline", "read_icp", "read_fit",
72
+ "read_commons", "write_commons", "decide",
73
+ ],
71
74
  },
72
75
  "decision": {
73
76
  "type": "string",
@@ -75,13 +78,17 @@ AGENT_STEP = _obj({
75
78
  },
76
79
  "reason": _STR,
77
80
  "done": {"type": "boolean"},
81
+ "note": _STR,
78
82
  })
79
83
 
80
84
  # ICP research vertical — extra strings are a flat filter patch (empty = no change).
81
85
  ICP_AGENT_STEP = _obj({
82
86
  "action": {
83
87
  "type": "string",
84
- "enum": ["read_icp", "preview_search", "read_filters", "decide"],
88
+ "enum": [
89
+ "read_icp", "preview_search", "read_filters",
90
+ "read_commons", "write_commons", "decide",
91
+ ],
85
92
  },
86
93
  "decision": {
87
94
  "type": "string",
@@ -89,6 +96,7 @@ ICP_AGENT_STEP = _obj({
89
96
  },
90
97
  "reason": _STR,
91
98
  "done": {"type": "boolean"},
99
+ "note": _STR,
92
100
  "titles_include": _STR,
93
101
  "titles_exclude": _STR,
94
102
  "locations_include": _STR,
@@ -101,7 +109,10 @@ ICP_AGENT_STEP = _obj({
101
109
  REPLAN_AGENT_STEP = _obj({
102
110
  "action": {
103
111
  "type": "string",
104
- "enum": ["read_plan", "read_signals", "read_thread", "read_status", "decide"],
112
+ "enum": [
113
+ "read_plan", "read_signals", "read_thread", "read_status",
114
+ "read_commons", "write_commons", "decide",
115
+ ],
105
116
  },
106
117
  "decision": {
107
118
  "type": "string",
@@ -109,6 +120,7 @@ REPLAN_AGENT_STEP = _obj({
109
120
  },
110
121
  "reason": _STR,
111
122
  "done": {"type": "boolean"},
123
+ "note": _STR,
112
124
  "remaining_json": _STR,
113
125
  })
114
126
 
@@ -116,7 +128,10 @@ REPLAN_AGENT_STEP = _obj({
116
128
  CLOSER_AGENT_STEP = _obj({
117
129
  "action": {
118
130
  "type": "string",
119
- "enum": ["read_thread", "read_contact", "read_calendar_target", "decide"],
131
+ "enum": [
132
+ "read_thread", "read_contact", "read_calendar_target",
133
+ "read_commons", "write_commons", "decide",
134
+ ],
120
135
  },
121
136
  "decision": {
122
137
  "type": "string",
@@ -124,6 +139,7 @@ CLOSER_AGENT_STEP = _obj({
124
139
  },
125
140
  "reason": _STR,
126
141
  "done": {"type": "boolean"},
142
+ "note": _STR,
127
143
  "attendee_email": _STR,
128
144
  "start_iso": _STR,
129
145
  "duration_minutes": _STR,
@@ -35,7 +35,7 @@ _DB_RETRY_BACKOFF = 0.1 # seconds, doubles each attempt
35
35
  # dedup read it as "already in a campaign" and never let the person back in.
36
36
  # 7: outreaches.chat_id (and headline A/B columns) — a v6 stamp skipped the
37
37
  # ALTER, so planning died with `no such column: o.chat_id`.
38
- SCHEMA_VERSION = 9 # 9: contacts.timezone (per-prospect planning windows); 8: versioned outreach sync
38
+ SCHEMA_VERSION = 10 # 10: agent_commons (beats + notes); 9: contacts.timezone
39
39
 
40
40
  # Singleton connection — avoids opening multiple connections per process
41
41
  # which causes "database is locked" errors with WAL mode.
@@ -605,6 +605,24 @@ CREATE TABLE IF NOT EXISTS company_posts_tracked (
605
605
  );
606
606
  CREATE INDEX IF NOT EXISTS idx_cpt_watchlist ON company_posts_tracked(watchlist_id);
607
607
  CREATE INDEX IF NOT EXISTS idx_cpt_post_id ON company_posts_tracked(post_id);
608
+
609
+ -- In-process agent scratch (heartbeat + short notes for the next tick)
610
+ CREATE TABLE IF NOT EXISTS agent_commons (
611
+ id TEXT PRIMARY KEY,
612
+ kind TEXT NOT NULL,
613
+ agent TEXT NOT NULL,
614
+ campaign_id TEXT NOT NULL DEFAULT '',
615
+ outreach_id TEXT,
616
+ body TEXT,
617
+ decision TEXT,
618
+ reason TEXT,
619
+ created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
620
+ expires_at INTEGER
621
+ );
622
+ CREATE INDEX IF NOT EXISTS idx_agent_commons_beat
623
+ ON agent_commons(campaign_id, agent, kind);
624
+ CREATE INDEX IF NOT EXISTS idx_agent_commons_notes
625
+ ON agent_commons(campaign_id, kind, expires_at);
608
626
  """
609
627
 
610
628
 
@@ -2278,6 +2296,31 @@ def _apply_migrations(conn: sqlite3.Connection) -> None:
2278
2296
  _reraise_if_migration_interrupted(e)
2279
2297
  logger.warning("Migration: outreach dedup failed: %s", e)
2280
2298
 
2299
+ try:
2300
+ conn.execute("SELECT 1 FROM agent_commons LIMIT 0")
2301
+ except sqlite3.OperationalError as e:
2302
+ _reraise_if_migration_interrupted(e)
2303
+ conn.executescript("""
2304
+ CREATE TABLE IF NOT EXISTS agent_commons (
2305
+ id TEXT PRIMARY KEY,
2306
+ kind TEXT NOT NULL,
2307
+ agent TEXT NOT NULL,
2308
+ campaign_id TEXT NOT NULL DEFAULT '',
2309
+ outreach_id TEXT,
2310
+ body TEXT,
2311
+ decision TEXT,
2312
+ reason TEXT,
2313
+ created_at INTEGER NOT NULL DEFAULT (strftime('%s', 'now')),
2314
+ expires_at INTEGER
2315
+ );
2316
+ CREATE INDEX IF NOT EXISTS idx_agent_commons_beat
2317
+ ON agent_commons(campaign_id, agent, kind);
2318
+ CREATE INDEX IF NOT EXISTS idx_agent_commons_notes
2319
+ ON agent_commons(campaign_id, kind, expires_at);
2320
+ """)
2321
+ conn.commit()
2322
+ logger.info("Migration: added agent_commons table")
2323
+
2281
2324
 
2282
2325
  def _backfill_global_contacts(conn: sqlite3.Connection) -> None:
2283
2326
  """One-time backfill: create global_contacts from existing campaign contacts.
@@ -28,6 +28,12 @@ from .ops_log import run_traced
28
28
  _CHANGELOG = """\
29
29
  # HeyLead Changelog
30
30
 
31
+ ## v0.10.333 (2026-09-09)
32
+ - New: in-process agents leave an inspectable commons — a heartbeat after
33
+ every loop, and one short note per run for the next tick.
34
+ inspect(action='commons') shows beats, live notes, and who went dark
35
+ (#275)
36
+
31
37
  ## v0.10.332 (2026-09-09)
32
38
  - New: brand_strategy(action="set_headline", focus="...") sets the LinkedIn
33
39
  headline to exactly the text you pass. execute and makeover both write a
@@ -1236,7 +1242,9 @@ mcp = FastMCP(
1236
1242
  " edit_campaign(enable_reply_agent='on'),\n"
1237
1243
  " edit_campaign(enable_strategist_replan_agent='on'), or\n"
1238
1244
  " edit_campaign(enable_hot_lead_closer='on'). Use 'observe' to\n"
1239
- " return to logging-only, 'off' to disable.\n"
1245
+ " return to logging-only, 'off' to disable. If they ask what the\n"
1246
+ " agents left for the next tick, or who went dark, call\n"
1247
+ " inspect(action='commons').\n"
1240
1248
  " - book_meeting() — put an agreed call on your Google Calendar and invite them\n"
1241
1249
  " - suggest_next_action() — AI-recommended next step\n"
1242
1250
  " - analytics(action='report') — detailed analytics with outcomes and stale lead warnings\n"
@@ -2299,6 +2307,7 @@ async def inspect(
2299
2307
  "closer" — today's hot_lead_closer_decision rows
2300
2308
  "skips" — recent reply skips (hard gates, cap, dedup, agent skip)
2301
2309
  "jobs" — pending scheduler jobs and recent gated-job refusals
2310
+ "commons" — last agent beats, live notes, and stale liveness
2302
2311
  campaign_id: Optional campaign filter (full id or prefix).
2303
2312
  outreach_id: Optional outreach filter (full id or prefix).
2304
2313
  limit: Max rows per slice, 1-100 (default 20).
@@ -0,0 +1,227 @@
1
+ """Official inspectable scratch for in-process agents.
2
+
3
+ Sidecar beats after every loop. Tools may leave one short note per run.
4
+ Nothing here writes LinkedIn, email, or calendar.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import time
10
+ import uuid
11
+ from collections.abc import Callable
12
+ from typing import Any
13
+
14
+ from ..db.schema import get_db
15
+
16
+ VALID_AGENTS = frozenset({"reply", "strategist", "closer", "icp_research"})
17
+ NOTE_MAX_CHARS = 500
18
+ BEAT_REASON_MAX = 240
19
+ NOTE_TTL_SECONDS = 24 * 3600
20
+ STALE_AFTER_SECONDS = 6 * 3600
21
+
22
+
23
+ def beat_is_stale(
24
+ beat: dict[str, Any],
25
+ *,
26
+ mode: str,
27
+ now: int | None = None,
28
+ ) -> bool:
29
+ if (mode or "").strip().lower() == "off":
30
+ return False
31
+ ts = int(beat.get("created_at") or 0)
32
+ when = int(now if now is not None else time.time())
33
+ return bool(ts) and (when - ts) > STALE_AFTER_SECONDS
34
+
35
+
36
+ def upsert_beat(
37
+ *,
38
+ agent: str,
39
+ campaign_id: str,
40
+ decision: str,
41
+ reason: str,
42
+ timestamp: int | None = None,
43
+ ) -> None:
44
+ if agent not in VALID_AGENTS:
45
+ return
46
+ cid = campaign_id or ""
47
+ ts = int(timestamp if timestamp is not None else time.time())
48
+ text = (reason or "")[:BEAT_REASON_MAX]
49
+ db = get_db()
50
+ try:
51
+ row = db.execute(
52
+ """SELECT id FROM agent_commons
53
+ WHERE kind = 'beat' AND campaign_id = ? AND agent = ?""",
54
+ (cid, agent),
55
+ ).fetchone()
56
+ if row:
57
+ db.execute(
58
+ """UPDATE agent_commons
59
+ SET decision = ?, reason = ?, created_at = ?
60
+ WHERE id = ?""",
61
+ (decision, text, ts, row["id"]),
62
+ )
63
+ else:
64
+ db.execute(
65
+ """INSERT INTO agent_commons
66
+ (id, kind, agent, campaign_id, decision, reason, created_at)
67
+ VALUES (?, 'beat', ?, ?, ?, ?, ?)""",
68
+ (str(uuid.uuid4()), agent, cid, decision, text, ts),
69
+ )
70
+ db.commit()
71
+ finally:
72
+ db.close()
73
+
74
+
75
+ def insert_note(
76
+ *,
77
+ agent: str,
78
+ campaign_id: str,
79
+ body: str,
80
+ outreach_id: str = "",
81
+ now: int | None = None,
82
+ ) -> str:
83
+ if agent not in VALID_AGENTS:
84
+ return "refused: unknown agent"
85
+ text = (body or "").strip()
86
+ if not text:
87
+ return "refused: empty note"
88
+ if len(text) > NOTE_MAX_CHARS:
89
+ return f"refused: note longer than {NOTE_MAX_CHARS} characters"
90
+ created = int(now if now is not None else time.time())
91
+ db = get_db()
92
+ try:
93
+ db.execute(
94
+ """INSERT INTO agent_commons
95
+ (id, kind, agent, campaign_id, outreach_id, body, created_at, expires_at)
96
+ VALUES (?, 'note', ?, ?, ?, ?, ?, ?)""",
97
+ (
98
+ str(uuid.uuid4()),
99
+ agent,
100
+ campaign_id or "",
101
+ outreach_id or None,
102
+ text,
103
+ created,
104
+ created + NOTE_TTL_SECONDS,
105
+ ),
106
+ )
107
+ db.commit()
108
+ finally:
109
+ db.close()
110
+ return "ok"
111
+
112
+
113
+ def list_beats(*, campaign_id: str = "") -> list[dict[str, Any]]:
114
+ db = get_db()
115
+ try:
116
+ if campaign_id:
117
+ rows = db.execute(
118
+ """SELECT * FROM agent_commons
119
+ WHERE kind = 'beat' AND (campaign_id = ? OR campaign_id = '')
120
+ ORDER BY created_at DESC""",
121
+ (campaign_id,),
122
+ ).fetchall()
123
+ else:
124
+ rows = db.execute(
125
+ """SELECT * FROM agent_commons
126
+ WHERE kind = 'beat'
127
+ ORDER BY created_at DESC""",
128
+ ).fetchall()
129
+ return [dict(r) for r in rows]
130
+ finally:
131
+ db.close()
132
+
133
+
134
+ def list_live_notes(
135
+ *,
136
+ campaign_id: str = "",
137
+ outreach_id: str = "",
138
+ now: int | None = None,
139
+ ) -> list[dict[str, Any]]:
140
+ when = int(now if now is not None else time.time())
141
+ db = get_db()
142
+ try:
143
+ params: list[Any] = [when]
144
+ where = ["kind = 'note'", "(expires_at IS NULL OR expires_at > ?)"]
145
+ if campaign_id:
146
+ where.append("(campaign_id = ? OR campaign_id = '')")
147
+ params.append(campaign_id)
148
+ if outreach_id:
149
+ where.append("(outreach_id = ? OR outreach_id IS NULL)")
150
+ params.append(outreach_id)
151
+ rows = db.execute(
152
+ f"""SELECT * FROM agent_commons
153
+ WHERE {' AND '.join(where)}
154
+ ORDER BY created_at DESC""",
155
+ params,
156
+ ).fetchall()
157
+ return [dict(r) for r in rows]
158
+ finally:
159
+ db.close()
160
+
161
+
162
+ def read_commons_text(
163
+ *,
164
+ campaign_id: str = "",
165
+ outreach_id: str = "",
166
+ now: int | None = None,
167
+ ) -> str:
168
+ beats = list_beats(campaign_id=campaign_id)
169
+ notes = list_live_notes(campaign_id=campaign_id, outreach_id=outreach_id, now=now)
170
+ lines = ["beats:"]
171
+ if beats:
172
+ for beat in beats:
173
+ lines.append(
174
+ f"- {beat.get('agent')}: {beat.get('decision') or '?'} "
175
+ f"— {(beat.get('reason') or '')[:120]}"
176
+ )
177
+ else:
178
+ lines.append("- (none)")
179
+ lines.append("notes:")
180
+ if notes:
181
+ for note in notes:
182
+ lines.append(f"- {note.get('agent')}: {note.get('body') or ''}")
183
+ else:
184
+ lines.append("- (none)")
185
+ return "\n".join(lines)
186
+
187
+
188
+ def commons_tools(
189
+ *,
190
+ agent: str,
191
+ campaign_id: str,
192
+ outreach_id: str = "",
193
+ ) -> dict[str, Callable[..., str]]:
194
+ wrote = {"n": 0}
195
+
196
+ def read_commons() -> str:
197
+ return read_commons_text(campaign_id=campaign_id, outreach_id=outreach_id)
198
+
199
+ def write_commons(reason: str = "", note: str = "") -> str:
200
+ if wrote["n"]:
201
+ return "refused: one note per run"
202
+ result = insert_note(
203
+ agent=agent,
204
+ campaign_id=campaign_id,
205
+ outreach_id=outreach_id,
206
+ body=(note or reason),
207
+ )
208
+ if result == "ok":
209
+ wrote["n"] += 1
210
+ return result
211
+
212
+ return {"read_commons": read_commons, "write_commons": write_commons}
213
+
214
+
215
+ def record_agent_beat(
216
+ *,
217
+ agent: str,
218
+ campaign_id: str,
219
+ decision: str,
220
+ reason: str,
221
+ ) -> None:
222
+ upsert_beat(
223
+ agent=agent,
224
+ campaign_id=campaign_id,
225
+ decision=decision,
226
+ reason=reason,
227
+ )