Skip to main content
Glama

Split a List Across Experiment Arms

li_experiment_assign

Run an A/B(/n) test without dealing people out by hand. An experiment has arms (key, strategy, engage on/off); this tool assigns each prospect to an arm SERVER SIDE and imports them, staged, through the same multi-campaign import as li_ingest_prospects: one campaign per (arm, ICP, account), named " | | ". Nothing sends until those campaigns are launched (the reply's launch.campaignIds).

ASSIGNMENT is stratified by ICP and account (stratifyBy) and deterministic: a hash of the experiment and the person, then the arm with the fewest people in that stratum, so arms stay balanced to within one person per stratum across calls. It is STICKY: someone assigned before keeps their arm and campaign. requirePostForArms keeps people without a reactable engagePostUrl out of those arms; they go to controlArm and are marked forced (left out of the comparison). An engage arm keeps each person's post and reacts before the invite; other arms drop it.

COPY per arm, most specific first: the row's armCopy[arm], the row's own text, armSteps[arm], then defaultSteps ({first} is the only variable, rendered at import).

First call: experiment {name, arms, ...}; later calls: experimentId. Up to 500 prospects. Dry-run unless confirmAssign=true; everything (accounts, ICPs, strategies, campaigns, copy) is checked before anything is written. Results: li_outreach_analytics(groupBy "arm", experimentId).

Input Schema

NameRequiredDescriptionDefault
icpIdNoDefault ICP label id for rows naming none
icpNameNoDefault ICP label by name
armStepsNoShared text per step for ONE arm, keyed by arm; a step it does not name falls back to defaultSteps
prospectsYes1-500 people: profileUrl, their account and ICP (or the call's), text or armCopy, optional engagePostUrl
sessionIdNoDefault sending account id for rows naming none
experimentNoA NEW experiment. Sent again with the same name and arms, it is reused.
defaultStepsNoShared text per step (connectionNote, firstMessage, followUp1, followUp2, followUp3, closer) for every arm, for each step its armSteps does not name
experimentIdNoAn experiment you already have (from an earlier reply); or give `experiment`
sessionLabelNoDefault sending account for rows naming none
confirmAssignYesMust be true to write; false previews the split and the campaigns
idempotencyKeyNoSame key on a retry after a timeout returns the stored answer (24h)

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed1 schema field changed
    • changedInput schema / properties / prospects / items / properties / engagePostUrl / description
      Previous value: -"Engage-before-invite: ONE of this person's own recent posts (https://www.linkedin.com/feed/update/urn:li:activity:<id>/ or a /posts/ link), the one the note and first message mention. The sending account visits their profile and reacts to it a day or more BEFORE the invite goes out (about one in seven is only viewed), and the invite then waits 20-60 hours. Connection stage only: ignored for message-stage prospects. A link that is not a LinkedIn post refuses the row. If the post is gone or turns out not to be theirs, nothing is clicked and the invite goes ahead without it."New value: +"Engage-before-invite: ONE of this person's own recent posts (https://www.linkedin.com/feed/update/urn:li:activity:<id>/ or a /posts/ link), the one the note and first message mention. The sending account visits their profile and reacts to it first (about one in seven is only viewed). The invite follows about a day later, never sooner than 16 hours, inside the window it goes out in: the person's own morning invite window when the account times invites to each prospect, otherwise the account's sending hours (18-30 hours when there is no window to aim at, such as sending hours switched off). Connection stage only: ignored for message-stage prospects. A link that is not a LinkedIn post refuses the row. If the post is gone or turns out not to be theirs, nothing is clicked and the invite goes ahead without it."
  2. Changed1 schema field changed
    • changedInput schema / properties / prospects / items / properties / profileUrl / description
      Previous value: -"Canonical LinkedIn profile URL"New value: +"LinkedIn profile URL. A search id from li_search_leads (/in/ACwAA…) is accepted as is and resolved to the public profile URL before anything is written (reported as `searchIds`)."
  3. Added

TDQS

A4.5/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Goes well beyond the annotations (readOnlyHint=false, destructiveHint=false, openWorldHint=true) by disclosing sticky/deterministic stratified assignment, the requirePostForArms/controlArm `forced` fallback, the copy-precedence ladder, validation performed before any write, and that nothing sends until the campaigns are launched.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

Long and dense, but front-loaded with the purpose and organized into labeled paragraphs (assignment, copy, first/later call) that earn their place for a tool with this much hidden behavior. Minor density cost, but little is redundant.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness4/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a mutation tool with no output schema, it covers most of what an agent needs: the reply's launch.campaignIds, searchIds resolution, messageStage handling, and per-account split reporting. Remaining reply-shape details (e.g. the full dry-run preview format) are only partially described.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds real meaning beyond the schema: stratifyBy semantics (balance within each stratum), controlArm/requirePostForArms interaction, the armCopy > row text > armSteps > defaultSteps resolution order, {first} interpolation, and experiment reuse by name+arms.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

Opens with a specific verb+resource ("Run an A/B(/n) test"), then names the mechanism precisely: assigns each prospect to an arm server-side and imports them through the same multi-campaign import as li_ingest_prospects. This clearly distinguishes it from the plain import sibling.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines4/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Gives explicit sequencing (first call with `experiment`, later calls with experimentId), states dry-run is the default and confirmAssign is required to write, and routes the user to li_outreach_analytics(groupBy "arm") for results. It does not explicitly contrast when to prefer this over li_ingest_prospects, but the context is otherwise clear.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

Resources