Cristian PeraltaARCHITECTURE16 oct 2026 · 5 min read

Portabilidad no es copiar archivos: auditar 260 antes de mover uno

Portabilidad no es copiar archivos: auditar 260 antes de mover uno

Quería clonar mi setup de agentes en cualquier computadora. La auditoría dijo que el repo que iba a clonar mezclaba configuración, memoria y credenciales.

La idea que parecía razonable

Mi configuración de agentes vivía en un repositorio: skills, hooks, comandos, ajustes. La idea era obvia: clonarlo en la laptop nueva y listo. Antes de hacerlo corrí una auditoría de solo lectura, y por suerte fue antes y no después.

260 archivos, ninguno leído

El repo tenía 260 archivos rastreados. Los clasifiqué por ruta, nombre y autoridad, sin abrir el contenido de ninguno. Si no podía saber de quién era un archivo, no entraba.

area                                  archivos  tratamiento
------------------------------------  --------  ------------------------------------------
projects/                                  119  excluir memoria, revisar el resto aparte
skills/                                     69  separar propias de terceros
agents/                                     30  revisar; lo administrado se reinstala
commands/                                   15  revisar con la misma regla de nombres
agent-memory/                               11  excluir entero
hooks/                                       4  candidatos, sujetos a revisión
resto (plugins, mcp, ajustes, etc.)         12  reinstalar, generar local o reemplazar
------------------------------------  --------  ------------------------------------------
total                                      260

Cinco clases de tratamiento: excluir, candidato, administrado por terceros, local o adaptador, y sin resolver. La incertidumbre sobre quién escribió algo bloquea el activo.

El número que más pesó fue 119 sobre 260. Casi la mitad del repo era la carpeta de proyectos, y casi toda era memoria que los agentes habían ido escribiendo ahí. Estaba versionada porque el .gitignore nunca la excluyó.

Lo que encontró el scanner de secretos

Corrí un scanner de secretos sobre el historial completo y sobre el árbol actual. Encontró tres coincidencias de clave genérica: dos en archivos de memoria y una en una skill. Eran credenciales que nunca debieron entrar en Git.

Hubo una sorpresa menor pero instructiva: la versión local de la herramienta solo ocultaba el fragmento detectado, y una inspección posterior mostró el valor completo en la salida de mi sesión. Un redactor que oculta parte de un secreto no es un redactor seguro.

Limpié el árbol actual y verifiqué que el scanner devolviera cero hallazgos. El historial conserva los valores originales. Decidí no reescribirlo: el repo seguirá privado, de solo lectura, como fuente de migración, y se archiva cuando lo nuevo pueda instalar lo aprobado. Reescribir historia solo vuelve a discusión si el repo se comparte, se hace público o aparece un requisito de cumplimiento.

Quién manda sobre cada cosa

De la auditoría salió una regla: cada tipo de cosa tiene una autoridad.

  • La configuración declarativa va en Git.
  • La memoria operativa de los agentes va en Engram.
  • Los secretos se quedan en un almacén local, fuera de ambos.
  • Lo de terceros se reinstala desde su gestor original, no se copia.

Quién manda sobre cada cosa: Git, Engram, almacén local y el gestor de cada herramienta de terceros

Descarté copiar todo, reescribir la historia y escribir un CLI compilado. Para lo último alcanza un entrypoint en Bash con la librería estándar de Python.

Por qué no un gestor de dotfiles

Es la pregunta obvia. chezmoi, por ejemplo, ya resuelve secretos con cifrado age y plantillas con gestores de contraseñas, y tiene .chezmoiignore para excluir archivos. No fue por ahí.

Mi razón es de modelo. Un gestor de dotfiles ve archivos para sincronizar. No tiene el concepto de que un agente va a seguir escribiendo memoria dentro de la misma carpeta que quiero versionar, ni de que uso varios runtimes (Claude, Codex, Pi, OpenCode) con preferencias distintas. Esa es mi lectura de la documentación, no una afirmación de que ninguna herramienta pueda hacerlo.

Esa fue la razón para construir agent-setup. Es lo que dije en agosto sobre filtrar por el por qué antes de abrir el editor, aplicado a mí.

Primero leer, después escribir

agent-setup empezó el 2 de agosto, el mismo día de la auditoría, como un control plane sin dependencias que solo leía. status y doctor verifican que el manifiesto sea válido y que estén las herramientas requeridas. Después vino plan, también de solo lectura.

$ agent-setup doctor workstation
Manifest: valid (schema 2, version 0.7.0)
Profile: workstation
Required: git (present)
Required: python3 (present)
Optional: node (present)

Tres días separan el primer commit de solo lectura del primer apply: del 2 al 5 de agosto. Primero había que poder declarar lo que debería existir y detectar drift. Mutar venía después.

Cómo se aplica algo

apply no hace nada sin que se apruebe un plan exacto. El plan es de solo lectura y su hash es el SHA-256 de un JSON canónico (claves ordenadas, activos por id, rutas relativas a HOME). Cada acción es noop, create, update o conflict. Un solo conflicto vuelve no aplicable todo el plan.

$ agent-setup plan laptop
Profile: laptop
Plan hash: <sha-256 del plan>

$ agent-setup apply laptop --approve-plan <hash>

Al aplicar, recalcula el plan bajo un lock y exige el mismo hash. Si algo cambió entre planear y aplicar, lo rechaza como obsoleto. Cada transacción deja un journal con fases monótonas: prepared, applying, backed-up, installed, committed. Las escrituras son atómicas y hacen fsync del archivo y de su directorio. create nunca pisa un archivo existente, y update hace primero un backup exacto.

Si una aplicación queda a medias, recover <txid> la revierte. Si ya se confirmó, rollback <txid> deshace esa transacción. Ambos se niegan si detectan drift, backups cambiados o una procedencia ajena, y no existe un "el último" implícito: siempre se nombra la transacción. La salida solo muestra datos permitidos, nunca contenidos, rutas absolutas ni variables de entorno.

Tiene límites declarados. Aplica un activo por vez, así que un apply de varios activos es recuperable pero no atómico globalmente. El lock solo cubre procesos de agent-setup.

Dónde estoy hoy

No es el único camino todavía. La laptop la armé combinando agent-setup, mis dotfiles y trabajo manual: las configuraciones de Claude, Pi y OpenCode con sus skills llegaron por una mezcla de esas tres vías. El control plane declara y verifica, pero aún no reemplaza todo el setup activo, y el repo viejo sigue como fuente de migración.

Portabilidad no es copiar archivos. Es decidir quién manda sobre cada cosa antes de mover el primero, y construir la herramienta para que esa decisión se pueda verificar.