@leadbay/mcp 0.27.0 → 0.28.0
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.
- package/CHANGELOG.md +55 -0
- package/dist/bin.js +481 -1
- package/dist/http-server.js +481 -1
- package/dist/installer-electron.js +1 -1
- package/dist/installer-gui.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,60 @@
|
|
|
1
1
|
# Changelog — @leadbay/mcp
|
|
2
2
|
|
|
3
|
+
## 0.28.0 — 2026-07-31
|
|
4
|
+
|
|
5
|
+
Add **`leadbay_top_accounts_to_activate`** — a prompt that builds a ranked
|
|
6
|
+
account-**conquest** plan from Leadbay data: the accounts worth activating,
|
|
7
|
+
ranked by the strongest Leadbay signal (not by money — Leadbay holds no
|
|
8
|
+
invoicing data), each carrying one of six strategic motifs (SAUVETAGE / PLAN DE
|
|
9
|
+
COMPTE / MONTÉE EN GAMME / RÉVEIL / CONQUÊTE / SUIVI) that drives its phone
|
|
10
|
+
pitch and three-step checklist (product#3863). Renders via `leadbay_artifact_kit` when the user
|
|
11
|
+
accepts the offered deck; the chat table stands alone otherwise.
|
|
12
|
+
|
|
13
|
+
**Leadbay-only by design.** Leadbay holds no invoicing data, so the plan ranks
|
|
14
|
+
by the strongest Leadbay signal and renders revenue-realized, per-family revenue
|
|
15
|
+
and cash-to-capture as **OMITTED — never estimated**, naming the extract columns
|
|
16
|
+
a cash-ranked version would require. An ERP-ingest mode was explored and
|
|
17
|
+
deliberately cut before release: it depended on inputs the platform cannot
|
|
18
|
+
supply (headcount for off-lens imported leads) and permissions it cannot assume
|
|
19
|
+
(`leadbay_import_leads` is admin-only). It is tracked separately.
|
|
20
|
+
|
|
21
|
+
**New `gates/data-provenance` snippet.** The deliverable mixes Leadbay
|
|
22
|
+
responses, public-registry counts and modelled assumptions in
|
|
23
|
+
front of a paying client, so every emitted number carries a provenance class
|
|
24
|
+
(`[ERP]` / `[LB]` / `[SIRENE]` / `[HYP]`), taint propagates through derived
|
|
25
|
+
figures, and a PROVENANCE LEDGER prints **before** the plan with un-sourceable
|
|
26
|
+
fields shown as OMITTED instead of quietly filled. The existing
|
|
27
|
+
`iron-laws/no-fabrication` covers identifiers only, so a derived quantity such
|
|
28
|
+
as a revenue figure could previously be invented without violating it.
|
|
29
|
+
|
|
30
|
+
Also adds `heuristics/account-activation-motifs` and `rendering/activation-card`,
|
|
31
|
+
plus WORKFLOWS entries 46–47 — the conquest plan and its fabrication guard.
|
|
32
|
+
|
|
33
|
+
The six motifs are SAUVETAGE / PLAN DE COMPTE / MONTÉE EN GAMME / RÉVEIL /
|
|
34
|
+
CONQUÊTE / **SUIVI**. The last is the honest label for an active known-pipeline
|
|
35
|
+
account when no order history is available: the four Pilotage motifs are
|
|
36
|
+
purchase-behaviour reads and CONQUÊTE means "not in the pipeline", so without
|
|
37
|
+
invoicing data an active Monitor row matches none of them. ERP order data would
|
|
38
|
+
split those rows into the Pilotage four.
|
|
39
|
+
|
|
40
|
+
Fixes a stale `server.json` npx pin that tracked the `0.25` line while the
|
|
41
|
+
package was on `0.26`, and bumps the `.claude-plugin` bundle off `@leadbay/mcp@0.13`,
|
|
42
|
+
which predated `leadbay_get_qualification_questions`,
|
|
43
|
+
`leadbay_scan_portfolio_signals` and `leadbay_artifact_kit`.
|
|
44
|
+
|
|
45
|
+
## 0.27.0 — 2026-07-31
|
|
46
|
+
|
|
47
|
+
Consent-gated friction reporting (#171) — see that PR for detail. Released from
|
|
48
|
+
`main`; this entry is a placeholder so the version line is unbroken.
|
|
49
|
+
|
|
50
|
+
## 0.26.0 — 2026-07-27 (tagged, never published)
|
|
51
|
+
|
|
52
|
+
Makes **`leadbay_build_campaign`** autonomous: runs to a target `count` with no
|
|
53
|
+
confirm gates, and accepts explicit `job_titles` for buyer enrichment (#169).
|
|
54
|
+
The version was bumped and `mcp-v0.26.0` tagged, but the npm publish did not
|
|
55
|
+
complete — npm's latest remained `0.25.0`. Documented here retroactively;
|
|
56
|
+
`0.27.0` supersedes it and carries these changes forward.
|
|
57
|
+
|
|
3
58
|
## 0.25.0 — 2026-07-13
|
|
4
59
|
|
|
5
60
|
Add `leadbay_set_telemetry` — an in-product control to enable, disable, or check
|
package/dist/bin.js
CHANGED
|
@@ -25241,6 +25241,457 @@ End with a NEXT STEPS chip via your host's choice widget (\`ask_user_input_v0\`
|
|
|
25241
25241
|
|
|
25242
25242
|
Done. The lens is live, the validated cohort is persisted as named campaigns, and the manager knows where the cross-user-visibility gap is.
|
|
25243
25243
|
`;
|
|
25244
|
+
var leadbay_top_accounts_to_activate = `
|
|
25245
|
+
## MEMORY
|
|
25246
|
+
|
|
25247
|
+
Before responding, glance at any \`_meta.agent_memory.summary\` returned by tool calls earlier in this session and reflect its top signals in your reasoning ("Filtering by your stated preference for healthcare"). After any material new signal from the user this conversation (sector, region, deal size, communication style, qualification rule, explicit retraction, or recurrence / scheduling preference such as "I do this every day" or "remind me every morning"), call \`leadbay_agent_memory_capture\` to persist it: \`source:"user_stated"\` if literal, \`source:"inferred"\` with confidence <=6 if inferred.
|
|
25248
|
+
|
|
25249
|
+
|
|
25250
|
+
Build me a **top-{{arg:count_or_default}} account-conquest plan** \u2014 the accounts worth activating, ranked, each one carrying a strategic motif, a phone pitch and a three-step checklist. {{arg:territory_block}}
|
|
25251
|
+
|
|
25252
|
+
This deliverable goes in front of a paying client, so **the honesty of the numbers matters more than their completeness**. Deliver the strongest plan the available data actually supports, and be explicit about what it doesn't.
|
|
25253
|
+
|
|
25254
|
+
**DATA PROVENANCE \u2014 every number carries its source.** This deliverable mixes
|
|
25255
|
+
four data sources with very different trust levels, and it is shown to a
|
|
25256
|
+
paying client. A figure whose origin is unstated reads as measured fact. Tag
|
|
25257
|
+
every number you emit with exactly one class:
|
|
25258
|
+
|
|
25259
|
+
| Tag | Meaning | Where it comes from |
|
|
25260
|
+
|---|---|---|
|
|
25261
|
+
| \`[ERP]\` | measured in the client's own invoicing / management extract | the file the user attached, or a \`leadbay_get_lead_custom_fields\` read of a value imported from it |
|
|
25262
|
+
| \`[LB]\` | returned by a \`leadbay_*\` tool THIS session | \`leadbay_pull_leads\`, \`leadbay_pull_followups\`, \`leadbay_bulk_qualify_leads\`, \`leadbay_enrich_titles\`, \`leadbay_scan_portfolio_signals\`, \`leadbay_account_history\`, \`leadbay_research_lead_by_id\` |
|
|
25263
|
+
| \`[SIRENE]\` | the French public company registry | \`recherche-entreprises.api.gouv.fr\` \u2014 **your own web tool, NOT Leadbay.** Leadbay does not proxy the registry |
|
|
25264
|
+
| \`[HYP]\` | a modelled assumption | the \u20AC/employee benchmark, the 35 % objective, the trade purchase mix, any Tier-1 threshold the client hasn't confirmed |
|
|
25265
|
+
|
|
25266
|
+
**Taint propagates.** A derived figure inherits the weakest class of its
|
|
25267
|
+
inputs. \`cash = pot12 \u2212 ca12\` where \`pot12\` is \`[HYP]\` makes **\`cash\` itself
|
|
25268
|
+
\`[HYP]\`** \u2014 say so in the artefact's own caveat block, not only in chat. A
|
|
25269
|
+
client who mistakes a modelled \`cash\` figure for an audited one will build a
|
|
25270
|
+
sales plan on it.
|
|
25271
|
+
|
|
25272
|
+
**Print the PROVENANCE LEDGER before you build anything**, BEFORE writing
|
|
25273
|
+
artifact code or the final table. The block below is a **shape, not a literal**:
|
|
25274
|
+
keep the header, the \`field / class / source\` columns and the closing rule, but
|
|
25275
|
+
**replace every \`<...>\` placeholder with the real field name, class and source**
|
|
25276
|
+
\u2014 one row per field you actually emit. A ledger still showing \`<field name>\` has
|
|
25277
|
+
passed the ordering check while telling the reader nothing, which defeats its
|
|
25278
|
+
entire purpose.
|
|
25279
|
+
|
|
25280
|
+
\`\`\`
|
|
25281
|
+
PROVENANCE LEDGER
|
|
25282
|
+
=================
|
|
25283
|
+
field class source
|
|
25284
|
+
<field name> ERP <file>:col "<column header>"
|
|
25285
|
+
<field name> LB <tool that returned it>
|
|
25286
|
+
<field name> SIRENE recherche-entreprises.api.gouv.fr
|
|
25287
|
+
<field name> HYP <the formula + which input is assumed>
|
|
25288
|
+
<field name> OMITTED <why it cannot be computed>
|
|
25289
|
+
=================
|
|
25290
|
+
\`\`\`
|
|
25291
|
+
|
|
25292
|
+
An \`OMITTED\` row is the point of the ledger: it makes a gap **visible** instead
|
|
25293
|
+
of silently filled with a plausible guess. Never drop a field from the ledger
|
|
25294
|
+
just because you couldn't source it \u2014 render it as \`OMITTED\` with the reason.
|
|
25295
|
+
|
|
25296
|
+
**When a number is unavailable, do NOT model it \u2014 switch modes.** Specifically:
|
|
25297
|
+
if the client's revenue-realized figure is absent, do not estimate it, do not
|
|
25298
|
+
proxy it from headcount / sector / score, and **do not sort by any quantity
|
|
25299
|
+
derived from it.** Say plainly which fields are unavailable, name the exact
|
|
25300
|
+
columns you'd need, and deliver the plan the prompt describes \u2014 ordered by the
|
|
25301
|
+
strongest \`[LB]\` ranking you actually have.
|
|
25302
|
+
|
|
25303
|
+
**Sorting is where fabrication hides.** Asked for a ranking "by cash to go
|
|
25304
|
+
get" with no revenue data, the tempting move is to invent a revenue figure per
|
|
25305
|
+
account purely so the sort produces a plausible-looking order. That is
|
|
25306
|
+
fabrication with a confident shape, and it is the single most likely failure of
|
|
25307
|
+
this workflow. Change the sort and say so; never invent the key.
|
|
25308
|
+
|
|
25309
|
+
**Client-specific parameters are to be CONFIRMED, not assumed as product
|
|
25310
|
+
constants.** The Tier-1 threshold, the \u20AC/employee benchmark and the purchase
|
|
25311
|
+
mix all come from one client's economics. State each as \`[HYP]\` with its value
|
|
25312
|
+
visible and offer to re-run when the client supplies the real figure.
|
|
25313
|
+
|
|
25314
|
+
|
|
25315
|
+
GATE \u2014 DEFER TO TOOL RENDERING. When you call a Leadbay composite that ships its own RENDERING block (every composite in 0.9.0+ does), render the response using that block's recipe verbatim \u2014 score bars, glyph palette, column order, hide-list, link priorities, all of it. Do NOT substitute prose, a numbered list, or a different column structure even when an orchestrating prompt's body suggests alternate framing. Prompt-specific commentary (motivational nudges, summaries, next-action recommendations) belongs ABOVE or BELOW the canonical table, never in place of it.
|
|
25316
|
+
|
|
25317
|
+
If the prompt's body and the tool's RENDERING appear to conflict, the tool's RENDERING wins for the structural layout; the prompt's voice wins for the commentary that surrounds it.
|
|
25318
|
+
|
|
25319
|
+
|
|
25320
|
+
# Resilience rules for Leadbay long-running tools
|
|
25321
|
+
|
|
25322
|
+
These four rules apply to every Leadbay workflow that calls \`leadbay_pull_leads\`, \`leadbay_bulk_qualify_leads\`, \`leadbay_research_lead_by_id\`, \`leadbay_import_and_qualify\`, or \`leadbay_enrich_titles\`. **Treat timeouts and stream-closed errors as transient, not as signals to replan.**
|
|
25323
|
+
|
|
25324
|
+
## Rule 1 \u2014 Pin the lens
|
|
25325
|
+
|
|
25326
|
+
After your first \`leadbay_pull_leads\` call, capture \`response.lens.id\` into your working memory and **pass it explicitly as the \`lensId\` argument to every subsequent call** in this session \u2014 including any re-pulls, bulk qualifies, or research calls that accept it. (Field-name caveat: the response nests it as \`lens.id\`; the parameter on subsequent calls is \`lensId\`.) The active lens can shift between calls (5-minute client cache + backend \`last_requested_lens\` can change if the user touches the web UI). A lens shift mid-workflow throws away your top-10 work.
|
|
25327
|
+
|
|
25328
|
+
## Rule 2 \u2014 Prefer async for bulk operations
|
|
25329
|
+
|
|
25330
|
+
\`leadbay_bulk_qualify_leads\` and \`leadbay_import_and_qualify\` accept \`wait_for_completion:false\`, which returns \`{status:'running', qualify_id}\` immediately. Then poll \`leadbay_qualify_status\` (or \`leadbay_import_status\`) every ~10s until the job completes. **Use the async pattern by default** \u2014 the blocking default can exceed the MCP client's per-call timeout on large batches and produce a misleading \`"Request timed out"\` even though the server is still working.
|
|
25331
|
+
|
|
25332
|
+
## Rule 3 \u2014 Serialize \`leadbay_research_lead_by_id\` fan-out
|
|
25333
|
+
|
|
25334
|
+
\`leadbay_research_lead_by_id\` is composite and reads many sub-resources. Calling it on 10 leads in parallel can saturate the transport and produce \`"Tool permission stream closed"\` errors that look like permission failures but are really backpressure. **Call it sequentially**, or at most 3 in parallel. If one call fails with a stream/timeout error, retry that one call once before moving on; on a second failure, note the lead and continue \u2014 do not abandon the remaining leads.
|
|
25335
|
+
|
|
25336
|
+
## Rule 4 \u2014 Retry, don't replan
|
|
25337
|
+
|
|
25338
|
+
If a Leadbay tool returns \`"Request timed out"\`, \`"stream closed"\`, or any other transport-level error (distinct from a Leadbay-issued error payload), the work may still be running server-side. Do this in order:
|
|
25339
|
+
|
|
25340
|
+
1. For bulk tools \u2014 retry with \`wait_for_completion:false\` and poll the status tool with the returned id. Don't re-pull leads; that can shift the lens.
|
|
25341
|
+
2. For single-lead tools \u2014 retry the same call once. If it still fails, record the lead id and continue with the rest of the workflow.
|
|
25342
|
+
3. **Do not** switch strategies (e.g. "the endpoint is broken, let me re-pull from scratch"). The earlier work is still valid; the timeout was the wire.
|
|
25343
|
+
|
|
25344
|
+
If \`pull_leads\` itself fails and you have no prior batch, then yes \u2014 retry it, explicitly pass the lensId you captured (if any), and continue.
|
|
25345
|
+
|
|
25346
|
+
|
|
25347
|
+
# PHASE 0 \u2014 SCOPE + STATE
|
|
25348
|
+
|
|
25349
|
+
Call \`leadbay_account_status\` for my quota and active lens.
|
|
25350
|
+
|
|
25351
|
+
**What this plan is, and what it deliberately isn't.** Leadbay knows who a company is, how it scores, what signals it has and who to call there. It does **not** know what any account buys from me \u2014 invoicing lives in my ERP, and no Leadbay tool exposes it. So this is a **conquest plan**: real accounts, real qualification, real signals, real contacts, ranked by the strongest Leadbay signal available. Revenue-realized, per-family revenue, addressable spend and cash-to-capture are **OMITTED \u2014 never estimated, never proxied from headcount, sector or lead score.**
|
|
25352
|
+
|
|
25353
|
+
Say that scope in one line up front, so nobody reads the ranking as a money sort. If I ask for a cash-ranked plan, tell me plainly that it needs my invoicing extract and that the MCP has no path to it today \u2014 then deliver this plan anyway rather than stopping.
|
|
25354
|
+
|
|
25355
|
+
**DELIVER FIRST, ASK ALONGSIDE \u2014 never gate the plan on a missing input.** Only ONE thing can stop you before you have shipped a ranked list of real accounts: not knowing **whose** plan this is (a company-identity mismatch you genuinely cannot resolve). Everything else is a question you carry *next to* the delivered plan, not a reason to withhold it:
|
|
25356
|
+
|
|
25357
|
+
- **No benchmark?** Costs nothing here \u2014 the money column is OMITTED regardless. Pull, qualify, rank by the Leadbay signal, deliver, and mention what a cash-ranked version would need.
|
|
25358
|
+
- **No Tier-1 threshold?** Not a blocker. Deliver, and ask alongside.
|
|
25359
|
+
- **No territory?** Not a blocker \u2014 but do NOT call the result "national". You'll be pulling my ACTIVE lens, which may already be scoped to a city, sector or rep patch. Say the plan covers **my active lens's existing scope** (name the lens), not the whole country, and offer to re-scope. Calling a city-scoped lens a national plan misdescribes the deliverable to a client.
|
|
25360
|
+
- **\`last_requested_lens: null\`?** Not a blocker \u2014 and **do NOT read it as "no lens exists".** \`leadbay_account_status\` deliberately WITHHOLDS the lens id unless the request mentioned the lens/audience, so a plain "top 50 accounts to activate" returns null even when I have a perfectly good active lens. Default to calling \`leadbay_pull_leads\` with **no** \`lensId\` and let it resolve my active lens; capture \`response.lens.id\` from that result and pin it thereafter. Only create or switch a lens when I explicitly asked to scope or change the audience (e.g. a \`territory\` argument) \u2014 inventing a new lens silently changes what I see in the product.
|
|
25361
|
+
- **Only 3 qualification questions instead of 5?** Not a blocker. Use the org's real questions, note the gap, recommend the additions \u2014 do not wait for permission before pulling.
|
|
25362
|
+
|
|
25363
|
+
Bundling a non-blocking question in with a blocking one turns a justified pause into an over-wide gate, and the user gets a plan-of-a-plan instead of a plan. The test is **"have I shipped a ranked list of real accounts yet?"** \u2014 if you're about to end a turn without one, you are almost certainly over-gating: deliver first, then ask.
|
|
25364
|
+
|
|
25365
|
+
If I gave a \`territory\`, scope discovery to it now, and **make sure the scoping actually took effect before you pull** \u2014 a territory request that silently returns out-of-territory accounts is worse than none.
|
|
25366
|
+
|
|
25367
|
+
- **Preferred: \`leadbay_adjust_audience\`** on my active lens, passing the place as \`locations\`. It applies directly, so the lens I already use is now scoped and \`leadbay_pull_leads\` needs no new id.
|
|
25368
|
+
|
|
25369
|
+
\u26A0 **Location criteria MERGE \u2014 they do not replace.** \`adjust_audience\` unions the new \`location_ids\` into any existing include-location criterion (and \`pull_followups\` merges its \`city\` shortcut the same way). So asking for "R\xE9gion Ouest" on a lens already scoped to Paris yields **Paris OR R\xE9gion Ouest** while your header claims R\xE9gion Ouest. Before adding a territory, check the current filter: if it already carries locations you were not asked to keep, clear or replace them (or build a fresh territory-only lens for this one-off plan) rather than stacking a union.
|
|
25370
|
+
- **If a new lens is genuinely warranted: \`leadbay_new_lens\` is a two-step call.** It returns \`status:"preview"\` and creates NOTHING unless you re-call the same args with \`confirm:true\`. So: preview \u2192 confirm \u2192 take \`lens.id\` from the \`created\` response \u2192 pass that id as \`lensId\` on every subsequent pull. Never continue on the previous active lens after previewing a new one; that delivers the old audience under a new heading.
|
|
25371
|
+
|
|
25372
|
+
A place name goes to \`locations\`, never to \`sectors\` or a refine prompt.
|
|
25373
|
+
|
|
25374
|
+
# PHASE 1 \u2014 THE FIVE QUALIFICATION QUESTIONS
|
|
25375
|
+
|
|
25376
|
+
Call \`leadbay_get_qualification_questions\` and use the org's **actual** questions \u2014 they become the qualification row on every card. Do NOT invent them.
|
|
25377
|
+
|
|
25378
|
+
If the org has none set, or they don't discriminate for this exercise, recommend this shape and offer to set it via \`leadbay_set_qualification_questions\` (max 5, and ask before replacing anything): **Q1** exercises a core-target trade \xB7 **Q2** big enough to matter \xB7 **Q3** operates in the covered territory \xB7 **Q4** recent activity signals \xB7 **Q5** likely need in the next quarter. Q4 and Q5 are the load-bearing pair \u2014 they separate "fits the profile" from "worth calling this week". Recommend; don't overwrite without my say-so.
|
|
25379
|
+
|
|
25380
|
+
# PHASE 2 \u2014 THE ACCOUNT UNIVERSE
|
|
25381
|
+
|
|
25382
|
+
\u26A0 **Monitor membership is not client status.** Monitor tells you what Leadbay is watching \u2014 lens scoring decides who lands there, not whether the company ever bought anything. Label that pane "Leadbay view membership", never "customer".
|
|
25383
|
+
|
|
25384
|
+
**Get the accounts.** \`leadbay_pull_followups\` for the known/identified side, \`leadbay_pull_leads\` for the not-yet-identified side.
|
|
25385
|
+
|
|
25386
|
+
\u26A0 **Monitor's scope must match the scope you put in the header \u2014 never leave it accidental.** \`leadbay_pull_followups\` defaults to applying whatever Monitor filter is persisted server-side from a previous session, and that filter has nothing to do with the lens Discover is using. Two stale-state traps, one rule:
|
|
25387
|
+
|
|
25388
|
+
- **A persisted filter you didn't ask for** silently shrinks the known side, so a rep who once filtered Monitor to a city gets a "whole base" plan missing most of it.
|
|
25389
|
+
- **Blindly passing \`filtered:false\`** does the opposite: Monitor goes org-wide while Discover stays on a scoped lens, so out-of-scope known accounts land in a plan headed with the lens's name.
|
|
25390
|
+
|
|
25391
|
+
\u26A0 **You cannot mirror a geography you haven't read.** \`leadbay_pull_leads\` returns only \`lens: {id}\` \u2014 not the lens's filter \u2014 so capturing the id tells you nothing about which locations it covers. Before scoping Monitor to match a lens, read the **\`lens://<id>/definition\` resource** \u2014 that is where the filter and its \`location_ids\` actually live. \`leadbay_my_lenses\` returns only id / name / description / active flags, so it cannot tell you a lens's geography and must not be used for this. If you cannot determine the lens's geography, do NOT guess: pull Monitor org-wide with \`filtered:false\` and say in the header that the known side is org-wide while Discover follows lens \`<id>\`, whose scope you could not read. An unstated mismatch is the failure; a stated one is honest.
|
|
25392
|
+
|
|
25393
|
+
So: **read the persisted filter first** (the response reports \`active_filters\`), then make it match the plan's declared scope. If the plan is scoped (a \`territory\`, or an active lens with its own geography), apply that same geography to Monitor. If the plan is genuinely org-wide, pass \`filtered:false\`. Either way, state the known side's scope in the header in the same breath as the Discover side \u2014 a plan whose two halves are scoped differently is misleading even when both halves are individually correct.
|
|
25394
|
+
|
|
25395
|
+
\u26A0 **A territory must scope BOTH sides.** Adjusting or creating a lens only scopes Discover; Monitor is filtered through its own path, so pass the territory to \`leadbay_pull_followups\` as well (its \`city\` free-text shortcut resolves to a \`location_ids\` filter, same resolver as the lens). Otherwise a territory-scoped plan quietly mixes in out-of-territory known accounts \u2014 and a client reading "R\xE9gion Ouest" at the top will not check every row. Unless I named a \`territory\`, call \`leadbay_pull_leads\` with **no \`lensId\`** so it resolves my active lens \u2014 do not create a lens just because \`account_status\` showed a null. Capture \`response.lens.id\` from the first pull and pass it as an explicit \`lensId\` on every later call \u2014 a mid-session lens shift discards the cohort. Keep pulling until you have a pool comfortably deeper than {{arg:count_or_default}}, topping up with \`leadbay_bulk_qualify_leads\` \u2192 \`leadbay_qualify_status\` \u2192 re-pull as needed.
|
|
25396
|
+
|
|
25397
|
+
# PHASE 3 \u2014 QUALIFY, SIGNAL, MOTIF
|
|
25398
|
+
|
|
25399
|
+
**Qualify \u2014 the SELECTED cohort, in chunks of 25.** \`leadbay_bulk_qualify_leads\` caps \`count\` at **25**, so a single call cannot cover a 50-account plan. Loop until the whole cohort is qualified, polling \`leadbay_qualify_status\` between chunks.
|
|
25400
|
+
|
|
25401
|
+
\u26A0 **Never qualify Monitor rows through a Discover \`lensId\`.** \`leadbay_qualify_status\` re-checks each lead against the lens it was launched on and returns them under **\`not_in_lens\`** \u2014 the backend does not qualify them, so those rows ship with permanently empty pills while the poll reads "still running". So split the cohort: qualify the Discover rows with the pinned \`lensId\`, and for known-side rows use the qualification data \`leadbay_pull_followups\` already returned rather than re-launching them off-lens. If a Monitor row has no qualification data, say so in its cell \u2014 do not leave a pill that will never fill. **Always read \`not_in_lens\` in the poll response** and report anything listed there rather than waiting on it.
|
|
25402
|
+
|
|
25403
|
+
\u26A0 **Pass explicit \`leadIds\` whenever the cohort isn't simply "the next N on the lens"** \u2014 e.g. after you've selected a shortlist, or when the plan mixes Monitor and Discover rows. The \`count\`-based path selects the next *unqualified leads from the lens wishlist*, so on any other cohort it qualifies unrelated leads and hands you handles whose pills belong to different companies. Use \`leadbay_bulk_qualify_leads({leadIds:[\u2026\u226425 of the cohort], wait_for_completion:false})\` and chunk through the cohort's own ids. The \`{lensId, count}\` form is only right when the cohort genuinely *is* the lens's top N.
|
|
25404
|
+
|
|
25405
|
+
**Qualify the plan cohort, not the whole base.** Select your ~{{arg:count_or_default}} candidates (plus a modest buffer for drop-outs) BEFORE qualifying \u2014 qualification is async and quota-bearing, so running it across an entire portfolio to produce a top-{{arg:count_or_default}} burns the user's quota for rows that will never appear. **Keep every returned \`qualify_id\`** \u2014 the deck's live qualification layer is wired from those handles, and a deck with none is a dead deck that still looks finished. Never ship a plan whose lower ranks have empty qualification pills because only the first 25 were ever qualified.
|
|
25406
|
+
|
|
25407
|
+
**Signals \u2014 scoped to the cohort.** \u26A0 **Always pass the selected \`leadIds\`.** With \`leadIds\` omitted, \`leadbay_scan_portfolio_signals\` builds its own portfolio by paging \`/monitor\` \u2014 so on an imported cohort or a freshly-pulled Discover set it would scan a *different population* and you'd render dashes for accounts whose signals were never read.
|
|
25408
|
+
|
|
25409
|
+
\`leadbay_scan_portfolio_signals\` is also a **filtered** read: it requires a concrete \`query\` and returns only the accounts whose cached signals match it. It is not a generic "read every signal" call. So run it **once per why-now theme, as SEPARATE calls** \u2014 expansion/new site \xB7 contract or tender won \xB7 funding \xB7 hiring \xB7 acquisition \xB7 new venue \u2014 and union the results.
|
|
25410
|
+
|
|
25411
|
+
\u26A0 **One comma-joined omnibus query is NOT six themed scans.** Cramming every keyword into a single string is one match attempt whose recall you cannot inspect: a lead that would have matched "hiring" alone can be missed, and you have no way to tell which themes actually returned anything. Six calls, six result sets, one union. If a theme returns nothing, that is information \u2014 record it rather than hiding it inside a broad string. An account that matched no query has **not** been shown to be signal-free; render it with an explicit \`\u2014\`, never an invented event. For the identified side, take interaction recency from the fields \`leadbay_pull_followups\` already returned. \u26A0 **Do NOT reach for \`leadbay_account_history\` on Monitor rows outside the active lens** \u2014 it calls \`research_lead_by_id\` first, which fetches \`/lenses/{lensId}/leads/{leadId}\` and 404s off-lens, so the very rows that need a SUIVI / R\xC9VEIL-LB decision are the ones it fails on. Use it only for a lead you know is in the pinned lens.
|
|
25412
|
+
|
|
25413
|
+
**SIGNAL HONESTY \u2014 never infer signals from freshness.** \`stale_at\`,
|
|
25414
|
+
\`web_fetch_in_progress\`, \`fetch_at\` are freshness markers, not signal
|
|
25415
|
+
indicators \u2014 signal presence is read ONLY from the actual \`signals[]\` /
|
|
25416
|
+
\`web_fetch.content\` entries. For "which of my leads have signal X" across a
|
|
25417
|
+
portfolio, call **\`leadbay_scan_portfolio_signals\`** (bulk-reads cached
|
|
25418
|
+
signals); don't loop \`leadbay_research_lead_by_id\` per lead or guess from
|
|
25419
|
+
freshness. A lead with no cached content is \`not_researched\`, not "no match";
|
|
25420
|
+
never report a signal verdict for a lead you never read.
|
|
25421
|
+
|
|
25422
|
+
|
|
25423
|
+
**Assign the motif.**
|
|
25424
|
+
|
|
25425
|
+
**THE ACTIVATION MOTIFS.** Every account on the plan carries exactly one motif
|
|
25426
|
+
from this closed set of six. The motif is not decoration \u2014 it decides the phone
|
|
25427
|
+
pitch, the checklist, and whether the account belongs to the *Pilotage* engine
|
|
25428
|
+
(already identified) or the *Conqu\xEAte* engine (not yet identified). Assign it
|
|
25429
|
+
from observable data and state the deciding evidence in one line per account.
|
|
25430
|
+
|
|
25431
|
+
| Motif | Assign when | Engine |
|
|
25432
|
+
|---|---|---|
|
|
25433
|
+
| **SAUVETAGE** | was buying steadily, has now stopped \u2014 a recent, sharp break (e.g. no order in ~60\u201390 days against a real history) | Pilotage |
|
|
25434
|
+
| **PLAN DE COMPTE** | large, still active, buying broadly \u2014 the risk is complacency, not loss; plan the coming half-year and lock volume terms | Pilotage |
|
|
25435
|
+
| **MONT\xC9E EN GAMME** | active but narrow \u2014 buys one product family while comparable accounts of the same size buy several; the gap is cross-sell | Pilotage |
|
|
25436
|
+
| **R\xC9VEIL** | account exists, essentially dormant \u2014 long-dead history (e.g. 12+ months at zero) but the company is demonstrably still trading | Pilotage |
|
|
25437
|
+
| **CONQU\xCATE** | not present in the Leadbay known pipeline \u2014 in the addressable market, absent from the base. \u26A0 Absence from Monitor is NOT proof they never bought (see below) | Conqu\xEAte |
|
|
25438
|
+
| **SUIVI** | in the known pipeline with recent activity, purchase behaviour unknown \u2014 the honest label for an active Monitor row when no order history is available | Pilotage |
|
|
25439
|
+
|
|
25440
|
+
**Decision order matters.** Test in this order and stop at the first match, or
|
|
25441
|
+
a big lapsed account will be labelled R\xC9VEIL when it is really a SAUVETAGE:
|
|
25442
|
+
recent sharp break \u2192 SAUVETAGE; long-dormant \u2192 R\xC9VEIL; never bought \u2192
|
|
25443
|
+
CONQU\xCATE; buying broadly at scale \u2192 PLAN DE COMPTE; buying narrowly \u2192
|
|
25444
|
+
MONT\xC9E EN GAMME.
|
|
25445
|
+
|
|
25446
|
+
**Without order history the first five tests cannot run at all.** In that case
|
|
25447
|
+
the split is simply: in the Leadbay known pipeline \u2192 **SUIVI**; not in the
|
|
25448
|
+
pipeline \u2192 **CONQU\xCATE**. Never reach for a Pilotage motif you cannot evidence,
|
|
25449
|
+
and never invent a seventh label \u2014 the set is closed at six.
|
|
25450
|
+
|
|
25451
|
+
When a known-pipeline row has long-dormant *Leadbay* activity, it stays
|
|
25452
|
+
**SUIVI** and you say what the dormancy measures in its why-now cell: "no
|
|
25453
|
+
Leadbay-logged action in N months". That is a qualifier on the evidence, not a
|
|
25454
|
+
new motif. It is NOT R\xC9VEIL \u2014 R\xC9VEIL means dormant *purchasing*, which needs
|
|
25455
|
+
order history you do not have.
|
|
25456
|
+
|
|
25457
|
+
**What each motif changes in the output.**
|
|
25458
|
+
|
|
25459
|
+
- The **pitch angle** \u2014 SAUVETAGE opens on the silence itself and offers terms
|
|
25460
|
+
to resume; PLAN DE COMPTE opens on the relationship and plans forward;
|
|
25461
|
+
MONT\xC9E EN GAMME opens on what comparable firms buy that this one doesn't;
|
|
25462
|
+
R\xC9VEIL asks what made them leave and offers a re-entry incentive; CONQU\xCATE
|
|
25463
|
+
introduces the company and asks for a short first meeting \u2014 **without
|
|
25464
|
+
asserting no prior relationship**. Never write "we've never worked together"
|
|
25465
|
+
or "as a new customer" on a Leadbay-only plan: absence from the known
|
|
25466
|
+
pipeline is not proof they never bought, and that line told to an existing
|
|
25467
|
+
customer is the one mistake a rep cannot walk back. Write the pitch in the
|
|
25468
|
+
client's own commercial voice, naming the specific families and figures the
|
|
25469
|
+
account's data actually supports. **SUIVI** picks up the existing thread \u2014
|
|
25470
|
+
a continuation, never an introduction and never a win-back.
|
|
25471
|
+
- The **checklist** \u2014 three concrete, checkable next actions matching the
|
|
25472
|
+
motif's shape: diagnose \u2192 schedule \u2192 send-terms for SAUVETAGE; review \u2192
|
|
25473
|
+
propose \u2192 open-a-family for PLAN DE COMPTE; visit-with-full-tariff \u2192 quote \u2192
|
|
25474
|
+
first-order-in-the-new-family for MONT\xC9E EN GAMME; understand-the-departure \u2192
|
|
25475
|
+
send-offer \u2192 first-order-back for R\xC9VEIL; reach-the-decision-maker \u2192
|
|
25476
|
+
open-the-account \u2192 first-test-order for CONQU\xCATE; confirm-the-state \u2192
|
|
25477
|
+
identify-the-current-need \u2192 agree-a-next-step for SUIVI. When a signal exists,
|
|
25478
|
+
promote "exploit <the signal>" to the top of that account's checklist.
|
|
25479
|
+
|
|
25480
|
+
**Motif assignment depends on order history, which is ERP data.** Without the
|
|
25481
|
+
client's extract, SAUVETAGE / PLAN DE COMPTE / MONT\xC9E EN GAMME / R\xC9VEIL cannot
|
|
25482
|
+
be assigned from purchase behaviour \u2014 do not guess them from a lead score, a
|
|
25483
|
+
sector, or a company's size. Two honest options, in order of preference:
|
|
25484
|
+
|
|
25485
|
+
**The Monitor gap \u2014 read this before assigning anything.** Four of the six
|
|
25486
|
+
motifs (SAUVETAGE / PLAN DE COMPTE / MONT\xC9E EN GAMME / R\xC9VEIL) are purchase-
|
|
25487
|
+
behaviour reads, and CONQU\xCATE means "not in the known pipeline". A Monitor row
|
|
25488
|
+
that is *actively* worked therefore matches none of them: it IS in the pipeline,
|
|
25489
|
+
and without order history you cannot tell whether it buys broadly, narrowly, or
|
|
25490
|
+
at all. Do NOT resolve that by guessing a purchase motif, and do NOT silently
|
|
25491
|
+
drop the row.
|
|
25492
|
+
|
|
25493
|
+
Those rows take **SUIVI** (row 6 of the table above). Say in the plan's legend
|
|
25494
|
+
that SUIVI exists precisely because purchase history is unavailable, and that
|
|
25495
|
+
ERP order data would split those rows into the four Pilotage motifs.
|
|
25496
|
+
|
|
25497
|
+
1. **CONQU\xCATE is assignable from Leadbay alone \u2014 but say what it actually
|
|
25498
|
+
means.** Discover membership proves a company is **not in the Leadbay known
|
|
25499
|
+
pipeline**; it does NOT prove they never bought. Monitor membership is set by
|
|
25500
|
+
lens scoring, not by purchase history, so an existing customer who was never
|
|
25501
|
+
scored into the known view will appear in Discover. Without order history
|
|
25502
|
+
there is no way to tell the two apart.
|
|
25503
|
+
|
|
25504
|
+
So label the motif for what the data supports \u2014 "fresh / not in the Leadbay
|
|
25505
|
+
pipeline" \u2014 and **write the pitch so it survives being wrong**: an opener
|
|
25506
|
+
that introduces the company works for a genuine prospect and merely sounds
|
|
25507
|
+
uninformed to a customer, whereas "we've never worked together" told to a
|
|
25508
|
+
current customer damages the relationship and the credibility of the whole
|
|
25509
|
+
plan. Only ERP order history can upgrade this to a true never-a-client
|
|
25510
|
+
claim. A Leadbay-only plan is still a legitimate *Conqu\xEAte* plan \u2014 say so in
|
|
25511
|
+
the title rather than implying it covers the whole base.
|
|
25512
|
+
2. **Leadbay-activity recency is a qualifier, never a motif.** A long-dormant
|
|
25513
|
+
known row stays **SUIVI** with "no Leadbay-logged action in N months" in its
|
|
25514
|
+
why-now cell \u2014 never "no orders in N months", and never a seventh label.
|
|
25515
|
+
Logged activity is not invoicing.
|
|
25516
|
+
|
|
25517
|
+
|
|
25518
|
+
# PHASE 4 \u2014 POTENTIAL AND RANKING
|
|
25519
|
+
|
|
25520
|
+
Rank by \`ai_agent_lead_score\`, then qualification boost, then headcount. **Name that key in the plan's own header** \u2014 a reader who assumes a money-sort misreads the whole order \u2014 and title the deliverable for what it is (a conquest plan), not for what it isn't.
|
|
25521
|
+
|
|
25522
|
+
Cash-to-capture is not available: it needs \`ca12\` from my invoicing system, which no Leadbay tool exposes. Show it as OMITTED in the ledger and say what a cash-ranked version would require (12-month revenue per account, per-family split, last order date, order count, plus a \u20AC/employee benchmark) \u2014 do not model it.
|
|
25523
|
+
|
|
25524
|
+
# PHASE 5 \u2014 CONTACTS (consent-gated)
|
|
25525
|
+
|
|
25526
|
+
Each card needs a reachable decision-maker. \`leadbay_enrich_titles({leadIds, lensId})\` in discovery mode first \u2014 that reveals what's enrichable and spends nothing. Render whatever contact detail is already on the record; many accounts already carry a named contact.
|
|
25527
|
+
|
|
25528
|
+
**Do NOT stop and wait for enrichment consent before delivering.** Asking for a plan is not authorization to spend quota on {{arg:count_or_default}} accounts \u2014 but neither is it a reason to end the turn on a spending question with no plan attached. Ship the ranked plan (Phase 6), then **offer** the paid reveal alongside it. The discovery call returned no \`titles\`, so it only told you what's *available* \u2014 **the offer must therefore carry the titles you propose to enrich AND the channels**, not just a volume: "enrich N contacts at these titles (\`<the titles you picked from available_titles / title_suggestions>\`), email only / email + phone \u2014 reveals consume quota". A bare "yes" to a volume-only question is not a mandate to pick titles yourself, and re-running discovery instead of launching wastes a turn.
|
|
25529
|
+
|
|
25530
|
+
\u26A0 **Do NOT quote a cost or a credits figure.** The per-reveal rate is backend-side and enrichment is gated by quota, not a credit balance; \`credits_remaining\` is advisory context only. A spend number invented to make the offer concrete is the same failure as an invented euro on a card.
|
|
25531
|
+
|
|
25532
|
+
On an explicit yes, launch with the agreed \`titles\` + channels, then poll \`leadbay_bulk_enrich_status\` until done and **keep the \`bulk_id\` handles** for the deck.
|
|
25533
|
+
|
|
25534
|
+
\u26A0 **Render only the channels that actually came back.** The default reveal is email-only unless phone was explicitly requested, so never emit a \`tel:\` link for a contact whose phone was never revealed \u2014 show the channels enrichment returned and mark the rest omitted. A fabricated phone link is the same failure as a fabricated euro.
|
|
25535
|
+
|
|
25536
|
+
# PHASE 6 \u2014 DELIVER
|
|
25537
|
+
|
|
25538
|
+
Render the PROVENANCE LEDGER and its legend FIRST, then the chat answer beneath it \u2014 never the other way round. A ranked money column read before its sourcing has already misled the reader:
|
|
25539
|
+
|
|
25540
|
+
## RENDERING \u2014 account activation plan
|
|
25541
|
+
|
|
25542
|
+
Two surfaces. The **chat table** is the default answer and must stand alone as
|
|
25543
|
+
useful. The **interactive deck** is offered, not forced (see the widget gate) \u2014
|
|
25544
|
+
build it only once the user accepts.
|
|
25545
|
+
|
|
25546
|
+
### Order on the page \u2014 ledger FIRST, then the plan
|
|
25547
|
+
|
|
25548
|
+
Print the PROVENANCE LEDGER (and the one-line provenance legend) **before** the
|
|
25549
|
+
chat table, the deck, or any other part of the deliverable. The reader must know
|
|
25550
|
+
which figures are measured and which are modelled *before* they read a ranking
|
|
25551
|
+
built on them \u2014 a cash column read first and sourced second has already done its
|
|
25552
|
+
damage. This ordering is the workflow contract, not a stylistic preference.
|
|
25553
|
+
|
|
25554
|
+
### The chat table (render immediately after the ledger)
|
|
25555
|
+
|
|
25556
|
+
**The chat answer must be the whole deliverable the user asked for** \u2014 the deck
|
|
25557
|
+
is optional, so a top-50 request whose chat half stops at 10 rows has delivered
|
|
25558
|
+
a fifth of the plan. Render the **requested count**, with its pitch + checklist
|
|
25559
|
+
block per row (see below).
|
|
25560
|
+
|
|
25561
|
+
If that is genuinely too long for one message, do NOT silently truncate: state
|
|
25562
|
+
the delivered count plainly ("here are 20 of the 50 \u2014 say the word for the
|
|
25563
|
+
rest"), so the user knows what they have. Never present a partial list as
|
|
25564
|
+
though it were the plan. Four columns:
|
|
25565
|
+
|
|
25566
|
+
Col 3's header is **the ranking key you actually used** \u2014 never a cash label,
|
|
25567
|
+
since cash-to-capture cannot be computed from Leadbay data:
|
|
25568
|
+
|
|
25569
|
+
\`\`\`
|
|
25570
|
+
| # \xB7 Account | Motif | Fit score | Why now |
|
|
25571
|
+
\`\`\`
|
|
25572
|
+
|
|
25573
|
+
- **Col 1** \u2014 rank number, then the company name linked to its website when one
|
|
25574
|
+
is known. Follow with a compact \` \xB7 \`-separated pill line: city \xB7 headcount \xB7
|
|
25575
|
+
any account reference you were given. **Every figure in that pill line carries
|
|
25576
|
+
its class too** \u2014 headcount is \`[LB]\` (a Leadbay size band, so render the band
|
|
25577
|
+
rather than a false-precision point value) or \`[SIRENE]\` if you read it from
|
|
25578
|
+
the registry. An untagged employee count is still an untagged number in front
|
|
25579
|
+
of a client; omit it rather than ship it bare.
|
|
25580
|
+
- **Col 2** \u2014 the motif, exactly one of SAUVETAGE / PLAN DE COMPTE / MONT\xC9E EN
|
|
25581
|
+
GAMME / R\xC9VEIL / CONQU\xCATE / SUIVI. Never invent a seventh.
|
|
25582
|
+
- **Col 3** \u2014 the ranking signal with its provenance class, e.g. \`AI 30 [LB]\`.
|
|
25583
|
+
Tagging is not optional; an untagged figure reads as measured fact. **There is
|
|
25584
|
+
no money column** \u2014 cash-to-capture needs invoicing data Leadbay does not
|
|
25585
|
+
hold, so it stays OMITTED in the ledger rather than being modelled. A column
|
|
25586
|
+
of invented euros next to a client's name is the exact failure this
|
|
25587
|
+
deliverable must not ship.
|
|
25588
|
+
- **Col 4** \u2014 the one-line reason to act now: the signal when there is one,
|
|
25589
|
+
otherwise the motif's deciding evidence. Never fill this with a
|
|
25590
|
+
plausible-sounding invented event; an account with nothing read shows \`\u2014\`.
|
|
25591
|
+
|
|
25592
|
+
Sort strictly by the ranking key named in the ledger (which was printed above).
|
|
25593
|
+
|
|
25594
|
+
### The pitch + checklist block (part of the chat answer, not the deck)
|
|
25595
|
+
|
|
25596
|
+
The table alone is a shortlist, not a plan \u2014 the pitch and the three-step
|
|
25597
|
+
checklist are what make it actionable, and the deck is **optional**, so they
|
|
25598
|
+
cannot live only there. Under the table, render a block for **every account you
|
|
25599
|
+
put in the table** \u2014 if a row is good enough to rank, it is good enough to carry
|
|
25600
|
+
its pitch. Do NOT ship the top 5 or 10 and offer the rest "on request": that
|
|
25601
|
+
puts the actionable half of the deliverable behind another user turn, and the
|
|
25602
|
+
rows you defer are the ones a rep is least likely to chase. If the full plan is
|
|
25603
|
+
genuinely long, shrink the TABLE (fewer rows, stated plainly) rather than
|
|
25604
|
+
shipping ranked rows with no pitch:
|
|
25605
|
+
|
|
25606
|
+
\`\`\`
|
|
25607
|
+
**<rank> \xB7 <Company>** \u2014 <MOTIF>
|
|
25608
|
+
\u260E <contact name>, <title> \xB7 <only the channels actually revealed>
|
|
25609
|
+
> "<the motif's pitch, in the client's commercial voice>"
|
|
25610
|
+
\u2610 <step 1> \u2610 <step 2> \u2610 <step 3>
|
|
25611
|
+
\`\`\`
|
|
25612
|
+
|
|
25613
|
+
Keep each pitch to one or two sentences a rep can say out loud, and each
|
|
25614
|
+
checklist to three concrete, checkable actions matching that motif's shape.
|
|
25615
|
+
When an account has a signal, lead the pitch with it and promote
|
|
25616
|
+
"exploit <the signal>" to the top of its checklist.
|
|
25617
|
+
|
|
25618
|
+
### The interactive deck (only after the user accepts)
|
|
25619
|
+
|
|
25620
|
+
One card per account, ordered by the same key. Per card:
|
|
25621
|
+
|
|
25622
|
+
- **Header** \u2014 rank badge, company name, city \xB7 trade \xB7 headcount, and the
|
|
25623
|
+
ranking signal right-aligned with its class tag (e.g. \`AI 30 [LB]\`). No cash
|
|
25624
|
+
figure: the deck and the chat answer must never disagree about which fields
|
|
25625
|
+
exist, and a card carrying a euro the table omitted means one was modelled.
|
|
25626
|
+
- **Motif badge** \u2014 the motif, visually distinct per motif so the deck can be
|
|
25627
|
+
scanned by strategy.
|
|
25628
|
+
- **Qualification row** \u2014 the org's **actual** questions as returned by
|
|
25629
|
+
\`leadbay_get_qualification_questions\` (there may be fewer than five), each with
|
|
25630
|
+
\u2713 / \u2717 / pending and labelled with its real text. Render exactly as many rows as
|
|
25631
|
+
the org has: never pad to five with invented labels or bogus pending pills, and
|
|
25632
|
+
never substitute invented wording.
|
|
25633
|
+
- **Signal line** \u2014 the event driving urgency, or omitted.
|
|
25634
|
+
- **Action block** \u2014 the named contact with **only the channels enrichment
|
|
25635
|
+
actually returned** as one-tap \`tel:\` / \`mailto:\` links (the default reveal is
|
|
25636
|
+
email-only unless phone was requested \u2014 never emit a \`tel:\` for a phone that
|
|
25637
|
+
was never revealed; mark it omitted instead), the motif's pitch as a quoted
|
|
25638
|
+
line, and the three-item checklist as checkboxes.
|
|
25639
|
+
- **Caveat block** \u2014 closing the deck: which classes fed it, an explicit line
|
|
25640
|
+
that \`[HYP]\` figures are modelled rather than measured, and which fields are
|
|
25641
|
+
OMITTED because Leadbay does not hold them. A missing input means an omitted
|
|
25642
|
+
field, never a stand-in number.
|
|
25643
|
+
|
|
25644
|
+
Header KPIs across the top, each carrying its provenance class: accounts on the
|
|
25645
|
+
plan, count qualified, count with a reachable contact. **No euro totals** \u2014 the
|
|
25646
|
+
same rule as the table, for the same reason.
|
|
25647
|
+
|
|
25648
|
+
\u26A0 **No "activated" KPI at build time.** Nothing in this workflow measures
|
|
25649
|
+
activation \u2014 the deck is built before any outreach happens \u2014 so a count would be
|
|
25650
|
+
fabricated or imply outcome tracking that doesn't exist. If the deck's checklists
|
|
25651
|
+
persist locally, an "activated" tile may count *checked* accounts and must be
|
|
25652
|
+
labelled as local checklist state, not a measured outcome.
|
|
25653
|
+
|
|
25654
|
+
|
|
25655
|
+
Then **offer** the interactive deck \u2014 don't force it:
|
|
25656
|
+
|
|
25657
|
+
## GATE \u2014 PREFER BUILT-IN HOST WIDGETS
|
|
25658
|
+
|
|
25659
|
+
Modern chat hosts (Claude, ChatGPT) expose first-party widgets the agent can route into. These ALWAYS produce a better UX than markdown tables / inline prose for the data shapes they support \u2014 they're tappable on mobile, persistent across turns, and integrate with the host's quick-actions.
|
|
25660
|
+
|
|
25661
|
+
**The Big Three** \u2014 when a tool result fits, route there:
|
|
25662
|
+
|
|
25663
|
+
| Host widget | Use when | Field map (from Leadbay payload) |
|
|
25664
|
+
|---|---|---|
|
|
25665
|
+
| \`places_map_display_v0\` + \`places_search\` (Claude) | \u22652 leads with coords / \`location.city\`, geographic / "in person" / travel intent | **Two-step**: \`places_search\` each lead (query = company + full street address) \u2192 real \`place_id\`/coords, THEN render with \`places_map_display_v0\` (Itinerary mode for a tour). Skipping \`places_search\` \u2192 schematic scatter, not a street map. |
|
|
25666
|
+
| \`message_compose_v1\` (Claude) | You're about to draft outreach (email / message / call opener) | \`{kind: "email", summary_title, variants: [{label, body, subject}]}\` \u2014 2\u20133 variants, labels describe STRATEGY ("Push for alignment", "Reference the M&A signal"), not tone ("Friendly", "Formal") |
|
|
25667
|
+
| \`ask_user_input_v0\` (Claude chat / ChatGPT) **or** \`AskUserQuestion\` (Claude cowork / Claude Code) \u2014 whichever is in your tool set; their schemas differ, match the one you have | The tool's NEXT STEPS block has 2\u20134 mutually-exclusive next moves and the user hasn't already chosen | Per-tool schema in the server instructions + NEXT STEPS routing block. Max 3 questions. |
|
|
25668
|
+
|
|
25669
|
+
ChatGPT exposes the same routing pattern via \`_meta.openai/outputTemplate\`. We don't ship any custom widgets ourselves \u2014 this gate is exclusively about routing into the host's first-party widgets when the data shape fits.
|
|
25670
|
+
|
|
25671
|
+
**Rules:**
|
|
25672
|
+
- The widget IS the visual. Do NOT emit a markdown table or prose list of the same data alongside \u2014 that produces two competing UIs.
|
|
25673
|
+
- Pass identifiers (place_id, lead.id, contact_id) verbatim. Don't rewrite.
|
|
25674
|
+
- When the host doesn't expose the named widget, the agent falls back to the prose/table rendering the per-tool description already specifies. The directive is host-conditional; the fallback is automatic.
|
|
25675
|
+
- One short intro sentence in chat is enough \u2014 "Here are your 5 NYC follow-ups." Then route into the widget.
|
|
25676
|
+
|
|
25677
|
+
|
|
25678
|
+
\u26A0 **The deck's contact layer depends on what actually happened in Phase 5.** Bind a \`leadbay_bulk_enrich_status\` resource ONLY if a paid reveal was launched and you hold a \`bulk_id\`. If the user accepted the deck but not the reveal, render the contacts already on record and carry the paid-reveal offer inside the deck \u2014 never wire a status resource with no handle (it renders permanently empty) and never launch enrichment from the deck to manufacture one.
|
|
25679
|
+
|
|
25680
|
+
On acceptance, call \`leadbay_artifact_kit\`, read its \`usage_guide\` before writing any code, and build a single-file deck. Wire the live layer from the handles you kept: a poll-until-done resource per \`qualify_id\` for the qualification pills, and one over \`leadbay_bulk_enrich_status\` for the contacts. \u26A0 **If enrichment already ran this session, bind the existing \`bulk_id\` \u2014 re-launching enrichment from the deck double-spends my quota.** Per-card notes and outcomes go through the pre-wired note/outreach view-models (they carry the required verification and \`_triggered_by\` fields; hand-rolling those is where it breaks). Keep the checklists in local storage, and always wire a Refresh \u2014 auto-poll is host-dependent. List every tool the deck calls in its \`mcp_tools\`, and render the bridge-unavailable branch, or the pills silently show empty.
|
|
25681
|
+
|
|
25682
|
+
# Iron laws
|
|
25683
|
+
|
|
25684
|
+
- **Never invent a number.** No revenue figure, registry count, signal or lead id that didn't come from a Leadbay response or a real registry query. A modelled figure is fine \u2014 tagged \`[HYP]\` and named as an assumption. An untagged one is not.
|
|
25685
|
+
- **The ledger ships before the deliverable**, with un-sourceable fields shown as OMITTED rather than dropped.
|
|
25686
|
+
- **A conquest plan is the deliverable, not a consolation prize.** Leadbay holds no invoicing data, so the money columns are OMITTED by design. Title it honestly, name what a cash-ranked version would need \u2014 never refuse, and never fill the gap with a guess.
|
|
25687
|
+
- **Deliver first, ask alongside.** Do not end a turn without a ranked list of real accounts. The benchmark, the Tier-1 threshold, the territory, a missing lens, a short question set and an unanswered enrichment offer are all NON-blocking \u2014 carry them next to the plan. Only an unresolvable identity mismatch (whose plan is this?) may stop delivery.
|
|
25688
|
+
- **One motif per account, from the closed set of six**, with its deciding evidence stated.
|
|
25689
|
+
- **The org's real qualification questions**, read from Leadbay \u2014 never invented.
|
|
25690
|
+
- **Consent before any paid enrichment**, and never re-launch a bulk that already exists.
|
|
25691
|
+
- **Offer the deck; don't force it.** The chat answer must stand alone as useful.
|
|
25692
|
+
- Carry the captured \`lensId\` on the calls whose schema **accepts** it (\`leadbay_pull_leads\`, \`leadbay_bulk_qualify_leads\`, \`leadbay_enrich_titles\`). Do NOT add it to \`leadbay_pull_followups\`, \`leadbay_scan_portfolio_signals\`, \`leadbay_qualify_status\` or \`leadbay_bulk_enrich_status\` \u2014 they declare no such argument and reject unknown properties.
|
|
25693
|
+
- Building a plan is not outreaching \u2014 do not send anything and do not call \`leadbay_report_outreach\`.
|
|
25694
|
+
`;
|
|
25244
25695
|
var leadbay_work_campaign = `
|
|
25245
25696
|
Work my **{{arg:campaign_or_default}}** campaign as an outreach session{{arg:mode_paren}}.
|
|
25246
25697
|
|
|
@@ -25364,6 +25815,7 @@ should I follow up on" to "I'll send via lemlist".
|
|
|
25364
25815
|
leadbay_refine_audience: { "name": "leadbay_refine_audience", "short_description": "Refine the kind of leads Leadbay surfaces beyond firmographics, with a\nfree-text instruction. Handles the clarification round-trip if the new\nprompt is ambiguous.\n", "arguments": [{ "name": "instruction", "description": "The refinement (e.g. 'focus on hospitals running their own IT'). Set to plain English.", "required": true }], "expected_calls": ["leadbay_refine_prompt", "leadbay_account_status"], "failure_modes": ["Calls leadbay_answer_clarification on the user's behalf instead of surfacing the clarification verbatim", "Glosses over the clarification options instead of presenting them as offered", "Promises immediate effect when status='applied' actually triggers an async intelligence recompute"] },
|
|
25365
25816
|
leadbay_research_a_domain: { "name": "leadbay_research_a_domain", "short_description": "Resolve a company by name or domain across the user's visible Discover,\nMonitor, and Activate corpus, then return everything Leadbay knows about it.\n", "arguments": [{ "name": "domain", "description": "Company name or domain (for example 'Acme Corporation' or 'acme.com'). The legacy argument key remains `domain` for client compatibility.", "required": true }], "expected_calls": ["leadbay_research_lead_by_name_fuzzy"], "failure_modes": ["Fabricates qualification answers not present in any tool response", "Calls leadbay_import_and_qualify before searching the existing visible corpus", "Treats the active lens as the entire search universe when the user did not request a lens scope", "Imports a missing company without the user's explicit permission", "Renders the research result as a freeform narrative instead of the canonical research-company-card layout (the card with header score bar, pill row, signal sections, contacts table is the structural contract; commentary belongs ABOVE or BELOW it)"] },
|
|
25366
25817
|
leadbay_setup_team_prospecting: { "name": "leadbay_setup_team_prospecting", "short_description": "Manager-led prospecting setup: conversationally turn a natural-language\naudience ask into a Leadbay lens, validate the candidate leads, and\npersist them as one or more named campaigns the rep(s) can work\nthrough. Closes #3630 US3 end-to-end (within the current\ncreator-scoped campaign visibility model).\n", "arguments": [{ "name": "audience", "description": "Natural-language audience description (e.g. 'plumbing companies with 10-50 employees in Seine-Maritime'). The lens-creation step (`leadbay_refine_prompt` \u2192 `leadbay_create_lens`) interprets it.", "required": true }, { "name": "rep_split", "description": "Optional: how to split the validated leads into per-rep campaigns. Free text \u2014 e.g. 'split by city' or 'one campaign per rep: John gets Tulsa, Sarah gets OKC'.", "required": false }], "expected_calls": ["leadbay_refine_prompt", "leadbay_create_lens", "leadbay_promote_lens", "leadbay_pull_leads", "leadbay_research_lead_by_id", "leadbay_create_campaign", "leadbay_add_leads_to_campaign"], "failure_modes": ["Skips the validation step \u2014 creates a campaign of unvetted leads from a freshly-created lens without giving the manager a chance to drop weak fits", "Creates ONE campaign for all reps without asking about the split \u2014 the user explicitly mentioned per-rep distribution and the prompt should honor it", "Pretends the backend supports cross-user assignment \u2014 campaigns are owned by the caller (creator-scoped). Surface this honestly instead of fabricating an assignment model", "Asks ALL clarifying questions inline before tool calls \u2014 instead, run the lens refinement loop with `leadbay_refine_prompt` which handles the clarification protocol natively"] },
|
|
25818
|
+
leadbay_top_accounts_to_activate: { "name": "leadbay_top_accounts_to_activate", "short_description": 'Build a ranked account-conquest plan from Leadbay data \u2014 the accounts worth\nactivating, each with a motif, a pitch and a checklist, ranked by the\nstrongest Leadbay signal. Every figure carries its source, and anything\nLeadbay can\'t measure is shown as OMITTED rather than estimated. Uses\n`leadbay_bulk_qualify_leads` and `leadbay_enrich_titles`. Trigger on\n"top 50 accounts to activate", "who should we go after".\n', "arguments": [{ "name": "count", "description": "Optional: how many accounts the plan should hold (default 50).", "required": false }, { "name": "territory", "description": "Optional: restrict the plan to a territory (e.g. 'Indre-et-Loire', 'R\xE9gion Ouest'). Sets geography on the Discover lens.", "required": false }], "expected_calls": ["leadbay_account_status", "leadbay_get_qualification_questions", "leadbay_pull_leads", "leadbay_pull_followups", "leadbay_bulk_qualify_leads", "leadbay_qualify_status", "leadbay_scan_portfolio_signals", "leadbay_enrich_titles", "leadbay_bulk_enrich_status", "leadbay_account_history", "leadbay_artifact_kit", "leadbay_new_lens", "leadbay_adjust_audience"], "failure_modes": ["Invents, estimates or proxies a revenue-realized figure \u2014 the single worst failure. Leadbay does not hold what an account buys, and headcount, sector and lead score are NOT proxies for it.", "Sorts by cash-to-capture, synthesizing a revenue figure per account purely to make that ranking work. Leadbay has no revenue data: rank by the Leadbay signal, say so in the header, and never invent the key.", "Emits \u20AC figures with no provenance class, so modelled numbers read as measured fact in front of a paying client.", "Skips the PROVENANCE LEDGER, or drops un-sourceable fields from it instead of rendering them as OMITTED \u2014 which hides the gap.", "Fabricates registry/TAM counts (France or regional company counts) instead of querying the registry or marking the figure NOT COMPUTED. Leadbay does not proxy SIRENE.", "Invents the five qualification questions from this prompt's own recommendations instead of reading the org's actual questions via leadbay_get_qualification_questions.", "Leaves the deck's live layer dead \u2014 qualification and enrichment handles never wired in, so the pills and contacts stay empty while the deck still looks finished.", "Invents lead ids to make the qualification pills appear populated.", "Fabricates a plausible-sounding signal ('just won a public tender') for an account whose signals were never read. No signal read means an explicit dash.", "Assigns a motif outside the closed set of six, or assigns SAUVETAGE / PLAN DE COMPTE / MONT\xC9E EN GAMME / R\xC9VEIL from a lead score or sector when order history was never available.", "Labels Monitor membership as 'is a client' \u2014 Monitor is a Leadbay view whose membership is decided by lens scoring, not by whether the company ever bought anything.", "Launches paid enrichment on the whole plan without consent. Asking for a plan is not authorization to spend on 50 accounts.", "Re-launches enrichment from inside the built deck when a bulk handle already exists this session \u2014 double-spends the user's quota.", "Forces the interactive deck without offering it first, or ships the deck INSTEAD of a chat answer that stands on its own.", "Refuses the task because revenue data is missing, instead of delivering the conquest plan and naming what a cash-ranked version would need.", "Ends the turn without a ranked list of real accounts \u2014 gating the whole plan on a NON-blocking question (the territory, a missing lens, or a 3-vs-5 qualification-question gap) so the user gets a plan-of-a-plan. Only an unresolvable company-identity mismatch may stop delivery; every other open question rides alongside the delivered plan.", "Stops after the discovery contact preview to wait for enrichment consent, delivering no plan that turn \u2014 the ranked plan ships first; the paid reveal is offered alongside it.", "Renders a contact channel enrichment never returned (e.g. a phone link when only email was approved and revealed) instead of showing the returned channels and marking the rest omitted."] },
|
|
25367
25819
|
leadbay_work_campaign: { "name": "leadbay_work_campaign", "short_description": "Work a campaign as a real outreach session: pick the campaign,\nassess what the user has (phones / emails / coords), then PROPOSE\nthe right session mode (call sheet, email sheet, enrich titles\nfirst, map). After they pick, render \u2014 and as they dictate\noutcomes per lead, record both note + epilogue via\n`leadbay_report_outreach` in one round trip.\n", "arguments": [{ "name": "campaign", "description": "Campaign name (fuzzy match against your own campaigns) or campaign UUID. Omit to list and pick interactively.", "required": false }, { "name": "mode", "description": "Optional: skip the readiness-assessment proposal and jump directly into 'call_sheet' / 'email_sheet' / 'map' / 'enrich_first'. Omit (recommended) and let the prompt propose based on the data.", "required": false }], "expected_calls": ["leadbay_list_campaigns", "leadbay_campaign_call_sheet", "leadbay_enrich_titles", "leadbay_report_outreach"], "failure_modes": ["Renders the call sheet immediately without proposing the right mode \u2014 if 60% of leads have no contacts, calling is futile; enrich first. Always assess `readiness` first.", "Auto-renders the map widget without asking \u2014 maps are intrusive when the user just wants to scroll a list. Map mode is a proposed option, not a default.", "Proposes map mode after the user has previously said they don't like maps \u2014 check conversation memory before adding 'View on a map' to the options list.", "Calls `leadbay_campaign_progression` instead of `leadbay_campaign_call_sheet` \u2014 progression has counts but no phones / LinkedIn / call-ready data; the user can't actually dial from progression rows.", "Renders contacts WITHOUT making the phone number a `[bare](tel:URL)` link \u2014 on mobile that breaks one-tap calling, which is the whole point of the cheat sheet.", "Records outreach WITHOUT epilogue_status \u2014 leaves the lead's pipeline state unchanged; the rep then sees the same lead surfaced again next session.", "Records outreach WITHOUT verification \u2014 verification.source/ref is REQUIRED. For calls, pass `{source: 'user_confirmed', ref: <user's exact words>}`.", "Loops through ALL leads in a 50-lead campaign before recording any outreach \u2014 the call-then-record loop must be per-lead, not batched."] }
|
|
25368
25820
|
};
|
|
25369
25821
|
var PROMPT_CATALOG_HEADER = `This server exposes the following workflow prompts via \`prompts/list\` and \`prompts/get\`. Some MCP clients render them as slash commands; if your client does not, you (the agent) should invoke them directly via \`prompts/get\` when the user's request matches one of the triggers described below.`;
|
|
@@ -25380,6 +25832,7 @@ var PROMPT_CATALOG_BULLETS = {
|
|
|
25380
25832
|
leadbay_refine_audience: `- \`leadbay_refine_audience\` (required args: instruction): Refine the kind of leads Leadbay surfaces beyond firmographics, with a free-text instruction. Handles the clarification round-trip if the new prompt is ambiguous.`,
|
|
25381
25833
|
leadbay_research_a_domain: `- \`leadbay_research_a_domain\` (required args: domain): Resolve a company by name or domain across the user's visible Discover, Monitor, and Activate corpus, then return everything Leadbay knows about it.`,
|
|
25382
25834
|
leadbay_setup_team_prospecting: `- \`leadbay_setup_team_prospecting\` (required args: audience; optional args: rep_split): Manager-led prospecting setup: conversationally turn a natural-language audience ask into a Leadbay lens, validate the candidate leads, and persist them as one or more named campaigns the rep(s) can work through. Closes #3630 US3 end-to-end (within the current creator-scoped campaign visibility model).`,
|
|
25835
|
+
leadbay_top_accounts_to_activate: `- \`leadbay_top_accounts_to_activate\` (optional args: count, territory): Build a ranked account-conquest plan from Leadbay data \u2014 the accounts worth activating, each with a motif, a pitch and a checklist, ranked by the strongest Leadbay signal. Every figure carries its source, and anything Leadbay can't measure is shown as OMITTED rather than estimated. Uses \`leadbay_bulk_qualify_leads\` and \`leadbay_enrich_titles\`. Trigger on "top 50 accounts to activate", "who should we go after".`,
|
|
25383
25836
|
leadbay_work_campaign: `- \`leadbay_work_campaign\` (optional args: campaign, mode): Work a campaign as a real outreach session: pick the campaign, assess what the user has (phones / emails / coords), then PROPOSE the right session mode (call sheet, email sheet, enrich titles first, map). After they pick, render \u2014 and as they dictate outcomes per lead, record both note + epilogue via \`leadbay_report_outreach\` in one round trip.`
|
|
25384
25837
|
};
|
|
25385
25838
|
|
|
@@ -25624,6 +26077,33 @@ var CATALOG = [
|
|
|
25624
26077
|
)
|
|
25625
26078
|
];
|
|
25626
26079
|
}
|
|
26080
|
+
},
|
|
26081
|
+
{
|
|
26082
|
+
name: "leadbay_top_accounts_to_activate",
|
|
26083
|
+
description: PROMPT_META.leadbay_top_accounts_to_activate.short_description,
|
|
26084
|
+
arguments: [
|
|
26085
|
+
{
|
|
26086
|
+
name: "count",
|
|
26087
|
+
description: "Optional: how many accounts the plan should hold (default 50).",
|
|
26088
|
+
required: false
|
|
26089
|
+
},
|
|
26090
|
+
{
|
|
26091
|
+
name: "territory",
|
|
26092
|
+
description: "Optional: restrict the plan to a territory (e.g. 'Indre-et-Loire'). Sets geography on the Discover lens via `locations`.",
|
|
26093
|
+
required: false
|
|
26094
|
+
}
|
|
26095
|
+
],
|
|
26096
|
+
render: (args) => {
|
|
26097
|
+
const n = args.count ?? "50";
|
|
26098
|
+
return [
|
|
26099
|
+
userMessage(
|
|
26100
|
+
substitutePlaceholders(leadbay_top_accounts_to_activate, {
|
|
26101
|
+
count_or_default: n,
|
|
26102
|
+
territory_block: args.territory ? `Scope the plan to **${args.territory}** \u2014 pass it as \`locations\` on the lens, never as a sector.` : ""
|
|
26103
|
+
})
|
|
26104
|
+
)
|
|
26105
|
+
];
|
|
26106
|
+
}
|
|
25627
26107
|
}
|
|
25628
26108
|
];
|
|
25629
26109
|
function listPrompts() {
|
|
@@ -28746,7 +29226,7 @@ var OAUTH_BASE_URLS = {
|
|
|
28746
29226
|
fr: "https://staging.api.leadbay.app"
|
|
28747
29227
|
}
|
|
28748
29228
|
};
|
|
28749
|
-
var VERSION = "0.
|
|
29229
|
+
var VERSION = "0.28.0";
|
|
28750
29230
|
var HELP = `
|
|
28751
29231
|
leadbay-mcp ${VERSION} \u2014 Leadbay Model Context Protocol server
|
|
28752
29232
|
|
package/dist/http-server.js
CHANGED
|
@@ -1185,6 +1185,457 @@ End with a NEXT STEPS chip via your host's choice widget (\`ask_user_input_v0\`
|
|
|
1185
1185
|
|
|
1186
1186
|
Done. The lens is live, the validated cohort is persisted as named campaigns, and the manager knows where the cross-user-visibility gap is.
|
|
1187
1187
|
`;
|
|
1188
|
+
var leadbay_top_accounts_to_activate = `
|
|
1189
|
+
## MEMORY
|
|
1190
|
+
|
|
1191
|
+
Before responding, glance at any \`_meta.agent_memory.summary\` returned by tool calls earlier in this session and reflect its top signals in your reasoning ("Filtering by your stated preference for healthcare"). After any material new signal from the user this conversation (sector, region, deal size, communication style, qualification rule, explicit retraction, or recurrence / scheduling preference such as "I do this every day" or "remind me every morning"), call \`leadbay_agent_memory_capture\` to persist it: \`source:"user_stated"\` if literal, \`source:"inferred"\` with confidence <=6 if inferred.
|
|
1192
|
+
|
|
1193
|
+
|
|
1194
|
+
Build me a **top-{{arg:count_or_default}} account-conquest plan** \u2014 the accounts worth activating, ranked, each one carrying a strategic motif, a phone pitch and a three-step checklist. {{arg:territory_block}}
|
|
1195
|
+
|
|
1196
|
+
This deliverable goes in front of a paying client, so **the honesty of the numbers matters more than their completeness**. Deliver the strongest plan the available data actually supports, and be explicit about what it doesn't.
|
|
1197
|
+
|
|
1198
|
+
**DATA PROVENANCE \u2014 every number carries its source.** This deliverable mixes
|
|
1199
|
+
four data sources with very different trust levels, and it is shown to a
|
|
1200
|
+
paying client. A figure whose origin is unstated reads as measured fact. Tag
|
|
1201
|
+
every number you emit with exactly one class:
|
|
1202
|
+
|
|
1203
|
+
| Tag | Meaning | Where it comes from |
|
|
1204
|
+
|---|---|---|
|
|
1205
|
+
| \`[ERP]\` | measured in the client's own invoicing / management extract | the file the user attached, or a \`leadbay_get_lead_custom_fields\` read of a value imported from it |
|
|
1206
|
+
| \`[LB]\` | returned by a \`leadbay_*\` tool THIS session | \`leadbay_pull_leads\`, \`leadbay_pull_followups\`, \`leadbay_bulk_qualify_leads\`, \`leadbay_enrich_titles\`, \`leadbay_scan_portfolio_signals\`, \`leadbay_account_history\`, \`leadbay_research_lead_by_id\` |
|
|
1207
|
+
| \`[SIRENE]\` | the French public company registry | \`recherche-entreprises.api.gouv.fr\` \u2014 **your own web tool, NOT Leadbay.** Leadbay does not proxy the registry |
|
|
1208
|
+
| \`[HYP]\` | a modelled assumption | the \u20AC/employee benchmark, the 35 % objective, the trade purchase mix, any Tier-1 threshold the client hasn't confirmed |
|
|
1209
|
+
|
|
1210
|
+
**Taint propagates.** A derived figure inherits the weakest class of its
|
|
1211
|
+
inputs. \`cash = pot12 \u2212 ca12\` where \`pot12\` is \`[HYP]\` makes **\`cash\` itself
|
|
1212
|
+
\`[HYP]\`** \u2014 say so in the artefact's own caveat block, not only in chat. A
|
|
1213
|
+
client who mistakes a modelled \`cash\` figure for an audited one will build a
|
|
1214
|
+
sales plan on it.
|
|
1215
|
+
|
|
1216
|
+
**Print the PROVENANCE LEDGER before you build anything**, BEFORE writing
|
|
1217
|
+
artifact code or the final table. The block below is a **shape, not a literal**:
|
|
1218
|
+
keep the header, the \`field / class / source\` columns and the closing rule, but
|
|
1219
|
+
**replace every \`<...>\` placeholder with the real field name, class and source**
|
|
1220
|
+
\u2014 one row per field you actually emit. A ledger still showing \`<field name>\` has
|
|
1221
|
+
passed the ordering check while telling the reader nothing, which defeats its
|
|
1222
|
+
entire purpose.
|
|
1223
|
+
|
|
1224
|
+
\`\`\`
|
|
1225
|
+
PROVENANCE LEDGER
|
|
1226
|
+
=================
|
|
1227
|
+
field class source
|
|
1228
|
+
<field name> ERP <file>:col "<column header>"
|
|
1229
|
+
<field name> LB <tool that returned it>
|
|
1230
|
+
<field name> SIRENE recherche-entreprises.api.gouv.fr
|
|
1231
|
+
<field name> HYP <the formula + which input is assumed>
|
|
1232
|
+
<field name> OMITTED <why it cannot be computed>
|
|
1233
|
+
=================
|
|
1234
|
+
\`\`\`
|
|
1235
|
+
|
|
1236
|
+
An \`OMITTED\` row is the point of the ledger: it makes a gap **visible** instead
|
|
1237
|
+
of silently filled with a plausible guess. Never drop a field from the ledger
|
|
1238
|
+
just because you couldn't source it \u2014 render it as \`OMITTED\` with the reason.
|
|
1239
|
+
|
|
1240
|
+
**When a number is unavailable, do NOT model it \u2014 switch modes.** Specifically:
|
|
1241
|
+
if the client's revenue-realized figure is absent, do not estimate it, do not
|
|
1242
|
+
proxy it from headcount / sector / score, and **do not sort by any quantity
|
|
1243
|
+
derived from it.** Say plainly which fields are unavailable, name the exact
|
|
1244
|
+
columns you'd need, and deliver the plan the prompt describes \u2014 ordered by the
|
|
1245
|
+
strongest \`[LB]\` ranking you actually have.
|
|
1246
|
+
|
|
1247
|
+
**Sorting is where fabrication hides.** Asked for a ranking "by cash to go
|
|
1248
|
+
get" with no revenue data, the tempting move is to invent a revenue figure per
|
|
1249
|
+
account purely so the sort produces a plausible-looking order. That is
|
|
1250
|
+
fabrication with a confident shape, and it is the single most likely failure of
|
|
1251
|
+
this workflow. Change the sort and say so; never invent the key.
|
|
1252
|
+
|
|
1253
|
+
**Client-specific parameters are to be CONFIRMED, not assumed as product
|
|
1254
|
+
constants.** The Tier-1 threshold, the \u20AC/employee benchmark and the purchase
|
|
1255
|
+
mix all come from one client's economics. State each as \`[HYP]\` with its value
|
|
1256
|
+
visible and offer to re-run when the client supplies the real figure.
|
|
1257
|
+
|
|
1258
|
+
|
|
1259
|
+
GATE \u2014 DEFER TO TOOL RENDERING. When you call a Leadbay composite that ships its own RENDERING block (every composite in 0.9.0+ does), render the response using that block's recipe verbatim \u2014 score bars, glyph palette, column order, hide-list, link priorities, all of it. Do NOT substitute prose, a numbered list, or a different column structure even when an orchestrating prompt's body suggests alternate framing. Prompt-specific commentary (motivational nudges, summaries, next-action recommendations) belongs ABOVE or BELOW the canonical table, never in place of it.
|
|
1260
|
+
|
|
1261
|
+
If the prompt's body and the tool's RENDERING appear to conflict, the tool's RENDERING wins for the structural layout; the prompt's voice wins for the commentary that surrounds it.
|
|
1262
|
+
|
|
1263
|
+
|
|
1264
|
+
# Resilience rules for Leadbay long-running tools
|
|
1265
|
+
|
|
1266
|
+
These four rules apply to every Leadbay workflow that calls \`leadbay_pull_leads\`, \`leadbay_bulk_qualify_leads\`, \`leadbay_research_lead_by_id\`, \`leadbay_import_and_qualify\`, or \`leadbay_enrich_titles\`. **Treat timeouts and stream-closed errors as transient, not as signals to replan.**
|
|
1267
|
+
|
|
1268
|
+
## Rule 1 \u2014 Pin the lens
|
|
1269
|
+
|
|
1270
|
+
After your first \`leadbay_pull_leads\` call, capture \`response.lens.id\` into your working memory and **pass it explicitly as the \`lensId\` argument to every subsequent call** in this session \u2014 including any re-pulls, bulk qualifies, or research calls that accept it. (Field-name caveat: the response nests it as \`lens.id\`; the parameter on subsequent calls is \`lensId\`.) The active lens can shift between calls (5-minute client cache + backend \`last_requested_lens\` can change if the user touches the web UI). A lens shift mid-workflow throws away your top-10 work.
|
|
1271
|
+
|
|
1272
|
+
## Rule 2 \u2014 Prefer async for bulk operations
|
|
1273
|
+
|
|
1274
|
+
\`leadbay_bulk_qualify_leads\` and \`leadbay_import_and_qualify\` accept \`wait_for_completion:false\`, which returns \`{status:'running', qualify_id}\` immediately. Then poll \`leadbay_qualify_status\` (or \`leadbay_import_status\`) every ~10s until the job completes. **Use the async pattern by default** \u2014 the blocking default can exceed the MCP client's per-call timeout on large batches and produce a misleading \`"Request timed out"\` even though the server is still working.
|
|
1275
|
+
|
|
1276
|
+
## Rule 3 \u2014 Serialize \`leadbay_research_lead_by_id\` fan-out
|
|
1277
|
+
|
|
1278
|
+
\`leadbay_research_lead_by_id\` is composite and reads many sub-resources. Calling it on 10 leads in parallel can saturate the transport and produce \`"Tool permission stream closed"\` errors that look like permission failures but are really backpressure. **Call it sequentially**, or at most 3 in parallel. If one call fails with a stream/timeout error, retry that one call once before moving on; on a second failure, note the lead and continue \u2014 do not abandon the remaining leads.
|
|
1279
|
+
|
|
1280
|
+
## Rule 4 \u2014 Retry, don't replan
|
|
1281
|
+
|
|
1282
|
+
If a Leadbay tool returns \`"Request timed out"\`, \`"stream closed"\`, or any other transport-level error (distinct from a Leadbay-issued error payload), the work may still be running server-side. Do this in order:
|
|
1283
|
+
|
|
1284
|
+
1. For bulk tools \u2014 retry with \`wait_for_completion:false\` and poll the status tool with the returned id. Don't re-pull leads; that can shift the lens.
|
|
1285
|
+
2. For single-lead tools \u2014 retry the same call once. If it still fails, record the lead id and continue with the rest of the workflow.
|
|
1286
|
+
3. **Do not** switch strategies (e.g. "the endpoint is broken, let me re-pull from scratch"). The earlier work is still valid; the timeout was the wire.
|
|
1287
|
+
|
|
1288
|
+
If \`pull_leads\` itself fails and you have no prior batch, then yes \u2014 retry it, explicitly pass the lensId you captured (if any), and continue.
|
|
1289
|
+
|
|
1290
|
+
|
|
1291
|
+
# PHASE 0 \u2014 SCOPE + STATE
|
|
1292
|
+
|
|
1293
|
+
Call \`leadbay_account_status\` for my quota and active lens.
|
|
1294
|
+
|
|
1295
|
+
**What this plan is, and what it deliberately isn't.** Leadbay knows who a company is, how it scores, what signals it has and who to call there. It does **not** know what any account buys from me \u2014 invoicing lives in my ERP, and no Leadbay tool exposes it. So this is a **conquest plan**: real accounts, real qualification, real signals, real contacts, ranked by the strongest Leadbay signal available. Revenue-realized, per-family revenue, addressable spend and cash-to-capture are **OMITTED \u2014 never estimated, never proxied from headcount, sector or lead score.**
|
|
1296
|
+
|
|
1297
|
+
Say that scope in one line up front, so nobody reads the ranking as a money sort. If I ask for a cash-ranked plan, tell me plainly that it needs my invoicing extract and that the MCP has no path to it today \u2014 then deliver this plan anyway rather than stopping.
|
|
1298
|
+
|
|
1299
|
+
**DELIVER FIRST, ASK ALONGSIDE \u2014 never gate the plan on a missing input.** Only ONE thing can stop you before you have shipped a ranked list of real accounts: not knowing **whose** plan this is (a company-identity mismatch you genuinely cannot resolve). Everything else is a question you carry *next to* the delivered plan, not a reason to withhold it:
|
|
1300
|
+
|
|
1301
|
+
- **No benchmark?** Costs nothing here \u2014 the money column is OMITTED regardless. Pull, qualify, rank by the Leadbay signal, deliver, and mention what a cash-ranked version would need.
|
|
1302
|
+
- **No Tier-1 threshold?** Not a blocker. Deliver, and ask alongside.
|
|
1303
|
+
- **No territory?** Not a blocker \u2014 but do NOT call the result "national". You'll be pulling my ACTIVE lens, which may already be scoped to a city, sector or rep patch. Say the plan covers **my active lens's existing scope** (name the lens), not the whole country, and offer to re-scope. Calling a city-scoped lens a national plan misdescribes the deliverable to a client.
|
|
1304
|
+
- **\`last_requested_lens: null\`?** Not a blocker \u2014 and **do NOT read it as "no lens exists".** \`leadbay_account_status\` deliberately WITHHOLDS the lens id unless the request mentioned the lens/audience, so a plain "top 50 accounts to activate" returns null even when I have a perfectly good active lens. Default to calling \`leadbay_pull_leads\` with **no** \`lensId\` and let it resolve my active lens; capture \`response.lens.id\` from that result and pin it thereafter. Only create or switch a lens when I explicitly asked to scope or change the audience (e.g. a \`territory\` argument) \u2014 inventing a new lens silently changes what I see in the product.
|
|
1305
|
+
- **Only 3 qualification questions instead of 5?** Not a blocker. Use the org's real questions, note the gap, recommend the additions \u2014 do not wait for permission before pulling.
|
|
1306
|
+
|
|
1307
|
+
Bundling a non-blocking question in with a blocking one turns a justified pause into an over-wide gate, and the user gets a plan-of-a-plan instead of a plan. The test is **"have I shipped a ranked list of real accounts yet?"** \u2014 if you're about to end a turn without one, you are almost certainly over-gating: deliver first, then ask.
|
|
1308
|
+
|
|
1309
|
+
If I gave a \`territory\`, scope discovery to it now, and **make sure the scoping actually took effect before you pull** \u2014 a territory request that silently returns out-of-territory accounts is worse than none.
|
|
1310
|
+
|
|
1311
|
+
- **Preferred: \`leadbay_adjust_audience\`** on my active lens, passing the place as \`locations\`. It applies directly, so the lens I already use is now scoped and \`leadbay_pull_leads\` needs no new id.
|
|
1312
|
+
|
|
1313
|
+
\u26A0 **Location criteria MERGE \u2014 they do not replace.** \`adjust_audience\` unions the new \`location_ids\` into any existing include-location criterion (and \`pull_followups\` merges its \`city\` shortcut the same way). So asking for "R\xE9gion Ouest" on a lens already scoped to Paris yields **Paris OR R\xE9gion Ouest** while your header claims R\xE9gion Ouest. Before adding a territory, check the current filter: if it already carries locations you were not asked to keep, clear or replace them (or build a fresh territory-only lens for this one-off plan) rather than stacking a union.
|
|
1314
|
+
- **If a new lens is genuinely warranted: \`leadbay_new_lens\` is a two-step call.** It returns \`status:"preview"\` and creates NOTHING unless you re-call the same args with \`confirm:true\`. So: preview \u2192 confirm \u2192 take \`lens.id\` from the \`created\` response \u2192 pass that id as \`lensId\` on every subsequent pull. Never continue on the previous active lens after previewing a new one; that delivers the old audience under a new heading.
|
|
1315
|
+
|
|
1316
|
+
A place name goes to \`locations\`, never to \`sectors\` or a refine prompt.
|
|
1317
|
+
|
|
1318
|
+
# PHASE 1 \u2014 THE FIVE QUALIFICATION QUESTIONS
|
|
1319
|
+
|
|
1320
|
+
Call \`leadbay_get_qualification_questions\` and use the org's **actual** questions \u2014 they become the qualification row on every card. Do NOT invent them.
|
|
1321
|
+
|
|
1322
|
+
If the org has none set, or they don't discriminate for this exercise, recommend this shape and offer to set it via \`leadbay_set_qualification_questions\` (max 5, and ask before replacing anything): **Q1** exercises a core-target trade \xB7 **Q2** big enough to matter \xB7 **Q3** operates in the covered territory \xB7 **Q4** recent activity signals \xB7 **Q5** likely need in the next quarter. Q4 and Q5 are the load-bearing pair \u2014 they separate "fits the profile" from "worth calling this week". Recommend; don't overwrite without my say-so.
|
|
1323
|
+
|
|
1324
|
+
# PHASE 2 \u2014 THE ACCOUNT UNIVERSE
|
|
1325
|
+
|
|
1326
|
+
\u26A0 **Monitor membership is not client status.** Monitor tells you what Leadbay is watching \u2014 lens scoring decides who lands there, not whether the company ever bought anything. Label that pane "Leadbay view membership", never "customer".
|
|
1327
|
+
|
|
1328
|
+
**Get the accounts.** \`leadbay_pull_followups\` for the known/identified side, \`leadbay_pull_leads\` for the not-yet-identified side.
|
|
1329
|
+
|
|
1330
|
+
\u26A0 **Monitor's scope must match the scope you put in the header \u2014 never leave it accidental.** \`leadbay_pull_followups\` defaults to applying whatever Monitor filter is persisted server-side from a previous session, and that filter has nothing to do with the lens Discover is using. Two stale-state traps, one rule:
|
|
1331
|
+
|
|
1332
|
+
- **A persisted filter you didn't ask for** silently shrinks the known side, so a rep who once filtered Monitor to a city gets a "whole base" plan missing most of it.
|
|
1333
|
+
- **Blindly passing \`filtered:false\`** does the opposite: Monitor goes org-wide while Discover stays on a scoped lens, so out-of-scope known accounts land in a plan headed with the lens's name.
|
|
1334
|
+
|
|
1335
|
+
\u26A0 **You cannot mirror a geography you haven't read.** \`leadbay_pull_leads\` returns only \`lens: {id}\` \u2014 not the lens's filter \u2014 so capturing the id tells you nothing about which locations it covers. Before scoping Monitor to match a lens, read the **\`lens://<id>/definition\` resource** \u2014 that is where the filter and its \`location_ids\` actually live. \`leadbay_my_lenses\` returns only id / name / description / active flags, so it cannot tell you a lens's geography and must not be used for this. If you cannot determine the lens's geography, do NOT guess: pull Monitor org-wide with \`filtered:false\` and say in the header that the known side is org-wide while Discover follows lens \`<id>\`, whose scope you could not read. An unstated mismatch is the failure; a stated one is honest.
|
|
1336
|
+
|
|
1337
|
+
So: **read the persisted filter first** (the response reports \`active_filters\`), then make it match the plan's declared scope. If the plan is scoped (a \`territory\`, or an active lens with its own geography), apply that same geography to Monitor. If the plan is genuinely org-wide, pass \`filtered:false\`. Either way, state the known side's scope in the header in the same breath as the Discover side \u2014 a plan whose two halves are scoped differently is misleading even when both halves are individually correct.
|
|
1338
|
+
|
|
1339
|
+
\u26A0 **A territory must scope BOTH sides.** Adjusting or creating a lens only scopes Discover; Monitor is filtered through its own path, so pass the territory to \`leadbay_pull_followups\` as well (its \`city\` free-text shortcut resolves to a \`location_ids\` filter, same resolver as the lens). Otherwise a territory-scoped plan quietly mixes in out-of-territory known accounts \u2014 and a client reading "R\xE9gion Ouest" at the top will not check every row. Unless I named a \`territory\`, call \`leadbay_pull_leads\` with **no \`lensId\`** so it resolves my active lens \u2014 do not create a lens just because \`account_status\` showed a null. Capture \`response.lens.id\` from the first pull and pass it as an explicit \`lensId\` on every later call \u2014 a mid-session lens shift discards the cohort. Keep pulling until you have a pool comfortably deeper than {{arg:count_or_default}}, topping up with \`leadbay_bulk_qualify_leads\` \u2192 \`leadbay_qualify_status\` \u2192 re-pull as needed.
|
|
1340
|
+
|
|
1341
|
+
# PHASE 3 \u2014 QUALIFY, SIGNAL, MOTIF
|
|
1342
|
+
|
|
1343
|
+
**Qualify \u2014 the SELECTED cohort, in chunks of 25.** \`leadbay_bulk_qualify_leads\` caps \`count\` at **25**, so a single call cannot cover a 50-account plan. Loop until the whole cohort is qualified, polling \`leadbay_qualify_status\` between chunks.
|
|
1344
|
+
|
|
1345
|
+
\u26A0 **Never qualify Monitor rows through a Discover \`lensId\`.** \`leadbay_qualify_status\` re-checks each lead against the lens it was launched on and returns them under **\`not_in_lens\`** \u2014 the backend does not qualify them, so those rows ship with permanently empty pills while the poll reads "still running". So split the cohort: qualify the Discover rows with the pinned \`lensId\`, and for known-side rows use the qualification data \`leadbay_pull_followups\` already returned rather than re-launching them off-lens. If a Monitor row has no qualification data, say so in its cell \u2014 do not leave a pill that will never fill. **Always read \`not_in_lens\` in the poll response** and report anything listed there rather than waiting on it.
|
|
1346
|
+
|
|
1347
|
+
\u26A0 **Pass explicit \`leadIds\` whenever the cohort isn't simply "the next N on the lens"** \u2014 e.g. after you've selected a shortlist, or when the plan mixes Monitor and Discover rows. The \`count\`-based path selects the next *unqualified leads from the lens wishlist*, so on any other cohort it qualifies unrelated leads and hands you handles whose pills belong to different companies. Use \`leadbay_bulk_qualify_leads({leadIds:[\u2026\u226425 of the cohort], wait_for_completion:false})\` and chunk through the cohort's own ids. The \`{lensId, count}\` form is only right when the cohort genuinely *is* the lens's top N.
|
|
1348
|
+
|
|
1349
|
+
**Qualify the plan cohort, not the whole base.** Select your ~{{arg:count_or_default}} candidates (plus a modest buffer for drop-outs) BEFORE qualifying \u2014 qualification is async and quota-bearing, so running it across an entire portfolio to produce a top-{{arg:count_or_default}} burns the user's quota for rows that will never appear. **Keep every returned \`qualify_id\`** \u2014 the deck's live qualification layer is wired from those handles, and a deck with none is a dead deck that still looks finished. Never ship a plan whose lower ranks have empty qualification pills because only the first 25 were ever qualified.
|
|
1350
|
+
|
|
1351
|
+
**Signals \u2014 scoped to the cohort.** \u26A0 **Always pass the selected \`leadIds\`.** With \`leadIds\` omitted, \`leadbay_scan_portfolio_signals\` builds its own portfolio by paging \`/monitor\` \u2014 so on an imported cohort or a freshly-pulled Discover set it would scan a *different population* and you'd render dashes for accounts whose signals were never read.
|
|
1352
|
+
|
|
1353
|
+
\`leadbay_scan_portfolio_signals\` is also a **filtered** read: it requires a concrete \`query\` and returns only the accounts whose cached signals match it. It is not a generic "read every signal" call. So run it **once per why-now theme, as SEPARATE calls** \u2014 expansion/new site \xB7 contract or tender won \xB7 funding \xB7 hiring \xB7 acquisition \xB7 new venue \u2014 and union the results.
|
|
1354
|
+
|
|
1355
|
+
\u26A0 **One comma-joined omnibus query is NOT six themed scans.** Cramming every keyword into a single string is one match attempt whose recall you cannot inspect: a lead that would have matched "hiring" alone can be missed, and you have no way to tell which themes actually returned anything. Six calls, six result sets, one union. If a theme returns nothing, that is information \u2014 record it rather than hiding it inside a broad string. An account that matched no query has **not** been shown to be signal-free; render it with an explicit \`\u2014\`, never an invented event. For the identified side, take interaction recency from the fields \`leadbay_pull_followups\` already returned. \u26A0 **Do NOT reach for \`leadbay_account_history\` on Monitor rows outside the active lens** \u2014 it calls \`research_lead_by_id\` first, which fetches \`/lenses/{lensId}/leads/{leadId}\` and 404s off-lens, so the very rows that need a SUIVI / R\xC9VEIL-LB decision are the ones it fails on. Use it only for a lead you know is in the pinned lens.
|
|
1356
|
+
|
|
1357
|
+
**SIGNAL HONESTY \u2014 never infer signals from freshness.** \`stale_at\`,
|
|
1358
|
+
\`web_fetch_in_progress\`, \`fetch_at\` are freshness markers, not signal
|
|
1359
|
+
indicators \u2014 signal presence is read ONLY from the actual \`signals[]\` /
|
|
1360
|
+
\`web_fetch.content\` entries. For "which of my leads have signal X" across a
|
|
1361
|
+
portfolio, call **\`leadbay_scan_portfolio_signals\`** (bulk-reads cached
|
|
1362
|
+
signals); don't loop \`leadbay_research_lead_by_id\` per lead or guess from
|
|
1363
|
+
freshness. A lead with no cached content is \`not_researched\`, not "no match";
|
|
1364
|
+
never report a signal verdict for a lead you never read.
|
|
1365
|
+
|
|
1366
|
+
|
|
1367
|
+
**Assign the motif.**
|
|
1368
|
+
|
|
1369
|
+
**THE ACTIVATION MOTIFS.** Every account on the plan carries exactly one motif
|
|
1370
|
+
from this closed set of six. The motif is not decoration \u2014 it decides the phone
|
|
1371
|
+
pitch, the checklist, and whether the account belongs to the *Pilotage* engine
|
|
1372
|
+
(already identified) or the *Conqu\xEAte* engine (not yet identified). Assign it
|
|
1373
|
+
from observable data and state the deciding evidence in one line per account.
|
|
1374
|
+
|
|
1375
|
+
| Motif | Assign when | Engine |
|
|
1376
|
+
|---|---|---|
|
|
1377
|
+
| **SAUVETAGE** | was buying steadily, has now stopped \u2014 a recent, sharp break (e.g. no order in ~60\u201390 days against a real history) | Pilotage |
|
|
1378
|
+
| **PLAN DE COMPTE** | large, still active, buying broadly \u2014 the risk is complacency, not loss; plan the coming half-year and lock volume terms | Pilotage |
|
|
1379
|
+
| **MONT\xC9E EN GAMME** | active but narrow \u2014 buys one product family while comparable accounts of the same size buy several; the gap is cross-sell | Pilotage |
|
|
1380
|
+
| **R\xC9VEIL** | account exists, essentially dormant \u2014 long-dead history (e.g. 12+ months at zero) but the company is demonstrably still trading | Pilotage |
|
|
1381
|
+
| **CONQU\xCATE** | not present in the Leadbay known pipeline \u2014 in the addressable market, absent from the base. \u26A0 Absence from Monitor is NOT proof they never bought (see below) | Conqu\xEAte |
|
|
1382
|
+
| **SUIVI** | in the known pipeline with recent activity, purchase behaviour unknown \u2014 the honest label for an active Monitor row when no order history is available | Pilotage |
|
|
1383
|
+
|
|
1384
|
+
**Decision order matters.** Test in this order and stop at the first match, or
|
|
1385
|
+
a big lapsed account will be labelled R\xC9VEIL when it is really a SAUVETAGE:
|
|
1386
|
+
recent sharp break \u2192 SAUVETAGE; long-dormant \u2192 R\xC9VEIL; never bought \u2192
|
|
1387
|
+
CONQU\xCATE; buying broadly at scale \u2192 PLAN DE COMPTE; buying narrowly \u2192
|
|
1388
|
+
MONT\xC9E EN GAMME.
|
|
1389
|
+
|
|
1390
|
+
**Without order history the first five tests cannot run at all.** In that case
|
|
1391
|
+
the split is simply: in the Leadbay known pipeline \u2192 **SUIVI**; not in the
|
|
1392
|
+
pipeline \u2192 **CONQU\xCATE**. Never reach for a Pilotage motif you cannot evidence,
|
|
1393
|
+
and never invent a seventh label \u2014 the set is closed at six.
|
|
1394
|
+
|
|
1395
|
+
When a known-pipeline row has long-dormant *Leadbay* activity, it stays
|
|
1396
|
+
**SUIVI** and you say what the dormancy measures in its why-now cell: "no
|
|
1397
|
+
Leadbay-logged action in N months". That is a qualifier on the evidence, not a
|
|
1398
|
+
new motif. It is NOT R\xC9VEIL \u2014 R\xC9VEIL means dormant *purchasing*, which needs
|
|
1399
|
+
order history you do not have.
|
|
1400
|
+
|
|
1401
|
+
**What each motif changes in the output.**
|
|
1402
|
+
|
|
1403
|
+
- The **pitch angle** \u2014 SAUVETAGE opens on the silence itself and offers terms
|
|
1404
|
+
to resume; PLAN DE COMPTE opens on the relationship and plans forward;
|
|
1405
|
+
MONT\xC9E EN GAMME opens on what comparable firms buy that this one doesn't;
|
|
1406
|
+
R\xC9VEIL asks what made them leave and offers a re-entry incentive; CONQU\xCATE
|
|
1407
|
+
introduces the company and asks for a short first meeting \u2014 **without
|
|
1408
|
+
asserting no prior relationship**. Never write "we've never worked together"
|
|
1409
|
+
or "as a new customer" on a Leadbay-only plan: absence from the known
|
|
1410
|
+
pipeline is not proof they never bought, and that line told to an existing
|
|
1411
|
+
customer is the one mistake a rep cannot walk back. Write the pitch in the
|
|
1412
|
+
client's own commercial voice, naming the specific families and figures the
|
|
1413
|
+
account's data actually supports. **SUIVI** picks up the existing thread \u2014
|
|
1414
|
+
a continuation, never an introduction and never a win-back.
|
|
1415
|
+
- The **checklist** \u2014 three concrete, checkable next actions matching the
|
|
1416
|
+
motif's shape: diagnose \u2192 schedule \u2192 send-terms for SAUVETAGE; review \u2192
|
|
1417
|
+
propose \u2192 open-a-family for PLAN DE COMPTE; visit-with-full-tariff \u2192 quote \u2192
|
|
1418
|
+
first-order-in-the-new-family for MONT\xC9E EN GAMME; understand-the-departure \u2192
|
|
1419
|
+
send-offer \u2192 first-order-back for R\xC9VEIL; reach-the-decision-maker \u2192
|
|
1420
|
+
open-the-account \u2192 first-test-order for CONQU\xCATE; confirm-the-state \u2192
|
|
1421
|
+
identify-the-current-need \u2192 agree-a-next-step for SUIVI. When a signal exists,
|
|
1422
|
+
promote "exploit <the signal>" to the top of that account's checklist.
|
|
1423
|
+
|
|
1424
|
+
**Motif assignment depends on order history, which is ERP data.** Without the
|
|
1425
|
+
client's extract, SAUVETAGE / PLAN DE COMPTE / MONT\xC9E EN GAMME / R\xC9VEIL cannot
|
|
1426
|
+
be assigned from purchase behaviour \u2014 do not guess them from a lead score, a
|
|
1427
|
+
sector, or a company's size. Two honest options, in order of preference:
|
|
1428
|
+
|
|
1429
|
+
**The Monitor gap \u2014 read this before assigning anything.** Four of the six
|
|
1430
|
+
motifs (SAUVETAGE / PLAN DE COMPTE / MONT\xC9E EN GAMME / R\xC9VEIL) are purchase-
|
|
1431
|
+
behaviour reads, and CONQU\xCATE means "not in the known pipeline". A Monitor row
|
|
1432
|
+
that is *actively* worked therefore matches none of them: it IS in the pipeline,
|
|
1433
|
+
and without order history you cannot tell whether it buys broadly, narrowly, or
|
|
1434
|
+
at all. Do NOT resolve that by guessing a purchase motif, and do NOT silently
|
|
1435
|
+
drop the row.
|
|
1436
|
+
|
|
1437
|
+
Those rows take **SUIVI** (row 6 of the table above). Say in the plan's legend
|
|
1438
|
+
that SUIVI exists precisely because purchase history is unavailable, and that
|
|
1439
|
+
ERP order data would split those rows into the four Pilotage motifs.
|
|
1440
|
+
|
|
1441
|
+
1. **CONQU\xCATE is assignable from Leadbay alone \u2014 but say what it actually
|
|
1442
|
+
means.** Discover membership proves a company is **not in the Leadbay known
|
|
1443
|
+
pipeline**; it does NOT prove they never bought. Monitor membership is set by
|
|
1444
|
+
lens scoring, not by purchase history, so an existing customer who was never
|
|
1445
|
+
scored into the known view will appear in Discover. Without order history
|
|
1446
|
+
there is no way to tell the two apart.
|
|
1447
|
+
|
|
1448
|
+
So label the motif for what the data supports \u2014 "fresh / not in the Leadbay
|
|
1449
|
+
pipeline" \u2014 and **write the pitch so it survives being wrong**: an opener
|
|
1450
|
+
that introduces the company works for a genuine prospect and merely sounds
|
|
1451
|
+
uninformed to a customer, whereas "we've never worked together" told to a
|
|
1452
|
+
current customer damages the relationship and the credibility of the whole
|
|
1453
|
+
plan. Only ERP order history can upgrade this to a true never-a-client
|
|
1454
|
+
claim. A Leadbay-only plan is still a legitimate *Conqu\xEAte* plan \u2014 say so in
|
|
1455
|
+
the title rather than implying it covers the whole base.
|
|
1456
|
+
2. **Leadbay-activity recency is a qualifier, never a motif.** A long-dormant
|
|
1457
|
+
known row stays **SUIVI** with "no Leadbay-logged action in N months" in its
|
|
1458
|
+
why-now cell \u2014 never "no orders in N months", and never a seventh label.
|
|
1459
|
+
Logged activity is not invoicing.
|
|
1460
|
+
|
|
1461
|
+
|
|
1462
|
+
# PHASE 4 \u2014 POTENTIAL AND RANKING
|
|
1463
|
+
|
|
1464
|
+
Rank by \`ai_agent_lead_score\`, then qualification boost, then headcount. **Name that key in the plan's own header** \u2014 a reader who assumes a money-sort misreads the whole order \u2014 and title the deliverable for what it is (a conquest plan), not for what it isn't.
|
|
1465
|
+
|
|
1466
|
+
Cash-to-capture is not available: it needs \`ca12\` from my invoicing system, which no Leadbay tool exposes. Show it as OMITTED in the ledger and say what a cash-ranked version would require (12-month revenue per account, per-family split, last order date, order count, plus a \u20AC/employee benchmark) \u2014 do not model it.
|
|
1467
|
+
|
|
1468
|
+
# PHASE 5 \u2014 CONTACTS (consent-gated)
|
|
1469
|
+
|
|
1470
|
+
Each card needs a reachable decision-maker. \`leadbay_enrich_titles({leadIds, lensId})\` in discovery mode first \u2014 that reveals what's enrichable and spends nothing. Render whatever contact detail is already on the record; many accounts already carry a named contact.
|
|
1471
|
+
|
|
1472
|
+
**Do NOT stop and wait for enrichment consent before delivering.** Asking for a plan is not authorization to spend quota on {{arg:count_or_default}} accounts \u2014 but neither is it a reason to end the turn on a spending question with no plan attached. Ship the ranked plan (Phase 6), then **offer** the paid reveal alongside it. The discovery call returned no \`titles\`, so it only told you what's *available* \u2014 **the offer must therefore carry the titles you propose to enrich AND the channels**, not just a volume: "enrich N contacts at these titles (\`<the titles you picked from available_titles / title_suggestions>\`), email only / email + phone \u2014 reveals consume quota". A bare "yes" to a volume-only question is not a mandate to pick titles yourself, and re-running discovery instead of launching wastes a turn.
|
|
1473
|
+
|
|
1474
|
+
\u26A0 **Do NOT quote a cost or a credits figure.** The per-reveal rate is backend-side and enrichment is gated by quota, not a credit balance; \`credits_remaining\` is advisory context only. A spend number invented to make the offer concrete is the same failure as an invented euro on a card.
|
|
1475
|
+
|
|
1476
|
+
On an explicit yes, launch with the agreed \`titles\` + channels, then poll \`leadbay_bulk_enrich_status\` until done and **keep the \`bulk_id\` handles** for the deck.
|
|
1477
|
+
|
|
1478
|
+
\u26A0 **Render only the channels that actually came back.** The default reveal is email-only unless phone was explicitly requested, so never emit a \`tel:\` link for a contact whose phone was never revealed \u2014 show the channels enrichment returned and mark the rest omitted. A fabricated phone link is the same failure as a fabricated euro.
|
|
1479
|
+
|
|
1480
|
+
# PHASE 6 \u2014 DELIVER
|
|
1481
|
+
|
|
1482
|
+
Render the PROVENANCE LEDGER and its legend FIRST, then the chat answer beneath it \u2014 never the other way round. A ranked money column read before its sourcing has already misled the reader:
|
|
1483
|
+
|
|
1484
|
+
## RENDERING \u2014 account activation plan
|
|
1485
|
+
|
|
1486
|
+
Two surfaces. The **chat table** is the default answer and must stand alone as
|
|
1487
|
+
useful. The **interactive deck** is offered, not forced (see the widget gate) \u2014
|
|
1488
|
+
build it only once the user accepts.
|
|
1489
|
+
|
|
1490
|
+
### Order on the page \u2014 ledger FIRST, then the plan
|
|
1491
|
+
|
|
1492
|
+
Print the PROVENANCE LEDGER (and the one-line provenance legend) **before** the
|
|
1493
|
+
chat table, the deck, or any other part of the deliverable. The reader must know
|
|
1494
|
+
which figures are measured and which are modelled *before* they read a ranking
|
|
1495
|
+
built on them \u2014 a cash column read first and sourced second has already done its
|
|
1496
|
+
damage. This ordering is the workflow contract, not a stylistic preference.
|
|
1497
|
+
|
|
1498
|
+
### The chat table (render immediately after the ledger)
|
|
1499
|
+
|
|
1500
|
+
**The chat answer must be the whole deliverable the user asked for** \u2014 the deck
|
|
1501
|
+
is optional, so a top-50 request whose chat half stops at 10 rows has delivered
|
|
1502
|
+
a fifth of the plan. Render the **requested count**, with its pitch + checklist
|
|
1503
|
+
block per row (see below).
|
|
1504
|
+
|
|
1505
|
+
If that is genuinely too long for one message, do NOT silently truncate: state
|
|
1506
|
+
the delivered count plainly ("here are 20 of the 50 \u2014 say the word for the
|
|
1507
|
+
rest"), so the user knows what they have. Never present a partial list as
|
|
1508
|
+
though it were the plan. Four columns:
|
|
1509
|
+
|
|
1510
|
+
Col 3's header is **the ranking key you actually used** \u2014 never a cash label,
|
|
1511
|
+
since cash-to-capture cannot be computed from Leadbay data:
|
|
1512
|
+
|
|
1513
|
+
\`\`\`
|
|
1514
|
+
| # \xB7 Account | Motif | Fit score | Why now |
|
|
1515
|
+
\`\`\`
|
|
1516
|
+
|
|
1517
|
+
- **Col 1** \u2014 rank number, then the company name linked to its website when one
|
|
1518
|
+
is known. Follow with a compact \` \xB7 \`-separated pill line: city \xB7 headcount \xB7
|
|
1519
|
+
any account reference you were given. **Every figure in that pill line carries
|
|
1520
|
+
its class too** \u2014 headcount is \`[LB]\` (a Leadbay size band, so render the band
|
|
1521
|
+
rather than a false-precision point value) or \`[SIRENE]\` if you read it from
|
|
1522
|
+
the registry. An untagged employee count is still an untagged number in front
|
|
1523
|
+
of a client; omit it rather than ship it bare.
|
|
1524
|
+
- **Col 2** \u2014 the motif, exactly one of SAUVETAGE / PLAN DE COMPTE / MONT\xC9E EN
|
|
1525
|
+
GAMME / R\xC9VEIL / CONQU\xCATE / SUIVI. Never invent a seventh.
|
|
1526
|
+
- **Col 3** \u2014 the ranking signal with its provenance class, e.g. \`AI 30 [LB]\`.
|
|
1527
|
+
Tagging is not optional; an untagged figure reads as measured fact. **There is
|
|
1528
|
+
no money column** \u2014 cash-to-capture needs invoicing data Leadbay does not
|
|
1529
|
+
hold, so it stays OMITTED in the ledger rather than being modelled. A column
|
|
1530
|
+
of invented euros next to a client's name is the exact failure this
|
|
1531
|
+
deliverable must not ship.
|
|
1532
|
+
- **Col 4** \u2014 the one-line reason to act now: the signal when there is one,
|
|
1533
|
+
otherwise the motif's deciding evidence. Never fill this with a
|
|
1534
|
+
plausible-sounding invented event; an account with nothing read shows \`\u2014\`.
|
|
1535
|
+
|
|
1536
|
+
Sort strictly by the ranking key named in the ledger (which was printed above).
|
|
1537
|
+
|
|
1538
|
+
### The pitch + checklist block (part of the chat answer, not the deck)
|
|
1539
|
+
|
|
1540
|
+
The table alone is a shortlist, not a plan \u2014 the pitch and the three-step
|
|
1541
|
+
checklist are what make it actionable, and the deck is **optional**, so they
|
|
1542
|
+
cannot live only there. Under the table, render a block for **every account you
|
|
1543
|
+
put in the table** \u2014 if a row is good enough to rank, it is good enough to carry
|
|
1544
|
+
its pitch. Do NOT ship the top 5 or 10 and offer the rest "on request": that
|
|
1545
|
+
puts the actionable half of the deliverable behind another user turn, and the
|
|
1546
|
+
rows you defer are the ones a rep is least likely to chase. If the full plan is
|
|
1547
|
+
genuinely long, shrink the TABLE (fewer rows, stated plainly) rather than
|
|
1548
|
+
shipping ranked rows with no pitch:
|
|
1549
|
+
|
|
1550
|
+
\`\`\`
|
|
1551
|
+
**<rank> \xB7 <Company>** \u2014 <MOTIF>
|
|
1552
|
+
\u260E <contact name>, <title> \xB7 <only the channels actually revealed>
|
|
1553
|
+
> "<the motif's pitch, in the client's commercial voice>"
|
|
1554
|
+
\u2610 <step 1> \u2610 <step 2> \u2610 <step 3>
|
|
1555
|
+
\`\`\`
|
|
1556
|
+
|
|
1557
|
+
Keep each pitch to one or two sentences a rep can say out loud, and each
|
|
1558
|
+
checklist to three concrete, checkable actions matching that motif's shape.
|
|
1559
|
+
When an account has a signal, lead the pitch with it and promote
|
|
1560
|
+
"exploit <the signal>" to the top of its checklist.
|
|
1561
|
+
|
|
1562
|
+
### The interactive deck (only after the user accepts)
|
|
1563
|
+
|
|
1564
|
+
One card per account, ordered by the same key. Per card:
|
|
1565
|
+
|
|
1566
|
+
- **Header** \u2014 rank badge, company name, city \xB7 trade \xB7 headcount, and the
|
|
1567
|
+
ranking signal right-aligned with its class tag (e.g. \`AI 30 [LB]\`). No cash
|
|
1568
|
+
figure: the deck and the chat answer must never disagree about which fields
|
|
1569
|
+
exist, and a card carrying a euro the table omitted means one was modelled.
|
|
1570
|
+
- **Motif badge** \u2014 the motif, visually distinct per motif so the deck can be
|
|
1571
|
+
scanned by strategy.
|
|
1572
|
+
- **Qualification row** \u2014 the org's **actual** questions as returned by
|
|
1573
|
+
\`leadbay_get_qualification_questions\` (there may be fewer than five), each with
|
|
1574
|
+
\u2713 / \u2717 / pending and labelled with its real text. Render exactly as many rows as
|
|
1575
|
+
the org has: never pad to five with invented labels or bogus pending pills, and
|
|
1576
|
+
never substitute invented wording.
|
|
1577
|
+
- **Signal line** \u2014 the event driving urgency, or omitted.
|
|
1578
|
+
- **Action block** \u2014 the named contact with **only the channels enrichment
|
|
1579
|
+
actually returned** as one-tap \`tel:\` / \`mailto:\` links (the default reveal is
|
|
1580
|
+
email-only unless phone was requested \u2014 never emit a \`tel:\` for a phone that
|
|
1581
|
+
was never revealed; mark it omitted instead), the motif's pitch as a quoted
|
|
1582
|
+
line, and the three-item checklist as checkboxes.
|
|
1583
|
+
- **Caveat block** \u2014 closing the deck: which classes fed it, an explicit line
|
|
1584
|
+
that \`[HYP]\` figures are modelled rather than measured, and which fields are
|
|
1585
|
+
OMITTED because Leadbay does not hold them. A missing input means an omitted
|
|
1586
|
+
field, never a stand-in number.
|
|
1587
|
+
|
|
1588
|
+
Header KPIs across the top, each carrying its provenance class: accounts on the
|
|
1589
|
+
plan, count qualified, count with a reachable contact. **No euro totals** \u2014 the
|
|
1590
|
+
same rule as the table, for the same reason.
|
|
1591
|
+
|
|
1592
|
+
\u26A0 **No "activated" KPI at build time.** Nothing in this workflow measures
|
|
1593
|
+
activation \u2014 the deck is built before any outreach happens \u2014 so a count would be
|
|
1594
|
+
fabricated or imply outcome tracking that doesn't exist. If the deck's checklists
|
|
1595
|
+
persist locally, an "activated" tile may count *checked* accounts and must be
|
|
1596
|
+
labelled as local checklist state, not a measured outcome.
|
|
1597
|
+
|
|
1598
|
+
|
|
1599
|
+
Then **offer** the interactive deck \u2014 don't force it:
|
|
1600
|
+
|
|
1601
|
+
## GATE \u2014 PREFER BUILT-IN HOST WIDGETS
|
|
1602
|
+
|
|
1603
|
+
Modern chat hosts (Claude, ChatGPT) expose first-party widgets the agent can route into. These ALWAYS produce a better UX than markdown tables / inline prose for the data shapes they support \u2014 they're tappable on mobile, persistent across turns, and integrate with the host's quick-actions.
|
|
1604
|
+
|
|
1605
|
+
**The Big Three** \u2014 when a tool result fits, route there:
|
|
1606
|
+
|
|
1607
|
+
| Host widget | Use when | Field map (from Leadbay payload) |
|
|
1608
|
+
|---|---|---|
|
|
1609
|
+
| \`places_map_display_v0\` + \`places_search\` (Claude) | \u22652 leads with coords / \`location.city\`, geographic / "in person" / travel intent | **Two-step**: \`places_search\` each lead (query = company + full street address) \u2192 real \`place_id\`/coords, THEN render with \`places_map_display_v0\` (Itinerary mode for a tour). Skipping \`places_search\` \u2192 schematic scatter, not a street map. |
|
|
1610
|
+
| \`message_compose_v1\` (Claude) | You're about to draft outreach (email / message / call opener) | \`{kind: "email", summary_title, variants: [{label, body, subject}]}\` \u2014 2\u20133 variants, labels describe STRATEGY ("Push for alignment", "Reference the M&A signal"), not tone ("Friendly", "Formal") |
|
|
1611
|
+
| \`ask_user_input_v0\` (Claude chat / ChatGPT) **or** \`AskUserQuestion\` (Claude cowork / Claude Code) \u2014 whichever is in your tool set; their schemas differ, match the one you have | The tool's NEXT STEPS block has 2\u20134 mutually-exclusive next moves and the user hasn't already chosen | Per-tool schema in the server instructions + NEXT STEPS routing block. Max 3 questions. |
|
|
1612
|
+
|
|
1613
|
+
ChatGPT exposes the same routing pattern via \`_meta.openai/outputTemplate\`. We don't ship any custom widgets ourselves \u2014 this gate is exclusively about routing into the host's first-party widgets when the data shape fits.
|
|
1614
|
+
|
|
1615
|
+
**Rules:**
|
|
1616
|
+
- The widget IS the visual. Do NOT emit a markdown table or prose list of the same data alongside \u2014 that produces two competing UIs.
|
|
1617
|
+
- Pass identifiers (place_id, lead.id, contact_id) verbatim. Don't rewrite.
|
|
1618
|
+
- When the host doesn't expose the named widget, the agent falls back to the prose/table rendering the per-tool description already specifies. The directive is host-conditional; the fallback is automatic.
|
|
1619
|
+
- One short intro sentence in chat is enough \u2014 "Here are your 5 NYC follow-ups." Then route into the widget.
|
|
1620
|
+
|
|
1621
|
+
|
|
1622
|
+
\u26A0 **The deck's contact layer depends on what actually happened in Phase 5.** Bind a \`leadbay_bulk_enrich_status\` resource ONLY if a paid reveal was launched and you hold a \`bulk_id\`. If the user accepted the deck but not the reveal, render the contacts already on record and carry the paid-reveal offer inside the deck \u2014 never wire a status resource with no handle (it renders permanently empty) and never launch enrichment from the deck to manufacture one.
|
|
1623
|
+
|
|
1624
|
+
On acceptance, call \`leadbay_artifact_kit\`, read its \`usage_guide\` before writing any code, and build a single-file deck. Wire the live layer from the handles you kept: a poll-until-done resource per \`qualify_id\` for the qualification pills, and one over \`leadbay_bulk_enrich_status\` for the contacts. \u26A0 **If enrichment already ran this session, bind the existing \`bulk_id\` \u2014 re-launching enrichment from the deck double-spends my quota.** Per-card notes and outcomes go through the pre-wired note/outreach view-models (they carry the required verification and \`_triggered_by\` fields; hand-rolling those is where it breaks). Keep the checklists in local storage, and always wire a Refresh \u2014 auto-poll is host-dependent. List every tool the deck calls in its \`mcp_tools\`, and render the bridge-unavailable branch, or the pills silently show empty.
|
|
1625
|
+
|
|
1626
|
+
# Iron laws
|
|
1627
|
+
|
|
1628
|
+
- **Never invent a number.** No revenue figure, registry count, signal or lead id that didn't come from a Leadbay response or a real registry query. A modelled figure is fine \u2014 tagged \`[HYP]\` and named as an assumption. An untagged one is not.
|
|
1629
|
+
- **The ledger ships before the deliverable**, with un-sourceable fields shown as OMITTED rather than dropped.
|
|
1630
|
+
- **A conquest plan is the deliverable, not a consolation prize.** Leadbay holds no invoicing data, so the money columns are OMITTED by design. Title it honestly, name what a cash-ranked version would need \u2014 never refuse, and never fill the gap with a guess.
|
|
1631
|
+
- **Deliver first, ask alongside.** Do not end a turn without a ranked list of real accounts. The benchmark, the Tier-1 threshold, the territory, a missing lens, a short question set and an unanswered enrichment offer are all NON-blocking \u2014 carry them next to the plan. Only an unresolvable identity mismatch (whose plan is this?) may stop delivery.
|
|
1632
|
+
- **One motif per account, from the closed set of six**, with its deciding evidence stated.
|
|
1633
|
+
- **The org's real qualification questions**, read from Leadbay \u2014 never invented.
|
|
1634
|
+
- **Consent before any paid enrichment**, and never re-launch a bulk that already exists.
|
|
1635
|
+
- **Offer the deck; don't force it.** The chat answer must stand alone as useful.
|
|
1636
|
+
- Carry the captured \`lensId\` on the calls whose schema **accepts** it (\`leadbay_pull_leads\`, \`leadbay_bulk_qualify_leads\`, \`leadbay_enrich_titles\`). Do NOT add it to \`leadbay_pull_followups\`, \`leadbay_scan_portfolio_signals\`, \`leadbay_qualify_status\` or \`leadbay_bulk_enrich_status\` \u2014 they declare no such argument and reject unknown properties.
|
|
1637
|
+
- Building a plan is not outreaching \u2014 do not send anything and do not call \`leadbay_report_outreach\`.
|
|
1638
|
+
`;
|
|
1188
1639
|
var leadbay_work_campaign = `
|
|
1189
1640
|
Work my **{{arg:campaign_or_default}}** campaign as an outreach session{{arg:mode_paren}}.
|
|
1190
1641
|
|
|
@@ -1308,6 +1759,7 @@ should I follow up on" to "I'll send via lemlist".
|
|
|
1308
1759
|
leadbay_refine_audience: { "name": "leadbay_refine_audience", "short_description": "Refine the kind of leads Leadbay surfaces beyond firmographics, with a\nfree-text instruction. Handles the clarification round-trip if the new\nprompt is ambiguous.\n", "arguments": [{ "name": "instruction", "description": "The refinement (e.g. 'focus on hospitals running their own IT'). Set to plain English.", "required": true }], "expected_calls": ["leadbay_refine_prompt", "leadbay_account_status"], "failure_modes": ["Calls leadbay_answer_clarification on the user's behalf instead of surfacing the clarification verbatim", "Glosses over the clarification options instead of presenting them as offered", "Promises immediate effect when status='applied' actually triggers an async intelligence recompute"] },
|
|
1309
1760
|
leadbay_research_a_domain: { "name": "leadbay_research_a_domain", "short_description": "Resolve a company by name or domain across the user's visible Discover,\nMonitor, and Activate corpus, then return everything Leadbay knows about it.\n", "arguments": [{ "name": "domain", "description": "Company name or domain (for example 'Acme Corporation' or 'acme.com'). The legacy argument key remains `domain` for client compatibility.", "required": true }], "expected_calls": ["leadbay_research_lead_by_name_fuzzy"], "failure_modes": ["Fabricates qualification answers not present in any tool response", "Calls leadbay_import_and_qualify before searching the existing visible corpus", "Treats the active lens as the entire search universe when the user did not request a lens scope", "Imports a missing company without the user's explicit permission", "Renders the research result as a freeform narrative instead of the canonical research-company-card layout (the card with header score bar, pill row, signal sections, contacts table is the structural contract; commentary belongs ABOVE or BELOW it)"] },
|
|
1310
1761
|
leadbay_setup_team_prospecting: { "name": "leadbay_setup_team_prospecting", "short_description": "Manager-led prospecting setup: conversationally turn a natural-language\naudience ask into a Leadbay lens, validate the candidate leads, and\npersist them as one or more named campaigns the rep(s) can work\nthrough. Closes #3630 US3 end-to-end (within the current\ncreator-scoped campaign visibility model).\n", "arguments": [{ "name": "audience", "description": "Natural-language audience description (e.g. 'plumbing companies with 10-50 employees in Seine-Maritime'). The lens-creation step (`leadbay_refine_prompt` \u2192 `leadbay_create_lens`) interprets it.", "required": true }, { "name": "rep_split", "description": "Optional: how to split the validated leads into per-rep campaigns. Free text \u2014 e.g. 'split by city' or 'one campaign per rep: John gets Tulsa, Sarah gets OKC'.", "required": false }], "expected_calls": ["leadbay_refine_prompt", "leadbay_create_lens", "leadbay_promote_lens", "leadbay_pull_leads", "leadbay_research_lead_by_id", "leadbay_create_campaign", "leadbay_add_leads_to_campaign"], "failure_modes": ["Skips the validation step \u2014 creates a campaign of unvetted leads from a freshly-created lens without giving the manager a chance to drop weak fits", "Creates ONE campaign for all reps without asking about the split \u2014 the user explicitly mentioned per-rep distribution and the prompt should honor it", "Pretends the backend supports cross-user assignment \u2014 campaigns are owned by the caller (creator-scoped). Surface this honestly instead of fabricating an assignment model", "Asks ALL clarifying questions inline before tool calls \u2014 instead, run the lens refinement loop with `leadbay_refine_prompt` which handles the clarification protocol natively"] },
|
|
1762
|
+
leadbay_top_accounts_to_activate: { "name": "leadbay_top_accounts_to_activate", "short_description": 'Build a ranked account-conquest plan from Leadbay data \u2014 the accounts worth\nactivating, each with a motif, a pitch and a checklist, ranked by the\nstrongest Leadbay signal. Every figure carries its source, and anything\nLeadbay can\'t measure is shown as OMITTED rather than estimated. Uses\n`leadbay_bulk_qualify_leads` and `leadbay_enrich_titles`. Trigger on\n"top 50 accounts to activate", "who should we go after".\n', "arguments": [{ "name": "count", "description": "Optional: how many accounts the plan should hold (default 50).", "required": false }, { "name": "territory", "description": "Optional: restrict the plan to a territory (e.g. 'Indre-et-Loire', 'R\xE9gion Ouest'). Sets geography on the Discover lens.", "required": false }], "expected_calls": ["leadbay_account_status", "leadbay_get_qualification_questions", "leadbay_pull_leads", "leadbay_pull_followups", "leadbay_bulk_qualify_leads", "leadbay_qualify_status", "leadbay_scan_portfolio_signals", "leadbay_enrich_titles", "leadbay_bulk_enrich_status", "leadbay_account_history", "leadbay_artifact_kit", "leadbay_new_lens", "leadbay_adjust_audience"], "failure_modes": ["Invents, estimates or proxies a revenue-realized figure \u2014 the single worst failure. Leadbay does not hold what an account buys, and headcount, sector and lead score are NOT proxies for it.", "Sorts by cash-to-capture, synthesizing a revenue figure per account purely to make that ranking work. Leadbay has no revenue data: rank by the Leadbay signal, say so in the header, and never invent the key.", "Emits \u20AC figures with no provenance class, so modelled numbers read as measured fact in front of a paying client.", "Skips the PROVENANCE LEDGER, or drops un-sourceable fields from it instead of rendering them as OMITTED \u2014 which hides the gap.", "Fabricates registry/TAM counts (France or regional company counts) instead of querying the registry or marking the figure NOT COMPUTED. Leadbay does not proxy SIRENE.", "Invents the five qualification questions from this prompt's own recommendations instead of reading the org's actual questions via leadbay_get_qualification_questions.", "Leaves the deck's live layer dead \u2014 qualification and enrichment handles never wired in, so the pills and contacts stay empty while the deck still looks finished.", "Invents lead ids to make the qualification pills appear populated.", "Fabricates a plausible-sounding signal ('just won a public tender') for an account whose signals were never read. No signal read means an explicit dash.", "Assigns a motif outside the closed set of six, or assigns SAUVETAGE / PLAN DE COMPTE / MONT\xC9E EN GAMME / R\xC9VEIL from a lead score or sector when order history was never available.", "Labels Monitor membership as 'is a client' \u2014 Monitor is a Leadbay view whose membership is decided by lens scoring, not by whether the company ever bought anything.", "Launches paid enrichment on the whole plan without consent. Asking for a plan is not authorization to spend on 50 accounts.", "Re-launches enrichment from inside the built deck when a bulk handle already exists this session \u2014 double-spends the user's quota.", "Forces the interactive deck without offering it first, or ships the deck INSTEAD of a chat answer that stands on its own.", "Refuses the task because revenue data is missing, instead of delivering the conquest plan and naming what a cash-ranked version would need.", "Ends the turn without a ranked list of real accounts \u2014 gating the whole plan on a NON-blocking question (the territory, a missing lens, or a 3-vs-5 qualification-question gap) so the user gets a plan-of-a-plan. Only an unresolvable company-identity mismatch may stop delivery; every other open question rides alongside the delivered plan.", "Stops after the discovery contact preview to wait for enrichment consent, delivering no plan that turn \u2014 the ranked plan ships first; the paid reveal is offered alongside it.", "Renders a contact channel enrichment never returned (e.g. a phone link when only email was approved and revealed) instead of showing the returned channels and marking the rest omitted."] },
|
|
1311
1763
|
leadbay_work_campaign: { "name": "leadbay_work_campaign", "short_description": "Work a campaign as a real outreach session: pick the campaign,\nassess what the user has (phones / emails / coords), then PROPOSE\nthe right session mode (call sheet, email sheet, enrich titles\nfirst, map). After they pick, render \u2014 and as they dictate\noutcomes per lead, record both note + epilogue via\n`leadbay_report_outreach` in one round trip.\n", "arguments": [{ "name": "campaign", "description": "Campaign name (fuzzy match against your own campaigns) or campaign UUID. Omit to list and pick interactively.", "required": false }, { "name": "mode", "description": "Optional: skip the readiness-assessment proposal and jump directly into 'call_sheet' / 'email_sheet' / 'map' / 'enrich_first'. Omit (recommended) and let the prompt propose based on the data.", "required": false }], "expected_calls": ["leadbay_list_campaigns", "leadbay_campaign_call_sheet", "leadbay_enrich_titles", "leadbay_report_outreach"], "failure_modes": ["Renders the call sheet immediately without proposing the right mode \u2014 if 60% of leads have no contacts, calling is futile; enrich first. Always assess `readiness` first.", "Auto-renders the map widget without asking \u2014 maps are intrusive when the user just wants to scroll a list. Map mode is a proposed option, not a default.", "Proposes map mode after the user has previously said they don't like maps \u2014 check conversation memory before adding 'View on a map' to the options list.", "Calls `leadbay_campaign_progression` instead of `leadbay_campaign_call_sheet` \u2014 progression has counts but no phones / LinkedIn / call-ready data; the user can't actually dial from progression rows.", "Renders contacts WITHOUT making the phone number a `[bare](tel:URL)` link \u2014 on mobile that breaks one-tap calling, which is the whole point of the cheat sheet.", "Records outreach WITHOUT epilogue_status \u2014 leaves the lead's pipeline state unchanged; the rep then sees the same lead surfaced again next session.", "Records outreach WITHOUT verification \u2014 verification.source/ref is REQUIRED. For calls, pass `{source: 'user_confirmed', ref: <user's exact words>}`.", "Loops through ALL leads in a 50-lead campaign before recording any outreach \u2014 the call-then-record loop must be per-lead, not batched."] }
|
|
1312
1764
|
};
|
|
1313
1765
|
var PROMPT_CATALOG_HEADER = `This server exposes the following workflow prompts via \`prompts/list\` and \`prompts/get\`. Some MCP clients render them as slash commands; if your client does not, you (the agent) should invoke them directly via \`prompts/get\` when the user's request matches one of the triggers described below.`;
|
|
@@ -1324,6 +1776,7 @@ var PROMPT_CATALOG_BULLETS = {
|
|
|
1324
1776
|
leadbay_refine_audience: `- \`leadbay_refine_audience\` (required args: instruction): Refine the kind of leads Leadbay surfaces beyond firmographics, with a free-text instruction. Handles the clarification round-trip if the new prompt is ambiguous.`,
|
|
1325
1777
|
leadbay_research_a_domain: `- \`leadbay_research_a_domain\` (required args: domain): Resolve a company by name or domain across the user's visible Discover, Monitor, and Activate corpus, then return everything Leadbay knows about it.`,
|
|
1326
1778
|
leadbay_setup_team_prospecting: `- \`leadbay_setup_team_prospecting\` (required args: audience; optional args: rep_split): Manager-led prospecting setup: conversationally turn a natural-language audience ask into a Leadbay lens, validate the candidate leads, and persist them as one or more named campaigns the rep(s) can work through. Closes #3630 US3 end-to-end (within the current creator-scoped campaign visibility model).`,
|
|
1779
|
+
leadbay_top_accounts_to_activate: `- \`leadbay_top_accounts_to_activate\` (optional args: count, territory): Build a ranked account-conquest plan from Leadbay data \u2014 the accounts worth activating, each with a motif, a pitch and a checklist, ranked by the strongest Leadbay signal. Every figure carries its source, and anything Leadbay can't measure is shown as OMITTED rather than estimated. Uses \`leadbay_bulk_qualify_leads\` and \`leadbay_enrich_titles\`. Trigger on "top 50 accounts to activate", "who should we go after".`,
|
|
1327
1780
|
leadbay_work_campaign: `- \`leadbay_work_campaign\` (optional args: campaign, mode): Work a campaign as a real outreach session: pick the campaign, assess what the user has (phones / emails / coords), then PROPOSE the right session mode (call sheet, email sheet, enrich titles first, map). After they pick, render \u2014 and as they dictate outcomes per lead, record both note + epilogue via \`leadbay_report_outreach\` in one round trip.`
|
|
1328
1781
|
};
|
|
1329
1782
|
|
|
@@ -1568,6 +2021,33 @@ var CATALOG = [
|
|
|
1568
2021
|
)
|
|
1569
2022
|
];
|
|
1570
2023
|
}
|
|
2024
|
+
},
|
|
2025
|
+
{
|
|
2026
|
+
name: "leadbay_top_accounts_to_activate",
|
|
2027
|
+
description: PROMPT_META.leadbay_top_accounts_to_activate.short_description,
|
|
2028
|
+
arguments: [
|
|
2029
|
+
{
|
|
2030
|
+
name: "count",
|
|
2031
|
+
description: "Optional: how many accounts the plan should hold (default 50).",
|
|
2032
|
+
required: false
|
|
2033
|
+
},
|
|
2034
|
+
{
|
|
2035
|
+
name: "territory",
|
|
2036
|
+
description: "Optional: restrict the plan to a territory (e.g. 'Indre-et-Loire'). Sets geography on the Discover lens via `locations`.",
|
|
2037
|
+
required: false
|
|
2038
|
+
}
|
|
2039
|
+
],
|
|
2040
|
+
render: (args) => {
|
|
2041
|
+
const n = args.count ?? "50";
|
|
2042
|
+
return [
|
|
2043
|
+
userMessage(
|
|
2044
|
+
substitutePlaceholders(leadbay_top_accounts_to_activate, {
|
|
2045
|
+
count_or_default: n,
|
|
2046
|
+
territory_block: args.territory ? `Scope the plan to **${args.territory}** \u2014 pass it as \`locations\` on the lens, never as a sector.` : ""
|
|
2047
|
+
})
|
|
2048
|
+
)
|
|
2049
|
+
];
|
|
2050
|
+
}
|
|
1571
2051
|
}
|
|
1572
2052
|
];
|
|
1573
2053
|
function listPrompts() {
|
|
@@ -25289,7 +25769,7 @@ function parseWriteEnv(env = process.env) {
|
|
|
25289
25769
|
}
|
|
25290
25770
|
|
|
25291
25771
|
// src/http-server.ts
|
|
25292
|
-
var VERSION = true ? "0.
|
|
25772
|
+
var VERSION = true ? "0.28.0" : "0.0.0-dev";
|
|
25293
25773
|
var PORT = Number(process.env.PORT ?? 8080);
|
|
25294
25774
|
var HOST = process.env.HOST ?? "0.0.0.0";
|
|
25295
25775
|
var logger = {
|
package/dist/installer-gui.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@leadbay/mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.28.0",
|
|
4
4
|
"mcpName": "io.github.leadbay/leadbay-mcp",
|
|
5
5
|
"description": "Model Context Protocol (MCP) server for Leadbay — AI lead discovery, qualification, and enrichment for Claude Desktop, Cursor, and Claude Code.",
|
|
6
6
|
"type": "module",
|