Volver a la lista

Blog

Dentro de la app complementaria para Mac: Arquitectura de un gestor de sesiones ligero

La app complementaria para Mac es la columna vertebral silenciosa de Tactic Remote: una utilidad en la barra de menú que gestiona sesiones tmux, ejecuta un servidor HTTP/WebSocket integrado y mantiene tu Mac despierto durante el trabajo activo con IA.

Publicado Etiquetas: architecture / mac / engineering
Blog

Sigue las novedades de producto e ingeniería de esta categoría.

Ver categoría

La app complementaria para Mac es la parte menos visible de Tactic Remote y, posiblemente, la más importante. Se ejecuta en tu barra de menú, gestiona cada sesión de Claude Code en tu máquina, sirve la API a la que se conecta tu iPhone, y se encarga del trabajo poco glamuroso de mantener las sesiones activas a través de los ciclos de suspensión y los cambios de red. Este artículo explica cómo funciona y por qué tomamos las decisiones arquitectónicas que tomamos.

Restricciones de diseño

Establecimos tres restricciones al inicio del desarrollo:

  1. La app debe ser imperceptible en reposo. Los desarrolladores ya ejecutan docenas de procesos en segundo plano. Un gestor de sesiones que consuma CPU o memoria de forma notable en reposo es inaceptable.
  2. Debe manejar múltiples sesiones concurrentes. Muchos desarrolladores ejecutan dos o tres tareas de Claude Code simultáneamente: una para una rama de funcionalidad, otra para pruebas, otra para documentación.
  3. Debe sobrevivir a la suspensión del Mac y los cambios de red con elegancia. Un desarrollador cierra su portátil, camina hasta una cafetería, lo abre de nuevo, y todo debe reconectarse en cuestión de segundos.

Estas restricciones dieron forma a cada capa de la arquitectura.

El modelo de servidor integrado

En lugar de ejecutar un proceso de servidor independiente, la app complementaria integra un servidor HTTP y WebSocket directamente. Esta fue una decisión deliberada sobre la alternativa de distribuir un daemon independiente.

¿Por qué integrado? Un daemon separado requiere su propia gestión de ciclo de vida: configuración de launchd, monitoreo de procesos, rotación de logs y recuperación de fallos. Cada uno de estos es un punto de fallo potencial y una carga de soporte. Al integrar el servidor en la propia app, el ciclo de vida es simple: cuando la app se ejecuta, el servidor se ejecuta. Cuando la app se cierra, todo se detiene de forma limpia.

El servidor integrado se vincula a un puerto configurable (por defecto 48736) ya sea solo en localhost (para modo de red local con una capa de proxy de red separada) o en todas las interfaces. Expone una API REST para la gestión de sesiones y consultas de estado, y un endpoint WebSocket para la transmisión en tiempo real del terminal y el envío de aprobaciones.

En reposo, el servidor mantiene los sockets abiertos pero no realiza trabajo. En nuestras pruebas de rendimiento, la app complementaria en reposo sin sesiones activas consume aproximadamente 18 MB de memoria y CPU insignificante, comparable a una utilidad típica de barra de menú.

tmux como columna vertebral de las sesiones

Cada sesión de Claude Code gestionada por Tactic Remote se ejecuta dentro de una sesión tmux. Elegimos tmux sobre alternativas como screen o la gestión directa de PTY por varias razones:

Persistencia de sesiones. Las sesiones tmux sobreviven de forma independiente al proceso que las creó. Si la app complementaria se bloquea y reinicia, las sesiones tmux y sus procesos de Claude Code continúan ejecutándose sin alteraciones. La app simplemente se reconecta a las sesiones existentes al arrancar.

Captura sin interferencia. El comando capture-pane de tmux nos permite leer el contenido del terminal sin inyectar entrada ni modificar el proceso en ejecución. Esto es crítico: necesitamos transmitir la salida del terminal al iPhone sin ningún riesgo de interrumpir la operación de Claude Code.

Sesiones con nombre. Cada sesión de Claude Code recibe un nombre de sesión tmux predecible (por ejemplo, claude-remote-0, claude-remote-1), lo que facilita enumerar, conectarse y gestionar sesiones de forma programática.

Maduro y universal. tmux viene incluido con macOS o es fácilmente instalable a través de Homebrew. Tiene décadas de estabilidad y maneja los casos extremos (redimensionamiento de terminal, Unicode, secuencias de escape) que de otro modo tendríamos que resolver nosotros mismos.

La app complementaria consulta las sesiones tmux activas a un intervalo configurable (por defecto 200ms) para capturar el contenido fresco del terminal. Cuando el contenido cambia, envía la diferencia por WebSocket a los clientes conectados. Este enfoque de sondeo es intencionalmente simple: evaluamos mecanismos de notificación basados en el sistema de archivos y los encontramos menos fiables entre versiones de macOS que el sondeo directo a este intervalo.

Gestión del ciclo de vida de las sesiones

Una sesión de Claude Code en Tactic Remote atraviesa un ciclo de vida definido:

  1. Creación — El iPhone envía una solicitud de "iniciar sesión". La app complementaria crea una nueva sesión tmux y lanza Claude Code dentro de ella con el directorio de trabajo y la descripción de tarea especificados.
  2. Monitoreo activo — El contenido del terminal se captura y transmite. Las solicitudes de aprobación del sistema de hook de Claude Code se interceptan y reenvían al iPhone vía WebSocket y notificación push.
  3. Detección de inactividad — Si una sesión no tiene cambios en la salida del terminal durante un período configurable, su frecuencia de sondeo disminuye para reducir el uso de recursos.
  4. Completado — Claude Code termina. La app complementaria detecta la salida, captura la salida final y notifica al iPhone.
  5. Limpieza — La sesión tmux se conserva durante un período de retención configurable (por defecto 30 minutos) para que los usuarios puedan revisar la salida, y luego se limpia automáticamente.

Gestionar múltiples sesiones concurrentes significa multiplexar estos ciclos de vida de forma independiente. Cada sesión tiene su propio bucle de captura, su propio canal WebSocket y su propia máquina de estados. La app complementaria actualmente soporta hasta 8 sesiones concurrentes, un límite basado en pruebas prácticas de recursos y no en restricciones arquitectónicas.

Prevención de la suspensión del Mac

Este fue uno de los desafíos de ingeniería menos obvios. macOS suspende agresivamente cuando detecta inactividad, y una tarea de Claude Code ejecutándose en tmux no genera el tipo de entrada de usuario que macOS reconoce como "actividad".

La app complementaria utiliza la API IOPMAssertionCreateWithName para mantener una aserción de energía siempre que al menos una sesión de Claude Code esté activa. El tipo de aserción es kIOPMAssertPreventUserIdleSystemSleep, que previene la suspensión del sistema mientras permite que la pantalla se apague. Cuando todas las sesiones se completan o terminan, la aserción se libera inmediatamente.

Consideramos usar caffeinate como proceso externo, pero lo rechazamos por la misma razón que rechazamos un daemon independiente: añade complejidad al ciclo de vida. La API de aserciones de IOKit nos da control preciso y se integra de forma limpia con el seguimiento de sesiones propio de la app.

Integración con la barra de menú

La app complementaria se presenta como un icono en la barra de menú con una interfaz desplegable mínima. El menú muestra:

  • Estado de conexión — Si el servidor está en ejecución, a qué puerto está vinculado y si un Cloudflare Tunnel está activo.
  • Sesiones activas — Un conteo de las sesiones de Claude Code en ejecución con indicadores de estado breves.
  • Acceso por código QR — Una opción de un clic para mostrar un código QR con la URL de conexión y el token de autenticación para el emparejamiento con el iPhone.
  • Acciones rápidas — Iniciar/detener servidor, abrir configuración, copiar URL de conexión.

Evitamos intencionalmente construir una interfaz completa con ventanas. El trabajo de la app complementaria es ser infraestructura, no un destino. Los desarrolladores interactúan con Tactic Remote a través de su iPhone; la app de Mac solo debería requerir atención durante la configuración inicial y cambios de configuración ocasionales.

Integración con Cloudflare Tunnel

Cuando el modo Cloudflare Tunnel está habilitado, la app complementaria gestiona el proceso cloudflared como proceso hijo. Inicia cloudflared con las credenciales de túnel configuradas, monitorea su salud mediante el endpoint de métricas local, y lo reinicia automáticamente si el túnel se cae.

La integración maneja un sutil problema de sincronización: el túnel debe estar completamente establecido antes de que el iPhone pueda conectarse a través de él. La app complementaria espera a que cloudflared reporte una conexión saludable (típicamente 1-3 segundos después del lanzamiento) antes de anunciar la URL del túnel como disponible. Durante esta ventana, el icono de la barra de menú muestra un estado distintivo de "conectando".

Si el proceso del túnel falla, la app complementaria intenta hasta 3 reinicios con retroceso exponencial (2s, 4s, 8s) antes de marcar el modo túnel como fallido y notificar a los clientes conectados que recurran al modo local si está disponible.

Autenticación

Cada solicitud de API y conexión WebSocket requiere un Bearer Token. El token se genera en el primer inicio y se almacena en el Llavero de macOS. Se incluye en el código QR mostrado durante la configuración, por lo que el emparejamiento es un solo escaneo en lugar de una copia manual del token.

El token es un valor aleatorio de 256 bits, generado usando SecRandomCopyBytes. Elegimos un modelo de token estático sobre autenticación basada en sesiones porque el modelo de amenaza principal es el acceso no autorizado a la red, no el control de acceso multiusuario. Para equipos que necesitan autenticación por usuario, recomendamos colocar Cloudflare Access delante del túnel.

Lo que consideramos y descartamos

Una app Electron completa. Solo la sobrecarga de memoria descalificó esta opción: la línea base de Electron es de 80-150 MB, aproximadamente 5-8 veces lo que nuestra app nativa en Swift usa en reposo.

Un daemon CLI independiente. Más simple de construir inicialmente, pero más difícil de gestionar para usuarios no técnicos e imposible de integrar con las convenciones de la barra de menú de macOS.

Gestión directa de PTY en lugar de tmux. Más control, pero habríamos tenido que reimplementar la persistencia de sesiones, los casos extremos de emulación de terminal y la gestión del ciclo de vida de procesos que tmux ya maneja de forma fiable.

Un LaunchAgent para el inicio automático. Lo implementamos pero lo hicimos opcional en lugar de predeterminado. Los desarrolladores tienen opiniones firmes sobre lo que se ejecuta al iniciar sesión, y respetamos eso.

Perfil de rendimiento

Bajo carga típica (2 sesiones activas, 1 iPhone conectado), el uso de recursos de la app complementaria:

MétricaValor
Memoria (RSS)22-35 MB
CPU (reposo, sin cambios de salida)< 0.1%
CPU (transmisión activa, 2 sesiones)1-3%
E/S de discoInsignificante (sin registro en disco por defecto)
Red (modo local, transmisión activa)10-50 KB/s

Estos números se midieron en un MacBook Pro M2 con macOS 14. El rendimiento en Macs Intel es comparable, con un uso de CPU ligeramente mayor durante la transmisión activa.

Mirando al futuro

La arquitectura de la app complementaria fue diseñada para soportar capacidades más allá de lo que hemos lanzado hoy. La capa de gestión de sesiones puede acomodar compartir sesiones (múltiples iPhones monitoreando la misma sesión), grabación de sesiones (captura del historial completo del terminal para reproducción), y una integración más rica con las APIs de extensibilidad de Claude Code a medida que evolucionen.

Para instrucciones de configuración, consulta Primeros pasos. Para la resolución de problemas de la app complementaria para Mac específicamente, consulta Problemas comunes.

Prueba Tactic Remote

Controla tus agentes de programación desde el móvil

Conéctate a Claude Code, Codex y otros agentes en tu Mac, Windows o Linux. Consulta el progreso y envía la siguiente instrucción desde el iPhone o iPad.