Files
agents_and_robots/dev/issues/0031-expand-file-tools.md
T
egutierrez 8f6958f856 chore: agregar issues 0026-0032 y worktrees a gitignore
Registra los nuevos issues pendientes en el indice y excluye
la carpeta worktrees/ del control de versiones.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-09 00:20:53 +00:00

7.8 KiB

0031 — Expandir tools/file/ con write, list, append, delete

Objetivo

Ampliar el paquete tools/file/ con operaciones de escritura, listado, append y borrado. Mantener el patron deny-by-default, validacion de symlinks, y respetar el flag read_only del config.

Contexto

  • tools/file/file.go actualmente solo tiene read_file (107 lineas)
  • Seguridad existente: deny-by-default, symlink resolution via EvalSymlinks, output truncation a 64KB
  • Helpers existentes: validatePath() y resolveReal() ya estan listos para reutilizarse
  • Config FileOpsCfg tiene campos AllowedPaths []string y ReadOnly bool — ReadOnly ya existe pero no se usa porque no hay operaciones de escritura
  • Los agentes necesitan interactuar con carpetas de trabajo (workspaces, proyectos, outputs)

Arquitectura

tools/file/file.go          → mantener read_file + validatePath (existente)
tools/file/write.go         NEW → write_file tool
tools/file/list.go          NEW → list_directory tool
tools/file/append.go        NEW → append_file tool
tools/file/delete.go        NEW → delete_file tool
tools/file/file_test.go     → ampliar tests existentes
tools/file/write_test.go    NEW → tests de escritura
tools/file/list_test.go     NEW → tests de listado
tools/file/delete_test.go   NEW → tests de borrado
agents/runtime.go           → registrar nuevas tools en buildToolRegistry()

Patron pure core / impure shell

  • pkg/ — sin cambios
  • tools/file/ — cada tool sigue el patron Def (puro) + Exec (impuro)
  • agents/ — solo cambio en registro de tools

Tareas

Fase 1: Refactor de validacion compartida

  • 1.1 Extraer validatePath() y resolveReal() a tools/file/validate.go (ya son funciones internas, solo moverlas para reutilizarlas)
  • 1.2 Crear helper validateWritePath(absPath, cfg) que ademas verifica ReadOnly == false

Fase 2: write_file

  • 2.1 Crear tools/file/write.go con NewWriteFile(cfg) tools.Tool
  • 2.2 Parametros: path (string, required), content (string, required)
  • 2.3 Validaciones:
    • Rechazar si cfg.ReadOnly == true
    • validatePath() contra AllowedPaths
    • Crear directorios padre si no existen (os.MkdirAll)
    • Limite de contenido: max 1MB de input
  • 2.4 Devolver confirmacion con bytes escritos y path

Fase 3: list_directory

  • 3.1 Crear tools/file/list.go con NewListDirectory(cfg) tools.Tool
  • 3.2 Parametros: path (string, required), recursive (boolean, optional, default false)
  • 3.3 Validaciones:
    • validatePath() contra AllowedPaths
    • Limite de entries: max 500 archivos en el output
    • No seguir symlinks fuera de AllowedPaths
  • 3.4 Output: lista con nombre, tamaño, tipo (file/dir), fecha modificacion

Fase 4: append_file

  • 4.1 Crear tools/file/append.go con NewAppendFile(cfg) tools.Tool
  • 4.2 Parametros: path (string, required), content (string, required)
  • 4.3 Validaciones:
    • Rechazar si cfg.ReadOnly == true
    • validatePath() contra AllowedPaths
    • Si el archivo no existe, crearlo (igual que write)
    • Limite de tamaño: verificar que archivo existente + contenido nuevo < 10MB
  • 4.4 Devolver confirmacion con bytes añadidos y tamaño total

Fase 5: delete_file

  • 5.1 Crear tools/file/delete.go con NewDeleteFile(cfg) tools.Tool
  • 5.2 Parametros: path (string, required)
  • 5.3 Validaciones:
    • Rechazar si cfg.ReadOnly == true
    • validatePath() contra AllowedPaths
    • Solo archivos: no permitir borrar directorios (prevencion de rm -rf accidental)
    • Resolver symlinks antes de borrar (no borrar el symlink si apunta fuera de AllowedPaths)
  • 5.4 Devolver confirmacion con path eliminado

Fase 6: Registro en runtime

  • 6.1 En agents/runtime.gobuildToolRegistry(), registrar las 4 tools nuevas condicionalmente:
    if cfg.Tools.FileOps.Enabled {
        reg.Register(file.NewReadFile(cfg.Tools.FileOps))
        if !cfg.Tools.FileOps.ReadOnly {
            reg.Register(file.NewWriteFile(cfg.Tools.FileOps))
            reg.Register(file.NewAppendFile(cfg.Tools.FileOps))
            reg.Register(file.NewDeleteFile(cfg.Tools.FileOps))
        }
        reg.Register(file.NewListDirectory(cfg.Tools.FileOps))
    }
    
  • 6.2 list_directory se registra siempre (no requiere escritura)

Fase 7: Tests

  • 7.1 Test: write_file crea archivo nuevo en AllowedPaths
  • 7.2 Test: write_file rechaza si ReadOnly es true
  • 7.3 Test: write_file rechaza paths fuera de AllowedPaths
  • 7.4 Test: write_file rechaza contenido > 1MB
  • 7.5 Test: list_directory lista correctamente archivos y subdirectorios
  • 7.6 Test: list_directory respeta limite de 500 entries
  • 7.7 Test: list_directory no sigue symlinks fuera de AllowedPaths
  • 7.8 Test: append_file añade contenido al final
  • 7.9 Test: append_file crea archivo si no existe
  • 7.10 Test: delete_file borra archivo existente
  • 7.11 Test: delete_file rechaza borrar directorios
  • 7.12 Test: delete_file rechaza si ReadOnly es true
  • 7.13 Test: symlink que apunta fuera de AllowedPaths es rechazado en todas las tools
  • 7.14 Test: path traversal (../) es rechazado en todas las tools

Fase 8: Cleanup

  • 8.1 Actualizar CLAUDE.md seccion de tools con las nuevas herramientas
  • 8.2 Actualizar .claude/rules/create_tool.md si hay nuevos patrones
  • 8.3 go build -tags goolm ./... y go test -tags goolm ./...

Ejemplo de uso

Config del agente:

tools:
  file:
    enabled: true
    allowed_paths:
      - "/home/ubuntu/workspace/proyecto-x"
    read_only: false

Interaccion en Element:

Usuario: Lista los archivos en /home/ubuntu/workspace/proyecto-x/src
Bot: [usa list_directory] Encontre 12 archivos:
  - main.go (2.3 KB, 2026-04-01)
  - handler.go (1.1 KB, 2026-04-02)
  - ...

Usuario: Escribe un archivo test.txt con "hola mundo"
Bot: [usa write_file] Archivo creado: /home/ubuntu/workspace/proyecto-x/test.txt (10 bytes)

Usuario: Añade una linea mas al test.txt
Bot: [usa append_file] Contenido añadido: 15 bytes (total: 25 bytes)

Usuario: Borra el test.txt
Bot: [usa delete_file] Archivo eliminado: /home/ubuntu/workspace/proyecto-x/test.txt

Intento fuera de AllowedPaths:

Usuario: Lee /etc/passwd
Bot: Error: path "/etc/passwd" not under any allowed path

Decisiones de diseno

  • ReadOnly como gate: write_file, append_file, delete_file solo se registran si ReadOnly == false. read_file y list_directory siempre se registran si file tools esta habilitado
  • Solo archivos en delete: borrar directorios es demasiado peligroso para un agente autonomo. Si necesita borrar un directorio, puede borrar archivos uno por uno
  • Limites de tamaño: 1MB para write (evita saturar disco), 64KB para read output (evita saturar contexto LLM), 500 entries para list (evita listados enormes)
  • Crear padres automaticamente: write_file hace MkdirAll para crear la estructura de directorios. Simplifica el uso sin riesgo de seguridad (los paths padre tambien estan bajo AllowedPaths)
  • Reutilizar validatePath(): misma logica de seguridad para todas las operaciones. Un solo punto de validacion

Prerequisitos

  • Ninguno

Riesgos

  • Escritura accidental: un agente con LLM podria decidir escribir archivos incorrectos. Mitigacion: AllowedPaths restringe donde puede escribir, y el system prompt debe instruir al agente sobre cuando escribir
  • Race conditions: dos agentes escribiendo el mismo archivo. Mitigacion: file locking no es necesario en la primera version; los agentes tipicamente tienen workspaces separados
  • Disk exhaustion: un agente escribiendo en loop. Mitigacion: rate limiting del tool registry + limite de 1MB por write