- Subrayado a trazos: decisión de diseño mía, redactada en conjunto.
- Nota al margen: argumento o hallazgo que puso el modelo, no yo.
Estado: funcionando · licencia MIT · solo macOS Repositorio: github.com/tomasgauthier/latido
Un agente que despierta solo, revisa si pasó algo que valga la pena, y escribe por Telegram cuando sí. En la enorme mayoría de los latidos se calla, que es el punto: uno que dice “todo bien” cada rato se silencia en dos días.
1. El hueco que llena
Los agentes de código que uso a diario tienen una limitación que no se nota hasta que se busca: todos esperan a que uno los llame. La sesión es la unidad. Escribes, responde, cierras la terminal, y deja de existir.
Eso alcanza para trabajar. No alcanza para lo otro: que algo te avise de una cosa que no le preguntaste, cuando no estás mirando. Un lunes a las ocho de la mañana, con el computador cerrado, porque una propuesta lleva nueve días parada en la bandeja.
Existen mecanismos que se parecen y ninguno sirve. Los cron dentro de la sesión viven en memoria y mueren con ella. Los agentes programados en la nube no ven tu disco. Y los controles remotos de sesión —que te conectan desde el teléfono a algo que ya está corriendo— resuelven la dirección contraria: te acercan a tu sesión, pero nunca te van a escribir ellos.
Los tres comparten el mismo defecto: dependen de que algo siga vivo. Y la sesión abierta es precisamente lo que no vas a tener el sábado en la mañana.
2. La idea
Que no dependa de ninguna sesión en particular. Ese fue el requisito, y de él sale todo lo demás.
launchd despierta un CLI de agente en su modo no interactivo. Cada latido es una sesión nueva que vive treinta segundos y muere. La continuidad no está en la sesión: está en un archivo de texto que el propio agente lee al despertar y reescribe antes de dormirse.
Puedes cerrar todo y sigue latiendo. El contexto que uno creería perder no se pierde, porque las fuentes son durables y viven en el disco — la bóveda de notas, la bandeja de entrada, el diario. Un agente en la nube no puede leer nada de eso. Este sí.
3. La regla que lo hace tolerable
Si no hay nada que decir, no dice nada.
Es la primera línea de sus instrucciones, y no es una preferencia estética. Un latido que reporta “todo bien” cada cuatro horas se silencia en dos días, y ahí muere el proyecto: la única falla que mata a un asistente proactivo es volverse ruido.
Para hablar tienen que cumplirse las tres condiciones a la vez:
- Es algo que querrías saber ahora, no cuando te sientes al computador.
- Todavía no lo sabes — no está en su memoria como ya avisado.
- Hay algo que hacer con eso. Un dato que no cambia ninguna decisión no es noticia, es ruido.
Y una regla de exclusión que evita la duplicación obvia: nada que ya se vea de un vistazo en la barra de menú. Servicios caídos, memoria, respaldos, porcentajes de uso. Para eso existen los monitores. Su trabajo es lo que requiere criterio, no lo que requiere un número.
4. Escuchar es gratis, pensar es lo que cuesta
La primera versión no escuchaba. El agente pasaba a buscar los mensajes al despertar, así que la latencia de una respuesta era la cadencia entera: con cuatro horas, una pregunta podía quedarse cuatro horas esperando.
La salida estaba en separar dos cosas que veníamos tratando como una sola. Un proceso en long polling no consume nada: es un socket abierto esperando. Lo caro es despertar al modelo.
Quedaron tres agentes de launchd:
| Agente | Qué es | Cómo vive |
|---|---|---|
| la oreja | Espera un mensaje tuyo y dispara un latido al instante | KeepAlive — si se cae, revive |
| el reloj | Mira por su cuenta cada N horas | StartInterval |
| la página | La configuración en localhost | KeepAlive |
Con eso responde al toque cuando le escribes, y conserva su ronda propia para avisarte de cosas sin que preguntes. La oreja no es una sesión de agente viva esperando: es un while True de treinta líneas.
La oreja es además el único que lee de Telegram. Dos consumidores de la misma cola se roban los mensajes entre ellos —un error que cometimos y que costó un rato entender—, así que deja lo que llega en un archivo y el latido lo lee de ahí.
5. Que no se muera en silencio
Acá hay una trampa que el diseño se pone a sí mismo.
Si el silencio es su resultado normal, un latido roto se ve exactamente igual que uno callado. Si mañana vence la sesión del CLI, o cambia una ruta, o el binario se mueve, el agente falla en silencio y no notas nada — porque no notar nada es justo lo que esperas de él. Lo podrías descubrir en tres semanas.
Por eso escribe un archivo de marca al terminar, pero solo si el latido salió bien. Si midiera ejecución en vez de éxito, uno que corre y falla cada vez se vería sano. Cualquier vigilante de frescura apuntado a ese archivo —en mi caso el menú de la barra que ya tenía— avisa cuando el pulso se detiene.
Queda una división limpia entre los dos: el monitor vigila al latido, el latido vigila tu vida. Cada uno hace lo que el otro tiene prohibido.
6. Cualquier CLI
La herramienta no debía quedar amarrada a un proveedor. La invocación vive en la configuración, no en el código:
"cli": {
"bin": "claude",
"args": ["-p", "{prompt}", "--model", "{modelo}",
"--permission-mode", "acceptEdits",
"--allowedTools", "{herramientas}"],
"flag_carpeta": "--add-dir"
}
Sirve cualquier CLI que reciba un prompt como argumento y devuelva texto por stdout. La página detecta cuáles tienes instalados y trae presets para los conocidos, con las banderas leídas de la ayuda de cada uno. Donde no se probó a fondo, la tarjeta lo dice: un preset que promete funcionar y no funciona es peor que ninguno.
Lo único innegociable del otro lado es que el modelo pueda escribir un archivo. Ese archivo es su única voz: si decide hablar lo escribe ahí, y el runner lo envía.
Sobre los términos de servicio
Es una pregunta legítima cuando algo corre desatendido con tu sesión, y merece una respuesta que se pueda sostener.
El latido no le pide nada especial a nadie: ejecuta el CLI oficial de tu proveedor, en su modo no interactivo, con tu propia sesión ya iniciada. No extrae credenciales, no las reenvía, no monta un proxy, no automatiza una interfaz y no raspa nada. Es exactamente lo que escribirías tú en la terminal — solo que lo escribe un temporizador.
Lo que el repositorio no afirma, a propósito, es que cumpla los términos de todos los proveedores: cada uno tiene los suyos y cambian. Prometer eso en un README es sostener algo que no se puede verificar. Leerlos es responsabilidad de quien lo corra.
7. El registro
Lo que el agente escribe sobre tu vida no va en el repositorio: va donde tú lees. En mi caso, la bóveda de Obsidian, como cualquier otra nota.
Son dos archivos. Una bitácora, con un archivo por día y una línea por latido —qué dijo, o por qué se calló—, y su memoria de trabajo entre un latido y otro. Esa bitácora es lo que hace calibrable la cosa: lees por qué se calló y ajustas las instrucciones hasta que hable cuando debe.
Mantenerlos fuera del repositorio no es solo comodidad. La memoria entra completa en el contexto de cada latido, y la bitácora es un diario: publicar el código junto con el registro de los miércoles de alguien es un accidente esperando pasar.
8. Qué no es
No es un asistente conversacional. Es un tiro corto, con una lista contada de herramientas y un sesgo fuerte hacia el silencio.
Para preguntar algo con una sesión abierta, un control remoto es mejor en todo: potencia completa, conversación de verdad, cualquier repositorio.
| Un control remoto | Un latido | |
|---|---|---|
| Quién empieza | Tú, siempre | Él también |
| Necesita una sesión viva | Sí | No: la crea |
| Alcance | Total, conversacional | Un tiro corto, herramientas contadas |
| Cuando no hay nada abierto | No hay a qué conectarse | Sigue funcionando |
La diferencia entera cabe en la primera fila. Un control remoto no tiene iniciativa. Es un foso angosto, pero es el único que un control remoto no puede cruzar.
9. Instalación
git clone https://github.com/tomasgauthier/latido && cd latido
cp config.example.json config.json && chmod 600 config.json
./servidor.py --instalar
open http://127.0.0.1:8737
Todo lo demás se configura desde esa página: el bot de Telegram, las carpetas que mira —con un explorador, sin escribir rutas—, el motor, el ritmo y las instrucciones. Que cualquiera pueda clonarlo y configurarlo sin tocar un archivo era parte del encargo, así que no hay rutas escritas a mano en ninguna parte: los agentes de launchd se generan con la ubicación del repositorio que acabas de clonar.
El servidor escucha solo en 127.0.0.1. Edita archivos y carga agentes del sistema; no tiene nada que hacer en la red.
Fuentes y referencias
El repositorio
- github.com/tomasgauthier/latido — código, README en español e inglés, y las instrucciones del agente, que son un archivo de texto editable.
- README en inglés — misma documentación, para quien clone desde fuera.
Las piezas del sistema operativo
launchdy los agentes de usuario — Apple. De dónde salenStartIntervalyKeepAlive, las dos formas de vida que usan el reloj y la oreja.
Telegram
- Bot API —
getUpdates— Telegram. El long polling, el offset que confirma los mensajes leídos, y las 24 horas que guarda los pendientes: es lo que permite que no haga falta un proceso escuchando siempre.
La convención de autoría de esta página
- weid — la convención con que está marcado este texto.