vip · git:20260828.b62bd4a · 2026-08-28 · sha256 212564c219b3d595

vip git:20260828.b62bd4aA

Immutable. This exact content is served forever at /api/v1/blob/212564c219b3d595.

---
name: vip
description: "OPS on-demand: This skill should be used when the user asks to \"vip list\", \"who is a VIP\", \"add someone…"
argument-hint: '[list|show <person>|set <person> --tier N|suggest|why <person>]'
allowed-tools:
  - Bash
  - Read
  - Grep
  - Skill
  - AskUserQuestion
---

# /ops:vip — VIP List

Load `ops-rules` before acting. Public repo (no personal data). Outbound: one draft → one approval → one send. If `AskUserQuestion` / `Workflow` are missing, follow Rule 10 in `ops-rules` (Hermes: numbered options / two-turn Telegram card; `delegate_task`).

The priority layer over every inbox. VIPs are checked FIRST in any sweep,
answered first, drafted with extra care, and never buried in a bulk digest.

## Scope (deliberately narrow)

This skill does four things and nothing else:

1. Enforce the list — VIP senders rank ahead of everything else in every sweep.
2. Require FULL context before answering or drafting for a VIP: the whole
   thread, every account on that channel, both directions, plus the ledger brief.
3. Order triage output by tier.
4. **Suggest** additions when the signals below appear.

It does not read histories for their own sake, does not draft unasked, and does
not open a second channel just because one record is open. A tier edit is
metadata only. Tagging someone is never an instruction to sweep their history.

## Configuration

Nothing here is machine-specific. Every value resolves from `userConfig`, an env
var, or `$HOME`.

| userConfig key        | Env override     | Default                                     | Meaning                              |
| --------------------- | ---------------- | ------------------------------------------- | ------------------------------------ |
| `vip_list_path`       | `VIP_LIST_PATH`  | `$HOME/.claude-ops/state/vip-list.json`     | Tier data + why/register notes       |
| `vip_max_tier`        | `VIP_MAX_TIER`   | `2`                                         | Highest tier treated as VIP          |
| `vip_suggest_enabled` | —                | `true`                                      | Emit VIP candidate suggestions       |
| `vip_volume_days`     | —                | `30`                                        | Window for the volume signal         |
| `vip_volume_min`      | —                | `12`                                        | Two-way messages that trip volume    |
| `relations_cli`       | `RELATIONS_CLI`  | `$HOME/.claude-ops/relations/bin/relations-cli` | Shared ledger CLI (see `relations`) |

```bash
PREFS_PATH="${PREFS_PATH:-${CLAUDE_PLUGIN_DATA_DIR:-$HOME/.claude/plugins/data/ops-ops-marketplace}/preferences.json}"
read_pref() { [ -f "$PREFS_PATH" ] && python3 -c 'import json,sys;print(json.load(open(sys.argv[1])).get(sys.argv[2],"") or "")' "$PREFS_PATH" "$1" 2>/dev/null; }

VIP_LIST_PATH="${VIP_LIST_PATH:-$(read_pref vip_list_path)}"
VIP_LIST_PATH="${VIP_LIST_PATH:-$HOME/.claude-ops/state/vip-list.json}"
VIP_MAX_TIER="${VIP_MAX_TIER:-$(read_pref vip_max_tier)}"
VIP_MAX_TIER="${VIP_MAX_TIER:-2}"
```

The list file lives OUTSIDE the repo tree — it is the operator's identity and
must never be committed (`ops-rules` Rule 0). Shape:
`templates/vip-list.example.json`.

## Tiers

| Tier | Meaning                | Handling                                                  |
| ---- | ---------------------- | --------------------------------------------------------- |
| 1    | Inner circle           | Never let it sit. Checked first, answered first, never batched |
| 2    | Key business           | Same day. Ranked above general triage                      |
| 3-5  | Reserved               | Not VIP by default (`vip_max_tier` = 2)                     |

The ledger's `importance_tier` column is the source of truth for the tier value;
the JSON file carries the why and the register notes (language, tone, dates to
avoid). Both are read; the DB wins on conflict.

## Operations

| Subcommand              | What it does                                                     |
| ----------------------- | ---------------------------------------------------------------- |
| `list` (default)        | Every person at or above `vip_max_tier`, grouped by tier          |
| `show <person>`         | Tier, type, why, channel, register notes                          |
| `set <person> --tier N` | Audited tier write via the ledger CLI, reason required            |
| `suggest`               | Read-only candidate scan (see signals below); never writes        |
| `why <person>`          | Which signal put a suggested candidate on the list                |

```bash
"$RELATIONS_CLI" vip-list --max-tier "$VIP_MAX_TIER"
"$RELATIONS_CLI" vip <person_id> --tier 1 --type personal --reason "<why>"
```

**Tier writes go through the CLI, never raw SQL.** The CLI owns
`source_reference`, the audit log, and the rollback path. A direct
`UPDATE` on the ledger DB — inline or wrapped in a script — leaves an
unauditable row and is forbidden even where no guard happens to fire.

## Sweep order

Any inbox, chat, email, or ticket sweep runs in this order:

1. Resolve the VIP set once (`vip list`), cache it for the sweep.
2. Tier 1 senders, every channel, both directions, every account.
3. Tier 2 senders, same.
4. Everything else, normal triage.

A VIP with an unanswered inbound is always surfaced individually, with the
thread context, never as a line in a digest.

## Suggesting additions

Suggest, never add silently. A tier change needs the owner's word, then the
audited CLI write. Three signals:

| Signal      | Trigger                                                                 |
| ----------- | ----------------------------------------------------------------------- |
| volume      | `vip_volume_min`+ two-way messages in `vip_volume_days` with someone off the list |
| frustration | They say they are being ignored or chased ("still waiting", "any update") |
| chain-break | Their last message is inbound and still unanswered                       |

Two filters keep this honest, both learned from real false positives:

- **Anchor every keyword on both sides.** A bare stem matches inside longer
  words (`ping` inside "tapping") and produces phantom frustration hits.
- **A thread ending on a sign-off is CLOSED, not broken.** "Thanks!", "sounds
  good", an emoji, or any short ack with no question mark is not an open ask.
  Without this filter most chain-breaks are noise that buries the real ones.

Present at most 5 a week: who, which signal, the evidence line, and the exact
CLI command to apply it. Then ask plainly. On "no", do not raise that person
again.

## Related skills

- `relations` — the ledger, decision queue and draft path this list ranks
- `ops-inbox` — the sweep that consumes the tier order
- `ops-comms` — the send path, still gated by Rule 6 for every VIP
- `people` — contact directory / identity resolution
- `ops-rules` — Rule 0 (the list is operator data, never committed)