Cómo organicé mi viaje a Japón con un skill de Hermes Agent
Cómo organicé mi viaje a Japón con Hermes Agent: un skill con cron, Google Maps y Telegram que envía el itinerario por mail y evita repetir comidas.

Cómo organicé mi viaje a Japón con un skill de Hermes Agent
Me quedé una semana antes de volar a Japón sin saber qué iba a hacer durante 15 días. Llevaba un año con mucho trabajo y metido en IA agéntica con proyectos personales, y la logística se me había quedado sin tocar. Decidí organizar mi viaje a Japón con Hermes Agent: un skill que lee un markdown con vuelos y hoteles, y cada noche me manda por email un itinerario con sitios, transporte, restaurantes y presupuesto. Aquí documento el diseño, los cinco problemas reales que me encontré (Tabelog, comidas repetidas, restaurantes que rechazan turistas, excursiones improvisadas y fallos del cron) y cómo quedó resuelto cada uno.
Contenido
Cómo organizar un viaje a Japón con Hermes Agent: arquitectura del skill
Qué hacer cuando Tabelog limita las peticiones por IP
Por qué Tabelog te sugiere restaurantes que no aceptan turistas
Cómo evitar que el agente repita comidas en el itinerario
Cómo gestionar excursiones improvisadas en un skill de Hermes Agent sin emails duplicados
Cómo mantener actualizada la lista de comidas probadas
Errores del cron al enviar el itinerario por email
Cómo organizar un viaje a Japón con Hermes Agent: arquitectura del skill
Partí de una idea mínima, con tres piezas:
Una fuente de verdad en markdown con vuelos, hoteles, fechas y ubicación exacta. En la versión actual vive en
/Users/hermes-agent/.hermes/skills/viaje-japon/plan-viaje.md.Un skill (
japan-travel-guide) que cada día consulta ese archivo, averigua dónde estoy y qué ciudad toca, y compone el itinerario.Un cron que lo ejecuta y envía el resultado por email.
Como mis viajes son sobre todo gastronómicos, el itinerario tenía que incluir restaurantes. Mi criterio inicial: más de 4,5 de puntuación con más de 700 votos.
Para no cambiar de hotel cada dos días hicimos base en ciudades grandes y excursiones de un día desde ahí. La tabla de la versión 2.0.0 del skill quedó así:
Noches | Ciudad | Hotel |
|---|---|---|
12–16 sep | Tokio (Asakusa) | the b asakusa |
16–17 sep | Hakone | Hakone Onsen Yuyado Yamanoshou |
17–23 sep | Kioto | hotel tou nishinotoin kyoto |
23–26 sep | Tokio (Shibuya) | sequence MIYASHITA PARK |
El frontmatter del skill es corto: name, description, version y tags (travel, japan, cron). Todo lo importante está en el cuerpo, escrito como reglas numeradas y un workflow del cron. Esa decisión fue la más rentable del proyecto. Un agente que se ejecuta sin supervisión necesita reglas enumeradas, con casos límite y respuestas literales. Por ejemplo: si no hay día de viaje, responde exactamente NO-TRIP. Cada vez que algo fallaba, el arreglo era añadir o endurecer una regla, no tocar código.
Los primeros días funcionaron bien. En Asakusa, el itinerario me llevó a Sensō-ji, y la pagoda de cinco pisos iluminada de noche, con el remate dorado recortado contra un cielo gris oscuro, fue una de las mejores fotos del viaje. Pero pronto empezaron los problemas.
Qué hacer cuando Tabelog limita las peticiones por IP
Había leído que Tabelog es la app de restaurantes de referencia en Japón, así que le pedí al agente que consultara primero allí la puntuación y la contrastara con Google Maps.
El primer problema apareció enseguida: Tabelog limita las peticiones por IP. Un agente que verifica varios restaurantes por comida, tres comidas al día, genera mucho tráfico en poco tiempo. Y el cron lo ejecuta desde la misma IP cada noche.

Mis opciones eran dos: racionar las consultas a Tabelog o abandonarlo. Racionar tenía un coste oculto: si el agente se queda sin cuota a mitad de la verificación, produce datos a medias sin avisar. Todavía no había descubierto el segundo problema de Tabelog (el siguiente apartado), pero este ya me hacía dudar de la dependencia.
Al final tomé las dos decisiones juntas, y la regla 7 del skill quedó así:
Restaurants — Google Maps is the ONLY rating source. Rank by Google rating, prefer ≥ 4.5 with solid review count (4.3–4.5 acceptable if the specialty justifies it). Never fabricate a rating — "sin verificar" if not found. No Tabelog ratings.
Esa línea hace tres cosas. Elimina el bloqueo por IP. Relaja el umbral cuando la especialidad lo justifica (4,3–4,5), porque con 4,5 y 700 votos a secas dejaba fuera sitios excelentes de barrio. Y obliga a escribir «sin verificar» antes que inventar una nota. Con LLMs, esa última cláusula no es opcional.
La regla 8 protege la fuente nueva: nada de navegador en vivo contra Google Maps y un máximo de unas 15–20 consultas web de verificación por ejecución. Prefiero perder una verificación a quemar la fuente.
Por qué Tabelog te sugiere restaurantes que no aceptan turistas
Este fue el fallo más molesto en la práctica. Tabelog es tan local que el agente me proponía restaurantes muy caseros, donde el personal no estaba acostumbrado a tratar con extranjeros. Varias veces no nos quisieron recibir. Llegábamos con hambre, tras una caminata, a un local pequeño que ya tenía colas o reservas internas, o directamente sin idioma común, y nos despachaban en la puerta.
El problema es de sesgo de la fuente. Tabelog puntúa a los locales por los locales. Un 3,8 en Tabelog suele equivaler a un sitio excelente, pero eso no dice nada de si aceptará a dos turistas sin reserva. Cambiar a Google Maps mitigó ese sesgo, porque sus reseñas incluyen muchos visitantes extranjeros y la ficha suele indicar si hay menú en inglés o si hace falta reservar.
Además, cambié el formato: en lugar de un único restaurante por comida, el skill debe dar al menos tres opciones por comida (desayuno, almuerzo y cena), cerca de donde estoy en ese punto del itinerario y diferenciadas por estilo o precio (clásico, barato, moderno). Cada opción lleva nota de Google, número de reseñas, precio por persona, si necesita reserva y enlace de Maps. Si el primer sitio no nos recibe, tenemos dos alternativas a pocos minutos. En el desayuno pido exactamente dos opciones, preferiblemente cafeterías de especialidad con horario verificado.
El resultado se vio en el plato. En una comida cerca de Kioto me tocó un cuenco de pollo a la parrilla sobre arroz, con cebollino fresco, puerro asado y una montaña de nori en tiras, junto a una cerveza. Era un sitio fiable para extranjeros y con buena nota verificada, justo lo que buscaba.
Cómo evitar que el agente repita comidas en el itinerario
El segundo problema era más sutil. Ya había comido ramen el día anterior y el itinerario me lo volvía a sugerir. El agente no tenía memoria de lo que yo había comido, solo de lo que él había sugerido, y eso es muy distinto.
La solución fue convertir la comida en estado explícito. Creé references/comidas.md, una lista por ciudad de platos que quiero probar, con tres estados:
[ ]pendiente.[x]comido, con lugar y fecha.🔁 repetible: se puede volver a sugerir.
La regla 5 del skill dice que solo sugiere platos que yo he añadido a esa lista y nunca uno ya comido salvo que sea repetible. Por Telegram escribo «añade <comida> a <ciudad>» y el agente lo apende al archivo y lo confirma en una línea.

Añadí además una cláusula para el caso en que una ciudad se queda sin pendientes: no mandar un informe de comidas vacío, sino buscar las especialidades típicas locales de esa ciudad y orientar las opciones hacia ellas. Solo me pregunta por Telegram si ni así encuentra especialidades fiables. En ese caso el informe avisa de que las propuestas no vienen de comidas.md.
Con la misma lógica nació references/zonas.md: puedo pedir «zona <atracción> en <ciudad> el <fecha o libre>» y el skill la cubre primero. Si sobra material, elige lo esencial y deja el resto pendiente; si falta, completa con barrios cercanos. En Kioto, por ejemplo, el recorrido por Kiyomizu-dera salió de ahí. La imagen de dos mujeres en kimono subiendo la escalera hacia la pagoda roja bajo el pórtico de entrada es de ese día.
Cómo gestionar excursiones improvisadas en un skill de Hermes Agent sin emails duplicados
Este es el problema que más me costó razonar. Nuestras excursiones de un día las decidíamos la víspera en función del clima. El skill, en cambio, necesita anticipación y odia la sorpresa. Añadí una regla: cualquier día puedo mandar un mensaje por Telegram («voy a <ciudad> el <fecha>») y el skill ajusta el itinerario del día siguiente.
Al poco empezaron los síntomas: me llegaban dos emails para el mismo día, uno para la ciudad planeada y otro para la improvisada. Y la lista de comidas quedaba incoherente.
Seguí el rastro hasta dos causas:
Dos caminos de generación. El cron generaba el informe del día D con el estado que había a las 21:00. Si yo mandaba el aviso de excursión después, el manejador de Telegram también disparaba una generación. Eran dos ejecuciones independientes, sin saber una de la otra.
Estado que se acumulaba en vez de reemplazarse. El protocolo hacía
appendareferences/excursions.md. Si cambiaba de idea (Hakone por Nikko, por ejemplo), el archivo tenía dos líneas para la misma fecha y el skill no sabía cuál ganaba.
Lo resolví con cuatro cambios:
Una línea por fecha, con reemplazo. El protocolo sustituye la línea
<fecha> <ciudad>si ya existe, en lugar de añadir otra. El archivo pasa a ser una función fecha → ciudad.Un único emisor. El manejador de Telegram solo modifica estado y confirma («anotado: 20 sep, Nara»). No envía nada. Quien genera y manda informes es el flujo del cron, con las mismas reglas anti-duplicación.
Un informe por fecha objetivo. Antes de componer, el skill lee los informes anteriores en
~/.hermes/cron/output/. Si ya existe uno para esa fecha y la ciudad cambió, genera uno nuevo, lo marca como actualización y declara nulo el anterior en la primera línea. Si no cambió nada, no reenvía.Más margen. Pasé la generación a dos días antes (D-2) a las 21:00 JST, que equivalen a las 14:00 CEST en el cron. Así recibo el informe con un día entero de colchón para pedir cambios, y la actualización por clima pasa a ser una excepción en lugar de la norma.
En las excursiones el formato también cambió: el desayuno se hace en la ciudad de destino, cerca de la primera parada o de su estación principal, y las comidas van en tres listas separadas, nunca mezcladas: almuerzo en destino, cena en destino y cena cerca del hotel, por si volvemos tarde.
Cómo mantener actualizada la lista de comidas probadas
El efecto secundario de las excursiones era que comidas.md no se actualizaba bien. La causa era una suposición cómoda del agente: si lo había sugerido en un itinerario, lo daba por comido. Si luego cambiaba el plan, los platos de la ciudad cancelada quedaban marcados [x] sin haberlos tocado, y los de la ciudad real, sin marcar.
La corrección fue una regla tajante, la segunda mitad de la regla 5: nunca asumir que un plato se comió solo porque se sugirió. Solo mi informe explícito marca [x]. Por la noche escribo por Telegram qué comí y dónde, y el agente anota lugar y fecha y confirma brevemente. Es un pequeño ritual de un minuto, pero convierte el estado en algo fiable. El workflow del cron termina actualizando comidas.md y respondiendo con el informe completo también en el chat.
Errores del cron al enviar el itinerario por email
Aparte de los problemas de diseño, tuve fallos operativos que cualquiera que monte un agente con cron va a encontrarse:
Variables de entorno. El shell del cron no carga
~/.hermes/.env, así que el SMTP fallaba. La solución es ejecutarset -a; . ~/.hermes/.env; set +aantes de lanzarsend_report.py.Contraseña de aplicación de Gmail. Gmail rechaza las contraseñas de aplicación con espacios y devuelve
5.7.8 BadCredentials. Normalicé conpw = ''.join(pw.split()).Destinatarios.
send_report.pytenía comoTOsolo mi dirección y Arturo no recibía nada. Lo corregí y ahora verifico en la salida que aparezcasent -> [...]con ambos. Tras cualquier parche del script, reenvío si el primer envío salió incompleto.Markdown crudo. Yo no leo markdown, así que el script lo convierte a HTML (negritas, tablas, enlaces) y la regla prohíbe dejar
**o--visibles.Tipo de cambio. Un día el agente usó un valor recordado de 163 JPY/EUR cuando el real rondaba 181, y el total en euros salió mal. Ahora se consulta en vivo cada ejecución (
curl -sL https://api.frankfurter.dev/v1/latest?base=JPY&symbols=EUR) y el informe indica la fecha de la cotización.Cierres semanales. Los museos de Ueno cierran los lunes; quedó anotado como pitfall para ajustar la tarde.
Mirando atrás, lo que funcionó no fue un modelo más listo, sino tratar al agente como un sistema distribuido pequeño: un archivo por cada tipo de estado, un único emisor, reglas idempotentes y cero suposiciones sobre lo que «seguramente» pasó. Montar el skill en una semana me salvó el viaje, y cada fallo se convirtió en una regla escrita. Si vas a hacer algo parecido, empieza con las reglas 3, 5 y 7 de mi skill y deja que el viaje te vaya diciendo cuáles faltan.