Tu alerta se fue al chat de grupo: ahora una pregunta de seguimiento ahí sí funciona

Son las dos de la mañana, el pipeline de despliegue se pone en rojo y el webhook empuja la alerta a tu Telegram. La ves a la mañana siguiente, respondes «¿es el mismo fallo de ayer?» y recibes un «¿a quién te refieres?». Se lo acabas de decir. No tiene ni idea. No es que el modelo vaya lento: es cómo están diseñadas las entregas de webhook: cada evento se ejecuta de principio a fin en su propia session, y el mensaje que se entregó no existe en absoluto en el transcript del chat de destino. Este artículo va del interruptor añadido en la v0.21.5, y de lo que arregla y lo que no.
De dónde viene esta brecha
Una ruta de webhook funciona así: un servicio externo hace POST de un evento, el payload se renderiza en un prompt mediante tu plantilla, se ejecuta una corrida del agent y el resultado se entrega a un chat. Durante todo eso, la session webhook:<route>:<delivery_id> es efímera: existe para esa corrida y desaparece después.
Cuando hablas con el agent en ese chat de Telegram, carga la session de ese chat. Los dos transcripts nunca se cruzan, así que:
- no sabe que acaba de enviarte una alerta;
- el «fallo de ayer» no apunta a nada en su historial;
- tu única opción es repetir la alerta o renunciar a la pregunta de seguimiento.
Activar el interruptor
Ahora una ruta puede activar el espejo. Añade un flag donde creas o actualizas la suscripción:
hermes webhook subscribe ci-alerts \
--prompt "build {route} failed: {payload}" \
--deliver telegram \
--mirror-to-session
La salida de subscribe gana una línea:
Created webhook subscription: ci-alerts
URL: https://…/webhooks/ci-alerts
Profile: default
Events: (all)
Deliver: telegram
Replies: each delivery is mirrored into the target chat's session
Las rutas existentes se pueden cambiar desde la configuración:
platforms:
webhook:
extra:
routes:
ci-alerts:
prompt: "build {route} failed: {payload}"
deliver: telegram
mirror_to_session: true
La comprobación del valor es deliberadamente estricta: solo un booleano true real lo activa. La cadena "true" no sirve: el espejo escribe en tu historial de conversación, así que el autor de la ruta tiene que pedirlo explícitamente.
Qué se espeja exactamente
No la petición HTTP en bruto. El mensaje entregado, con una etiqueta:
[Webhook delivery: ci-alerts]
build 4821 failed on main
Se añade a la session del chat de destino como turno de usuario con autoridad de usuario, por la misma ruta de código y la misma convención de rol que los briefs de cron continuables. ¿Por qué usuario y no asistente? Porque ese texto no es el agent de la session de destino hablando: eres tú a quien han informado de algo. Guardado como turno de asistente produciría asistente → asistente en el historial, lo que rompe de plano a los providers de alternancia estricta; guardado como usuario, dos turnos de usuario consecutivos se fusionan sin problema y la forma de la conversación sobrevive.
La ganancia: responde «¿es el mismo fallo de ayer?» y el agent puede ver la etiqueta y el texto entregado en su propio historial, así que por fin puede contestar.
Tres límites que conviene conocer antes de activarlo
1. Es opt-in, desactivado por defecto
Las rutas sin mirror_to_session se comportan exactamente igual que antes. La misma idea que el mirror_delivery de cron: no se escribe nada en una session si no se pide.
2. Está acotado por profile
El espejo corre dentro de _profile_scope(profile), así que escribe en el state.db del profile al que pertenece la ruta. El detalle no es cosmético: el chat_id de un DM de Telegram es el id de usuario de la misma persona en todos los bots, así que un espejo sin acotar acabaría en el DM de otro profile con esa misma persona. Conviene tenerlo presente sobre todo en rutas /p/<profile>/.
3. Es best-effort
Un fallo del espejo nunca hace fallar una entrega (la implementación nunca lanza excepciones), y el espejo se omite cuando el chat de destino todavía no tiene session de gateway (nadie ha hablado con el agent ahí). Dicho de otro modo: activar el interruptor no garantiza que puedas hacer seguimiento. Si el grupo nunca ha tenido una conversación con el agent, no hay session donde escribir.
Cuándo no activarlo
Ten cuidado con las rutas deliver_only. Esas se saltan al agent por completo (la plantilla renderizada es el cuerpo literal del mensaje, con coste cero de tokens), así que el texto espejado es el payload renderizado en bruto.
Por tanto, todo lo que pueda influir en ese texto acaba en tu historial de conversación como turno de usuario:
- el webhook de un rastreador de incidencias público: lo que un desconocido escriba en el título de una incidencia puede aparecer en tu historial de conversación;
- cuerpos de alerta en bruto de un sistema de monitorización: sí, si llevan campos que controla el usuario (mensajes de error, rutas de petición, cadenas de user-agent).
La regla es sencilla: espeja solo fuentes cuyo contenido te merezca confianza — tus propios servicios, tus propios pipelines. Con cualquier cosa en la que pueda escribir un tercero, piénsalo dos veces antes de activarlo.
Confirmar que ha surtido efecto
hermes webhook list
La lista muestra los detalles de cada ruta, y crear o actualizar una suscripción imprime la línea Replies: each delivery is mirrored into the target chat's session.
Para comprobar el comportamiento real, la prueba más barata es enviar un evento hermes webhook test a la ruta y luego preguntar en el chat de destino «¿qué me acabas de enviar?»: si responde en lugar de preguntar a qué te refieres, el espejo funciona.
Cómo encaja con el resto
Esto y el ledger de entrega de respuestas duplicadas son dos caras del mismo problema a nivel de gateway: aquel evita que una respuesta se entregue dos veces, y este evita que una entrega sea invisible para la conversación después.
Si ya lanzas comprobaciones periódicas con cron, los briefs de cron llevan tiempo con el flag equivalente mirror_delivery. Las entregas de webhook ahora lo igualan, y ambas rutas usan la misma convención (una etiqueta más un turno de usuario), así que el seguimiento se siente idéntico en los dos casos.
La versión corta
- El comportamiento por defecto no cambia: las entregas de webhook son fire-and-forget y el chat de destino no sabe nada.
- Con
mirror_to_session: true(o--mirror-to-session), el contenido entregado se escribe en la session del chat de destino como[Webhook delivery: <route>]más un turno de usuario. - Tres límites: desactivado por defecto, acotado por profile, best-effort (se omite cuando no hay session).
- En rutas
deliver_onlyel texto espejado es el payload en bruto: actívalo solo para fuentes que te merezcan confianza.
Esto llegó en la v0.21.5 (tag v2026.9.24), dentro de esa ventana de patch sin notas de versión; para las demás cosas que ya se pueden usar de ella, consulta la guía de autocomprobación de la ventana v0.21.5 y nuestra página del release v0.21.5. La tabla completa de flags está en la referencia de comandos.