Ir al contenido

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

Arquitectura

Esta página describe el stack de producción, el camino de un mensaje de chat, el modelo de base de datos y las fronteras de confianza. Es una inmersión técnica — lee la Visión general primero si quieres la foto a nivel de producto, o Conceptos de IA desde cero para el vocabulario de machine learning que la arquitectura implementa.

Los conceptos que esta arquitectura implementa

Sección titulada «Los conceptos que esta arquitectura implementa»

Todo lo de abajo conecta las ideas del glosario de IA:

  • El camino de request es una llamada LLM a través de un proxy. El cliente tiene la clave (BYOK), la envuelve en un sobre de handoff, y el servidor hace de proxy de streamText hacia el proveedor — así el bucle de tokens del modelo corre en el edge mientras tu clave nunca toca nuestro almacenamiento.
  • Local-first responde al problema del contexto. Como el modelo solo ve lo que se pone en su ventana, la plataforma debe re-suministrar memoria, documentos recuperados e historial en cada request — así que guarda los datos pesados (mensajes, código, diffs) localmente (IndexedDB/bridge SQLite) y solo un índice ligero en servidor. Lecturas offline rápidas, privacidad por defecto.
  • La salida estructurada es un contrato de primera clase. Los planes y artefactos se recuperan del texto del modelo como objetos validados contra esquema (Output.object / Zod + escaneo tolerante), no como prosa libre, lo que permite que otras superficies (studio, artefactos) los consuman programáticamente.
  • Agentes / herramientas / MCP convierten a un único modelo en un sistema capaz de actuar sobre tus repos, dispositivos y servicios.

La tabla de stack y el camino de request de abajo son cómo esas ideas aparecen en tecnología concreta.

Capa Tecnología Notas
Framework web Astro 6 Páginas marketing estáticas + rutas de servidor; islas para componentes interactivos
Routing cliente TanStack Router (file-based) El shell del dashboard (/space) usa un árbol de rutas construido a mano en DashboardApp.tsx; las páginas marketing usan rutas de Astro
UI React 18+ (solo function components), Tailwind CSS v3, primitivas shadcn/ui, Radix No hay class components en ningún sitio
Runtime Cloudflare Workers Stateless por isolate, sin fs, sin APIs solo-Node
Base de datos Postgres (Supabase) vía Drizzle ORM Migraciones generadas con drizzle-kit generate, nunca escritas a mano
IA AI SDK v4 streamText + Output.object({ schema }) para salida estructurada; generateText para no-streaming
Almacenamiento de medios R2 (Cloudflare) Assets de contenido y subidas
Estado IndexedDB + localStorage (cliente) con vault cifrado Híbrido local-first; índice ligero en Postgres para sync entre dispositivos
Auth Magic-link + códigos OTP src/lib/auth.ts / auth-server.ts
Pagos LemonSqueezy El webhook cambia users.tier entre free y paid
  1. Cliente lee el proveedor/modelo activos y la API key guardada del vault local cifrado (credentials.ts).
  2. El cliente construye un sobre de handoff de un solo uso: la clave en texto plano se envuelve para la clave pública E2EE del servidor con expiración corta (buildVaultHandoff en credentials.tskdf.ts).
  3. El sobre viaja a POST /api/chat (src/pages/api/chat.ts). La ruta llama primero a requireResolvedAuth(cookies, request) — la única puerta de auth de todas las rutas API.
  4. El servidor desempaqueta el sobre, resuelve el modelo del proveedor (ai-providers.ts) y devuelve la respuesta en streaming con streamText (AI SDK v4). Las salidas estructuradas usan Output.object({ schema }).
  5. Cada mensaje se persiste en el servidor vía chat-persistence.ts (Postgres) y en el cliente vía local-chat-store.ts (IndexedDB). Los registros de uso (recordUsage) se escriben en usage_records.
  6. El cliente renderiza el stream de forma incremental. Los artefactos y planes se extraen del texto del asistente en el cliente (extractPlanFromContent en chat-helpers.ts).

¿Por qué local-first con índice ligero en servidor?

Sección titulada «¿Por qué local-first con índice ligero en servidor?»

Los mensajes, el código y los diffs son pesados. Guardarlos solo en el navegador (IndexedDB) y en el bridge emparejado (SQLite) mantiene la app rápida y privada. Una fila de ~200 B por conversación en Postgres (conversations) basta para sincronizar títulos y marcas de tiempo entre dispositivos.

El esquema Drizzle vive en src/lib/db/schema.ts. Tablas centrales:

Tabla Propósito
users Identidad: email, nombre, tier (free/paid), preferencias (JSONB), flag de verificación
sessions Sesiones de servidor (token + expiración) para llamadas API autenticadas
verification_codes Códigos OTP por email con expiración y contador de intentos
conversations Sesiones de chat: título, proveedor/modelo, árbol de forks (parentId, rootId, childCount), workspace ref (JSONB), nivel de thinking, contador de tokens, pinned/tags/status
messages Mensajes por conversación: enum de rol, contenido JSONB, conteo de tokens, modelo, metadatos, columna tsvector de búsqueda
folders Carpetas de conversaciones con color/icono/orden
agents Catálogo de agentes: rol, descripción, system prompt, modelo/proveedor, tools (JSONB), categoría, tier de precio
subscriptions Vínculos de suscripción LemonSqueezy (suscripciones de agentes)
workflows, workflow_nodes, workflow_edges, workflow_runs Persistencia del pipeline studio
content_assets Medios generados (imagen/vídeo/audio/3D)
vault_entries, user_vault_config Sync opcional del vault en servidor (solo con opt-in explícito por proveedor)
usage_records Métricas de tokens/coste por request: tokens prompt/completion/cacheados, reasoning, duración, modo, coste USD, tier
devices, device_tokens, pairing_codes, device_action_log Emparejamiento de bridges, tokens de dispositivo, códigos de emparejamiento y el log de auditoría de consentimiento
chunk_hashes Dedup por dirección de contenido para subidas
task_outbox Patrón outbox para trabajos asíncronos
  • Tus claves jamás salen de tu dispositivo salvo dentro de un sobre de handoff E2EE de corta vida, y solo durante la duración de la request que tú inicias. Esta es la promesa BYOK — ver Seguridad y BYOK.
  • El daemon bridge (un repo Rust aparte) se conecta solo saliente vía WebSocket a la plataforma; no se abren puertos de entrada en tu máquina. Cada acción requiere consentimiento limitado a una capacidad (desktop.shell.execute, desktop.fs.read, …).
  • Las rutas API están controladas por requireResolvedAuth; CORS/rate limiting/redacción viven en security.ts; los logs sensibles pasan por el único sumidero log-redaction.ts.

Todas las claves de persistencia del cliente llevan el prefijo synthhires:* (ver src/lib/nickname.ts para los helpers canónicos). Claves notables: synthhires-credentials (vault cifrado), synthhires-tokens (tokens de servicios), synthhires:activeConvId, synthhires:activeProvider, synthhires:enabledModels, synthhires:systemInstruction, synthhires:globalParams, synthhires:sidebar:width.

  • Los IDs son siempre crypto.randomUUID() — nunca strings basados en timestamp (propensos a colisión con forks concurrentes).
  • Los eventos entre componentes usan sintaxis de dos puntos: synthhires:conv:focus, synthhires:nickname:update, synthhires:sidebar:set.
  • Toda transición lleva motion-reduce:transition-none motion-reduce:duration-0; transition-all está prohibido.