# Keyword Playbook

Doctrina consolidada de keywords: dónde tienen que vivir, qué condiciones son válidas, cómo agregar una nueva, y cómo auditar que estén bien configuradas (que existan en plataforma y que las que tienen recurso lo entreguen).

Esto consolida la doctrina de keywords que estaba dispersa en `AGENTS.md` (PAT-006/008/010). Es la fuente única para cualquier trabajo de keywords. Todo se hace por **MCP** (`deploy_agent_sdk`, `sync_keyword_triggers`, `list_triggers`, `get_agent_config`) — nunca por scripts ni acceso directo a la DB.

---

## 1. Dónde tiene que vivir una keyword (los 3 lugares)

Una keyword es un objeto de **3 mitades** que tienen que estar sincronizadas. Si falta una, hay split-brain (PAT-010):

| # | Capa | Tabla / ubicación | Qué hace |
|---|------|-------------------|----------|
| 1 | **Plataforma — DM** | `trigger_keywords` | Captura el match en mensajes directos. Sin esto, el trigger nunca dispara. |
| 2 | **Plataforma — IG comments** | `trigger_comments` + `trigger_comment_responses` | Captura comentarios en posts/reels + el texto de reply ("Revisá tu DM 💫"). Solo si la keyword entra por comentario. |
| 3 | **Prompt** | `agent_configs.config → v5Config → keywords → keywords` (espejado localmente en `sdk/keywords.json`) | Lo que consulta `lookup_keyword`. Define la condición de entrega y el recurso. |

**Reglas de oro:**
- Solo plataforma (1/2) sin prompt (3) → el trigger captura al lead pero el agente no sabe qué hacer → cae a **DIRECT_FLOW genérico**.
- Solo prompt (3) sin plataforma (1/2) → el agente sabe responder pero **el trigger nunca lo captura**.
- `sdk/keywords.json` debe espejar `trigger_keywords` **exacto** (PAT-008). El JSON solo necesita la forma canónica — los operators agregan variantes con acento en la DB para tolerancia a typos sin inflar el JSON (la auditoría normaliza NFD para comparar).

**En `system.md`** las keywords NO se listan inline (PAT-006). La sección `## Keywords` solo:
1. Describe el fuzzy match (stripear emojis/puntuación, case-insensitive)
2. Instruye a llamar `lookup_keyword`/`get_keywords` con el keyword canónico
3. Va **primero** (antes de Flujo/Identidad)

Listar "Keywords activas: FECHA, GUIA..." en system.md crea drift cuando se actualiza keywords.json. El agente obtiene las keywords del tool, no del prompt.

---

## 2. Las únicas 3 condiciones válidas

El enum tipado de la plataforma es:

```ts
condition: 'literal_response' | 'resource_with_flow' | 'contextual'
```

**Cualquier otro valor no es reconocido** y la plataforma NO maneja la entrega — cae a juicio del LLM + `get_resource` tool, que es poco confiable (el LLM pasa el topic equivocado, alucina "no tengo el link cargado").

> ⚠️ **Trampa histórica**: vika usaba `resource_with_qualify`, `resource_first`, `resource_first_then_qualify`, `immediate` — ninguno existe en el enum. Delivery rate de esas keywords ≈ **14%** vs ≈ **98%** de las que usan `resource_with_flow`. Si ves una condición que no sea una de las 3, está rota. Ver migración en curso: memoria `[[project-vika-keyword-migration]]`.

| Condición | Comportamiento |
|-----------|----------------|
| `literal_response` | La plataforma envía el texto exacto de `literal_response` al matchear. Para video: embeber la URL en el texto. |
| `resource_with_flow` | Plataforma maneja la entrega del recurso dentro de un flujo de calificación. Forma recomendada para recursos. |
| `contextual` | El agente decide cómo responder según contexto (sin entrega forzada). |

### Cómo poner una URL directo en keywords.json (sin tool call, confiable)

```json
{
  "keyword": "EJEMPLO",
  "condition": "resource_with_flow",
  "video_url": "<URL>",
  "literal_response": null,
  "match_type": "isolated_word",
  "send_without_permission": false
}
```

- `send_without_permission: false` → califica primero, después entrega
- `send_without_permission: true` → entrega inmediata
- Embeber `video_url` en la keyword **bypassa el `get_resource` tool** (poco confiable). Ejemplos reales con ~98% delivery: juanmahuss SACRO/TESTIMONIO, vicunacoach (todas), sebaperotti SECRETOS.

### Otros campos

- `match_type`: `isolated_word` (solo la palabra sola) o `any_mention` (dentro de una frase). Debe matchear el `match_mode` de la DB (`KEYWORD_ONLY` ≈ isolated, `ANYWHERE` ≈ any_mention).
- `resource_key`: si la keyword apunta a un recurso en `resources.json`, debe apuntar a una entrada **válida** (PAT-008). No al catch-all genérico.

---

## 3. Checklist: agregar / cambiar una keyword

1. **keywords.json** (local): agregar entrada con `condition` válida + `video_url`/`resource_key` correcto + `match_type`.
2. **resources.json** (si usa `resource_key`): crear la entrada con la URL real. Probar el link manualmente.
3. **Deploy**: `deploy_agent_sdk { agent_id, keywords: [...] }` — escribe `v5Config.keywords` **y** re-sincroniza `trigger_keywords` (DM) en un solo paso (dual-write idempotente). El cambio va **live** de inmediato. Para un cambio keyword-only sin tocar el resto del SDK, `sync_keyword_triggers { agent_id, keywords }` hace solo la sync (full diff: pasá la lista completa, no solo la keyword nueva).
4. **Paridad comment + DM**: para triggers por comentario, además creá/actualizá el trigger `comments` con `create_trigger`/`update_trigger` (incluye el texto de reply público).
5. **Verificación post-cambio** — los 3 lugares deben contener la keyword (PAT-009: re-leer siempre, los writes pueden reportar success sin persistir): `get_agent_config` muestra `v5Config.keywords`; `list_triggers` muestra `trigger_keywords` (DM) y `trigger_comments`. Confirmá que la keyword aparezca donde corresponde.
6. **Smoke test**: DM con la keyword → agente califica (si aplica) → entrega el recurso correcto.

Patrón establecido para cada keyword nueva/re-pointed: **keyword → resource artifact propio + condición válida + paridad comment & DM trigger**.

---

## 4. Cómo auditar (que existan y que entreguen)

Todo con herramientas MCP — sin scripts. Dos chequeos:

### Sync — ¿coinciden los 3 lugares?

`list_triggers { agent_id }` (lo que dispara de verdad) contra `sdk/keywords.json` (la forma canónica):

- **CRITICAL**: keyword en `trigger_keywords` sin entrada en `keywords.json` → comportamiento indefinido, drift silencioso al redeployar.
- **MISSING**: keyword en `keywords.json` sin trigger en plataforma → el agente sabe responder pero nunca se captura.
- **MISMATCH**: `match_type` (JSON) ≠ `match_mode` (trigger).
- La dirección JSON-sin-trigger puede ser decisión del operator; la dirección trigger-sin-JSON casi siempre es un bug.
- Re-deployar con `deploy_agent_sdk { keywords }` (o `sync_keyword_triggers`) sana el drift — el dual-write es idempotente.

### Delivery — ¿la keyword entrega el recurso?

`get_conversations` (modos `bad` / `stuck`): buscá conversaciones donde la keyword entró en un inbound pero el recurso **nunca se entregó** en un outbound. Causas típicas: condición inválida (§2), `get_resource` que falló sin fallback, o `video_url` no embebido. Por cada keyword con recurso, confirmá que el link real aparezca en la respuesta del agente.

---

## 5. Modos de falla frecuentes

| Síntoma | Causa probable | Fix |
|---------|----------------|-----|
| Keyword captura pero agente improvisa / cae a flow genérico | Split-brain: keyword en plataforma pero no en v5Config | PAT-010: sync los 3 lugares |
| Agente promete recurso pero nunca lo manda | Condición inválida (`resource_with_qualify` etc.) → entrega vía LLM + get_resource | Migrar a `resource_with_flow` + `video_url` embebido |
| "No tengo ese link cargado" / inventa URL | `get_resource` falló sin fallback, o fuzzy match no matcheó (emoji) | PAT-002 (datos inline fallback) + fuzzy match en system.md |
| Recurso equivocado entregado | `resource_key` apunta al catch-all genérico | Apuntar a la entrada dedicada en resources.json (PAT-008) |
| Keyword se envía 2-3x | Keyword duplicada en DB Y en prompt con literal_response | Una sola fuente de entrega |
| Operator agregó keyword, "success", pero no aparece | Write no persistió en v5Config | PAT-009: re-leer con get-* y verificar |
| MODE MISMATCH (json=any_mention, DB=KEYWORD_ONLY) | Drift entre match_type local y match_mode DB | Alinear; decidir con cliente cuál es correcto |

---

## 6. Cross-references

- **`knowledge/AGENTS.md`** — PAT-006 (keyword section tool-based), PAT-008 (URL change = full SDK grep + sync), PAT-009 (always re-read after write), PAT-010 (new keyword = sync both sides), PAT-011 (resource type-aware completion check). Sección "Keywords.json / Fuzzy Matching / Resources.json".
- **`knowledge/triggers.md`** — modelo completo de triggers (tipos, match modes, negative configs).
- **`.claude/skills/cortex-triggers/SKILL.md`** — el flujo operativo para inspeccionar y modificar triggers por MCP.
- **Herramientas MCP**: `list_triggers`, `create_trigger` / `update_trigger` / `delete_trigger`, `sync_keyword_triggers`, y `deploy_agent_sdk` (campo `keywords`, dual-write).
