# AGENTS.md — Librería de patrones para agentes Ninjo

> Referencia de conocimiento (MCP-agnóstica) para diseñar e iterar agentes Ninjo.
> El **cómo operar** (conexión, deploy, skills) vive en `CLAUDE.md`. Este archivo es el **qué**:
> filosofía, arquitectura del SDK y patrones validados (`PAT-xxx`) con evidencia real.
>
> **MCP-first.** Todo se hace con las tools de `cortex-gateway`: deploy con `deploy_agent_sdk`,
> verificación con `get_agent_config`, rollback con `restore_prompt_version`, conversaciones con
> `get_conversations`, métricas con `get_agent_metrics` / `get_agent_insights`, keywords con
> `sync_keyword_triggers` (o la skill `cortex-triggers`). No hay scripts Python ni acceso directo a DB.
>
> Los números `PAT-xxx` son estables (otros archivos los citan). Hay huecos donde se retiraron patrones datados.

---

## 1. Filosofía

Principios destilados de correcciones reales. Internalizarlos ANTES de tocar un agente.

### 1.1 The Golden Rule
> "The prompt only does what ONLY the prompt can do. Everything else goes to the platform."

Si la plataforma puede enforcearlo (stop, timing, splitting, filtrado), NO va en el prompt.

### 1.2 Examples > Rules > Hard Rules
El modelo sigue examples con 2-3x más peso que reglas. Si un example contradice una regla, gana el example.
Implicación: cuando algo falla, primero tocá un example, después un principle. Hard rules es el ÚLTIMO recurso.

### 1.3 Máximo 5 principles. Nunca 15 rules.
15+ rules = cognitive overload → el modelo ignora la mayoría.
- **Principles** = reglas guía de alto nivel con "por qué" inline. Target 3-5, nunca >5.
- **Hard rules** = restricciones absolutas no-negociables (ej: "Nunca dar pricing en DM"). Target ≤3.
- Juntos no deben pasar ~8 items antes de volverse ruido.

### 1.4 Prompt vs Plataforma

| Necesitás… | Es de… | Solución |
|---|---|---|
| Que no responda a X | Plataforma | Contact limit |
| Que no entre a ciertas convos | Plataforma | Evaluator |
| Fix de caracteres (¿ ¡) | Plataforma | Custom regex post-messaging |
| Control de splitting | Plataforma | Custom split / first contact msg |
| Timing de follow-ups | Plataforma | Workflows |
| Blacklist de cuentas | Plataforma | Negative triggers |
| Identidad, tono, flujo | Prompt | system.md + examples |
| Calificación de leads | Prompt | Principles + flow |
| No sigue estilo/tono | Prompt | Curar examples |
| Inventa URLs | Prompt | Fuzzy matching keywords |

### 1.5 "Siempre cinco. No cincuenta."
5 sintéticas/manuales que LEÉS > 50 automáticas. Las 50 dan falsa seguridad. Leer CADA conversación: tono, flujo, tool calls, links, NO_RESPONSE.

### 1.6 Podar, no parchear
Mensajes largos o reglas que no se cumplen → eliminar 3-5 palabras, no agregar más reglas. Si falla 2-3 veces en lo mismo, es estructural: reescribir.

### 1.7 Patrones cognitivos > métricas numéricas
- ❌ "Máximo 50 caracteres por línea"
- ✅ "Patrón: reconocer + presentar + preguntar. Nunca 4+ oraciones."

Los patrones describen CÓMO pensar. Las métricas dicen QUÉ medir (y el modelo no sabe medir).

### 1.8 NO_RESPONSE es el ÚNICO stop token
No usar "PARAR", "STOP", "no continuar", copys de despedida. Solo el string exacto `NO_RESPONSE`.
- Sin NO_RESPONSE → el agente responde igual en el turno actual.
- Sin contact limit → el agente puede reactivarse después.
- AMBOS son necesarios y complementarios. Nunca quitar uno por el otro. (Ver §2 NO_RESPONSE.)

### 1.9 Contact limits son SILENT
`closure_message = null`. SIEMPRE. "Fue un gusto" / "Gracias por escribir" → REVELA que es IA. El contact limit corta sin que el lead se entere.

### 1.10 Orden de fix
```
¿Qué example puedo tocar para que funcione mejor?
     ↓ no alcanza
¿Qué principio tengo que poner/ajustar?
     ↓ no alcanza
¿Esto se soluciona con un NO_RESPONSE?
     ↓ no aplica
¿Esto es de plataforma, no de prompt? (contact limit, evaluator, regex, split)
     ↓ no aplica
El problema es ESTRUCTURAL → reescribir
```
1 vez → ajustar. 2-3 veces el mismo error → REESCRIBIR.

### 1.11 Openers: sin easy-outs
Blacklist de openers que matan conversión (dan al lead salida fácil): *"¿En qué te puedo ayudar?"*, *"¿Querés info?"*, *"Did I catch you at a bad time?"*.
Reemplazar por **acknowledgement específico + open pain question**: *"Vi tu comment en el post del lunes — ¿hace cuánto venís dándole vueltas a eso?"*
Al hacer fix de tono, auditar `examples.md` buscando easy-out phrasing.

### 1.12 Talk/listen ratio target: 1.3-2.0x
El mensaje del agente NO debe superar **~2x el largo del último mensaje del lead**. Si el lead manda 10 palabras, el agente manda 10-20, nunca 50. Top closers escuchan más de lo que hablan. Ratio >2.5x = casi siempre over-talking ("interrogate + lecture").
Excepción: Phase 5 (Invitation) y Phase 4 (Micro-positioning) pueden tener 1-2 mensajes ~2.5x al presentar el programa.

### 1.13 KPI: median_exchanges_to_booking = 4-6
Entregar el booking link después de 4-6 intercambios (1 turno user + 1 turno agent = 1 exchange).
- **<3 exchanges** → premature, lead no qualified → ghost o no-show.
- **>7 exchanges** → over-qualifying, el interés decae.
- **4-6** → gates pasaron (commitment + investment readiness).

---

## 2. Arquitectura del SDK

Un agente = un **system prompt** + los **8 campos de v5Config**. Se autoran como archivos y se deployan con `deploy_agent_sdk`.

### Estructura de archivos (`agents/<name>/`)

| Archivo | Qué es | Obligatorio |
|---|---|---|
| `sdk/system.md` | Identidad, voz, principles, flujo, tools, NO_RESPONSE | SÍ — con contenido |
| `sdk/examples.md` | 10+ conversaciones de referencia | SÍ — con contenido |
| `sdk/knowledge_base.md` | Contexto interno — NUNCA se envía al lead | SÍ — con contenido |
| `sdk/keywords.json` | Keywords con URLs y match types | SÍ — keywords **o** program |
| `sdk/program.json` | Definición del producto/programa | SÍ — keywords **o** program |
| `sdk/resources.json` | Índice de recursos (videos, PDFs, links) | SÍ — `[]` si no aplica |
| `sdk/objections.md` | Manejo de objeciones | SÍ — vacía si no aplica |
| `sdk/personal_story.md` | Timeline, hitos, anécdotas del creador | SÍ — vacía si no aplica |
| `sdk/case_studies.json` | Casos de éxito con tags | SÍ — `[]` si no aplica |
| `meta.md` | `influencer_id`, `agent_id`, canal. VERIFICAR `influencer_id` antes de deploy | SÍ |
| `changelog.md` | Historial de versiones (habilita rollback reasoning) | SÍ |

> **Las 8 secciones del v5Config son obligatorias — se deployan como set completo.** No hay
> secciones "opcionales", pero **pueden ir vacías**. Tres **siempre llevan contenido real**:
> `system`, `examples`, `knowledge_base`. Las otras cinco pueden ir **vacías** (`[]` en los JSON,
> mínimas en la prosa) cuando no aplican — pero **al menos una de `keywords` / `program`** tiene que
> estar poblada (el agente o dispara por keywords o vende un programa definido). Lo que **nunca** es
> válido es un config **solo-keywords** con `examples`/`knowledge_base` vacíos: eso está
> **degenerado** (incompleto) — deployá el set completo con `deploy_agent_sdk` y verificá
> con `get_agent_config`.

### Sizing targets

| Métrica | Target | Señal de rewrite |
|---|---|---|
| system.md tokens | 2.5-3.5K | >4K |
| system.md líneas | ~60-150 | >200 |
| Principles | 3-5 | >5 |
| Hard rules | ≤3 | >3 |
| Flow steps | ≤10, lineal | branches anidados |
| Examples | ≥10 | <7 |
| examples.md tokens | 800-1.2K | — |
| objections.md tokens | 400-600 | — |
| knowledge_base.md tokens | 400-800 | — |

### Orden de system.md (PAT-024)

El orden ES el comportamiento. Lo operativo lidera, las restricciones cierran.
```
1. Identidad          — quién es, misión, objetivo (2-3 líneas)
2. Voz                — vocabulario, formalidad, idioma. Patrones cognitivos, no métricas
3. Keywords           — descripción de la lógica de lookup (si aplica; ver keyword-playbook.md)
4. Flujo              — 8-10 pasos lineales con inline guards
5. Calibración        — read de energía del lead + disparadores inline de NO_RESPONSE
6. Principios         — 3-5, al FINAL (no al principio). Cada uno con "por qué" inline
7. Fallback           — si las tools fallan, qué responder
8. Herramientas       — máx 4, con triggers explícitos de cuándo usar cada una
```
- **Headers en Title Case español** (`## Identidad`, `## Voz`, `## Flujo`) — NO UPPERCASE ni inglés.
- **Por qué Principios al final:** atención posicional — lo primero tiene más peso, y lo primero debe ser "qué hacer", no "qué no hacer".
- **Prioridad de keywords (STEP 0) es flexible** (PAT-020): vale como header literal, como principle dedicado, o inline en el Flujo. Lo que importa: que sea explícita y esté antes del flujo de calificación.

### examples.md — la pieza más importante (PAT-019)

- **Mínimo 10** (7 no alcanza). Distribución: 3-4 happy path, 2-3 objeciones, 2-4 NO_RESPONSE, 1 returning user, 1 edge case del negocio.
- **Basados en conversaciones REALES** (bajar conversaciones con `get_conversations` y modelar perfiles).
- **Mensajes del agente: 1-2 oraciones** — el modelo replica la longitud.
- **Cada example demuestra un principle** — sin contradicciones con system.md.
- **NUNCA duplicar examples en system.md** — elegir UN lugar.
- **Tool order explícito** — si envía recurso y después pregunta, mostrarlo así.

**Formato canónico:**
```
### Example N: título descriptivo

> Nota previa del escenario (opcional).

**User:** mensaje del lead
**Agent:** respuesta del agente
línea 2 si aplica

**User:** ...
**Agent:** NO_RESPONSE - razón corta
```
`**User:**` / `**Agent:**` en bold = turn boundaries nítidos. Notas en `>` blockquote. Evitar: `usuario:`/`nombre:`, `## N. title`, comments HTML `<!-- -->`, em dashes (—).

### NO_RESPONSE (§1.8 + PAT-018 + PAT-061)

Bloque canónico al final de `system.md` (adaptar triggers por caso):
```markdown
## NO_RESPONSE

Responder `NO_RESPONSE` (y nada más) cuando:
- El lead declina explícitamente ("no gracias", "no me interesa")
- El lead no califica y ya recibió valor
- Off-topic sin relación con el programa
- Emoji solo sin texto, sin intención clara
- "Gracias" de cierre sin acción pendiente
- [Triggers específicos del agente: menores, abuso, competitor, tema sensible, etc.]

`NO_RESPONSE` es literal — no agregar texto ni despedida.
```
- **No usar un principle dedicado** que enumere casos (genera bloat y citas rotas). Los disparadores van inline en Calibración / objections.md / examples.md.
- **En examples.md** el marker enseña por demostración: `**Agent:** NO_RESPONSE - razón corta` (la razón no se envía al lead). Incluir mínimo 4-5 markers cubriendo los cierres principales — la sección en system.md es necesaria pero **no suficiente**, el modelo aprende el token de los examples.

### keywords (PAT-006 / PAT-010 / PAT-022)

> Playbook completo: `knowledge/keyword-playbook.md` (fuente única). Resumen acá.

- **`## Keywords` en system.md NO lista las keywords inline** — describe la lógica de fuzzy lookup (stripear emojis/puntuación, case-insensitive) e instruye llamar la tool de lookup con el keyword canónico. Listarlas inline crea drift.
- **Estructura canónica en keywords.json:** 1 entry con `aliases: [...]` por resource/flow (no N entries duplicadas). `match_type` default `isolated_or_clear` (no `any_mention`, que da falsos positivos). Excepción: priority interrupts pueden usar `any_mention` documentado en `notes`.
- **Dual-write obligatorio (PAT-010):** una keyword vive en `v5Config.keywords` (lo que el lookup consulta) Y en `trigger_keywords` (lo que dispara). `deploy_agent_sdk` con `keywords` re-sincroniza ambos lados (idempotente, sana drift). Si solo está en uno → split-brain: el trigger captura pero el agente improvisa, o el agente sabe pero el trigger no captura.
- **keywords.json espeja `trigger_keywords` (PAT-008):** el JSON tiene solo la forma canónica; los operators agregan variantes con acento en la plataforma para tolerancia a typos.

### resources.json (PAT-051)

Schema canónico que el deploy valida:
```json
{
  "resources": [
    {"key": "...", "title": "...", "url": "...", "type": "...", "topics": [...], "description": "..."}
  ],
  "topic_index": { "topic_keyword": ["resource_key"] }
}
```
- Todos los links acá o hardcodeados en system.md. NO depender de `get_resources` como única fuente. NO placeholders. Probar cada link antes de deploy.
- Schemas no-canónicos (keys top-level sueltos en vez de array entries) hacen fallar el deploy. Recursos TBD van en un `_note` al final, no como entries a medio poblar.

### knowledge_base.md (PAT-025)

No es documentación suelta. Es el almacén de:
1. **Datos factuales** que NO van en el prompt (team, direcciones, horarios, precios, fallback phrases). El prompt referencia: *"Si el lead pregunta por el equipo, consultá knowledge_base sección Equipo."* Hardcodear el roster en system.md vuelve el prompt inmutable y genera drift.
2. **Commercial insights** — 2-3 reframes (no features) tipo Challenger: *"La mayoría piensa [X]. Pero el verdadero bloqueo es [reframe]."* **Gate:** ≥2 insights antes de deploy, si no el agente produce nurturing genérico sin ángulo.

---

## 3. Patterns validados

Cada uno tiene evidencia real. Números estables (citados por otros archivos).

### PAT-001 — Los archivos del SDK deben estar en sync
Después de editar system.md, verificar que examples.md y program.json no lo contradigan. El modelo sigue la mayoría: si 2/3 archivos dicen algo distinto a system.md, ignora system.md. *(Estilo/voz repetidos entre system.md y examples.md es esperado; solo cuentan las contradicciones.)*

### PAT-002 — Datos inline como fallback
Si system.md referencia datos como fallback ("usá la URL de la tabla"), esos datos DEBEN existir inline en system.md. Si la tool falla, el agente no tiene nada y dice "no tengo el recurso".

### PAT-003 — El deploy colapsa newlines: preservar estructura
El pipeline de deploy colapsa single newlines en prosa (preserva tablas/listas/ejemplos). Verificar con `get_agent_config` post-deploy que los examples con `**User:**`/`**Agent:**`, tablas y listas sobrevivan legibles en el v5Config.

### PAT-004 — Inline guards en acciones
Toda instrucción de acción que pueda violar un constraint lleva guard inline ("si no se envió antes", "si no se preguntó ya"). Un constraint separado de la acción es una sugerencia; inline es enforceable. *(Evidencia: agente mandó URL 4x porque el subflujo no chequeaba duplicados.)*

### PAT-005 — "Por qué" en reglas
`**Regla** — instrucción. Por qué: razón.` El modelo generaliza mejor a edge cases cuando entiende la razón. *(Evidencia: rule_compliance 8.0 → 8.5 al agregar "por qué".)*

### PAT-006 — Keyword section tool-based, no lista inline
`## Keywords` describe la lógica de fuzzy match y manda llamar el lookup con el keyword canónico; **no lista las keywords** (viven en keywords.json). Sin esta sección el LLM decide ignorar las reglas de keywords. Ver §2 y `keyword-playbook.md`.

### PAT-008 — Cambio de URL = grep en todo el SDK
Al cambiar una URL en cualquier archivo, `grep -r "OLD_URL" agents/<agent>/sdk/` para encontrar TODAS las instancias. Las flow instructions de system.md son las más críticas (el modelo las ejecuta literal). *(Evidencia: booking URL fixeada en examples+resources pero system.md seguía mandando el link 404.)*

### PAT-009 — Siempre re-leer después de escribir
Los writes pueden reportar "success" sin persistir. Después de cualquier deploy, re-leer con `get_agent_config` y verificar que el estado matchea lo escrito. `deploy_agent_sdk` ya re-fetchea y verifica (versión bumpeada, campos match, sin drift v5Config↔trigger_keywords); en `verified:false`/`partial:true`, revertir con `restore_prompt_version` usando el `rollback_version_id` del reporte. Nunca cerrar con un mismatch conocido.

### PAT-010 — Keyword nueva = sync ambos lados
Una keyword requiere `v5Config.keywords` (lo que el lookup consulta) Y `trigger_keywords` (lo que dispara). `deploy_agent_sdk`/`sync_keyword_triggers` los mantienen en sync. Solo plataforma → el agente improvisa; solo prompt → el trigger nunca captura. Ver §2 keywords + `keyword-playbook.md`.

### PAT-011 — Completion check según tipo de resource
El step de "confirmar entrega" depende del tipo: **download** (PDF) → "¿te llegó el email? revisá spam"; **event** (webinar) → "¿guardaste tu lugar?"; **booking** (1:1) → "¿confirmaste el slot?". Calibrar por examples (1 por tipo en happy path), no por reglas.

### PAT-012 — Review = QA técnico + juicio de diseño (capas separadas)
1. **QA técnico** (mecánico): ¿se deployó? ¿viola el playbook? ¿regresa bugs?
2. **Juicio de diseño** (sobre hechos citados): ¿resuelve el problema o solo el síntoma? ¿hay contradicción regla↔example en el mismo archivo? ¿la misma instrucción vive en >1 archivo (canon ambiguo)?
La capa 1 sin la 2 declara "aplicado correctamente" cambios mal diseñados. Para la capa 2: citar `file:line`/diff/conversación; si falta evidencia, se skipea — no se infiere.

### PAT-013 — Ramp-up controlado: keywords antes de All DMs
Mejor performance arrancando por keyword + evaluator + contact limits, y recién después promover a All DMs. All DMs introduce inputs impredecibles (tags en stories, etiquetas externas) que generan quejas antes de tener calibración. Excepción: cuentas grandes con audiencia muy segmentada pueden ir directo.

### PAT-014 — Contaminación cross-agent al usar otro agente de referencia
Al usar un agente existente como referencia arquitectural, hay riesgo de copiar **contenido** (team members, programas, URLs, call types) en vez de solo **arquitectura**. Pre-deploy: grepear en el target nombres/programas del source; cualquier cosa que no esté en el material original del target → remover.

### PAT-015 — Dialect audit obligatorio post-split (idioma consistente)
Al armar/splitear un SDK, el dialect del creator se puede corromper (voseo rioplatense filtrado por el operador). Los examples ganan sobre las reglas: si el system dice "tuteo neutro" pero los examples vosean, el modelo vosea.
- Fuentes de verdad del dialect: `meta.md` Language + material original del creator.
- Audit: `grep -wiE "\b(sos|tenés|podés|querés|contame|escribime|mirá|elegí|dejá|usá)\b" agents/<agent>/sdk/*.md *.json` — si el creator no es de AR/UY, no debe aparecer ninguna; pasar a tuteo (sos→eres, tenés→tienes, contame→cuéntame…).
- **Y al revés (PAT-062):** principios/voz en el mismo idioma del agente. Castellano: "Consultá antes de inventar", no "Look it up before inventing". (Los nombres de tools internas quedan en inglés.)

### PAT-020 — STEP 0 Keyword Check: header literal es OPCIONAL
La prioridad de keywords puede comunicarse como header literal, principle dedicado, o inline en el Flujo. Lo que importa: que sea explícita y antes del flujo de calificación. El formato es flexible. Ver §2.

### PAT-021 — Discovery flexible > secuencia rígida de pasos
Para recolectar 2-3 piezas (experiencia, motivación, objetivo), consolidar en **una fase Discovery con calibración** (1 pregunta si el lead es verbose, hasta 3 si es terse; no repreguntar lo ya dicho). Pasos separados = el modelo los trata como opcionales y los saltea. Cada pregunta lleva un micro-insight (no interrogatorio); antes de la Invitation, una Consequence question ("Si seguís así X meses más, ¿qué pasa con Y?").

### PAT-023 — Deflección 2x de info crítica: nurture > silence (default)
Si el lead deflecta info crítica (capital, experiencia) 2 veces, el default NO es `NO_RESPONSE` silent: asumir el rango más bajo + mandar resource ligero + re-entry con keyword. Lead que no declaró ≠ lead descalificado; silent kill = perder recovery. Silent solo aplica (vía contact_limit) para menor de edad, conducta inapropiada, anti-scam, o "no me interesa" explícito post-oferta.

### PAT-040 — Tool params verbatim: descripción imperativa + lookup server-side (incl. PAT-057)
Cuando un param debe pasarse **literal** desde la output de otra tool (timestamp, ID, token), el modelo tiende a reconstruirlo desde el contexto humano-legible (año, timezone, display string) y rompe. Dos defensas:
1. **Descripción imperativa** con MUST + 3× NEVER, anclada al campo exacto: *"ISO copied verbatim from raw_slots[].start_time… NEVER reconstruct… NEVER change timezone or year."*
2. **Mejor aún (arquitectónico):** que la tool reciba un **display string** ("Friday 15th May 5pm") y el backend haga el match contra los slots y use el ISO real. El agente nunca construye timestamps. Aplica a Calendly/Cal.com/Zoom/Google Calendar.
*(Evidencia: agente mandó año 2025 reconstruyendo desde el display → POST a Calendly falló; bookings perdidos.)*

### PAT-041 — Reproducir aislado antes de culpar al código nuevo
"Se rompió justo después de X" ≠ "X lo rompió". Antes de rollback/hot-fix, reproducir el componente nuevo aislado con casos conocidos. Si está limpio, la causa es otra y el rollback no la arregla. Aislamiento cuesta minutos; rollback equivocado cuesta horas. *(Caso: bug de timestamp post-merge de hydration; el helper pasó 8/8 aislado → root cause era PAT-040, no el merge.)*

### PAT-042 — El comportamiento de plataforma NO se escribe como reglas de prompt (incl. PAT-063)
Traducir un requerimiento ("ante tema sensible, escalar y cortar") como instrucciones en system.md ("setear property X=true, copy fijo Y, no continuar") es ruido inerte o race condition:
1. El agente **no setea custom properties** — las setea el extractor de plataforma (LLM separado). "Setear X=true" no ejecuta nada.
2. El agente **no corta conversaciones** — el stop real lo hace el contact_limit silencioso.
3. Copy fijo + contact_limit = handoff duplicado / revela IA.

Patrón correcto (3 capas): **Detección** = extractor marca la property (plataforma) · **Silencio** = `NO_RESPONSE` ante las palabras gatillo (1 bullet en system.md) · **Cierre** = contact_limit silent con la property como condición (plataforma). En system.md, solo las palabras gatillo en NO_RESPONSE.
Comportamientos que ya hace la plataforma y NO se repiten en el prompt: chunking/split, follow-up timing, contact limits, notifications. Antes de escribir una regla: *¿esto ya lo hace la plataforma?*

### PAT-044 — Integridad referencial: nombres en MD ↔ entries en JSON
Toda mención por nombre propio (caso, testimonio, recurso) en `system.md`/`examples.md`/`knowledge_base.md` debe tener entry en el JSON de lookup (`case_studies.json`/`resources.json`). Si no, el lookup no matchea y, como el nombre ya está en contexto, el modelo improvisa (quote/URL/edad falsos). Audit grepable barato: cruzar nombres propios de los MD contra los `name` de los JSON.

### PAT-045 — Cuantificar antes de iterar por alarma del operador (incl. PAT-039)
Ante una queja de calidad, NO defender inline ni iterar reflexivamente. Cuantificar con `get_conversations` + `get_agent_metrics`/`get_agent_insights`: N convos del período, M errores reales clasificados, error rate, 1-2 turnarounds positivos. Los operadores ven 1-3 convos malas y proyectan "el agente está mal"; el número re-encuadra ("1 error real en 251 convos = 0.4%").
**Ojo con conteos inflados:** un "N hits" de un detector basado en pares puede venir de **1 sola conversación** outlier (12 mensajes idénticos en 1 conv → 66 pares). Verificar **distinct conversation_ids** antes de escalar: bug rate honesto = convos distintas flaggeadas / total, no hits / total.

### PAT-046 — Reasoning leak: SÍ es fixable (ver `knowledge/reasoning-leak.md`)
Síntoma: el agente narra su lógica interna al lead ("necesito verificar los ingresos del lead… aplicar guard ICP"). **Corrección de la guía vieja:** lo que NO sirve son los blacklists de tokens por-agente y los baneos agresivos de "scratchpad" (estos últimos rompen el comportamiento — el guard deja de aplicarse). Lo que SÍ sirve, validado: (1) la **regla genérica de OUTPUT** en el Principio 1 (prohíbe conductas de salida, no el razonamiento interno) — 0/19 en el set de reproducción; (2) el **filtro CoT de plataforma** (`responseFilter` en `agent_configs.config`, on por default) — bajó la tasa de leak de fleet 0.62%→0.21%. El leak es un **evento de decisión-de-rama**, no un problema de vocabulario: las reglas always-on nunca leakean; las reglas condicionales imperativas sí. Workflow: prevenir con la regla genérica en cada agente, atrapar el residuo con el filtro, y empujar la rama upstream/a un tool en casos tercos. **Detalle completo, catálogo de shapes A-K y loop diagnóstico → `knowledge/reasoning-leak.md`.**

### PAT-047 — Keywords genéricas en agente de expertise estrecho = anti-pattern
Un agente de una vertical específica con keywords genéricas del idioma ("estudiar", "trabajar", "carrera") dispara en convos de otro contenido del cliente, no encuentra qué responder y dice "solo tengo consultas de [X]" → percepción de calidad pobre. Agentes verticales se triggerean solo por `trigger_ads` específicos + keywords del programa (nombre/oferta/terminología única). Regla: dejar solo triggers que un humano leería y diría "esto sí es para este agente".

### PAT-049 — El operador puede pisar archivos del SDK entre versiones
El bot operador puede sobrescribir el v5Config y borrar archivos satélite (knowledge_base, personal_story, objections, case_studies, resources, program) entre versiones. Señales: alta rotación de versiones, archivos satélite faltantes en `get_agent_config`, alucinaciones por gaps de conocimiento. **Mitigación:** commitear el SDK a git antes de dejar que el operador toque el agente (punto de recovery), y mantener el workspace local en sync con producción.

### PAT-051 — Schema canónico de resources.json (validado por deploy)
Ver §2 resources.json. Schemas no-canónicos hacen fallar el deploy.

### PAT-054 — `git fetch origin main` antes de declarar el estado del working tree
`git status` sin `fetch` puede reportar "0 behind" falso cuando HEAD local está atrás del remoto; deployar sobre esa versión vieja pisa cambios estructurales del operador que sí están en `origin/main`. Workflow al cargar contexto: `git fetch origin main` → `git status --short` → `git log --oneline HEAD..origin/main`. Si hay líneas behind, no editar hasta resolver (`pull --rebase` o `checkout origin/main -- <path>`). *(Caso real: deploy corrió 20 commits atrás y pisó la versión del cliente.)*

### PAT-055 — No sincronizar todas las keywords a triggers ciegamente
Sincronizar a `trigger_keywords` TODAS las entries de keywords.json sin filtrar por `condition` infla la tabla: incluye entries `contextual`/routing (señales internas de SDK que orientan al modelo) que NO deben disparar respuesta automática. Sincronizar a plataforma solo las **active** (`resource_with_flow`, `do_not_respond`, `literal_response`). En agentes maduros con triggers ya en DB + audios/respuestas attached manualmente, sincronizar selectivamente (la skill `cortex-triggers`/`sync_keyword_triggers` maneja el caso; revisar keywords.json antes de cada sync).

### PAT-056 — `split_prompt_override` reemplaza el default entero: replicarlo
El override de split **sustituye** el prompt default de la plataforma, no se compone. Si seteás solo tu caso especial, perdés el comportamiento default (split en `\n\n`, statement+question, intro+URL+CTA, merge de URL sola) y todos los demás mensajes quedan en una burbuja. Plantilla: caso especial como sección 1 + las 4 reglas genéricas replicadas como sección 2. Si solo necesitás 1 caso, considerá ajustar el SDK para producir `\n\n` que el default ya splittea.

### PAT-058 — Failure path explícito cuando una tool puede devolver `success:false`
Si una tool puede fallar, system.md debe explicitar qué NO hacer, no solo el happy path. Sin cláusula negativa el modelo completa el happy path por inercia y le miente al lead ("all set, te llega el email") sobre un booking que nunca se creó. Escribir: `on success:false → NO mandar el framing post-booking, NO afirmar que se creó, disculparse + fallback link`.

### PAT-059 — Wording de error sugerido en el prompt = el LLM lo usa preemptive
Si system.md sugiere un wording literal para fallos ("decí 'el sistema está fallando'"), el modelo lo usa **antes** de llamar la tool, alucinando un failure. Describir el **comportamiento** ("on failure, disculparse + fallback link"), no el wording; modelar el wording en examples.md (donde se usa solo si el contexto matchea).

### PAT-060 — Rollback > re-comprimir cuando hay versión validada por el cliente
Si el cliente iteró el prompt directamente (deploys consecutivos con feedback puntual), esa versión es oro validado. Si un refactor posterior rompe valor, `restore_prompt_version` a esa versión + delta quirúrgico mínimo. Re-comprimir el refactor arrastra sus problemas. Subagentes de reescritura de SDK son peligrosos: aplican métricas correctas pero pierden matices que el cliente validó turn-by-turn.

### PAT-064 — 1 acción por paso del flow (evitar fused actions)
Un paso que fusiona acciones ("proponé la call, pasá el link") hace que el modelo ejecute ambas en el mismo turn → turns colapsados, sin cadencia ni espacio para objection handling. Regla: 1 acción = 1 paso. Evitar adjetivos vagos ("breve pitch"); especificar qué va en el mensaje. *(Refactor de claridad, 0 hard rules nuevas.)*

### PAT-066 — Examples-first no siempre alcanza — a veces refactor de system.md
Default es examples-first (PAT-001), pero agregar examples para "cubrir variantes" a veces empeora (cognitive overload). Si el bug afecta un punto core del flow y los examples canónicos YA cubren el pattern pero el modelo no generaliza, el lever es **refactor de claridad** del paso (separar acciones fusionadas, suavizar lenguaje sesgado), no más examples ni hard rules.

### PAT-067 — Migración ManyChat → Ninjo: auditar `literal_response` contra los principios
Copiar el default de ManyChat palabra por palabra a `literal_response` no es seguro. Auditar antes de deploy: (1) emoji en primer mensaje (anti-bot tell) → remover si el agente lo prohíbe; (2) nombre del programa high-ticket disclosed → reemplazar por phrasing genérico si el agente lo reserva para la call; (3) recurso prometido sin URL real → pausar la keyword. **Aliases colisión:** keywords son globales por agente (no per-reel) — dos keywords con alias compartido = comportamiento indefinido, resolver antes.

### PAT-069 — Voice examples que comparten frase con la apertura → eco
Si una frase aparece en voice examples Y en la apertura (o en un literal_response), el modelo encadena ambas como mensajes consecutivos → eco que rompe la naturalidad. Garantizar que voice examples no compartan frases distintivas con apertura/literal_response.

### PAT-070 — Asimetría de operators: contact_limit ≠ workflow conditions
`contact_limit_property_conditions` solo soporta `EQUALS`/`LESS_THAN`/`GREATER_THAN_OR_EQUAL_TO` (no `NOT_EQUALS` ni `IS_NULL`). Diseñar custom properties **booleanas explícitas** (ej: `booking_completed: boolean`) en vez de depender de "string IS NOT NULL" en contact limits. Los workflows sí aceptan `NOT_EQUALS`.

### PAT-071 — Quick-reply de ManyChat aislado dispara over-inferencing
Cuando el lead manda el texto fijo de un quick reply ("Desarrollo personal") aislado, el modelo lo infiere como afirmación rica y encadena 2-3 mensajes "profundizando" sin nuevo input. Fix por example (no hard rule): "lead manda quick reply → el agente le pregunta con sus palabras antes de avanzar" + principio corto "No deduzcas lo que el lead no dijo".

### PAT-072 — No listar tools que el runtime no provee
La sección `# Herramientas` solo puede listar tools que el agente-v5 **realmente tiene** en su config. Listar una inexistente (clásico: `analyze_image`) hace que el modelo intente llamarla y falle, o narre el error al lead. La comprensión de imágenes/capturas es **nativa multimodal** — el agente VE la imagen directamente, no existe ninguna tool `analyze_image`/vision. Tools de entrega reales: `send_resource`, `list_sendable_resources` (+ las que el agente tenga configuradas). Antes de deployar, auditar `# Herramientas` contra el tool config real; si una tool no está en la config, sacarla del prompt. *(Origen: operadores de ideal-hectorino y zaliwealthgroup listaron `analyze_image` copiándolo entre agentes; nunca existió.)*

### PAT-073 — v5Config estructurado se pasa como JSON, nunca como string
`keywords`, `program`, `resources`, `case_studies` son los cuatro campos **estructurados** del v5Config: se pasan a `deploy_agent_sdk` como valor JSON (objeto/array), **nunca como string JSON-encodeado** (`JSON.stringify(...)`). Una sección doble-encodeada se guardaba verbatim como string y **crasheaba el runtime** (el agente hace `key in section` / `Object.keys()` y un string expone `"0","1",…`). El gateway ahora **normaliza** los cuatro campos en cada write (`normalizeV5JsonSections`): un string que parsea a objeto/array lo **auto-parsea** (repara el doble-encoding), y un string que es JSON inválido o parsea a escalar lo **rechaza con 400** (no escribe nada) — o sea un deploy actual ya no puede persistir un string verbatim. Igual, pasar objeto/array de entrada (no depender del auto-repair) y verificar post-deploy con `get_agent_config` que los cuatro vuelven como objetos/arrays. Un agente **legacy** deployado antes del guard puede tener todavía una sección en string; redeployar el valor parseado una vez lo sana. Los campos de prosa (`examples`, `objections`, `knowledge_base`, `personal_story`) sí son strings y no aplican. Ver `platform-internals.md` §3e y PAT-051.

---

## 4. Anti-Patterns

Errores ya cometidos. Si algo de esta lista aparece, PARAR.

### Arquitectura
| ❌ Mal | ✅ Bien | Por qué |
|---|---|---|
| 15+ hard rules | 3-5 principles + examples | Cognitive overload |
| Estilo en reglas | Estilo en examples | Examples llevan el estilo (PAT-001) |
| Parchear regla que falla | Mejorar un example | Examples > rules en peso |
| Prompt >200 líneas | Reescribir, no parchear | A 200L el problema es estructural |
| "Burbuja 1/2/3" | "Máx 2 oraciones, ~15-25 palabras" | El LLM no controla el split (ver abajo) |
| Branches anidados en flow | Flow lineal ≤10 pasos | El modelo se pierde |
| Examples duplicados en system + examples | Elegir UNO | Doble fuente = contradicciones |

### NUNCA usar "burbuja" en prompts
El LLM no sabe qué es una "burbuja" (concepto de plataforma: split via `\n\n` decidido por GPT-mini). "Máx 2 burbujas" no se puede cumplir. Usar: *"Máx 2 oraciones por mensaje, ~15-25 palabras; al presentar el programa o enviar un link con contexto, hasta 3."* El largo se enseña por examples. (Si un agente existente usa "burbuja" y funciona, no tocar hasta iterar por otra razón.) Misma familia: *"MANDA EN UN SOLO MENSAJE"*, *"prohibido splitear"*, *"(Fin del mensaje)"* → el split lo decide la plataforma (chunking / `split_prompt_override`), el prompt no lo controla. Sacarlo.

### Stops y filtrado
| ❌ Mal | ✅ Bien | Por qué |
|---|---|---|
| "PARAR", "STOP", "no continuar" | Solo `NO_RESPONSE` | Único token que la plataforma procesa |
| "No contestes a menores" como regla | Contact limit `is_minor` | El LLM puede fallar; el limit no |
| "Si ya agendó, no insistas" como regla | Contact limit `booking_completed` | El LLM no trackea state bien |
| `closure_message` con texto | `closure_message = null` | "Fue un gusto" revela IA |
| Quitar NO_RESPONSE porque "el limit se encarga" | AMBOS | Son complementarios |
| "Setear property X=true" como instrucción al agente | Sacarlo del prompt | El extractor (plataforma) marca el flag; el agente no tiene tool (PAT-042) |
| "Manda una notificación al canal de Slack" / "dispará el workflow" / "mandá un email" | Sacarlo del prompt | El agente no tiene esa tool; notifications/workflows los maneja la plataforma (custom_notification / workflow), no el prompt (PAT-042) |
| Copy fijo de handoff + "no continuar" en system.md | `NO_RESPONSE` + contact_limit silent con la property | El limit corta determinístico; el copy duplica/revela IA (PAT-042) |

### Tool syntax en texto libre
`"enviar doc con get_resource('doc')"` en flow → el modelo puede mandar `[get_resource("doc")]` literal al lead. Las tools se invocan en prosa ("el link del documento"), nunca con sintaxis en instrucciones free-text. URLs hardcodeadas en examples.

### Herramientas que no existen
Listar en `# Herramientas` una tool que el runtime NO provee (clásico: `analyze_image`) → el modelo intenta llamarla y falla, o narra el error al lead. La lectura de imágenes/capturas es **nativa** (el agente ve la imagen), no hay tool de visión. Solo listar lo que está en el tool config real del agente (`send_resource`, `list_sendable_resources`, etc.). Auditar `# Herramientas` contra la config antes de deployar (PAT-072).

### Otros
- 50 sintéticas automáticas → 5 que leés (las 50 dan falsa seguridad).
- Confiar en scoring automático → revisión MANUAL (el scoring no catchea tool calls).
- "Esperar respuesta" sin decir QUÉ hacer → `trigger → espera → ACCIÓN explícita`.
- HTML comments `<!-- -->` y em dashes (—) en examples **o en mensajes citados dentro de system.md** (texto que el agente emite) → el modelo los reproduce verbatim. (Headers/instrucciones de system.md que el agente NO emite: tolerable.)

---

## 5. Platform Constraints

Reglas de la plataforma que todo prompt debe respetar:

1. **History window ~10 mensajes** — el agente no recuerda más atrás.
2. **Mensajes ≤80 chars nunca se splitean** (hardcodeado).
3. **El primer mensaje NUNCA se splitea** — diseñar la entrada como 1 burbuja.
4. **GPT-4.1-mini decide los splits** para >80 chars (no es `\n\n`).
5. **system.md es el primer bloque** inyectado al LLM (mayor peso posicional).
6. **RAG = text dump completo** (~20K chars), no retrieval inteligente.
7. **Audio = transcripción** — no hay análisis de tono.
8. **Custom properties = 1-3K tokens** compitiendo con el prompt.
9. **`shouldRespondCheckEnabled` = dead code** — no usar.
10. **Prompt Evaluator solo en triggers ALL_DM** — no funciona en keywords ni comments.
11. **Follow-ups cuentan desde el último INBOUND** (no outbound).
12. **`only_run_once` es per-conversation** (no global).
13. **`collapse_markdown()` colapsa single newlines** en prosa, preserva estructura.
14. **Delay entre burbujas ~10-18 seg** — fijo, no configurable por prompt.
15. **Todos los agentes v5 usan el mismo modelo** — no se elige por agente.
16. **`utm_term=<contact_uuid>` se inyecta automáticamente** en toda URL saliente (runtime). El agente no necesita escribir `[[CONTACT_ID]]` para que el tracking funcione.

---

## 6. Platform Settings

### Triggers
| Tipo | Qué hace | Cuándo usar |
|---|---|---|
| `ALL_DM` | Activa en todo DM entrante | Agentes orgánicos, setters de alto volumen |
| `ALL_COMMENTS` | Activa en todo comentario de IG | Engagement + lead capture desde posts |
| `KEYWORD_ONLY` | Solo si el lead manda una keyword exacta | Campañas con CTA específico |
| `ANYWHERE` | Keyword matchea en cualquier parte del mensaje | Keywords genéricas, más reach |
| `AD_REFERRAL` | Activa cuando un lead llega de un Click-to-Messenger ad | Paid ads (IG/FB) al DM |

**Ad Referral (Click-to-Messenger):** el trigger matchea contra el **Ad ID** (el del anuncio creativo individual), **nunca** `campaign_id` ni `ad_set_id` — los tres son numéricos de ~14-16 dígitos y se confunden. Cruzar el ID que pasa el cliente contra el dropdown de ads sincronizados antes de cargar. Si no aparece: o es Campaign/Ad Set ID (pedir el correcto), o es un Ad pausado (cargar igual, dispara al reactivarse), o es de otra cuenta no conectada.

### Agent Config
| Setting | Controla | Valores típicos |
|---|---|---|
| `delay` (min/max) | Segundos antes de responder | 15-30s |
| `chunking` | Divide respuestas largas | ON para IG, OFF para WhatsApp |
| `language` | Idioma | es, en, pt |
| `split` | Cómo splitea | <80 chars nunca; >80 → GPT-mini decide |

### Evaluator Prompt — LAXO (siempre)
```
Siempre activar si el mensaje podría ser de un potencial lead, aunque no sea explícito.
Saludos genéricos, mensajes cortos o ambiguos, y cualquier cosa que no sea claramente
spam o propuesta comercial → ACTIVAR. El contact limit corta si no califica.
```
**Pattern: evaluator LAXO + contact limits ESTRICTOS.** Ante la duda, activar. Mejor entrar y salir limpio que perder un lead.

### Custom Regex Post-Messaging
Reemplaza caracteres DESPUÉS de que el modelo genera la respuesta (ej: quitar ¡, ¿). **Si es reemplazo de caracteres → regex, nunca regla en el prompt.**

### First Contact Message
Mensaje configurable que se envía antes de la primera respuesta. Útil como workaround para splitting.

### Follow-up Config
Timing (24h/48h), max follow-ups, tono. Preguntar al cliente ANTES de activar.

---

## 7. Evaluator + Contact Limits

- **Evaluator = LAXO**: ante la duda, activar.
- **Contact Limits = ESTRICTOS**: cortan cuando una condición clara se cumple.

### Contact limits defaults (obligatorios en TODO agente)
| Limit | Trigger | Acción |
|---|---|---|
| `human_escalation_requested` | Lead pide persona real | Stop + Slack notification |
| `inappropriate_behavior` | Abuso, amenazas | Stop + Slack notification |
| `{goal_completed}` | Conversión lograda | Stop + conversion notification |

Todos con `closure_message = null` (SILENT, §1.9). Diseñar las conditions con custom properties **booleanas** (PAT-070).

### Scorecard: `commitment_quality` (`reflexive | committed`)
Silent assessment post-Gate 2. `reflexive` = respuesta de 1 palabra a la invitation ("dale", "ok"); `committed` = elabora. Si `reflexive`, antes de droppear el link probar una Consequence question — cachea el patrón "books but no-shows".

### Labeling de leads escépticos
Para leads skeptical ("ya probé de todo"), antes de manejar la objeción, **nombrar la emoción** (no validar ni contradecir): *"Parece que ya viste muchas cosas así y sentís que no terminan de funcionar."* Desactiva la defensa antes del argumento. Va en `objections.md` (sección `skeptical_tried_everything`) + ≥1 example.

### 7 Pilares de Activación (ningún agente se lanza sin todos)
1. **Contact Limits** — 3 defaults + `commitment_quality` + específicos.
2. **Notifications** — Slack alerts por cada limit.
3. **Properties** — custom properties para tracking.
4. **Evaluator** — routing laxo (§6).
5. **CD SB** — Content Do Not Respond list (confirmar con cliente).
6. **Follow-up Strategy** — cuándo, cuántos, qué tono.
7. **Contact Blacklist** — cuentas donde el agente NO activa (staff, familia, amigos).

---

## 8. Deploy & Verify (MCP-first)

> Contrato completo en `CLAUDE.md`. Resumen operativo acá.

**Antes de iterar:** `git fetch origin main` (PAT-054) → revisar conversaciones reales con `get_conversations` (no codear a ciegas) → iterar sobre el existente (git tiene rollback).

**Deploy:** siempre con **`deploy_agent_sdk`** (`agent_id` + los campos que cambian; PATCH semantics — lo omitido no se toca). Diffea cada campo contra la config viva, escribe solo lo que cambió, re-fetchea y verifica (versión bumpeada, campos match, sin drift `v5Config.keywords ↔ trigger_keywords`). `keywords` se dual-escribe (v5Config + trigger_keywords). El `system` nunca va a v5Config.

**Verificación post-deploy** (con `get_agent_config`, no SQL):
1. v5Config presente y completo (los 8 campos esperados, sin satélites borrados — PAT-049).
2. `prompt` actualizado (buscar un string clave de la versión nueva).
3. Top-level config keys preservados (`responseFormatting`, `isTestAgent`, etc.) — `deploy_agent_sdk` hace merge, no overwrite.
4. Sin `isTestAgent: true` en producción.
5. Smoke real: mandar un DM de prueba, verificar tono/reglas/tool calls/links.

**Rollback:** en `verified:false` o `partial:true`, o si un refactor rompe valor (PAT-060), `restore_prompt_version` con el `rollback_version_id` del reporte.

**Verificar `influencer_id` antes de deploy:** el de `meta.md` debe ser el de producción; si está mal, follow-ups y configs se crean para el influencer equivocado (invisibles en UI).

---

## 9. Skills

Cuatro skills, todas MCP-first (cada una en `.claude/skills/<name>/SKILL.md`):

| Skill | Para qué | Tools clave |
|---|---|---|
| **`cortex-create`** | Provisionar un agente nuevo desde un brief | `create_agent`, `deploy_agent_sdk`, `get_agent_config` |
| **`cortex-iterate`** | Un fix dirigido (una hipótesis) desde conversaciones reales, con rollback | `get_conversations`, `deploy_agent_sdk`, `restore_prompt_version`, `get_agent_config` |
| **`cortex-analyze`** | Diagnóstico read-only de performance | `get_agent_metrics`, `get_agent_insights`, `get_conversations` |
| **`cortex-triggers`** | Keyword/comment/ad triggers, sync de keywords, audios, bloqueos temporales | `list_triggers`, `create_trigger`, `update_trigger`, `delete_trigger`, `sync_keyword_triggers` |

**Scope lock por tipo de fix** (cada fix solo toca lo permitido):
| Tipo | Permitido | Prohibido |
|---|---|---|
| `bug-fix` | `examples.md`, `system.md` (sección del bug) | `program.json`, `keywords.json` |
| `tone` | `system.md` (solo Voz), `examples.md` | `program.json`, `keywords.json`, `resources.json` |
| `no-response` | `system.md` (solo NO_RESPONSE), `examples.md` | `program.json`, `keywords.json` |
| `flow` | `program.json`, `system.md` (solo Flujo) | `keywords.json`, `resources.json` |
| `resource` | `resources.json`, `knowledge_base.md` | `system.md` (salvo refs), `program.json` |

Si un fix necesita salir del scope → es cross-cutting, escalar. Después de 2-3 iteraciones sin resolver → reescribir, no parchear.

Loop típico: `cortex-create` → `cortex-analyze` → `cortex-iterate` (repetir) → `cortex-triggers` cuando haga falta.
