Ir al contenido

Espacios de trabajo y aislamiento

Un espacio de trabajo es el inquilino de aislamiento de nivel superior de Control Center. Agrupa agentes, repositorios, espacios, tickets, memoria, pipelines y miembros en un contexto acotado. Todo lo que ocurre dentro de un espacio de trabajo se queda dentro de él.

Un espacio de trabajo típico corresponde a un proyecto o a una base de código. Es posible que tengas espacios de trabajo separados para «api-service», «frontend-v2» e «infrastructure».

Las filas de un espacio de trabajo viven en el propio archivo SQLite de ese espacio de trabajo, en <dataDir>/<workspaceId>/workspace.db. Cada espacio de trabajo recibe un directorio en lugar de un archivo suelto, porque un espacio de trabajo acumula más que una base de datos —worktrees por conversación, los archivos de agentes y skills, credenciales de bots de chat— y darle una carpeta hace que todo eso viva y se elimine junto.

Entidad Ámbito
Agentes Pertenecen a exactamente un espacio de trabajo
Repositorios Se registran en un espacio de trabajo (ver más abajo)
Espacios, conversaciones y mensajes Con ámbito de espacio de trabajo
Tickets, proyectos, planes, orquestaciones Con ámbito de espacio de trabajo
Hechos de memoria, políticas, dominios, permisos de acceso Con ámbito de espacio de trabajo
Ejecuciones de pipeline, plantillas, disparadores Con ámbito de espacio de trabajo
Estado de las pull requests, espacios de revisión, cohortes de revisión Con ámbito de espacio de trabajo
Registros de ejecución de agentes y memoria de trabajo Con ámbito de espacio de trabajo
Miembros, invitaciones, permisos por repositorio Con ámbito de espacio de trabajo
Símbolos y aristas del grafo de código Con ámbito de repoId dentro del archivo del espacio de trabajo

La mayoría de estas filas llevan una columna workspaceId; los archivos de base de datos independientes imponen el aislamiento. Consulta la invariante de aislamiento que viene más abajo.

Los repos tienen ámbito de espacio de trabajo; la identidad entre espacios de trabajo es por ruta

Sección titulada «Los repos tienen ámbito de espacio de trabajo; la identidad entre espacios de trabajo es por ruta»

La fila de un repo vive en el propio archivo de base de datos de su espacio de trabajo —el archivo es el ámbito, así que la tabla no tiene columna workspaceId ni tabla de unión.

Agregas un repo a un espacio de trabajo en Ajustes → Espacio de trabajo → Repositorios. Registrar el mismo checkout en dos espacios de trabajo crea dos filas independientes con dos identificadores; la identidad de un repo entre espacios de trabajo es por ruta del sistema de archivos (o por propietario/nombre de GitHub), nunca por identificador.

El registro es estricto: la ruta debe estar dentro de un árbol de trabajo de git, debe tener un remoto origin y ese remoto debe apuntar a un host de código compatible (github.com, gitlab.com o bitbucket.org). El host de código se lee del remoto y se guarda en el repo, así que un espacio de trabajo puede contener repos de los tres. Cualquier otra cosa se rechaza. El explorador de carpetas en el que eliges lista el sistema de archivos del host del servidor, limitado a la lista de rutas permitidas de --repo-roots del servidor (que de forma predeterminada es el directorio personal del usuario del servidor).

Quitar un repositorio de un espacio de trabajo elimina la fila de inmediato y propaga el borrado a sus símbolos de código, aristas, archivos, puntos de control del índice y permisos por miembro. El checkout en disco queda intacto.

El grafo de código vive en el mismo archivo, indexado por repoId, porque distintos espacios de trabajo pueden tener el mismo repositorio en ramas diferentes con código distinto.

Las filtraciones de datos entre espacios de trabajo se tratan como errores. El aislamiento es estructural y se aplica en varios niveles:

  • Capa de base de datos. Cada espacio de trabajo tiene su propio archivo SQLite, entregado por WorkspaceDatabaseManager.of(workspaceId). Una base de datos de espacio de trabajo no declara users, workspaces ni las tablas de ningún otro espacio de trabajo, así que una lectura entre espacios de trabajo no es un error que puedas llegar a escribir: no compila. Los repositorios resuelven su DAO en cada llamada en lugar de cachear uno y, como todo método de repositorio recibe un workspaceId obligatorio, el espacio de trabajo nunca se puede inferir ni dejar de forma predeterminada.
  • El identificador del espacio de trabajo es un segmento de ruta. Como el identificador se convierte en un nombre de directorio, debe coincidir con un patrón seguro de un solo segmento (empieza con un alfanumérico y sigue con alfanuméricos, puntos, guiones bajos y guiones; sin ..). En todo el producto los identificadores son UUID. Cualquier otra cosa lanza una excepción en lugar de sanearse: esa es la protección contra el salto de ruta, y falla de forma ruidosa en vez de escribir los datos de un espacio de trabajo en una ruta que eligió alguien más.
  • Defensa en profundidad. Las columnas workspaceId siguen ahí y se siguen escribiendo. Redundantes dentro de un archivo, mantienen sin cambios los disparadores del feed de sincronización, los índices FTS y las formas de fila existentes, y hacen que un archivo se describa a sí mismo cuando se inspecciona por separado, que es en lo que se apoyan la exportación y la importación.
  • Capa de dominio. Los servicios que modifican entidades por identificador validan entity.workspaceId == workspaceId en un único punto de control antes de continuar y lanzan WorkspaceMismatchException cuando no coinciden: el fallo ruidoso para un identificador que llegó desde el contexto equivocado. Denegar de forma ruidosa importa: un no-op silencioso oculta el error, y continuar filtra.
  • Capa RPC. El despachador de RPC de repos es el punto de control. Toda operación con ámbito de espacio de trabajo debe llevar workspace_id en sus propios argumentos: no hay ningún «espacio de trabajo actual» por sesión que se pueda filtrar, y un identificador desconocido se rechaza como no encontrado antes de que nada abra una base de datos, así que un identificador incorrecto no puede materializar un workspace.db fantasma. Las operaciones genuinamente globales (el feed de noticias, la cola de la flota) se declaran sin ámbito como una decisión explícita y revisada. Las herramientas MCP siguen la misma regla: cualquier herramienta que toque datos con ámbito de espacio de trabajo requiere workspace_id.
  • La expansión entre espacios de trabajo es enumerable. Responder una pregunta sobre varios espacios de trabajo implica abrir varias bases de datos. Esa expansión queda confinada a un único auxiliar, CrossWorkspaceQueries, de modo que la lista completa de lo que cruza legítimamente la frontera vive en los puntos de llamada de un solo archivo en lugar de difuminarse detrás de comentarios de documentación. Esos puntos de llamada son: la agregación de todo el servidor, los reconciliadores del arranque, los barridos de retención y de recolección de basura, el respaldo, los enrutadores de eventos — y la consulta de pertenencia que viene más abajo.
  • El acceso solo por identificador no basta. Buscar una entidad por su UUID no demuestra que pertenezca al espacio de trabajo de quien llama. La separación de archivos hace inalcanzable el archivo equivocado; la validación de la capa de dominio atrapa un identificador equivocado que llegue a través de un archivo legítimo.

La pertenencia, y no la clave de emparejamiento, es lo que concede el acceso

Sección titulada «La pertenencia, y no la clave de emparejamiento, es lo que concede el acceso»

Tener una credencial de dispositivo autentica a un dispositivo. No autoriza nada dentro de un espacio de trabajo. Toda operación con ámbito de espacio de trabajo resuelve el rol de pertenencia de quien llama y aplica un mínimo derivado del tipo de operación —una lectura necesita guest, una mutación necesita member y una operación destructiva necesita admin—, y las operaciones que exponen contenido de repos comprueban además el permiso por repositorio de quien llama. Una clave de emparejamiento válida sin pertenencia recibe Not a member of this workspace.

La consecuencia para el aislamiento es elegante: las filas de pertenencia viven en el archivo del propio espacio de trabajo, así que «¿quién está en este espacio de trabajo?» es una lectura con ámbito corriente. La pregunta inversa —«¿en qué espacios de trabajo estoy?»— está repartida entre archivos por definición, así que es una de las pocas expansiones autorizadas de CrossWorkspaceQueries y es lo que ejecuta el selector de espacios de trabajo antes de que se haya elegido ninguno.

Consulta Multijugador para la escala de roles y los permisos por repositorio.

No todo tiene ámbito de espacio de trabajo. Una segunda base de datos, <dataDir>/global.db, contiene lo que es genuinamente de todo el servidor:

  • el registro de espacios de trabajo, para que el conmutador pueda listar todos los espacios de trabajo sin abrir un solo archivo de espacio de trabajo, y para que el nombre, el logotipo, el propietario, el orden manual y el marcador de borrado lógico de un espacio de trabajo vivan fuera del archivo que describen;
  • la identidad: usuarios, preferencias de usuario y dispositivos emparejados, porque una persona es un único usuario en todos los espacios de trabajo y un dispositivo emparejado sobrevive al borrado de un espacio de trabajo;
  • la cola de la flota (workers, trabajos, registro de colocación), cuyo planificador contrasta toda la cola con cada worker en cada ciclo;
  • el feed de noticias, que es por usuario (no por espacio de trabajo): agregar una fuente te sigue a todos los espacios de trabajo;
  • workspace_routes, el índice previo a la autenticación que responde «¿qué espacio de trabajo es dueño de esta clave?» para los puntos de entrada que llegan solo con un secreto o un identificador opaco (un hash de invitación, un token de webhook, un enlace directo) y sin espacio de trabajo. Un fallo ahí es un no encontrado; deliberadamente no hay ningún recorrido de reserva;
  • ajustes e identidad de toda la instalación: el identificador de la instalación, los ajustes de nivel de host, las conexiones SSO y las políticas de acciones administradas (un tope de toda la instalación que solo puede endurecer las barandillas de los espacios de trabajo).

El arranque abre únicamente este archivo. Los archivos de los espacios de trabajo se abren de forma perezosa en el primer acceso, así que el costo de inicio se mantiene constante por mucha historia que acumulen los espacios de trabajo, y el quick_check de cada archivo se paga en el primer uso y no en el camino hasta el aviso de preparado.

Un archivo de espacio de trabajo solo contiene una workspace_meta de una sola fila para identificarse a sí mismo (su identificador, la instalación que lo creó, la versión del esquema con la que se creó y cuándo). Todo lo descriptivo de un espacio de trabajo está en el registro.

Crear un espacio de trabajo hace más que insertar una fila en el registro. En una sola operación, el servidor marca al usuario que lo crea como ownerUserId, registra la pertenencia de ese usuario con el rol de propietario y publica un evento de dominio WorkspaceCreated. Los escuchadores de ese evento siembran entonces, de forma idempotente y en segundo plano:

  • un agente CEO y cuatro especialistas —qa, architect, engineer, librarian—, y los especialistas reportan al CEO;
  • las plantillas de pipeline integradas y sus disparadores (los disparadores por evento y manuales vienen habilitados; los programados son opcionales, salvo el barrido diario de recolección de basura de worktrees);
  • las suites de evaluación iniciales.

Esto ocurre en todos los espacios de trabajo, no solo en el primero. Los agentes sembrados se crean sin adaptador, así que se ejecutan en Control Center (built-in) con el modelo predeterminado de Anthropic (claude-opus-4-8 salvo que fijes otro) hasta que establezcas un adaptador y un modelo para cada agente en Ajustes → Espacio de trabajo → Agentes. Consulta El modelo de agentes.

La escritura de la pertenencia del propietario importa más de lo que parece: sin ella, el espacio de trabajo recién creado no tendría miembros y se denegaría toda llamada con ámbito de espacio de trabajo que hiciera después quien lo creó.

Eliminar un espacio de trabajo marca la fila del registro y desvincula el directorio. El espacio de trabajo desaparece de todas las listas y búsquedas. La fila del registro permanece (con borrado lógico), de modo que el identificador sigue siendo conocido para el respaldo y el mantenimiento, pero workspace.delete elimina de inmediato el directorio de ese espacio de trabajo —base de datos, worktrees, archivos de agentes y lo demás— mediante WorkspaceDatabaseManager.dropAndClose. Solo el propietario del espacio de trabajo puede eliminarlo.

Como un espacio de trabajo es un archivo, pasar un espacio de trabajo de un lugar a otro es una operación de archivo en lugar de un volcado tabla por tabla:

  • workspace.export es un único VACUUM INTO: una instantánea coherente y desfragmentada tomada dentro de una transacción de lectura mientras el servidor sigue atendiendo.
  • workspace.import adopta un archivo así y reemplaza lo que el espacio de trabajo de destino contenga en ese momento. La workspace_meta incrustada permite que una importación distinga «mi propio archivo, readoptado» de «un archivo de otra instalación» (esto último está permitido, pero se registra en lugar de pasar inadvertido).
  • server.backupNow hace una instantánea de toda la instalación en un directorio con marca de tiempo que replica el directorio de datos activo —manifest.json, global.db y un <workspaceId>/workspace.db por espacio de trabajo—, así que restaurar es copiarlo de vuelta.
  • server.listBackups vuelve a leer ese directorio, de la instantánea más reciente a la más antigua, con el tamaño y la lista de espacios de trabajo de cada instantánea. Una instantánea cuyo manifiesto falte o que nombre archivos que no están se sigue reportando, marcada como incompleta: ocultarla es como un operador llega a creer que tiene un respaldo que en realidad no tiene.

Las operaciones de respaldo están en Ajustes → Servidor → Copia de seguridad y restauración: Crear copia ahora toma una instantánea de la instalación, la lista muestra qué instantáneas están completas y permite al propietario de la instalación eliminar una tras confirmarlo, y cada espacio de trabajo ofrece exportación e importación. El pipeline integrado Workspace backup toma únicamente la base de datos de ese espacio de trabajo con una programación semanal y elimina sus propios respaldos más antiguos que la antigüedad de retención configurada. Restaurar un espacio de trabajo desde una instantánea de la instalación es workspace.import apuntando al <workspaceId>/workspace.db de la instantánea; restaurar una instalación completa sigue siendo una recopia del directorio de la instantánea con el servidor detenido.

Lo que ninguno de ellos transporta es el resto del directorio del espacio de trabajo. Un espacio de trabajo acumula más que una base de datos —imágenes pegadas, archivos de skills y de agentes, credenciales de chat, worktrees— y la exportación y la importación mueven el archivo, así que todo lo que está junto a él se queda donde está. Eso es deliberado en el caso de las credenciales y prescindible en el de los worktrees; para el resto es un límite que conviene conocer y no una función. La referencia de respaldo tiene la tabla completa.

Mover un respaldo entre el servidor y tu dispositivo

Sección titulada «Mover un respaldo entre el servidor y tu dispositivo»

Todas las operaciones anteriores hablan en rutas del servidor, lo cual es una respuesta completa solo cuando el servidor es tu propia computadora. Tres rutas HTTP firmadas —/backup/workspace, /backup/snapshot y /backup/restore— transportan los bytes para cualquier otra topología, por el mismo carril autenticado que ya usan el proxy de medios y el almacén de imágenes. En la página esto es un botón Descargar junto a cada exportación y cada instantánea (tú eliges dónde acaba) y Elegir un archivo y subirlo para restaurar desde un archivo que esté en la computadora que estás usando. La subida es la que funciona cuando el servidor no es tu computadora: workspace.import necesita que el archivo ya esté en el host, y así es como llega ahí.

Vale la pena conocer tres propiedades:

  • Una descarga genera una copia nueva y el servidor no conserva nada de ella. Descargar no es «traer el archivo que creó el botón de exportación»: exporta de nuevo, transmite eso y lo elimina. La respuesta es no-store con Accept-Ranges: none por la misma razón: reanudar un rango empalmaría dos exportaciones distintas en un solo archivo que parece válido y no lo es.
  • Una subida se transmite a disco en streaming, nunca se almacena en búfer, en ambos extremos, y la copia preparada se elimina en todas las salidas posibles, incluido un rechazo. La base de datos de un espacio de trabajo no es una captura de pantalla.
  • Una conexión retransmitida no tiene origen HTTP, así que ahí los controles de transferencia están deshabilitados con una nota que explica por qué. Todo lo que habla en rutas del lado del servidor sigue funcionando a través del relé; solo los carriles de bytes necesitan una conexión directa.

Puedes ejecutar varios espacios de trabajo en paralelo. Cada uno opera de forma independiente: los agentes de un espacio de trabajo no pueden enviar mensajes a los agentes de otro, no pueden leer la memoria de otro espacio de trabajo ni ver sus tickets. La resolución de destinatarios para la mensajería entre agentes es exacta y nunca cruza un espacio de trabajo.

No hay ningún panel entre espacios de trabajo. La superficie de analíticas en /workspaces/:workspaceId/observability se suscribe a un feed global acotado de ejecuciones recientes y lo reduce de inmediato al espacio de trabajo activo, así que lo que ves ahí es un espacio de trabajo a la vez. Las únicas superficies genuinamente multi-espacio de trabajo que una persona toca son el selector de espacios de trabajo y el conmutador.

Un límite práctico que conviene conocer: un espacio de trabajo que alguien está usando se queda abierto, no hay LRU. Pasados 32 de esos, el servidor advierte en lugar de rechazar, porque cada archivo abierto mantiene un isolate en segundo plano y una caché de páginas. La expansión entre espacios de trabajo abre archivos de forma transitoria y los cierra después si nada más los ha reclamado.