# Voc Source Chat

> Harvest customer signal out of internal chat — the channels where sales, support and CS paste what customers actually said. Filters relay noise from real quotes, preserves attribution, and writes normalized signal files for the VoC pipeline. Trigger on /voc-source-chat, "pull customer quotes from Slack", "what did the team hear this week".

- Skill: `aatirs-vault/voc-source-chat` (Agent Skill)
- Install (CLI): `npx skillmds@latest add aatirs-vault/voc-source-chat`
- Raw SKILL.md: https://api.skillmd.com/api/skills/aatirs-vault/voc-source-chat/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: DevOps & Infra
- Author: Aatirs-Vault (https://skillmd.com/u/aatirs-vault)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/aatirs-vault/voc-source-chat

---


# /voc-source-chat — Mine Internal Chat for Customer Voice

Your team already does VoC work. They paste customer quotes into channels named things like
`#wins`, `#churn-risk`, `#feature-requests` and `#sales-questions`, where the quote gets four
emoji reactions and is never seen again.

This is the highest-yield source per unit of setup effort in the whole pipeline, and it is the
one every VoC tool ignores because it is not a customer-facing system.

## Requires

| What | Value |
|---|---|
| Chat MCP server | Slack, Discord, Teams — whatever your team uses |
| Config | `context/product-context.md` → `chat_channels` |
| Cost | Free. |

### Wiring the tools

**This skill deliberately does not hardcode chat MCP tool names.** They vary by server, and a
skill that declares a tool name your server does not expose fails at its first step while looking
like it ran.

To connect yours:

1. Add your chat MCP server to `.mcp.json` (see `.mcp.json.example`)
2. Run `/mcp` in Claude Code to list the tool names your server actually exposes
3. Add those names to this file's `allowed-tools`

If you skip step 2 and guess, you will get a skill that reads no messages and reports no error.

---

## Execution

### Step 1 — Resolve channels

Read `chat_channels` from your context file. Each entry:

```yaml
chat_channels:
  - name: "#wins"
    signal_type: praise
    reliability: high
  - name: "#sales-questions"
    signal_type: objection
    reliability: medium
```

If unconfigured, ask which channels carry customer quotes. Do not scan every channel — most
chat volume is internal coordination and scanning it wastes tokens and buries the signal.

### Step 2 — Pull the window

Read messages from the configured window. Include thread replies: the quote is often in the
parent and the crucial context in a reply three deep.

### Step 3 — Separate quote from relay

This is the work, and it is where naive implementations fail. Chat contains three things:

| Type | Example | Keep? |
|---|---|---|
| **Direct quote** | `Customer said: "we can't get the data out fast enough"` | **Yes**, verbatim |
| **Paraphrase** | `Northwind is frustrated with export speed` | Yes, but mark `paraphrased: true` |
| **Internal chatter** | `has anyone seen the deck for tomorrow` | No |

Preserve the distinction all the way through. A paraphrase is real signal about a real
conversation, but it is your colleague's words, and it must never be published as a customer
quote. Downstream skills rely on this flag.

**Extract quoted spans precisely.** Look for text inside quotation marks, after `said:` /
`told me:` / `their words:`, and inside blockquote formatting. When a message mixes relay and
quote, keep only the quoted span as `verbatim` and put the rest in `context`.

### Step 4 — Attribute

For each signal, resolve:

- **Company** — usually named in the message or its thread. If absent, check the channel topic
  and the surrounding messages before giving up.
- **Who relayed it** — the team member. Useful for follow-up, and useful for spotting when one
  enthusiastic rep is generating a disproportionate share of your "signal."
- **Date** — the message timestamp, not the date of the underlying conversation. Note the
  difference when it is knowable; chat relays often lag the call by days.

Unattributable signals still get written. They become tier U at the profiling step and are
excluded from confirmation counts automatically.

### Step 5 — Write

One JSON per signal to `outputs/voc/sources/chat/<date>-<slug>.json`:

```json
{
  "channel": "chat",
  "chat_channel": "#wins",
  "verbatim": "we can't get the data out fast enough",
  "paraphrased": false,
  "context": "relayed after the QBR",
  "company": "Northwind Trading",
  "relayed_by": "person who posted",
  "message_date": "2026-04-22",
  "permalink": "...",
  "signal_type": "pain"
}
```

Report counts by channel and by signal type, plus how many were unattributable.

---

## What this gets wrong

- **Relay bias is real and large.** You hear what your team found notable enough to paste. Quiet
  problems and boring-but-common complaints never make it to chat.
- **Enthusiasm skews the sample.** One rep who posts every customer comment will dominate the
  corpus. Check the `relayed_by` spread before treating volume as prevalence.
- **Paraphrase drift compounds.** By the time a customer's comment reaches a channel it has
  passed through one person's memory and framing. This is why the flag matters.
- **Chat is a lagging indicator.** Something posted Friday may have been said the previous week.
  Date arithmetic on this source is approximate.
- **Reading team channels is a trust matter.** Tell your team this is running. A tool that
  silently harvests colleagues' messages is a bad thing to be discovered doing, even when the
  intent is benign.

