---
name: keyword-agent
description: >
  Собирает в Савви агента-лидмагнит для Instagram*: человек пишет кодовое слово в комментарии
  или в директ, ему в директ приходит «Сейчас пришлю, только подпишись» с кнопкой «Подписался»,
  Савви проверяет подписку и выдаёт материал (ссылку, гайд, промокод) только подписчикам.
  После выдачи агент замолкает. Нейросеть не вызывается — диалог стоит 0 ₽.
  Пользователь подключает Instagram и ставит галочки в канале, остальное скилл делает через MCP Савви.
  Триггеры: «агент на кодовое слово», «лид-магнит в инстаграме», «бот на ключевое слово»,
  «воронка на кодовое слово», «проверка подписки в инстаграме», «/keyword-agent».
---

# Лид-магнит на кодовое слово с проверкой подписки (Instagram*, Савви)

Схема как у ManyChat: комментарий → директ с кнопкой → проверка подписки → материал.
Всё собрано на действиях по триггеру с `refuse_on_call: true`, поэтому модель не вызывается: **0 токенов на диалог**.
Схема проверена на рабочих агентах и в тестовом чате (октябрь–ноябрь 2026).

*Instagram принадлежит компании Meta, признанной экстремистской и запрещённой в РФ.

## 0. Что нужно до старта

- MCP Савви подключён к Claude Code (сервер `suvvy`). Проверка: вызови `get_info_about_self_user` —
  должен вернуться аккаунт пользователя. Не отвечает → попроси пользователя выполнить `/mcp` → `suvvy` → Authenticate.
- Instagram-аккаунт **Бизнес или Автор** (с личного не подключится).

### Спроси у пользователя (одним сообщением, коротко)
1. Кодовые слова (1–3 для начала). Для каждого — что отдаём: текст материала, ссылки, подписи кнопок
   (до 3 кнопок-ссылок, подпись до 20 символов).
2. Ник аккаунта Instagram — для кнопки «Подписаться».
3. Новый агент или уже есть агент с подключённым Instagram (тогда его название).
4. Instagram уже подключён к агенту в кабинете Савви? (Нужно до шага с кнопками, см. 3.0.)

Нет ссылки на материал — не выдумывай, поставь явный плейсхолдер `<ССЫЛКА>` и скажи об этом.
Покажи план (слова, тексты трёх сообщений, кнопки) и дождись «да». Потом делай.

## 1. Как выглядит для человека

1. Пишет «гайд» в комментарии под рилсом или сразу в директ.
2. В директ приходит: «Привет! 👋 Сейчас пришлю …, только подпишись на аккаунт. Потом нажми «Подписался» — и всё придёт.»
   + кнопки **«Подписаться»** (профиль) и **«Подписался ✅ Гайд»**.
3. Жмёт «Подписался»: подписан → материал, агент замолкает; не подписан → «Пока не вижу подписки 🙈
   Подпишись и нажми «Подписался» ещё раз» + те же кнопки.
4. Напишет потом другое кодовое слово — получит и его материал.

## 2. Как Савви узнаёт о подписке

- На каждое входящее в директ и на каждое нажатие кнопки Савви спрашивает у Instagram профиль человека
  и кладёт ответ в переменную диалога **`is_follower`** (`true` / `false` / пусто).
- Её видит условие вызова типа `variable`: `{"type":"variable","key":"is_follower","operator":"eq","value":true}`.
- ⚠️ **Пока человек сам ничего не написал и не нажал в директе, Instagram профиль не отдаёт** → `is_follower` пустой.
  Поэтому комментатору **всегда** первым уходит «подпишись», даже если он подписчик. Отсюда текст
  «Сейчас пришлю, только подпишись»: подписчик просто жмёт кнопку и сразу получает материал.
- Ветка «не подписан» = `is_follower ne true`: так она ловит и `false`, и пустое значение.
- Отдельного шага «проверить подписку» для Instagram в Савви нет (он есть только для Telegram и MAX).

## 3. Что делает скилл через MCP Савви

### 3.0. Порядок
Действие с шагом `instagram` (кнопки) **не сохраняется, пока Instagram не подключён к агенту** —
ошибка `instance_channel_is_disabled` (424) или пустая ошибка. Поэтому:
- Instagram уже подключён → собирай всё сразу.
- Ещё не подключён → создай агента (3.1), попроси пользователя подключить Instagram (раздел 4) и жди.
  Пока ждёшь, можно собрать теги, условия и действия только с шагом `send_message` (ссылку временно положи в текст)
  и прогнать тест (3.6) — это проверка логики, не боевой вариант. После подключения добавь в каждое действие шаг
  `instagram` первым (`update_custom_tool_by_id`, передай весь список `actions`) и убери ссылку из текста.

### 3.1. Агент
`create_instance` (имя вроде «Кодовое слово — Instagram»). Инструкция нужна только для сообщений мимо сценария.
Обязательно впиши в неё:
«Материалы выдают действия по триггеру. Сам ты материалы не отправляешь и никогда не пишешь, что что-то отправил.
Если человек пишет не кодовое слово — коротко ответь и подскажи: напиши <слово> — пришлю <что>.»
Без первой строки модель на слово без сработавшего триггера отвечает «Материал отправлен!» — неправда.
Стандартную функцию «Установка тега» держи выключенной: теги ставят сами действия.

### 3.2. Теги (`create_knowledge_tag`)
По тегу на слово («Кодовое слово: гайд») + один общий «Не подписан: просили подписаться». Это аналитика, бесплатно.
Передавай `title` и `color` (hex, например `#3A3DD4`).

### 3.3. Условия вызова на каждое слово (`create_call_condition`)
Группы соединяются через **И**, условия внутри группы — через **ИЛИ**.
`incoming_message_phrase in` — поиск подстроки без учёта регистра.

| Условие | Группы |
|---|---|
| A. Подписан | `[phrase in "<слово>"]`, `[is_follower eq true]`, `[channel_name ne "instagram_comments"]` |
| B. Не подписан, первый запрос | `[phrase in "<слово>"]`, `[is_follower ne true]`, `[phrase nin "подписался"]`, `[channel_name ne "instagram_comments"]` |
| C. Нажал «Подписался», подписки нет | `[phrase in "<слово>"]`, `[phrase in "подписался"]`, `[is_follower ne true]`, `[channel_name ne "instagram_comments"]` |

Пример условия B целиком:
```json
{"title":"Гайд: не подписан, первый запрос","groups":[
  {"condition_list":[{"type":"incoming_message_phrase","operator":"in","value":"гайд"}]},
  {"condition_list":[{"type":"variable","key":"is_follower","operator":"ne","value":true}]},
  {"condition_list":[{"type":"incoming_message_phrase","operator":"nin","value":"подписался"}]},
  {"condition_list":[{"type":"channel_name","operator":"ne","value":"instagram_comments"}]}]}
```
- Группа `channel_name ne "instagram_comments"` нужна, чтобы действие не срабатывало в отдельном диалоге
  комментариев по посту (там нет `is_follower` и кнопок).
- Варианты написания — в одну группу через ИЛИ: `[phrase in "гайд" ИЛИ phrase in "guide"]`.
- Слово пиши в нижнем регистре и основой: «воронк», а не «воронка» — иначе «воронку» не поймается.
  ⚠️ Подстрока: «агент» поймает и «агентство». Если мешает — выбери слово, которое внутри других не встречается.

### 3.4. Действия на каждое слово (`create_custom_tool`)
Общее для всех трёх: `trigger_settings: {"condition":"new_customer_message","call_condition_id":"<id>","argument_values":{}}`,
`refuse_on_call: true` (модель не вызывается — это и даёт 0 токенов), **`is_enabled: true`** (по умолчанию false!),
`tool_name` — латиницей (`^[a-zA-Z0-9_-]{1,64}$`, например `send_guide`, `ask_subscribe_guide`),
`title` — по-русски для кабинета, `tool_description` — одна строка, что делает действие.

**send_<слово>** (условие A) — материал, `stop_dialogue_on_call: true`, тег слова.
```json
[{"type":"instagram","parameters":{"action":"add_button_template","buttons":[
   {"type":"web_url","title":{"parts":[{"type":"string","content":"Открыть гайд"}]},
    "url":{"parts":[{"type":"string","content":"https://…?utm_source=instagram&utm_medium=<ник>&utm_campaign=<слово>"}]}}]}},
 {"type":"send_message","parameters":{"message_text":{"parts":[{"type":"string","content":
   "Спасибо, держи! 🙌\n\n<материал: 1–3 строки, что внутри>"}]}}}]
```

**ask_subscribe_<слово>** (условие B) — `stop_dialogue_on_call: false`, тег «Не подписан».
```json
[{"type":"instagram","parameters":{"action":"add_button_template","buttons":[
   {"type":"web_url","title":{"parts":[{"type":"string","content":"Подписаться"}]},
    "url":{"parts":[{"type":"string","content":"https://www.instagram.com/<ник>/"}]}},
   {"type":"postback","title":{"parts":[{"type":"string","content":"Подписался ✅ <Слово>"}]}}]}},
 {"type":"send_message","parameters":{"message_text":{"parts":[{"type":"string","content":
   "Привет! 👋 Сейчас пришлю <что>, только подпишись на аккаунт.\n\nПотом нажми «Подписался» — и всё придёт."}]}}}]
```

**still_not_subscribed_<слово>** (условие C) — те же две кнопки, текст
«Пока не вижу подписки 🙈\n\nПодпишись на аккаунт и нажми «Подписался» ещё раз.», `stop_dialogue_on_call: false`, тег «Не подписан».

Правила сообщения с материалом:
- **Всё одним сообщением.** Кнопки цепляются к последнему текстовому сообщению действия. На комментарий Instagram
  даёт ответить в директ один раз; второе сообщение до ответа человека не доходит, кнопки пропадают.
  Второй `send_message` в действие не добавляй никогда.
- Ссылка в тексте директа не нажимается — важные ссылки только кнопками (до 3 кнопок `web_url`).
  Ссылок больше трёх — шаг `instagram` не ставь, пиши ссылки прямо в тексте.
- Лимиты Meta: подпись кнопки до 20 символов («Подписался ✅ Гайд» — влезает), текст сообщения с кнопками до 640.
- UTM на каждой ссылке: `utm_source=instagram&utm_medium=<ник>&utm_campaign=<слово>` — видно, какой ролик привёл человека.

Почему так устроено:
- Нажатие postback-кнопки приходит в диалог **сообщением клиента с текстом подписи кнопки**, и перед этим Савви заново
  читает подписку. В подписи есть кодовое слово → снова срабатывает нужное условие. Поэтому слово в подписи обязательно.
- Ветки «не подписан» **не закрывают диалог**: в остановленном диалоге триггеры молчат, нажатие «Подписался» потеряется.

### 3.5. Фразы возобновления (обязательно)
Ветка с материалом закрывает диалог. Чтобы тот же человек мог потом получить другое слово, все кодовые слова
(и варианты) — во фразы возобновления диалога клиентом:
`update_instance_mcp` → `resume_customer_dialogue_patterns: [{"pattern":"гайд","can_be_part_of_word":true}, …]`
(передай весь список, до 25 фраз). Слово возобновляет диалог → триггер → материал → диалог снова закрыт;
на «спасибо» по-прежнему тишина.

### 3.6. Проверка в тестовом чате
`get_new_test_dialogue_for_instance_by_instance_id` → `send_message_to_test_dialogue_by_id` с `fake_channel: "instagram"`,
подписку подставляй через `channel_variables` → смотри `get_dialogue_messages_by_dialogue_id` и `get_dialogue_by_id`.

| Шаг | channel_variables | Сообщение | Ждём |
|---|---|---|---|
| 1 | `{"is_follower": null}` | `гайд` | сработало `ask_subscribe_guide`, диалог открыт |
| 2 | `{"is_follower": false}` | `Подписался ✅ Гайд` | `still_not_subscribed_guide` |
| 3 | `{"is_follower": true}` | `Подписался ✅ Гайд` | `send_guide`, диалог остановлен (`is_stopped: true`) |
| 4 | `{"is_follower": true}` | `Спасибо!` | ответа нет, у сообщения клиента `refuse_reason: dialogue_stopped` |
| 5 | `{"is_follower": true}` | второе кодовое слово (если есть) | событие `dialogue_resumed`, пришёл второй материал, диалог снова закрыт |

Как читать: `get_dialogue_messages_by_dialogue_id(dialogue_id, elements=6)` отдаёт последние сообщения (новые сверху);
какое действие сработало — по `tool_name` в сообщении-результате действия.
Везде: ровно одно действие на шаг и **0 токенов** (`total_tokens_amount` диалога = 0).
Ответила модель (токены не ноль) — триггер не сработал, проверь условие.
Тестовый чат кнопок не показывает — их проверяют вживую (раздел 5).

### 3.7. Побочные действия (если нужны)
Савви перебирает действия-триггеры по порядку создания и **останавливается на первом с `refuse_on_call: true`**.
Своё побочное действие (вебхук в CRM, тег) создавай **раньше** действий слов и с `refuse_on_call: false`,
иначе до него не дойдёт.

## 4. Что пользователь делает в кабинете (через MCP не выставляется)

Ключевые слова комментариев, галочки канала и защита от спама настраиваются только в кабинете app.suvvy.ai.
Отдай пользователю шаги текстом и жди. Если у тебя есть доступ к браузеру пользователя с открытым кабинетом — можешь
сделать сам, но сначала сверь почту аккаунта в правом верхнем углу. Кнопку «Удалить подключение» не трогай никогда.

**Подключение (один раз):** агент → «Каналы» → Instagram → войти в аккаунт → привязать к агенту.

**Настройки канала** (карточка Instagram → панель «Ответы агента», вкладка «Главное», внизу «Сохранить»):

| Настройка | Значение | Почему |
|---|---|---|
| Отвечать на личные сообщения | **ВКЛ, обязательно** | без неё материал после «Подписался» собирается, но не уходит: в директ Савви отвечает только при этой галочке (исключение — первый ответ на комментарий) |
| Отвечать на комментарии | ВКЛ | |
| Ключевые слова для ответа на комментарий | **все кодовые слова**, каждый вариант отдельным полем («Добавить») | ⚠️ пустой список = агент отвечает на ВСЕ комментарии моделью, за деньги |
| Отвечать в личные сообщения (на комментарий) | **ВКЛ** | комментарий уходит в директ — только там есть кнопки и проверка подписки |
| Обрабатывать нажатия на кнопки | **ВКЛ** | иначе «Подписался» не дойдёт до агента |
| Отвечать только подписчикам | **ВЫКЛ** | неподписчик молча не получит вообще ничего, воронка оборвётся |

Слова комментариев ищутся как подстрока без учёта регистра — пиши ту же основу, что в условиях.
Проверка: `get_instance` → `channel_settings.instagram.comments_answer_settings.keywords_for_answer`.

**Защита от спама:** агент → «Доп. настройки» → «Защита от спама» → лимит одинаковых сообщений клиента → **20**
(по умолчанию 5; сохраняется само). Иначе неподписчик, нажавший «Подписался» 5 раз, упрётся в антиспам и агент замолчит.
Проверка: `get_instance` → `anti_spam_settings.customer_identical_message_limit`.

## 5. Живая проверка (обязательно)
Попроси пользователя с другого аккаунта, без подписки:
1. Комментарий со словом → в директ пришло «подпишись» с двумя кнопками.
2. Нажать «Подписался», не подписываясь → «Пока не вижу подписки».
3. Подписаться → «Подписался» → материал с кнопками → написать «спасибо» → тишина.
Потом открой диалог через MCP: у всех сообщений 0 токенов, у ответа нет пометки «не доставлено».

## 6. Добавить ещё одно слово
Слово (и варианты) → в ключевые слова канала (кабинет) → во фразы возобновления → тег → три условия (A, B, C) →
три действия. Агент тот же. В инструкции агента обнови подсказку «Напиши … — пришлю …».

## 7. Грабли
1. **Пустые ключевые слова комментариев** = агент отвечает на каждый комментарий моделью, за деньги.
2. **«Отвечать только подписчикам»** режет неподписчиков молча — им не приходит даже просьба подписаться.
3. **«Отвечать на личные сообщения» выключено** → «подпишись» приходит, а материал после «Подписался» — нет.
   Держать ВКЛ. Лишние ответы модели гасить инструкцией агента, а не этой галочкой.
4. **Комментатору подписка неизвестна** до его первого действия в директе → первым всегда «подпишись». Тексты пиши под это.
5. **Остановленный диалог глух к триггерам** → ветки «не подписан» не закрывают диалог; все слова — во фразы возобновления (3.5).
6. **`refuse_on_call` обрывает перебор действий** — побочные действия создавай первыми и без refuse.
7. **Действия с кнопками нельзя создать до подключения Instagram.** Копия агента со старыми кнопочными действиями
   тоже не пересохранится, пока канал не подключён.
8. **Модель пишет «Материал отправлен!»**, если триггер не сработал, — нужна строка в инструкции (3.1).
9. **Антиспам**: 5 одинаковых сообщений клиента среди последних 10 → агент молчит. Подними лимит до 20 (раздел 4).
10. **Второе сообщение в директ не доходит** — материал и кнопки одним сообщением.
11. **Публичный ответ под комментарием** («Отправил в директ») вместе с директом-кнопкой средствами Савви не сделать:
    в режиме «Отвечать в личные сообщения» Савви под комментарием не отвечает. Нужен свой сервер вне РФ с Graph API.
12. **Поиск слова — подстрока** без учёта регистра: «агент» ловит «агентство», «150» ловит «1500».
13. **Инструменты `suvvy` пропали** — проверь `/mcp`: сервер мог отключиться или потерять вход.

## 8. Тексты
- Instagram читают с телефона: 2–4 строки, максимум два эмодзи, без markdown (звёздочки видны символами).
- Сначала то, что человек получит, потом условие: «Сейчас пришлю X, только подпишись».
- Суммы, бонусы и промокоды — только те, что подтвердил пользователь.

## Когда схема не подходит
Ловится вхождение слова, а не смысл: «хочу гайд» пройдёт, «скинь материал» — нет. Нужна свободная беседа —
это уже модель и база знаний, и каждый диалог стоит токенов.
