Saltar al contenido

Capítulo 9 de 18

Workspace, sandbox y artefactos

Ejecuta trabajo sobre versiones identificables y entiende qué aislamiento necesitas cuando el agente puede ejecutar código.

#Un directorio de trabajo no es una barrera de seguridad

El workspace organiza los archivos de una tarea. El sandbox limita lo que puede hacer el proceso: filesystem, red, recursos y capacidades del sistema operativo. Cambiar cwd a una carpeta solo cambia desde dónde se interpretan rutas relativas; no impide leer /etc, conectarse a internet o consumir toda la memoria.

Para un agente documental que solo consulta objetos autorizados quizá no necesites ejecutar código. Para uno que instala dependencias o corre scripts generados, la ejecución es una frontera de confianza y necesita aislamiento real.

#Separar ejecuciones antes de paralelizarlas

Dos agentes que escriben en el mismo checkout pueden sobrescribir cambios y ejecutar pruebas sobre una mezcla que ninguno conoce. Un workspace por run permite relacionar resultado y versión. Puede ser un checkout separado, un worktree de Git o un conjunto de archivos controlado por la aplicación.

Un worktree comparte el repositorio Git, pero tiene su propio directorio de trabajo y rama. Es una herramienta de organización, no un sandbox. Si un proceso tiene acceso al resto del disco, seguirá pudiendo modificarlo. Mantén separadas ambas decisiones: dónde trabaja y qué puede tocar.

Para integrar cambios exige una base conocida. Si el agente produjo un patch sobre el commit A y la rama principal avanzó a B, debes aplicar, resolver conflictos y volver a verificar sobre el resultado integrado. Las pruebas sobre A más patch no prueban B más patch.

#Ejecutar un comando conocido

subprocess es el módulo estándar de Python que crea procesos. Para una acción acotada, usa una lista de argumentos y evita que el shell interprete texto del modelo:

import os
import subprocess
import sys
from pathlib import Path


def ejecutar_pruebas(workspace, log_path):
    # Ambas rutas las proporciona el servidor, no el modelo.
    workspace = Path(workspace).resolve(strict=True)
    log_path = Path(log_path)
    env = {"PATH": os.defpath, "PYTHONUTF8": "1"}
    with log_path.open("wb") as log:
        result = subprocess.run(
            [sys.executable, "-m", "unittest", "discover", "-v"],
            cwd=workspace,
            env=env,
            stdin=subprocess.DEVNULL,
            stdout=log,
            stderr=subprocess.STDOUT,
            timeout=60,
            check=False,
        )
    return {"exit_code": result.returncode, "log_path": str(log_path)}

La lista evita interpretación de ;, sustituciones y redirecciones por un shell. No impide que el propio programa ejecutado sea peligroso. Las pruebas de un repositorio también son código y pueden realizar efectos.

El entorno explícito evita heredar todas las credenciales del servicio. DEVNULL impide que un comando quede esperando entrada interactiva. Guardar salida en un archivo evita acumularla toda en RAM, pero no limita el tamaño del archivo: el supervisor o filesystem debe imponer cuotas si el código no es confiable.

El timeout de subprocess.run mata y espera al proceso hijo directo al expirar. No garantiza terminar todos sus descendientes ni constituye una cuota total de CPU. Para procesos no confiables necesitas grupos de procesos o, preferiblemente, un runtime de aislamiento que pueda destruir la unidad completa.

#Una frontera práctica con contenedores

Un contenedor puede limitar recursos, montar solo directorios necesarios, ejecutar con usuario sin privilegios y restringir la red. Las decisiones esenciales son:

Recurso Política inicial Motivo
Filesystem Raíz de solo lectura y workspace explícito Reducir cambios fuera del trabajo
Usuario Sin root y sin capacidades innecesarias Limitar operaciones privilegiadas
Memoria y CPU Cuotas por ejecución Evitar que un run agote el host
Procesos Límite de procesos y supervisor Contener forks y terminar descendientes
Red Denegada o destinos permitidos Evitar exfiltración y accesos internos
Credenciales Solo las necesarias para una acción Reducir alcance de una fuga

Montar el socket de Docker dentro del agente suele darle poder sobre el host; no es una forma de aislamiento. Tampoco montes toda tu carpeta personal por comodidad. El recorrido de Docker explica imágenes, redes, volúmenes y servicios para entender estas decisiones desde sus fundamentos.

Un contenedor comparte kernel con el host. El aislamiento adecuado depende de la confianza en el código y del impacto de una fuga. Para ejecutar código hostil de usuarios diferentes puedes necesitar microVMs o una plataforma diseñada para ese límite; no presentes un contenedor básico como aislamiento absoluto.

#Red y secretos sin entregárselos al modelo

Si el agente debe llamar a una API privada, una opción es un proxy que exponga solo operaciones autorizadas. El proxy añade la credencial y aplica límites. El modelo recibe el resultado necesario, no el token.

Permitir cualquier URL abre otra frontera: una herramienta puede acceder a servicios internos, direcciones locales o endpoints de metadatos. La política debe validar destino y red, manejar redirecciones y comprobar el destino efectivo. Una lista textual de dominios sin control del transporte puede fallar ante resolución o redirección inesperada.

#Conservar artefactos que se puedan verificar

Un artefacto es un resultado persistido: informe, patch, archivo, log o paquete. Registra identidad, tipo, tamaño, hash, autor/run y versión de origen. No uses un nombre como resultado_final_2.txt como única identidad.

{
  "artifact_id": "a-29",
  "run_id": "r-17",
  "kind": "patch",
  "base_commit": "commit-base",
  "sha256": "hash-del-contenido",
  "validation": {"command": "python -m unittest", "exit_code": 0}
}

El ejemplo abrevia hashes; en datos reales conserva valores íntegros y una ubicación recuperable. La validación debe referirse al contenido probado. Si el artefacto cambia después, invalida o repite la prueba. Un nombre de archivo igual no implica una versión igual.

#Recuperar un workspace perdido

El procedimiento comienza por recuperar la base, aplicar cambios persistidos y verificar hashes. Después instala dependencias desde una especificación bloqueada o imagen versionada y ejecuta una comprobación de coherencia. No intentes restaurar sockets y memoria a partir de un resumen de conversación.

Puede que falten archivos temporales necesarios. En ese caso debes recrearlos mediante pasos conocidos o declarar que no se puede continuar con certeza. El checkpoint debe señalar qué artefactos eran obligatorios para reanudar.

#Verificación que cambia una decisión

Antes de permitir ejecución, prueba un comando que intenta escribir fuera del workspace, abrir un destino de red no permitido y crear procesos hijos. Comprueba el efecto real, no solo el mensaje del harness. Si falla el aislamiento, reducir la confianza en el prompt no lo repara: cambia la política del runtime.

#Referencias

    ↑ ↓ para recorrer · Intro para abrir · Ctrl K para buscar