Systems Lab

Agent skill

gtmos-sales-win-loss

Review recently closed deals for patterns a sales leader can act on -- win rate by rep, source, size, industry and deal type, where losses die, cycle length, loss reasons and what wins have in common, each backed by evidence.

activeSelf-containedInstructions only1,630 words

Filed under Calls, demos and discovery.

From ay-automate/gtmos-skills · 107 skill entries · 0 · pushed 2026-11-02

What it does when it runs

Review recently closed deals for patterns a sales leader can act on -- win rate by rep, source, size, industry and deal type, where losses die, cycle length, loss reasons and what wins have in common, each backed by evidence. With GTM OS connected it counts closed deals with records.aggregate, reads each deal's stage history, loss reason, call transcripts and replies, groups revenue by the org's own deal types, and says "not enough data yet" when the sample is too small to call a pattern. Use for "win-loss review", "why are we losing deals", "loss reasons", "what do our wins have in common", "win rate by rep", "where do deals die", "closed-lost analysis", "quarterly deal review". For live pipeline risk see gtmos-crm-pipeline-review.

Automated analysis of the skill and the 0 files bundled beside it. A skill’s own description is written to be selected by an agent, so it describes the job and not the dependencies.

Keys and connectors you must supply
None found.
Hosts it reaches
No third-party host appears in the skill or its bundled files.
Tool permissions it declares
No allowed-tools in the frontmatter. It only issues instructions, so there is nothing to bound.
Actions present in the files
None. Instructions only.

Ask about gtmos-sales-win-loss

Opens your assistant with this page's recorded context. Check the linked sources before relying on it.

Is this safe to install?ClaudeChatGPT
Adapt it to my stackClaudeChatGPT
What else do I need for it to workClaudeChatGPT
Rather ask a human? Talk to Cheetah
git clone --depth 1 --filter=blob:none --sparse https://github.com/ay-automate/gtmos-skills.git /tmp/gtmos-skills
git -C /tmp/gtmos-skills sparse-checkout set "skills/gtmos-sales-win-loss"
mkdir -p ~/.claude/skills/gtmos-sales-win-loss
cp -R "/tmp/gtmos-skills/skills/gtmos-sales-win-loss/." ~/.claude/skills/gtmos-sales-win-loss/

Picked up without a restart. A project skill of the same name is shadowed by your personal one. For one repository only, swap ~/.claude/skills for .claude/skills. Claude Code docs ↗

Or take the whole library

This repo ships a .claude-plugin manifest, so Claude Code can install all 107 skills at once. Plugin skills are invoked as /<plugin>:<skill>, so they never collide with your own.

/plugin marketplace add ay-automate/gtmos-skills
/plugin

The folder is the same in every client that implements the format — 46 of them — so if yours is not above, only the destination changes.

Reproduced in full from ay-automate/gtmos-skills/blob/86780d2f545cdc0a1de971917aa04b41bb0b3ccd/skills/gtmos-sales-win-loss/SKILL.md, which is licensed APACHE-2.0 (skill frontmatter). 1,630 words, 19 headings.

Win-loss review

Part of GTM OS Skills. Adapted from win-loss-review in salesforce/salesforce-skills (Apache-2.0, Copyright (c) 2026 Salesforce, Inc.). Changed for GTM OS: the operating section below, rewritten cross-references and tool pointers. Notices: THIRD-PARTY-NOTICES.md (installed beside the skills as THIRD-PARTY-NOTICES.md).

With GTM OS connected

Rules (GTMOS-RULES.md). Read policy.get_dry_mode first; in dry mode every effect stops at a draft. E-class operations are proposed and a different person approves them; never approve your own proposal. Numbers come only from operation results, named with the operation. Claims about results, customers or metrics must be backed by active proof from proof.list.

GTM OS holds every closed deal with its stage history, loss reason, deal type, calls and replies. This skill only reads; it changes no record. Confirm each schema with gtm_describe before the first call.

1. Count first, with exact numbers. records.aggregate runs over all matching rows, not a page:

ReadWhy
records.aggregate {objectType: "gtm.deal", filter: {status: {in: ["won", "lost"]}}, groupBy: "status", sum: "baseAmount"}Won and lost counts and totals in the reporting currency. Add a close-date filter for the period (check the filter shape with gtm_describe records.aggregate).
The same call with groupBy: "ownerPrincipalId", "dealType" or "stageId"Win rate by rep, by deal type, and the stage distribution.
deal_type.list and definition.deal_taxonomyThe org's deal types in its own words (pilot, expansion...), so cuts use their labels.
pipeline.listStage names and order, to say where losses die.
money.revenue {from, to, groupBy: "deal_type"}Revenue from the org's revenue-truth source, by deal type.

2. Then read the deals behind the pattern. records.list {objectType: "gtm.deal", filter: {status: "lost"}, sort: {field: "baseAmount", dir: "desc"}, limit: 50} and the same for won. For the five largest of each:

  • records.get for lossReason, stageChangedAt, closedAt, the account;
  • activity.list {subject: {objectType: "gtm.deal", id}} for the stage-change entries (the last open stage before close is where the deal died);
  • transcripts.list {dealId} then transcripts.get for stated objections, competitors and decision criteria;
  • replies.list {dealId} for late-stage replies and their labels.

3. Honest numbers. Every rate shows its n. If a cut has fewer than ten closed deals, show the count and write "not enough data yet" instead of a percentage. A loss reason comes from lossReason, a transcript or an email, with its date; an inferred reason is labelled as inferred. Totals in the headline must match the detail rows.

4. Output. Use the text format in step 5 of the method below. If the user wants a document, the output-docgen connector can render it; delivering it to a channel is channel.deliver (class E, proposed for approval).

When the learning loop is available, score.learning_report shows which score criteria actually predicted wins; cite it next to your patterns rather than in place of them.

Without GTM OS

On Salesforce, steps 1 to 2b of the method below work as written through the Salesforce MCP tools. On another CRM, export closed deals for the period with owner, amount, created and closed dates, final stage, loss reason, source and industry, then apply steps 3 and 5. Never fill a loss reason the data does not hold; say how many deals have none.


The upstream skill opens with a Salesforce connector bootstrap (connectivity checks and global rules for the Salesforce Headless 360 MCP server). It is not carried here, because it applies only to that connector. Steps 1, 2 and 2b below are the Salesforce path; with GTM OS connected, use the reads in With GTM OS connected instead.

Rules

  • Reconcile headline totals with the detail rows.

Method

Ground → (read + evidence, ONE turn) → find patterns → output. Leader-focused: look across recently closed opportunities for patterns a leader can act on.

Inputs

  • Scope: "team" (default - all reps under leader) or a specific rep
  • Period: last quarter (default) or specified range
  • Focus: all / wins only / losses only

1. Ground (hardcoded — Opportunity only)

Ground only Opportunity — it always exists (naming a missing object fails the whole call). Its fields reveal this org's loss-reason __c schema; don't assume names.

dispatch_readonly(method: "GET", url: "/services/data/v65.0/graphql",
  queryParams: { "queryInput": "{\"query\":\"query { uiapi { objectInfos(apiNames: [\\\"Opportunity\\\"]) { fields { ApiName label } childRelationships { childObjectApiName relationshipName } } } }\"}" })

Step 1 is the authority on names — use exact strings, never guess. Note the scalar __c field(s) that carry a loss-reason/lost-reason category — that's <OPP_CUSTOM> below. If none is grounded, omit it and rely on transcript/email evidence for loss reasons instead.

2. Read (fill from step 1)

Compute explicit gte/lte date-literal boundaries for the review period from today's date — do not use a relative literal like LAST_N_DAYS:90, it silently drifts the window as today moves. scope: MINE for "my closed deals"; swap for OwnerId: { eq: \"<UserId>\" } after resolving one User by name for a named rep (team default = no owner filter, all reps roll up together — ask once if genuinely ambiguous).

One call pulls every closed opportunity in the window — with Account spanned for industry/size AND the stage-history child nested (no separate round-trip). Loss death-stage: a lost opp's closing StageName is just "Closed Lost", so "where losses die" must come from the stage-history child — the last non-closed StageName before close. Nest <HISTORY_CHILD> = the OpportunityHistory child's exact relationshipName from Step 1 grounding (commonly OpportunityHistories); keep its first: small (history rows are tiny, but the child rides on up to 200 parents).

dispatch_readonly(method: "GET", url: "/services/data/v65.0/graphql",
  queryParams: { "queryInput": "{\"query\":\"query { uiapi { query { Opportunity(scope: MINE, where: { IsClosed: { eq: true }, CloseDate: { gte: { value: \\\"%START%\\\" }, lte: { value: \\\"%END%\\\" } } }, first: 200, orderBy: { Amount: { order: DESC } }) { edges { node { Id Name { value } StageName { value displayValue } Amount { value displayValue } CloseDate { value } CreatedDate { value } LeadSource { value } Type { value } IsWon { value } <OPP_CUSTOM> Account { Name { value } Industry { value } NumberOfEmployees { value } } Owner { Name { value } } <HISTORY_CHILD>(first: 15, orderBy: { CreatedDate: { order: DESC } }) { edges { node { StageName { value } CreatedDate { value } } } } } } } } } }\"}" })

Empty edges → no closed deals in this window/scope; say so plainly, skip the rest. first: 200 is a bounded sample; if it truncates on a very high-volume team, note the cap rather than imply full coverage. If the history child is empty/unavailable, fall back to the record's final StageName for the death-stage line and omit the median-days-in-stage column — don't fire a separate history round-trip.

2b. Evidence (run IN PARALLEL with step 2 — same turn, issue all at once)

Issue step 2 and all of step 2b as tool calls in a single turn — do not wait for the SF read to return before firing the searches. They key on the account/rep names from the request, not the SF result:

  • docs: call transcripts for the largest wins and losses (will narrow to the top 5 each once step 2 returns names, but a first broad pass can start now on account names already known from the request)
  • email: late-stage threads on the same accounts → stated objections, loss reasons

Use whatever doc/email tools are available; skip silently if none are present (SFDC-only is fine). Cite source + date for anything you use.

3. Find patterns (record only; reconcile totals; invent nothing)

  • Win rate overall and by: rep, lead source, deal size band, industry, type (new vs expansion)
  • Loss stage distribution: where do losses die — the last non-closed StageName in each lost opp's history child (NOT "Closed Lost"). Most at Discovery? at Proposal? (Fall back to final StageName only if history is unavailable.)
  • Median days in stage: from consecutive history-row CreatedDate deltas (omit if no history child).
  • Cycle length: median days (CloseDate − CreatedDate) for wins vs losses
  • Size: median Amount for wins vs losses
  • Qualitative: for the 5 largest losses and 5 largest wins (by Amount, from step 2), pull the evidence gathered in 2b — stated loss reasons (competitor, budget, timing, no decision), objections in losses but not wins, what wins had in common. Cite specifics: "[Account] - lost at Proposal, transcript on [date] shows pricing objection with no follow-up."

4. Output

The upstream skill renders a Salesforce widget (display_widget) here; that template is not carried, because it only works in the Salesforce host. Use the text format in step 5 everywhere else.

5. Text output

# Win-Loss Review — <Scope> — <Period>
**Headline:** <the pattern that matters most, 2 sentences>

## Win Rate
| Cut | Win Rate | n |
|---|---|---|
| Overall | <pct>% | <N> |
| By rep / source / size band / industry | <pct>% | <N> |

## Where Losses Die
| Stage | % of losses | Median days in stage |
|---|---|---|
| <Stage> | <pct>% | <N>d |
(Stage = last non-closed stage from history; omit the median-days column if no history child.)

## Loss Reasons
| Reason | Count | Example |
|---|---|---|
| <Reason> | <N> | <Account>: <one-line evidence w/ date+source> |

## What Wins Have In Common
- <pattern> (<N of M> wins) …

## Largest Losses
- **<Account>** $<X> — died at <Stage> — <one-line evidence>

## Recommended Actions
1. Systemic — <process/enablement tied to the headline>
2. Coaching — <which rep, on what>
3. Data — <what to start capturing>

Other skills for the same job

Different authors, same problem. Matched on the words in the skill name, across every library in the catalogue except this one.

Need help setting it up?

This page tells you what gtmos-sales-win-loss does and what it needs. Cheetah builds the agent setup it runs inside: data, CRM, sequencing and the guardrails.

Book a call →

The directory stays free. There is nothing gated behind this.

Build my GTM engine

Tell us what you want to improve. Fedor reviews the workflow and replies by email when there is a useful fit.

Fedor reviews every request personally. If there is a useful fit, he will reply by email.