Ir al contenido

La búsqueda solo está disponible en las versiones de producción. Intenta construir y previsualizar el sitio para probarlo localmente.

Runtimes y bridge

La página de Runtimes (/space/runtimes) documenta dónde se ejecutan realmente tus agentes: el daemon bridge local/móvil para trabajo en el dispositivo, dispositivos emparejados, sandboxes cloud en Cloudflare Workers y los próximos runtimes Docker/SSH.

¿Nuevo en el lado de IA? Lee primero Conceptos de IA desde cero — los agentes necesitan herramientas para actuar, y esta página trata de dónde y cómo corren esas herramientas de forma segura.

Los agentes actúan mediante herramientas, y las herramientas necesitan algún sitio donde ejecutarse. Esta página lo divide en dos riesgos que la plataforma debe resolver:

  • ¿Dónde puede actuar el agente? El bridge ejecuta acciones en tu máquina (archivos, shell, procesos, red) o en tu teléfono (notificaciones, SMS, contactos); los sandboxes cloud ejecutan código en jails Docker efímeras en nuestra nube privada; los runtimes Docker/SSH apuntarán a contenedores y máquinas remotas. Cada una es una superficie de ejecución distinta.
  • ¿Cómo lo mantenemos seguro? Un modelo autónomo llamando a desktop.shell.execute es genuinamente peligroso, así que cada capacidad está explícitamente acotada y sujeta a consentimiento (consent_prompt/consent_response), auditada en device_action_log, y protegida por un filtro de comandos destructivos. La conexión es WebSocket solo saliente, así que ni siquiera el propio daemon acepta tráfico entrante.

El bridge es también donde vive físicamente la promesa local-first: sync.chat.push refleja tu historial en el SQLite del dispositivo, así que los datos existen en tu hardware, no solo en un índice cloud.

El daemon bridge (conector de entorno local)

Sección titulada «El daemon bridge (conector de entorno local)»

El SynthHires Bridge es un daemon ultraligero de escritorio/móvil que permite a los agentes ejecutar tareas y comandos en tu propia máquina — sin subir tus archivos a la nube y sin abrir puertos en tu router.

  • Plataformas: Windows (exe), macOS (universal), Linux (binario suelto, .deb, .rpm, .AppImage vía /api/downloads/desktop), NixOS (flake — jamás el binario suelto). CI no publica hoy APK de Android, iOS ni .dmg.
  • Modelo de conexión: 100% WebSocket solo saliente a la plataforma (sin puertos de entrada), TLS 1.3.
  • Seguridad: filtro de comandos destructivos, acciones con consentimiento, log de auditoría. El emparejamiento exige sesión real (no invitada): POST /api/devices/pair/start devuelve {code, pairingId, backend} con TTL 5 min; el daemon lo canjea en POST /api/devices/pair/complete por {deviceId, token, wsUrl} (token 30 días, solo hash en DB).
  • Sondeo de estado: la página consulta el endpoint HTTP de estado del propio daemon (http://127.0.0.1:7333/status) — la fuente autoritativa de paired, WebSocket connected, RTT, version y la identidad os del host (distribución exacta desde /etc/os-release: NixOS vs Ubuntu vs Fedora, kernel, arquitectura, libc) — y cae a /api/devices cuando el daemon no es alcanzable desde el navegador (p. ej. móvil o producción).
  • Estados: comprobando → esperando emparejamiento → reconectando → conectado (con RTT) → offline / requiere login.

El lado web del protocolo vive en src/lib/agent/bridge-protocol.ts (versión 1), con espejo serde-camelCase en el daemon Rust. Los frames son JSON sobre WebSocket:

Frame Propósito
hello / hello_ack Handshake con versión de protocolo + capacidades
heartbeat / heartbeat_ack Liveness con medición de RTT
scope_update Cambios de ámbito de capacidades (grant/revoke)
resume Reconexión con reanudación de sesión
action_request / action_cancel Solicitar o cancelar una acción de dispositivo
consent_prompt / consent_response Diálogo de consentimiento humano-en-el-bucle
action_result / action_stream Finalización de acción (o salida en streaming)
revoke Revocación de dispositivo/ámbito
error Errores de protocolo con close codes

El protocolo expone capacidades limitadas (BRIDGE_CAPABILITIES), cada una requiere consentimiento explícito:

  • Escritorio: desktop.shell.execute, desktop.fs.read / write / delete / verify / list / watch, desktop.process.list / kill, desktop.network.fetch.
  • Sync: sync.chat.push (espejo de conversaciones en el SQLite del dispositivo).
  • Móvil: mobile.notifications.read / dismiss, mobile.sms.read / send, mobile.contacts.read, mobile.location.read, mobile.automation.perform, mobile.clipboard.read / write.

El emparejamiento ocurre vía /space/connections (pestaña Dispositivos) o la página de runtimes: descarga el bridge, ejecútalo, y usa “Emparejar nuevo” con un código de emparejamiento (tabla pairing_codes). Los dispositivos emparejados aparecen en DeviceList, con estado online/offline, RTT y un log de auditoría de las acciones consentidas (device_action_log).

Emparejar EXIGE sesión real con login — las sesiones invitadas reciben 403 login_required y jamás pueden poseer dispositivos. Los tramos /api/devices/pair/complete + /api/devices/ws no llevan cookie de usuario por diseño (el daemon aún no tiene sesión): se autentican con el código de un solo uso y con el token de dispositivo resultante, respectivamente.

  • Misma máquina (local): el exe con GUI (o synthhires-bridge run) más el auto-pair del navegador contra http://127.0.0.1:7333/status|/pair y el deep link synthhires://pair?code=…&pairing_id=…&backend=… lo resuelven todo. Abre /space/connections → copia el código → el daemon local se empareja solo.
  • Máquina remota / terminal headless / VM (otro host): 127.0.0.1:7333 NO es alcanzable desde tu navegador, así que el auto-pair no funciona. Copia el CODE de Connections, anota el backend que muestra la web (prod vs staging vs dev local), y en el host remoto ejecuta: synthhires-bridge pair "<BACKEND>" <CODE> [--pairing-id <ID>] y después synthhires-bridge run (o la unidad systemd de install.sh). La conexión posterior es WebSocket 100% saliente — sin puertos de entrada. Revoca desde la web (Revoke) o en local (synthhires-bridge unpair).
Host Cómo
Windows Descarga el .exe del release edge y ejecútalo (GUI + tray). El mismo binario corre headless: synthhires-bridge --headless run.
macOS / Linux genérico Descarga el asset de tu arch desde /api/downloads/desktop (o el release edge), chmod +x, luego pair + run. curl …/install.sh | bash -s -- --backend <URL> --systemd lo automatiza incl. unidad systemd de usuario.
NixOS NO uses el binario suelto (no arranca: FHS + libs dinámicas). Vía experta con el flake del repo bridge: nix run github:planobinario/synth-hires-bridge, o declarativo con services.synth-hires-bridge.enable = true; (servicio systemd de usuario que corre synth-hires-bridge run). CI compila el artefacto synthhires-bridge-nixos.
Docker / VM Imagen headless (ENTRYPOINT ["synthhires-bridge"]): pair/run vía docker exec, HOME=/data persiste state.json.

DeviceList muestra cada dispositivo emparejado con estado de conexión y te permite revocar el acceso en cualquier momento. Los registros de dispositivo viven en devices + device_tokens; revocar mata el token y cierra el WebSocket.

La pestaña de Sandboxes gestiona sandboxes Docker efímeros en nuestra nube privada (sandboxd, alcanzado en dev por el túnel SSH -L 8789; SANDBOX_* en .dev.vars, token solo en servidor):

  • Un contenedor por sandbox, creado bajo demanda (~300ms con caché caliente), destruido al expirar el TTL, con DELETE explícito o al parar el daemon.
  • Imágenes: python/node stock (solo stdlib) + ds (numpy, pandas, matplotlib, openpyxl, chromium, playwright — build único en el host, sin registry). ds pide 1 GB de RAM (chromium no cabe en 512 MB).
  • Endurecido: --cap-drop=ALL, no-new-privileges, rootfs read-only, tmpfs /scratch (único RW, 256MB) + /tmp noexec, pids-limit (64, 256 para ds — los hilos de Chromium abortan por debajo de ~100), caps de memoria+swap y CPU (default 512MB/0.5 CPU, máx 1GB/1 CPU), usuario nobody, reaper --init, network=none por defecto, log-driver none.
  • Egress allowlist: gestores (pip, npm, cargo, go, apt) y hosts explícitos de una lista curada de registros (pypi, npmjs, crates, golang, ubuntu, github, huggingface) suben la caja sticky una vez de none a weblist (bridge + proxy de egress con filtrado y auditoría por flujo en el daemon), con migración automática de /scratch (fail-closed tras 100 ficheros / 8 MiB — las cajas grandes rechazan el upgrade en vez de perder datos). Cualquier otro host, IP literal o secreto en un comando con red se deniega antes de tocar una caja; cada decisión weblist queda auditada. Un nftables en sandboxd sería más estricto que el proxy, pero el daemon deliberadamente no tiene net_admin: proxy + pre-chequeo del harness es la frontera diseñada, tal cual.
  • Las recetas skill viajan dentro del jail (/mnt/skills/cloudshell, solo lectura): análisis de datos, documentos office, patrones de navegador.
  • Entregables: el dispatcher recoge *.png de /scratch/outputs (máx 3, ≤400 KB) y los muestra al usuario; el modelo recibe nombres y tamaños. Los no-imagen (PDF, office, csv) viajan inline si son pequeños (descarga + preview) y se traen bajo demanda (GET /api/sandbox/outputs) si son grandes — un fichero generado nunca queda varado en el jail. Los entregables inline además persisten en IndexedDB local al completarse, así que descargas y previews sobreviven recargas y expiraciones del jail sin tocar el servidor.
  • Exec solo-argv (sin shell-string), cwd enjaulado a /scratch (estricto: exactamente /scratch o hijo real, sin ..), timeouts 1–300s con timeout(1) KILL, caps de salida 256 KiB/stream, sweep post-exec (corre como nobody — nada en el contenedor corre jamás como uid 0).
  • Multi-tenant: pool global máx 6 sandboxes / 4 execs concurrentes, cuota por owner máx 2 (X-Sandbox-User = userId web, enforced en el servidor y anunciada vía /v1/health), propiedad exec/delete enforced (403 ante mismatch), Bearer único fail-closed.
  • Vía prod: Workers llegan a sandboxd por TLS en https://sandbox.synthhires.com con un Cloudflare Tunnel DEDICADO (cloudflared-sandbox en el host: solo-salida, sin puertos abiertos, sin proxy compartido, sin LE que renovar — el sandbox no comparte nada con los demás propósitos del host) con SANDBOX_* como secrets del Worker — el túnel SSH local -L 8789 es solo-dev y no existe en el edge. Rota el Bearer con node scripts/sync-sandbox-token.mjs.
  • Postura del edge para el hostname API: las heurísticas anti-bot de Cloudflare (Bot Fight Mode, Browser Integrity Check) deben estar OFF para sandbox.* — devuelven 1010 a clientes máquina (incluidos subrequests de Workers). El sitio público se defiende con Turnstile en los formularios.
  • Sticky por conversación: con (owner, conversationId) el jail se REUSA entre turnos (/scratch sobrevive dentro del TTL de 600 s) vía el hint de afinidad cloud_sandbox_sessions — misses y 404/410 recrean. Sin conversación, cada llamada es efímera.
  • Adjuntos del chat: se depositan solos en /scratch/uploads (solo lectura) tras cada envío con ficheros (POST /api/sandbox/attachments, idempotente por nombre, 10 ficheros / 1 MiB cada uno / 6 MiB total). El modelo lista ese directorio antes de asumir que no llegó nada.
  • Honestidad UX: los pasos del chat muestran badge “nube efímera” (jamás confundible con tu PC), tarjetas de error estructuradas con Reintentar sin estado para fallos transitorios, y Runtimes → Sandboxes muestra el pool vivo con Liberar por sesión. El texto “ejecutando” es neutral hasta que llega el resultado.
  • Runtime explícito: cloud_shell apunta al jail cloud A PROPÓSITO (el usuario pide nube/VM, código no confiable, o no hay Desktop pareado) — con o sin bridge pareado. bridge_desktop_shell sigue siendo la tool del PC (su fallback silencioso queda solo como red de seguridad).
  • Los sandboxes son el objetivo de ejecución remota por defecto cuando no hay un bridge local emparejado. En prod (Cloudflare Workers) hace falta exponer sandboxd por TLS (p. ej. cloudflared) + secrets SANDBOX_* — el túnel local no existe en el edge; hasta entonces la rama cloud es dev-only.

La idea de ventana de contexto también aparece aquí: el código de los agentes corre en un runtime aislado y con recursos limitados en lugar de en tu máquina, así que un bucle desbocado queda contenido en lugar de tocar tu entorno.

Runtime Timeline Descripción
Docker Q3 2026 Pull de cualquier imagen OCI y ejecución de agentes dentro de contenedores efímeros — ideal para pipelines CI y builds reproducibles
SSH Q4 2026 Conexión a cualquier máquina (workstation, VPS, bare metal) con acceso terminal completo y comandos con consentimiento

El bridge participa en el modelo de almacenamiento local-first: sync.chat.push hace espejo del historial de chat en el SQLite local del dispositivo, así que tu historial completo de mensajes/código/diffs existe en tu hardware incluso cuando el índice cloud solo lleva las filas ligeras por conversación (~200 B cada una) necesarias para la sincronización del sidebar entre dispositivos.