You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Closesleadbay/product#3704.
Two root causes, both fixed:
1. Capability gap — no way to scan a portfolio for a signal in bulk. JM built
a 497-lead Monitor portfolio, asked which had an M&A signal since 2025, and
the agent fell back to one research_lead_by_id call per lead (~60 calls)
before abandoning the task. New composite leadbay_scan_portfolio_signals
bulk-reads CACHED web_fetch signals across the portfolio (read-only GET
fan-out, no quota burn), filters entries by a diacritic/case-folded query
+ optional `since` date, and returns the matched cohort campaign-ready.
2. stale_at hallucination — the agent inferred signal presence/absence from
freshness fields and reported confident-but-wrong results. New
snippets/gates/signal-honesty.md guardrail, included in pull-followups,
research-lead-by-id, and the followup_check_in prompt: freshness markers are
not signal indicators; route portfolio-wide signal questions to the bulk
tool; unresearched leads are reported (not_researched), never fabricated.
The tool separates "no matching signal" (researched, no match) from "not yet
researched" (no data to search) via a not_researched[] bucket — the structural
antidote to the original hallucination.
Verified live against the US test account end-to-end through the MCP server:
one scan_portfolio_signals call over 169 leads correctly surfaced genuine
post-2025 acquirers, with the agent reading the signal text to discard
false-positive senses of "acquisition" and reporting 0 unresearched.
- Extracts web_fetch reshaping into shared _web-fetch-helpers.ts (reused by
research-lead-by-id).
- Registered in compositeReadTools, _composite-file-names, TOOLS_WITH_ROUTING,
output-schema conformance, WORKFLOWS.md.
- Unit tests (new file) cover match/since/diacritics/not_researched/429/cap.
- Two eval scenarios authored (underdeliver + honesty); runner glue not yet on
this branch, so they are fixture-ready, not wired.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: .claude-plugin/plugins/leadbay/skills/leadbay_followup_check_in/SKILL.md
+23Lines changed: 23 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -166,6 +166,29 @@ Unlike `leadbay_daily_check_in` which deep-dives on every promising lead in Phas
166
166
167
167
When the user picks a row, call `leadbay_research_lead_by_id` on that single lead (or `leadbay_research_lead_by_name_fuzzy` if they only have the name) and offer to `leadbay_prepare_outreach` once they say "let's reach out".
168
168
169
+
# PHASE 3b — BULK SIGNAL SCAN (when the user asks "which of these have signal X")
170
+
171
+
If the user wants to filter the whole portfolio by a web-research signal — "which of my leads acquired a company since 2025", "find everyone with a funding signal", "who changed CEO" — do NOT loop `leadbay_research_lead_by_id` per row, and do NOT guess from freshness fields. Call `leadbay_scan_portfolio_signals({query, since?})` once: it bulk-reads the cached signals across the portfolio and returns only the matches, campaign-ready. Report any `not_researched` leads honestly ("K aren't researched yet — want me to qualify them and re-scan?"), and offer to build a campaign from the matches.
172
+
173
+
**SIGNAL HONESTY — never infer signals from freshness.**`stale_at`,
174
+
`web_fetch_in_progress`, `fetch_at` and `web_insights_fetched_at` are
175
+
FRESHNESS markers, not signal indicators. A fresh timestamp does **not** mean a
176
+
given signal (M&A, funding, a new hire, a CEO change) is present; a stale or
177
+
missing one does **not** mean it's absent. The presence of a signal is
178
+
determined ONLY by reading the actual `signals[]` / `web_fetch.content`
179
+
entries.
180
+
181
+
To answer "which of my leads have signal X" across a portfolio, call
182
+
**`leadbay_scan_portfolio_signals`** — it bulk-reads the cached signals and
183
+
filters them for you. Do NOT loop `leadbay_research_lead_by_id` one lead at a
184
+
time, and do NOT guess from list-level freshness flags.
185
+
186
+
If a lead has no cached signal content, say so honestly — "not yet researched,
187
+
want me to qualify it?" — and surface it as `not_researched`. Never fabricate
188
+
or imply a scan you didn't actually run, and never report a confident
189
+
signal-presence verdict for a lead whose signals you never read.
190
+
191
+
169
192
# CROSS-MODE PIVOT
170
193
171
194
Below the table, offer the cross-mode pivot in one short line so the user can redirect if you guessed wrong on entry-point routing: "Want to see NEW leads from your wishlist instead?" — that routes back to `leadbay_daily_check_in` (Discovery via `leadbay_pull_leads`).
Copy file name to clipboardExpand all lines: WORKFLOWS.md
+1Lines changed: 1 addition & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -26,6 +26,7 @@ The table is the human-readable index. The `yaml expected` + `yaml scenario` blo
26
26
| 12 |**Lens extension — on-demand fill for bigger appetite** — "I want more leads on this lens / I need a bigger batch today" |`leadbay_extend_my_lens`| "I want more leads on this lens — bigger batch today" |
27
27
| 13 |**Lens management — list / switch audiences** — "show me my lenses", "which audiences do I have", "switch to my Joinery lens" |`leadbay_my_lenses`| "Show me my lenses and switch to the Joinery one" |
28
28
| 14 |**Lens creation — make a named audience** — "create a lens called X for sector Y", "set up a new audience" |`leadbay_new_lens`| "Create a lens called Joinery for the fintech sector" |
29
+
| 15 |**Bulk portfolio signal scan** — "which of my leads acquired a company since 2025", "scan my portfolio for funding signals", "find everyone who changed CEO" — filters a known portfolio by a web-research signal in ONE call instead of looping `leadbay_research_lead_by_id` per lead |`leadbay_scan_portfolio_signals`| "Which of my leads acquired a company since 2025?" |
0 commit comments