partner-pipeline-push
Turns a selected cohort of client stubs into Bitrix deals, wires up the client + tier fields, and drops each client's research into the deal as one clean comment. End to end: cohort -> cohort mapping -> create deals -> link contact + User Group -> (locate stubs by stage) -> post enrichment comment.
This is a live CRM write workflow. Confirm scope and cost-of-mistake before mass writes, and always verify with a single test write before batching.
Verified against live Bitrix 2026-08-12. Names and enum values below were wrong in earlier versions of this skill and made it fail outright. If anything here disagrees with
list_pipelines/find_field, trust the live call and fix this file.
Prerequisites
- Read
03-Clients/CLAUDE-clients.mdand rootMEMORY.md. - The target Bitrix funnel must be write-enabled in the "Bitrix24 CRM - Meridian
Display" connector. Key the allowlist on categoryId 24, not the display
name. The funnel was renamed from "Partner Outreach" to
"Partner Tier Upgrade" on 2026-08-06, and the stale name sitting in
the allowlist silently broke every deal write in every funnel for five days.
If a write returns
Configured write funnel '...' not found, that is this bug. Editing the config is not enough: the app must be fully restarted, because the MCP server holds the allowlist in memory. - The cohort's stubs should already be enriched (their
## Researchpopulated byclient-enrich). This skill only reads the stub research; it doesn't create it. - Bitrix tools are usually deferred, so load them first with ToolSearch, e.g.
select:mcp__Bitrix24_CRM__Meridian_Display___create_deal,...link_deal_contact,...set_deal_fields,...add_deal_comment,...list_records,...list_pipelines,...pipeline_summary,...get_record,...find_field(the server-id prefix may differ; match by the..._create_dealetc. suffix).
Reference values (Partner Tier Upgrade)
- Funnel: Partner Tier Upgrade, categoryId 24. Renamed 2026-08-06;
the vault/XML pipeline key
partner_outreachis unchanged. - Entry stage: LinkedIn Research & Connection =
C24:UC_7KD3PA. - Other live stages include
Update Info Plinth,TIER CHANGE EMAIL SENT,Need Artwork/Mockup,Gift Mockup Sent,Client Approved,Client Replied,Qtly Touch-Point Scheduled,Deal won=C24:WON,Not Interested=C24:LOSE, plus several... - HAZEL ONLYPotential-Partner stages. - Re-read stages with
list_pipelinesorpipeline_summarybefore relying on any code. Stages get retired:1st Reachout (Email/LinkedIn)(C24:PREPARATION) no longer exists but appears in older notes and returns a bare token. - User Group field =
UF_CRM_5F2C19A7B3D41(enumeration). Options:Retail | Palladium Partner | Platinum Partner | Iridium Partner | Rhodium Partner | Potential Partner | Reseller. The values are the full labels ("Palladium Partner"), not the bare tier ("Palladium"). An earlier version of this skill passed the bare tier andset_deal_fieldsrejects it. They match stubuser_group_correctexactly. Set by label:set_deal_fields(id, {"User Group": "Palladium Partner"}). - The deal card's "Client" = the standard contact binding. Set it with
link_deal_contact(there is no custom field literally named "Client").
The pipeline
1. Select the cohort
Filter 03-Clients/client-stubs/*.md frontmatter. All stubs live flat in that
folder, one .md per client. Segmentation lives in user_group_correct (the
computed, true tier: this is the one to filter on) and user_group (the raw
Plinth store group, which is blank on most stubs; do not filter on it).
Example (the graduates case): user_group == Retail AND
user_group_correct == "Palladium Partner".
Run scripts/select_cohort.py to list matches and emit the cohort mapping.
2. Compile the cohort mapping
scripts/select_cohort.py writes cohort-mapping.xml: one <client> per person
with bitrix_id, first_name, last_name, outreach_pipeline, stub_file_name,
relative_path. Keep the tags valid (<first_name>...</first_name>).
This file is NOT the vault census. The vault's master list is
03-Clients/partner-client-master-list.xml(schema 3.0), a complete census of the partner client base maintained by the weekly pipeline.cohort-mapping.xmlis a throwaway working file for one push. Never write one over the other. (An older version of this skill emittedcrm_pipeline_mapping.xml, which was the census's former filename, hence the confusion.)
3. Create the deals (one per client)
For each client: create_deal(pipeline="Partner Tier Upgrade", title="<first> <last>", stage="LinkedIn Research & Connection").
- Test one first, confirm it returns
Created deal <id>..., then batch the rest. - For scale, fan the batch out to parallel sub-agents; the cost-of-mistake is real so tell them the cohort is pre-approved and to record every returned deal id.
- Record
deal_id -> titlefor the next steps. - "One deal per client": if some clients may already have a deal in this funnel, ask Matt whether to skip existing (true one-per-client) or create regardless.
- Deal ids are not ordered against your input list. Row 2 of a CSV can become a higher id than row 100. There is no arithmetic shortcut from list position to id; capture each returned id as you go.
4. Link the contact + set User Group
For each deal:
link_deal_contact(deal_id, <client email from stub>)links by email (reliable key; it refuses if no contact matches, so record those). This fills "Client".set_deal_fields(deal_id, {"User Group": "<their user_group_correct>"}).
5. (Optional) Locate stubs by stage
When asked "who's in stage X, find their stubs": list_records(entity_type=deal, pipeline=..., stage=<stage id or name>, created_from=..., created_to=...).
Match deal title -> client-stubs filename (normalized); fall back to last-name
fuzzy for name variants (e.g. deal "Jo-Anne Pritchard" -> Margaret Pritchard.md,
"Katherine Lowell" -> Kathy Lowell.md).
Enumerating a large stage: a whole-day created_on filter truncates at 200 rows.
Narrow sub-day windows do work, but the bounds resolve against Bitrix's own server
offset: pass tz=Europe/Moscow with your local wall-clock window and walk the
windows. Always verify coverage (union of windows == the stage's reported total)
rather than trusting one call.
6. Post the enrichment comment (one per deal)
For each deal, take the client stub's ## Research block and convert it to Bitrix
BBCode with scripts/research_to_bbcode.py --stub PATH --title "<first last>", then
add_deal_comment(deal_id, <bbcode>).
- Bitrix comments render BBCode, not markdown:
[B]HEADER[/B],[LIST]/[*], bare URLs auto-link.[TBD: ...]renders as literal text (fine). - Verify no truncation:
add_deal_commentechoes the posted char count, and it must match the generated comment length. Comments here run ~650-2400 chars, well under limits, so one comment holds the full profile. - Batch via sub-agents for scale; pass each row's comment string verbatim.
Gotchas (the expensive lessons)
- Write-enabled funnel gate. Creation/updates refuse non-allowlisted funnels, and a stale funnel name in the allowlist breaks writes everywhere, not just there. Allowlist by categoryId 24. Restart the app after any config change.
- User Group values are full labels ("Palladium Partner"), not bare tiers ("Palladium").
set_deal_fieldsrejects the wrong label. - Link contacts by email, never by the stub
bitrix_id. Where populated,bitrix_idnow holds a verified deal-linked Bitrix contact id (backfilled 2026-08-06, wrong values blanked), but coverage is partial and email→contact is not 1:1: duplicate contacts per email are normal. Resolve via the client's live deal when it matters. - Connector list/summary index lags live edits.
pipeline_summary/list_recordscan miss a just-added stage or just-moved deals for a bit, whileget_record(single deal) is live. If counts look stale, re-sync/reconnect the connector or read individual deals withget_record. - Shared/ops mailboxes (marketing@, info@, careers@, supplychain@, or a colleague's address on the stub) will link whatever contact owns that email, which may not be the named person. Flag, don't assume. A deal-name / contact-email mismatch is not automatically a defect: the named client is often the department head while the email belongs to whoever approves the proof.
- Screen out internal and role accounts. Exclude any
@meridiandisplay.exampleaddress outright, plus role-shaped names (Marketing Vendors, Testing Testing). Name-only screening has missed internal staff before. - Stub frontmatter is maintained by the weekly driver (
07-Automation/weekly_partner_pipeline.py). Don't overwrite it from CRM. - Attribution/logging: CRM-only actions don't change the vault, so no
LEDGERentry is required. Only log toLEDGERwhen you edit vault files.
Bundled scripts
scripts/select_cohort.py: filterclient-stubsby user_group / user_group_correct; print the cohort and writecohort-mapping.xml.scripts/research_to_bbcode.py: read a stub's## Researchand emit the BBCode comment body (bold headers + bulleted lists) foradd_deal_comment.
Both scripts must be the 2026-08-12 revision or later. Check select_cohort.py: its
--vault help must read 03-Clients/client-stubs and its --out default must be
cohort-mapping.xml. If it still says tracker-people or crm_pipeline_mapping.xml,
it is the stale copy and will write a wrong <relative_path> into every record.