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 conceptos detrás de los runtimes
Sección titulada «Los conceptos detrás de los runtimes»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.executees genuinamente peligroso, así que cada capacidad está explícitamente acotada y sujeta a consentimiento (consent_prompt/consent_response), auditada endevice_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,.AppImageví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/startdevuelve{code, pairingId, backend}con TTL 5 min; el daemon lo canjea enPOST /api/devices/pair/completepor{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 depaired, WebSocketconnected, RTT,versiony la identidadosdel host (distribución exacta desde/etc/os-release: NixOS vs Ubuntu vs Fedora, kernel, arquitectura, libc) — y cae a/api/devicescuando 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 protocolo de red
Sección titulada «El protocolo de red»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 |
Capacidades
Sección titulada «Capacidades»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.
Emparejamiento
Sección titulada «Emparejamiento»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.
Local vs remoto
Sección titulada «Local vs remoto»- Misma máquina (local): el exe con GUI (o
synthhires-bridge run) más el auto-pair del navegador contrahttp://127.0.0.1:7333/status|/pairy el deep linksynthhires://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:7333NO es alcanzable desde tu navegador, así que el auto-pair no funciona. Copia elCODEde Connections, anota elbackendque 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éssynthhires-bridge run(o la unidad systemd deinstall.sh). La conexión posterior es WebSocket 100% saliente — sin puertos de entrada. Revoca desde la web (Revoke) o en local (synthhires-bridge unpair).
Matriz de instalación
Sección titulada «Matriz de instalación»| 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. |
Dispositivos emparejados
Sección titulada «Dispositivos emparejados»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.
Sandboxes cloud (nube privada, Docker)
Sección titulada «Sandboxes cloud (nube privada, Docker)»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/nodestock (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) +/tmpnoexec, 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=nonepor 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
noneaweblist(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
*.pngde/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/scratcho hijo real, sin..), timeouts 1–300s contimeout(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.comcon un Cloudflare Tunnel DEDICADO (cloudflared-sandboxen 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) conSANDBOX_*como secrets del Worker — el túnel SSH local-L 8789es solo-dev y no existe en el edge. Rota el Bearer connode 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 (
/scratchsobrevive dentro del TTL de 600 s) vía el hint de afinidadcloud_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_shellapunta 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_shellsigue 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.
Docker y SSH (próximamente)
Sección titulada «Docker y SSH (próximamente)»| 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 |
Estrategia de almacenamiento local-first
Sección titulada «Estrategia de almacenamiento local-first»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.