Arquitectura de AI Workforce OS
AI Workforce OS separa la decisión de la ejecución: un backend en Go es la fuente de verdad, un runtime en Python solo razona con el modelo y un frontend en Next.js dibuja la oficina a partir de eventos. Esta página es para quien despliega, extiende o audita el sistema: cómo encajan las piezas y cómo viaja una petición de punta a punta.
En esta página
- Componentes
- Fronteras entre backend y runtime
- Flujo de una petición
- Canal en tiempo real
- Modelo de datos
- Multi-tenencia
- Simulación frente a IA real
- Despliegue y borde
- Estructura del repositorio
Componentes#
Todo el estado de negocio vive en el backend; el runtime no guarda estado entre llamadas. Postgres, Redis, el almacenamiento de objetos y el sandbox son opcionales en desarrollo y tienen un modo de reserva.
| Componente | Tecnología | Responsabilidad |
|---|---|---|
| Frontend | Next.js, React Three Fiber | Oficina 3D y modo dashboard. Consume REST y el WebSocket; toda animación deriva de eventos reales. |
| Backend | Go, chi | API REST /api/v1, WebSocket /ws, orquestador, aprobaciones, auditoría, presupuesto, autenticación y RBAC. |
| Agent runtime | Python, FastAPI, CrewAI | Recibe tarea y contexto, llama al proveedor del modelo y devuelve un resultado estructurado. |
| Postgres | PostgreSQL | Datos de todas las organizaciones, con row-level security y migraciones SQL numeradas. Sin DATABASE_URL el backend corre en memoria para demos. |
| Redis | Redis | Pub/sub de eventos hacia el hub WebSocket, bloqueos y límites de tasa. Sin REDIS_URL los eventos y bloqueos son locales al proceso. |
| Almacenamiento de objetos | Compatible con S3 (RustFS en desarrollo) | Bytes de los archivos del drive. Sin S3_ENDPOINT y S3_BUCKET se guardan en Postgres. |
| Sandbox runner | Go, Docker | Ejecuta en contenedores desechables y sin red el código que un agente pidió y alguien aprobó. Solo con SANDBOX_URL y SANDBOX_TOKEN. |
| Caddy | Caddy | Proxy de producción: TLS automático, envía /api/* y /ws al backend y el resto al frontend. |
Fronteras entre backend y runtime#
La regla central: el runtime nunca habla con Postgres, Redis ni sistemas externos. Recibe una tarea con su contexto y devuelve un resultado, solicitudes de herramientas y consumo; solo contacta al proveedor del modelo. El backend decide permisos, autonomía y presupuesto, y es el único que ejecuta herramientas.
El backend llama al runtime por HTTP con JSON en RUNTIME_URL. Si RUNTIME_TOKEN está definido en ambos lados, cada llamada lleva ese secreto como token bearer. El motor de IA queda detrás de una interfaz propia (AgentEngine), de modo que CrewAI puede reemplazarse sin tocar el backend.
| Endpoint del runtime | Uso |
|---|---|
POST /v1/plan, /v1/plan-phases, /v1/plan-phase, /v1/replan | Descomponer una petición o un proyecto en tareas con dependencias. |
POST /v1/run-task | Ejecutar una tarea; devuelve output, consults, tool_requests y usage. |
POST /v1/consult, /v1/review, /v1/counterargument | Consulta entre agentes, revisión de calidad y contraargumento para aprobaciones de alto riesgo. |
POST /v1/synthesize, /v1/summarize | Reporte consolidado y resúmenes. |
POST /v1/route, /v1/chat-reply | Enrutar un mensaje de chat y generar la respuesta de un agente. |
POST /v1/estimate, /v1/context-sizes, /v1/provider-test | Estimación de costo, tamaño del prompt y prueba de la clave de una organización. |
GET /healthz | Estado y modo: simulation o live. |
Consejo
El runtime de agentes tiene su propia página con el contrato completo, los proveedores y la política de datos.
Flujo de una petición#
La solicitud del usuario llega al orquestador, que pide un plan al runtime, lo valida y crea tareas. Cada tarea la ejecuta un agente que puede pedir herramientas; las acciones sensibles esperan aprobación humana y al final se sintetiza un reporte.
- El usuario envía el texto con
POST /api/v1/requests(o por el chat, que lo enruta). El backend responde202conrequest_idy emiterequest.received. - El orquestador pide el plan al runtime y lo valida antes de aceptarlo: claves duplicadas, dependencias desconocidas o circulares y profundidad máxima. Luego emite
plan.createdytask.created. - Las tareas se ejecutan respetando
depends_on. Cada una cambia el estado del agente (agent.state_changed) y puede generar consultas entre agentes (message.sent) y una revisión de calidad (task.reviewed). - Las herramientas que pide el runtime pasan por la política de autonomía y los permisos. Si hace falta aprobación, se crea un registro pendiente (
approval.requested) y la tarea queda enawaiting_approval. - Un usuario con permiso decide con
POST /api/v1/approvals/{id}/decision. La tarea continúa o se bloquea. - Cuando todas las tareas terminan, el runtime sintetiza el reporte y el backend emite
report.createdyrequest.completed. - En paralelo se registra el costo de cada llamada y se aplica el presupuesto:
budget.warningavisa ybudget.exceededpausa la ejecución.
POST /api/v1/requests {"text": "..."} -> 202 {"request_id": "..."}
orchestrator -> runtime POST /v1/plan -> tasks + depends_on
backend validates the plan -> plan.created, task.created*
per task: runtime POST /v1/run-task -> output, consults, tool_requests, usage
tool_requests -> policy/autonomy check -> approval.requested (if needed)
POST /api/v1/approvals/{id}/decision -> approval.resolved, task resumes
runtime POST /v1/synthesize -> report.created, request.completed
Canal en tiempo real#
GET /ws abre un WebSocket por organización. El backend publica cada evento en Redis (canal aiworkforce:events) y un hub reenvía el cuadro solo a los clientes cuyo org_id coincide; un cuadro sin org_id se descarta. Un cuadro con audience llega solo a esos usuarios.
Al conectar llega un evento hello con la lista de agentes y después los eventos de dominio. El cliente debe recargar el estado por REST tras reconectar, porque no hay replay.
- El servidor envía ping cada 30 s y cierra si no recibe pong en 60 s.
- Un cliente lento, con 256 cuadros en cola, se desconecta.
- Con autenticación activa, el socket se cierra con el código
4401a los 15 minutos (la vida por defecto del token de acceso) para que el cliente reconecte con un token nuevo.
{
"id": "<uuid>",
"type": "task.completed",
"ts": "2026-01-01T12:03:22Z",
"org_id": "<org id>",
"agent_id": "sales",
"payload": { "task": { "id": "...", "status": "done" } }
}
| Grupo | Tipos de evento |
|---|---|
| Conexión | hello, error, metrics.updated, activity.logged |
| Peticiones | request.received, request.queued, request.dequeued, request.resumed, request.status_changed, request.completed, plan.created, report.created |
| Tareas | task.created, task.started, task.completed, task.failed, task.blocked, task.retrying, task.reviewed, task.steered, task.reassigned |
| Agentes y chat | agent.created, agent.state_changed, message.sent, chat.message, chat.typing, route.decided, initiative.raised |
| Aprobaciones | approval.requested, approval.progress, approval.reminder, approval.resolved |
| Costos | cost.estimated, budget.warning, budget.exceeded, budget.resumed, provider.no_balance |
| Proyectos | project.created, project.launched, project.delta, project.status_changed, project.budget_warning, project.plan_change_* |
| Recompensas y oficina | coins.earned, shop.purchased, item.equipped, item.unequipped, office.item_placed, office.item_removed, office.layout_updated |
Nota
La tabla agrupa los eventos principales; algunos módulos emiten tipos adicionales con el mismo formato de cuadro.
Modelo de datos#
Las tablas viven en Postgres y todas las de negocio llevan org_id. Esta es la vista resumida; el esquema completo está en las migraciones numeradas de backend/internal/infrastructure/postgres/migrations/.
| Entidad | Tablas principales | Notas |
|---|---|---|
| Organizaciones y usuarios | organizations, users, memberships, refresh_tokens, invitations | Roles por membresía: owner, admin, member, viewer. |
| Agentes | agents, agent_profiles, agent_settings, agent_controls | Estado, herramientas, permisos y nivel de autonomía. |
| Peticiones y tareas | requests, tasks, request_runs, task_checkpoints, reports | Estados de tarea: pending, running, blocked, awaiting_approval, done, failed. |
| Aprobaciones | approvals, standing_approvals, approval_executions | Doble aprobación, permisos permanentes por proyecto y ejecución única de lo aprobado. |
| Proyectos | projects, project_templates, plan_changes | Planes con nodos, presupuesto y versiones de plantilla. |
| Memoria | memories, memory_docs | Memoria a largo plazo por agente. |
| Habilidades | skills, skill_versions, skill_assignments | Versionadas, con flujo de revisión. |
| Recompensas | coin_wallets, coin_ledger, shop_inventory, shop_equipment, shop_office | Monedas por trabajo terminado y objetos de personaje y oficina. |
| Gobierno y costo | audit_logs, audit_chain_heads, usage_entries, budget_caps | Auditoría encadenada, ledger de consumo y topes. |
Multi-tenencia#
El org_id viaja dentro del token de acceso y nunca se toma de la entrada del cliente. La capa HTTP lo coloca en el contexto de la petición y todas las consultas se filtran por él. Un usuario puede pertenecer a varias organizaciones y cambia de una a otra abriendo una sesión nueva.
Como defensa en profundidad, Postgres aplica row-level security:
- Cada transacción fija
app.org_idconset_config(..., true), local a la transacción; las políticas comparanorg_idconapp_current_org(). - Si el valor falta, la consulta no ve ni escribe filas: falla cerrado.
- La aplicación hace
SET ROLEa un rol sin privilegios de propietario (DB_APP_ROLE, por defectoapp_user, sinBYPASSRLS); las migraciones usanMIGRATE_DATABASE_URL. - El hub del WebSocket también filtra por organización.
Consejo
La página de autenticación y multitenencia detalla roles, sesiones y las políticas de RLS.
Simulación frente a IA real#
El contrato HTTP del runtime es el mismo en ambos modos; cambia el motor. En simulación (con SIMULATION=true o sin ningún proveedor configurado) el runtime devuelve resultados guionados con latencias simuladas, sin red y sin costo. En modo real usa los proveedores configurados y su GET /healthz informa mode: live y el proveedor.
Si hay un proveedor configurado pero el motor real no arranca, el runtime responde en simulación con status: degraded y un aviso; no hay fallback silencioso. El backend refleja el modo en su propio GET /healthz como runtime_mode.
curl http://localhost:8080/healthz
# {"status":"ok","runtime":"up","runtime_mode":"simulation"}
Consejo
SIMULATION=true siempre gana: no se hace ninguna llamada de red, incluso con claves presentes.
Despliegue y borde#
Hay tres archivos de Compose:
docker-compose.yml: desarrollo con cuentas. Levantapostgres,redis,objects(almacenamiento S3),agent-runtime,backendyfrontend; el sandbox runner está en el perfilsandbox.docker-compose.demo.yml: se combina con el anterior para el modo demo abierto (AUTH_ENABLED=false).docker-compose.prod.yml: producción.caddy,postgres,redis,agent-runtime,backendyfrontend; solo Caddy publica puertos (80 y 443).
Nota
En producción Caddy termina TLS, agrega cabeceras de seguridad (HSTS, X-Content-Type-Options, X-Frame-Options, Referrer-Policy) y enruta por ruta: /api/* y /ws al backend, todo lo demás al frontend.
Estructura del repositorio#
El backend sigue capas: api depende de application, que depende de domain; infrastructure implementa los puertos.
backend/ Go (chi)
cmd/server/ main.go: composition root (wires every service)
cmd/sandbox-runner/ code-execution runner
internal/ domain/ application/ api/ auth/ events/ projects/ drive/
skills/ memory/ rewards/ connections/ controls/ audit/ ...
internal/infrastructure/{postgres,redis,runtime,memory}
internal/infrastructure/postgres/migrations/ numbered SQL migrations
agent-runtime/ Python FastAPI (CrewAI engine, simulation engine)
app/ main.py, engine.py, simulation.py, crewai_engine.py, providers.py ...
frontend/ Next.js + React Three Fiber (src/app, src/components, src/lib)
e2e/ Playwright
sandbox/ sandbox container images
Caddyfile production edge (TLS, reverse proxy)
docker-compose.yml dev stack; docker-compose.demo.yml, docker-compose.prod.yml
docs/ SPEC.md and architecture/*.md
landing/ this public site