# Runbook — the agent is not answering

> **Symptom.** *"el agente no responde los DMs"* · *"dejó de contestar"* · *"mi agente está activo
> pero no le llega ningún mensaje"* · *"the agent stopped replying"* · *"it answered yesterday and
> today it's silent"*. Work the hypotheses in order and stop at the first hit. The prompt is the
> **last** suspect here, not the first — in our own fix history, silence is almost always something
> muting the agent or never routing the message to it.

## What you need first

- The `agent_id` and the `influencer_id`.
- If the operator pointed at one conversation, its `conversation_id`.
- Whether it is **every** conversation or **one lead**. This splits the whole runbook:
  **all conversations → hypotheses 1–4 (routing).** **One lead → hypotheses 5–7 (per-contact mute).**

Run `diagnose_agent` first if you have not. It collapses several of the wiring checks below into
one call and returns ranked findings with the fix tool for each; the hypotheses here cover what it
cannot detect deterministically.

---

## All conversations are silent

### H1 — The agent is not linked to a connected account

**The most common cause of a brand-new agent that has never worked.** `is_active = true` is *not*
enough. Without the link, incoming DMs never reach the agent at all: the platform creates the
conversation with no agent attached and no trigger is ever evaluated. Nothing in the prompt or the
keywords can produce a reply, so every other check below is wasted time until this one is green.

- **Test:** `list_agent_channels` for the agent.
- **Stop condition:** empty result, or the agent's channels do not include the one the operator is
  DMing → **you're done** — but run `list_influencer_connected_accounts` before you say the account
  is unconnected. `list_agent_channels` only shows what is already routed to *this* agent, so an
  account connected to the workspace and assigned to a **different** agent returns empty here and
  looks identical from the operator's side.
- **Fix:** if the account is not connected at all, the operator links it in Studio → Agent settings →
  *Connected accounts*. This has to be done in the UI, not by tooling, because the same action also
  subscribes the Meta webhooks. If it is connected but assigned elsewhere, fix the assignment
  instead — telling them to link it again does nothing.

*Evidence: recurring across new-agent activations; a marketing-agency agent reached production with
`is_active = false` and zero trigger keywords, and no one noticed until a live/local reconciliation.*

### H2 — Trigger mode is keyword-only and nothing matched

An agent in keyword-only mode answers **only** messages containing a registered keyword. Every other
DM is correctly ignored, which the operator experiences as "it doesn't answer". Trigger mode is set
**per agent**, not per channel, so an account that behaves differently on WhatsApp and Instagram is
two agent records, not one misconfigured one.

- **Test:** `list_triggers` for the agent.
- **Stop condition:** mode is keyword-only and the lead's opening message contains none of the
  registered keywords → **you're done**.
- **Fix:** either register the keyword (`sync_keyword_triggers`) or switch the agent to answer all
  DMs. Ask which the operator wants — catch-all changes the volume they pay for.
- **Trap:** keywords are matched after accent normalization, but *not* across phrasings. A CTA of
  "Comenta YO" and a lead who writes "Yo.!!! Gracias" is a miss unless `yo` is registered.

*Evidence: a creator agent — the post CTA said "Comenta YO", `yo` was never registered as a keyword,
and the agent answered with a dead-end social reply instead of opening the conversation.*

### H3 — An external relay is answering first

If the account is served through ManyChat or a CRM relay, an automation outside Ninjo can reply
before the agent does. The agent sees a recent outbound message and stays quiet, and the operator
sees a conversation that "the bot abandoned".

- **Test:** read the conversation's messages and look at where each outbound came from.
  A message with **no** source attribution at all — not the agent, not a workflow, not a contact
  limit, not a human in Studio, not a WhatsApp template — came from outside the platform.
- **Stop condition:** an unattributed outbound sits seconds after the lead's inbound → **you're
  done**, ManyChat (or equivalent) captured the conversation.
- **Fix:** this is a routing decision, not a bug to patch in the prompt. Either the relay's
  automation is disabled for these leads, or the agent is not the owner of that flow. See
  `knowledge/messaging-channels.md`.

*Evidence: an agent served through ManyChat — a ManyChat welcome on new follows produced 72 captured
conversations in 14 days.*

### H4 — The CRM messaging channel is wired but not carrying traffic

Only for an agent served by a **GoHighLevel or Kommo messaging channel** (`source: "crm"` in
`list_agent_channels`). The channel exists, `list_agent_channels` shows it, and the operator sees it in the CRM —
and it still moves nothing, in one of two directions.

- **Test:** find the channel with `list_crm_connections` (the row whose `event_name` contains
  `.messaging.`) and ask which half is broken: does the operator see the lead's inbound
  **inside Ninjo**?
- **Stop condition A — nothing arrives at all, Kommo.** The account webhook never registered
  (`create_messaging_channel` returned `webhook_registered: false`), so Kommo does not know
  where to deliver. **Fix:** `register_messaging_channel_webhook(sync_connection_id)`. If it
  comes back false again the Kommo token is stale — the account needs a reconnect; say that
  instead of reporting the channel as live.
- **Stop condition B — inbound arrives, replies never send, GHL WhatsApp.** The
  `third_party_whatsapp` flag is wrong: GHL rejects official-WhatsApp sends for a number that
  reaches it through an external QR/app provider. **Fix:**
  `set_messaging_channel_whatsapp_provider(sync_connection_id, third_party)` — recreating the
  channel is not an option (one per type per account). Confirm with the returned `third_party`,
  not `channel`, which looks identical either way.
- Also check the channel is not simply **paused** (`enabled: false`) and that it has an
  `agent_id` — an unrouted channel receives messages and answers nothing (H1).

Full model in `knowledge/messaging-channels.md`.

---

## One lead is silent, others are fine

### H5 — A contact limit muted this contact

**Start here.** This is the single most common cause of per-lead silence in our fix history, and the
one most often misdiagnosed as a prompt problem. A contact limit that fires sets the conversation's
agent inactive, and that is **permanent** — the agent will not resume for that contact on its own,
even if whatever justified the limit stops being true.

- **Test:** `list_contact_limits` for the agent, then check whether this contact hit one.
- **Stop condition:** the contact has a limit event, or the conversation shows the agent inactive
  → **you're done**.
- **Fix:** depends on whether the limit was right. If it was, this is working as designed and the
  answer to the operator is "the lead converted / opted out, so the agent stopped". If it was not,
  the limit's gating property is mis-scoped — go to H6.
- **Two traps that cost us real time:**
  - Do not decide by limit *name*. "Blocked" means any limit event or an inactive agent on the
    conversation, not one specific limit. Counting by name undercounts badly.
  - A limit's `enabled` flag can contradict its own description. Read the condition, not the label.

*Evidence: the dominant cluster in the 52 documented no-response fixes, across a finance agent, a
marketing-agency agent, and a three-agent fleet sharing one property set.*

### H6 — The gating property is over-broad, so a normal lead got cut

A property that gates a contact limit is filled by an **external evaluator**, not by the
conversational agent. If the property's description covers more situations than the operator
intended, ordinary leads get permanently silenced and it looks like random failure.

Two failure shapes we have hit repeatedly:

- **A soft tier merged into a hard one.** A property meant for "clearly not a fit" that also lists
  polite refusals will mute leads who merely said "mejor no" — and because the property is
  check-until-match, they never come back.
- **A false positive on a converting lead.** An evaluator marked a paying lead as a bad fit and the
  contact limit silenced them.

- **Test:** `list_custom_properties`, then read the description of whichever property the limit
  gates on. Compare it against what the operator believes it means.
- **Stop condition:** the description covers the lead's actual behavior → **you're done**, the
  property is the bug, not the prompt.
- **Fix:** `upsert_custom_property` to narrow the description, and move the soft cases to a
  softer property that only pauses follow-ups. Never promise reversibility in the description: under
  check-until-match the value does not come back, and a contact-limit kill is permanent.

*Evidence: a three-agent fleet sharing one property set, 2026-08-05 — a `bad_fit` property absorbed
the soft-rejection tier; two independent stratified samples put 25 of 125 sampled cuts (20%) outside
the property's own description, and 589 of 592 affected conversations were still latched TRUE. A
separate 2026-07-23 case on a finance agent silenced a paying depositor.*

### H7 — A manual-outbound pause is holding the agent

The platform can pause the agent after a human replies in Studio. If the setting does not ignore the
very first message, **any** automated welcome — including one from a relay — pauses the agent before
it has said anything, and the lead who answers two minutes later gets silence.

- **Test:** read the agent's pause configuration and the conversation's pause fields.
- **Stop condition:** a pause is active, or the pause-after-human-reply setting does not ignore the
  first message → **you're done**.
- **Fix:** set it to ignore the first message. Check this against the fleet default before
  arguing about it.

*Evidence: an agent served through ManyChat, 2026-07-02 — pause-after-manual-outbound with the first
message **not** ignored, against a fleet where 516 of 541 agents ignore it. The ManyChat welcome
paused the agent 60 minutes before its own first message.*

---

## Only now: the agent chose to stay silent

### H8 — The prompt's silence rules fired on a valid message

If routing is clean and nothing muted the contact, the agent genuinely decided not to answer. This
is real, and it has a specific shape: prompts with many silence rules and examples that end in the
stop token build a strong prior toward silence, and **smaller models act on that prior more
aggressively than the model you tested with**. A short valid reply ("ambas") can trip it while a
near-identical one ("las dos") does not — it is model judgment, not a deterministic gate, so it will
look random.

- **Test:** re-run the exact deployed prompt against the same message on the agent's **configured**
  model, not your local default.
- **Stop condition:** it reproduces on the configured model and not on a larger one → **you're
  done**, this is the silence prior, not a rule bug.
- **Fix:** reduce the number of silence rules and stop ending examples on the stop token; or move
  the agent to a stronger model. Adding another rule makes it worse.

*Evidence: a coaching agent, 2026-06-16, validated — identical deployed prompt, one model answered
and the smaller one went silent on a valid short reply.*

### H9 — A hard rule created a dead end

A rule that tells the agent it cannot do something the platform actually does for it will stop the
conversation dead. The classic is a rule denying a capability the platform already injected.

- **Test:** grep the deployed prompt for a prohibition covering what the lead just did.
- **Fix:** remove the rule. Do not add a counter-rule.

*Evidence: a pet-care agent — a rule saying "I can't receive images" contradicted the platform's own
image interpretation, which the agent had already been given. A mentoring agent — a
send-once-then-stop invariant silenced the agent right when the lead confirmed a booking.*

---

## When to stop

If all nine come back clean, stop and file it. Put in `report_feedback`: the `conversation_id`, the
`agent_id`, which hypotheses you ruled out and how, and the inbound message that got no reply. That
is enough for the dev team to reproduce; a description without the ids is not.
