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.
- {heylead-0.10.332 → heylead-0.10.333}/AGENTS.md +1 -1
- {heylead-0.10.332 → heylead-0.10.333}/PKG-INFO +1 -1
- {heylead-0.10.332 → heylead-0.10.333}/SKILL.md +1 -1
- {heylead-0.10.332 → heylead-0.10.333}/clawhub/SKILL.md +1 -1
- {heylead-0.10.332 → heylead-0.10.333}/clawhub.json +1 -1
- heylead-0.10.333/docs/superpowers/plans/2026-09-09-agent-commons-heartbeat.md +53 -0
- heylead-0.10.333/docs/superpowers/specs/2026-09-09-agent-commons-heartbeat-design.md +84 -0
- {heylead-0.10.332 → heylead-0.10.333}/pyproject.toml +1 -1
- {heylead-0.10.332 → heylead-0.10.333}/server.json +2 -2
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/agent_loop.py +16 -1
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/schemas.py +20 -4
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/db/schema.py +44 -1
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/server.py +10 -1
- heylead-0.10.333/src/heylead/services/agent_commons.py +227 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/hot_lead_closer.py +12 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/icp_research.py +9 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/reply_agent.py +11 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/strategist_replan.py +12 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/inspect.py +91 -2
- {heylead-0.10.332 → heylead-0.10.333}/.cursorrules +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/.github/workflows/publish.yml +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/.github/workflows/test.yml +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/.gitignore +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/Dockerfile +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/LICENSE +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/README.md +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/clawhub/clawhub.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/docs/AUDIT-2026-08.md +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/docs/CLI_AND_DX_IMPROVEMENTS.md +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/docs/EXPLICIT_IMPROVEMENT_PLAN.md +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/docs/HANDOFF-2026-08-06.md +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/docs/QUALITY_AND_SATISFACTION_ASSESSMENT.md +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/docs/TOOL_TEST_REPORT.md +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/docs/USER_STORY_TEST.md +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/docs/product-listing-copy.md +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/plans/2026-08-07-cloud-sync-push.md +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/prompts/2026-09-09-quontex-feedback/00-shared-context.md +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/prompts/2026-09-09-quontex-feedback/01-first-degree-client.md +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/prompts/2026-09-09-quontex-feedback/02-first-degree-backend-dashboard.md +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/prompts/2026-09-09-quontex-feedback/03-duplicate-sends-hardening.md +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/prompts/2026-09-09-quontex-feedback/04-reply-context-backend.md +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/prompts/2026-09-09-quontex-feedback/05-reply-context-client.md +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/prompts/2026-09-09-quontex-feedback/06-decision-maker-targeting.md +0 -0
- {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
- {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/prompts/2026-09-09-quontex-feedback/08-stop-feedback-learning-loop.md +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/prompts/2026-09-09-quontex-feedback/README.md +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/specs/2026-08-23-premium-mixed-outreach-design.md +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/specs/2026-08-23-unanswered-lead-alerts-design.md +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/specs/2026-09-08-campaign-prospect-provenance-design.md +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/specs/2026-09-09-edit-campaign-agent-flags-design.md +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/specs/2026-09-09-host-ops-playbook-design.md +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/specs/2026-09-09-hot-lead-closer-design.md +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/specs/2026-09-09-icp-research-agent-design.md +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/specs/2026-09-09-inspect-agent-ops-design.md +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/specs/2026-09-09-inspect-jobs-design.md +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/specs/2026-09-09-reply-exception-agent-design.md +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/docs/superpowers/specs/2026-09-09-strategist-replan-agent-design.md +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/llms-full.txt +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/llms.txt +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/scripts/find_query_contracts.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/scripts/fix_stuck_prospects.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/scripts/reactivate_existing_network.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/scripts/rekey_unipile_account.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/scripts/release.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/scripts/seed_shared_directory.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/scripts/test_reaction_live.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/smithery.yaml +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/__init__.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/__main__.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/__init__.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/brand_strategist.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/brief_builder.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/comment_generator.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/context_summarizer.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/draft_guard.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/email_generator.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/embeddings.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/evidence.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/followup_generator.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/followup_schemas.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/hot_lead_closer.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/hume_client.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/hume_voice.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/icp_generator.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/icp_generator_v2.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/icp_pipeline.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/icp_research.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/icp_schemas.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/inbound_qualifier.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/ingestion/__init__.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/ingestion/chunker.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/ingestion/crawler.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/ingestion/store.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/intent.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/length_fixer.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/linkedin_enricher.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/llm.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/llm_router.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/llm_validator.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/memory_extractor.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/memory_formatter.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/message_fixer.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/message_generator.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/message_improver.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/message_validator.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/news_service.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/persona_cards.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/post_analyzer.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/prompt_loader.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/prospect_analyzer.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/referral_extractor.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/reply_agent.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/reply_generator.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/reply_pipeline.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/sentiment.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/signal_classifier.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/strategist_prompts.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/strategist_replan.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/strategy_prompts.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/targeting_recheck.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/vector_search.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/voice_analyzer.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ai/voice_memo_generator.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/author_identity.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/cli/__init__.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/cli/log_analyzer.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/collectors/__init__.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/collectors/comment_mining_collector.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/collectors/company_follower_collector.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/collectors/company_page_collector.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/collectors/competitor_collector.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/collectors/distributed_post_collector.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/collectors/hiring_collector.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/collectors/job_change_collector.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/collectors/keyword_collector.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/collectors/network_post_collector.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/collectors/news_collector.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/collectors/post_intent_collector.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/collectors/profile_view_collector.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/collectors/prospect_post_collector.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/collectors/watchlist_web_collector.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/config.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/constants.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/correlation.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/daemon.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/data/country_signals/brazil.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/data/country_signals/france.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/data/country_signals/germany.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/data/country_signals/india.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/data/country_signals/mexico.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/data/country_signals/nigeria.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/data/country_signals/philippines.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/data/country_signals/poland.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/data/country_signals/ukraine.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/data/country_signals/united_kingdom.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/data/country_signals/vietnam.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/db/__init__.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/db/aio.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/db/async_bridge.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/db/backup.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/db/connection_queries.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/db/global_contact_queries.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/db/post_queries.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/db/queries.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/db/signal_queries.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/db/strategist_queries.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/db/strategy_queries.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/flags.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/formatter.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/guardrails.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/linkedin/__init__.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/linkedin/api_metrics.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/linkedin/backend_client.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/linkedin/chat_lookup_cache.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/linkedin/circuit_breaker.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/linkedin/experience.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/linkedin/messaging.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/linkedin/profile.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/linkedin/profile_normalize.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/linkedin/rate_limiter.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/linkedin/relations.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/linkedin/search.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/linkedin/search_traffic.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/linkedin/unipile.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/linkedin/voyager_health.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/logging_setup.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/ops_log.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/__init__.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/comment_main.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/filter_candidates.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/followup_fix.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/followup_message.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/followup_message_buy.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/followup_news.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/followup_reasoning.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/followup_reasoning_buy.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/followup_validate.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/followup_validate_buy.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/fragments/voice_rules.txt +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/message_fix.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/message_improve.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/outreach_inmail.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/outreach_inmail_buy.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/outreach_invitation.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/outreach_invitation_buy.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/outreach_job_search.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/outreach_response.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/outreach_response_buy.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/outreach_system.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/outreach_system_buy.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/outreach_system_partner.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/outreach_system_recruit.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/outreach_validate.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/outreach_validate_buy.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/prospect_analysis.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/prompts/voice_analysis.json +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/scheduler/__init__.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/scheduler/engine.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/scheduler/enqueue_gate.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/scheduler/executors.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/scheduler/leader.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/scheduler/planner.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/scheduler/trace.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/__init__.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/action_health.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/action_timeline.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/action_verifier.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/anomaly_detector.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/brand_service.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/calendar_booker.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/campaign_optimizer.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/campaign_refill_service.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/campaign_spawner.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/channel_selector.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/cloud_sync.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/communication_strategist.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/company_name.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/connection_sync.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/conversation_enricher.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/daily_digest.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/dedup_service.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/directory_card.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/dm_connection_guard.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/engagement_verifier.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/experiment_service.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/fit_gate.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/health_score.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/hubspot_service.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/icp_match_scorer.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/icp_search.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/inbound_helpers.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/inbound_pipeline.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/inbound_policy.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/inbound_relationship.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/inbound_service.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/inbox_match.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/intent_signals.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/live_thread_enroll.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/outreach_channel.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/own_identity.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/own_profile_sync.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/pattern_detector.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/post_research_service.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/process_caches.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/profile_signals.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/project_brief.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/prospect_email.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/prospect_timezone.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/provider_id_repair.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/provider_id_resolver.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/recover_stuck_outreaches.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/referral_enroll.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/revenue_estimator.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/search_account_resolver.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/seed_shared_directory.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/session_health.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/signal_activator.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/signal_analytics.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/signal_linker.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/signal_optimizer.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/signal_scorer.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/signal_service.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/strategy_engine.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/tabular_reader.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/timezone_resolver.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/unanswered_lead_alerts.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/services/unipile_email.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/textutil.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tier.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/timeutil.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/__init__.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/account.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/analytics.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/archive_campaign.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/backfill_inbox.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/book_meeting.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/brand_strategy.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/campaign.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/campaign_control.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/campaign_report.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/check_replies.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/close_outreach.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/compare_campaigns.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/contacts.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/create_campaign.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/create_post.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/crm_sync.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/delete_campaign.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/delete_message.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/dismiss_lead.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/edit_campaign.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/emergency_stop.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/engage_prospect.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/experiment_loop.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/export_campaign.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/generate_icp.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/generate_send.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/icp.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/import_prospects.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/inbox.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/manage_watchlist.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/network.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/organization.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/partner_followup.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/profile_editor.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/profile_history.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/profile_signals.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/prospect.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/prospect_timeline.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/reply_to_prospect.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/retry_failed.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/scheduler.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/scheduler_status.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/send_email.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/send_followup.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/send_inmail.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/send_message.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/send_voice_memo.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/setup_profile.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/show_conversation.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/show_signals.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/show_status.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/show_strategy.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/signal_report.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/signals.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/skip_prospect.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/suggest_next_action.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/switch_account.py +0 -0
- {heylead-0.10.332 → heylead-0.10.333}/src/heylead/tools/unlink_account.py +0 -0
- {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.
|
|
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
|
|
|
@@ -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')`.
|
|
@@ -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.
|
|
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.
|
|
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": [
|
|
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": [
|
|
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": [
|
|
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": [
|
|
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 =
|
|
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
|
|
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
|
+
)
|