Warm-Path Deal Workspace
Turn "here is a company, here are our LinkedIn connections" into one self-contained,
shareable HTML workspace that answers two questions an account team actually has: who
decides, and who do we already know who can get us to them.
Most stakeholder maps stop at the first question. The second is where deals are won, and it
is the part people fake - they draw a line from every connection straight to the buyer, which
makes an intern look as valuable as a VP. This skill scores every route instead, so the
operator spends their scarcest resource (a teammate's willingness to make an ask) on routes
that will actually land.
What gets built
One HTML file, published with the
tool, with a tab set per account and an account
switcher on top when there is more than one:
| Tab | Contents |
|---|
| Overview | Account brief, headcount and funding tiles, hiring-signal chart, recent activity |
| Stakeholders | Buying group grouped by deal role; each card opens a drawer with the LinkedIn deep dive, priorities, talking points, objections, a copy-ready opener, notable posts, career timeline - every claim behind an evidence dropdown |
| Relationship Map | YOU → warm connection → named intermediary → decision maker, colour and thickness weighted by route strength, plus a ranked chain list |
| Signals | Hiring surges, job changes, intent posts, funding, newest first |
| Plays | 3-6 prioritized moves, each citing the signal or path behind it |
Inputs
Ask only for what is missing.
| Input | Required | Default |
|---|
| Target account(s) | yes | - |
| LinkedIn connection export(s) | no, but the point of the skill | skip warm paths, say so |
| Seller company + one-line product | no | infer from context |
| Deal stage | no | "Prospecting" |
| Depth | no | 6-8 stakeholders per account |
Exports arrive as a bare
, an unzipped
Basic_LinkedInDataExport_*
folder,
or the raw
. All three work. When the export contains
the owner's name is
read from it automatically - do not ask the user who each file belongs to when the file
already says. Confirm the detected names back to them, because attributing a connection to the
wrong teammate sends the intro request to the wrong person.
Phase 0 - preflight and an isolated working directory
Create a run-scoped working directory and use it for everything:
bash
WORK="$(mktemp -d -t wpw)" # or <somewhere>/wpw-<account>-<timestamp>
mkdir -p "$WORK"
This matters more than it looks. Two runs sharing a scratch path will clobber each other's
, and the failure is silent: in testing one run briefly reported that an
owner had zero connections at an account when they had ten thousand. Cross-owner
contamination is the worst corruption this tool can produce, because the operator ends up
asking the wrong teammate for an intro to someone they have never met. One directory per run,
always.
Then check Crustdata is reachable and surface the balance in one line. All Crustdata calls in
this skill run through the Code Mode MCP (
install.crustdata.com/mcp):
plain-JavaScript
scripts calling
await callTool(name, params)
— every
script opens with a
or
comment, branches on
,
and returns only the compact projection it needs (
is a response whitelist: the result
carries only the groups you list). The preflight is
(free):
js
// model query: preflight - Crustdata reachable + credit balance
const r = await callTool("account_credits", {});
return r.ok ? r.data : { error: r.message };
Phase 1 - account brief (per account)
(free) to resolve the company — it is fuzzy, one identifier can match
several companies, so take the top
match — then one
by
with
and
fields: ["basic_info", "headcount", "funding", "competitors", "people", "news", "hiring", "followers", "taxonomy"]
(the
group carries the decision-maker and CXO snapshot; list every group you read). Then
twice -
with a
aggregation on
for
the aggregate, then
sorted by
desc for notable roles. Then
on the company (
; 1 credit per post, so set
deliberately — ~10 is plenty for the brief).
Check whether the account still exists as an independent company. Acquisitions change who
holds budget and which brand people list. A recent deal belongs in the brief and in the alias
list. In testing, Confluent turned out to be an IBM company mid-run, which changed the entire
buying process.
Write a 3-4 sentence
: what they do, their scale, what is changing, why now. Ground
every number in the payload you just pulled.
Take the logo while you are here. basic_info.logo_permalink
comes back from the free
call (and from the enrich above) at no extra credit cost. Download it,
base64 it, and write it to
as a
data:image/jpeg;base64,...
URI - the
media CDN serves these as
, so a remote
renders blank. The
workspace shows it in the header and the account switcher; without it you get a monogram.
Phase 2 - buying group
Seed with contacts the user named plus the
group from Phase 1, then discover with
filtered on
experience.employment_details.current.company_id
and senior
experience.employment_details.current.seniority_level
, biased to the function you sell
into. Resolve enum values with
first - a wrong enum silently returns
zero rows (the seniority vocabulary is a closed set:
,
,
,
,
, ...).
Then run a second pass on stack ownership, not title. Seniority is a proxy for authority;
owning the budget and the tools is the real thing. Search for people whose title or headline
says they own the relevant systems (ops, platform, systems, enablement, tooling, "GTM
systems", "talent operations"). In testing the single best-fit buyer at one account was a
Senior Manager who owned 18 tools and a $5M budget, and a director-and-above sweep missed
him entirely.
Trim to 6-8 people. Assign a provisional
and
(1-5). Set
when
someone sits in an acquired sub-brand rather than the parent - that is what lets a connection
at that sub-brand route straight to them.
Acquisitions are where the warm paths hide. People at acquired companies often still list
the original brand, so a connection at "Red Hat" never matches a search for "IBM". Web-search
the acquisition history and write the full alias list to
:
json
{"IBM": ["ibm", "red hat", "hashicorp", "apptio", "nordcloud"]}
Multi-word aliases match as phrases, single words as whole tokens, so fragments like "edge"
will not drag in unrelated firms. Watch for genuine collisions - "Observe.AI" is not the
"Observe, Inc." that Snowflake bought, and both will appear.
Phase 3 - stakeholder deep dives
Spawn one sub-agent per stakeholder, batched in a single message, using
references/stakeholder-agent.md
. Keep raw post dumps out of the main context. Assemble into
$WORK/<slug>/dossier.json
following
references/dossier-schema.md
.
Each person's photo (
basic_profile.profile_picture_permalink
) is already inside the
group the sub-agent requests - free. Base64 it into
on the
stakeholder, same
rule as the logo. Cards and drawers look like a real
buying group instead of a wall of initials.
Phase 4 - connections, bench, and verification
bash
python3 scripts/parse_connections.py --out "$WORK/connections.json" \
--input "/path/Connections.csv" \
--input "/path/Basic_LinkedInDataExport_2026.zip" # append ':Name' to override the owner
Report per-owner totals back to the user. Then
verify the bench before presenting it as
live routes. Exports carry the employer as of export day and go stale silently - in testing
one bench member had already left the account. Spot-check the matched rows with
(
fields: ["basic_profile", "experience"]
) and drop or flag anyone who has
moved on.
Two blind spots are structural, cheap to state, and worth stating rather than shipping a map
that merely looks complete:
- connections with a blank company field match no alias and are invisible
- ex-employees are invisible - the export carries current employer only, so someone who
spent a decade at the target and just left will never appear despite being an ideal broker
Phase 5 - route resolution
Read
references/chain-scoring.md
for the model and the honesty constraints. Three kinds of
route exist, best first, and the builder detects the first two automatically:
- Zero hop - the connection is a member of the buying group. Nothing beats this and it
is easy to miss, because the bench and the buying group are built by different phases. The
builder matches on LinkedIn URL then name; you get it for free.
- Date-verified shared history - the connection and a stakeholder worked at the same
company at overlapping times. This is the strongest evidenced bridge available, but only if
the dates actually overlap: of 13 apparent "we both worked at X" ties in testing, only 3
survived the date check - the rest joined after the target had left. To enable it, enrich
the bench members' work history and write :
json
{"https://www.linkedin.com/in/someone": [{"company": "Red Hat", "start": "2021-03", "end": "2023-08"}]}
Keyed by LinkedIn URL or lowercase name. The builder does the date arithmetic and records
non-overlapping pairs as documented dead ends so nobody rediscovers them.
- Org-layer bridge - everyone else. Resolve the senior leader of each function present in
the bench, one per (account, function) rather than per connection, and
write :
json
{"Acme": {"Engineering": [{"name": "...", "title": "VP, Platform Engineering",
"linkedin_url": "...",
"basis": "most senior Engineering leader at Acme (person_search, seniority=VP)"}]}}
Always fill with how you found them and why they qualify. Reporting lines are not in
the data, so never phrase a basis as a confirmed manager relationship.
Phase 6 - signals and plays
Assemble
from the hiring surge, the buying signals the sub-agents surfaced, job
changes, funding and recent news, newest first with a severity. Then write 3-6
naming the connection, the intermediary and the decision maker, citing the signal that makes
now the moment. "Reach out to the team" is not a play.
Phase 7 - build and publish
bash
python3 scripts/build_workspace.py \
--dossier "Acme=$WORK/acme/dossier.json" \
--dossier "Globex=$WORK/globex/dossier.json" \
--connections "$WORK/connections.json" \
--aliases "$WORK/aliases.json" \
--intermediaries "$WORK/intermediaries.json" \
--careers "$WORK/careers.json" \
--template assets/workspace-template.html \
--title "Acme + Globex Deal Workspaces" \
--out "$WORK/workspace.html"
The builder does all the deterministic work: matching connections to accounts, detecting
zero-hop and shared-history routes, scoring and ranking every chain, wiring in intermediaries,
recording dead ends, and injecting the data. Check its printed summary - warm counts, strong
counts, zero-hop, verified ex-colleague, named org layers - before publishing.
Then
publish it. Load the
skill (required before the first
call), then call
with
=
, a stable title, a
one-sentence description and a favicon. The artifact is the deliverable; a workspace left on
disk helps nobody. Re-publishing the same file path redeploys to the same URL, which is what
makes iteration cheap.
Report the headline numbers: stakeholders mapped, posts analysed, warm chains and how many are
strong, zero-hop routes, signals, plays.
Phase 8 - iterate
Common follow-ups: add a teammate's export and rebuild, add or re-run a stakeholder, change a
deal role, widen the buying group. Each edits the inputs and re-runs Phase 7 to the same URL.
Guardrails worth holding
Never fabricate, and make the artifact prove it. Every claim carries evidence citing the
payload it came from; unevidenced claims go in
. The evidence dropdowns are why anyone
trusts the page, and one confident invention undoes them all.
"No strong chains" is a success, not a failure. If the team's network does not reach the
buying function, say so plainly and call the account warm-assisted outbound. Dressing up
four weak chains as a way in wastes real social capital. In testing both an unaided run and a
skill run reached that verdict independently on the same account, which is the system working.
Watch for name collisions. Company names are not unique and people share names. Verify a
match is the right entity before it reaches the bench, and drop procurement "strategic
sourcing" roles when you are looking for talent sourcing.
House style for anything sendable. Openers and talking points get pasted into real emails:
no em dashes or en dashes, and no legal-entity suffixes in company names.
Adapt the layout to the content - never let it hide anything. The brand system below is
fixed; the layout is not. If real content does not fit - a long company or person name, a
12-word title, 40 stakeholders, more chains than the graph can plot - change the layout, not
the content: let the card grow, wrap instead of truncating, drop to one column, widen the
column, raise the cap, or put the wide thing in its own scroll container. Never solve a fit
problem by clipping a card, ellipsis-ing a name, or silently dropping rows. Where a cap really
is unavoidable, say so in the UI ("showing the 18 strongest of 34 routes") so the reader knows
what they are not seeing. Look at the rendered page (Phase 7's self-review) and fix what is cut
off before you hand it over.
Crustdata branding on rendered artifacts. The bundled workspace template carries the real
Crustdata brand system - keep it, and never strip it when editing the template:
- Wordmark: the official pair ships in this skill's -
(dark text, for light backgrounds) and (white text, for dark),
the same files app.crustdata.com's header renders. Base64-inline the theme-appropriate
variant at ~17px tall, linking to crustdata.com; the template shows both and switches them
in CSS. Never hotlink a logo - rendered artifacts cannot fetch remote images.
- Color: brand purple (the product's primary) as the accent; (the
product's own lightened purple) as the dark-theme accent.
- Type: Geist, the product font, embedded as a data-URI with the system
stack as fallback.
- Icons: Lucide, the dashboard's icon set, inlined as SVG with a stroke
(the template's helper holds the path data). No emojis in artifact UI.
Any OTHER page or document this skill renders (a one-off summary page, an exported report)
carries the same system: base64-inline the wordmark pair from
behind a "Powered by"
eyebrow, and copy the
, the accent variables, and the
icon helper from
assets/workspace-template.html
. This is conditional: chat output and
data files stay unbranded, and nothing gets rendered as an artifact just to carry the mark.
Tool dependencies
- Crustdata MCP server (install.crustdata.com/mcp): a
single Code Mode MCP exposing , , and . All Crustdata data
tools are reached inside an plain-JavaScript script via
await callTool(name, params)
— author against the typed surface from , but
write zero type annotations in the body. Tools used here: ,
, , , ,
, , , (alias
research),
- Python 3 for
scripts/parse_connections.py
and scripts/build_workspace.py
(pure local
processing, no network calls)
- Artifact tool for publishing the workspace HTML
Bundled resources
scripts/parse_connections.py
- normalises LinkedIn exports, auto-detects owner from Profile.csv
scripts/build_workspace.py
- bench matching, zero-hop and shared-history detection, chain scoring, HTML injection
assets/workspace-template.html
- the self-contained multi-account workspace renderer
references/dossier-schema.md
- the per-account dossier contract; read before writing one
references/stakeholder-agent.md
- the deep-dive sub-agent prompt; read before Phase 3
references/chain-scoring.md
- how routes are scored and resolved honestly; read before Phase 5