Herramientas de desarrollo (adk-devtools)

adk-devtools es el conjunto de herramientas de ciclo interno que necesita un agente de programación: leer, editar, buscar y ejecutar, con cada operación limitada a un directorio de trabajo. Es un crate independiente y publicable que depende solo de adk-core, por lo que se compone con cualquier LlmAgent (el harness de CodingAgent lo conecta por ti).

Las herramientas

DevToolset es un Toolset que agrupa seis herramientas:

HerramientaParámetrosComportamiento
read_filepath, offset?, limit?Devuelve el contenido del archivo, con numeración de líneas
write_filepath, contentCrear/sobrescribir un archivo (crea directorios padre)
edit_filepath, old_string, new_string, replace_all?Reemplazo exacto de cadena
globpattern, path?Lista archivos que coinciden con un glob (p. ej. src/**/*.rs)
greppattern, path?, glob?, case_insensitive?Búsqueda de contenido con expresiones regulares
bashcommand, timeout_secs?Ejecuta un comando de shell en la raíz del espacio de trabajo

Dos comportamientos de seguridad que conviene conocer:

  • edit_file requiere una read_file previa de ese archivo en la sesión, y por defecto la cadena objetivo debe aparecer exactamente una vez (replace_all para anularlo). Esto protege contra sobrescrituras ciegas.
  • grep omite los directorios comunes de compilación/VCS (target, .git, node_modules, …) y los archivos binarios o de tamaño excesivo.

La herramienta bash transmite su stdout/stderr línea por línea mediante ToolContext::emit_progress a medida que se ejecuta el comando, de modo que las UI pueden mostrar una terminal en vivo. Cada fragmento llega como un evento parcial en el EventStream del agente (detectarlo con event.tool_progress_stream()); la salida completa sigue devolviéndose como resultado final de la herramienta. Ver el ejemplo streaming_bash y Streaming Progress from a Tool.

El Workspace

Un Workspace ancla cada operación a un directorio y aplica una pequeña política:

use adk_devtools::Workspace;
use std::time::Duration;

let ws = Workspace::new("./my-repo");              // read-write, bash enabled
let ws = Workspace::read_only("./my-repo");        // explore/plan: no writes, no bash
let ws = Workspace::new("./my-repo")
    .allow_bash(false)                              // file edits, but no shell
    .bash_timeout(Duration::from_secs(60))
    .max_output_bytes(512 * 1024);
  • Contención de ruta — cualquier ruta que se resuelva fuera de la raíz se rechaza, así que el agente no puede leer ni escribir ../../etc/.... La contención se impone contra la ruta resuelta, no solo la literal: un enlace simbólico que apunte fuera de la raíz se rechaza aunque esté lexicográficamente dentro. Eso cubre un componente final enlazado simbólicamente y un directorio padre enlazado simbólicamente, así que también se deniega la creación a través de un directorio redirigido. Un enlace simbólico cuyo destino permanezca dentro del espacio de trabajo sigue funcionando, ya que los repositorios contienen legítimamente enlaces internos.

    La comprobación no es un bloqueo. Un enlace simbólico insertado entre la comprobación y la posterior apertura todavía se seguiría; cerrar esa ventana requiere recorrido relativo al descriptor con semántica de no-follow de la plataforma. Trata las herramientas de archivos como contención frente a un agente que deambula, no como aislamiento frente a un adversario que puede escribir en el espacio de trabajo de forma concurrente.

  • Modo de solo lecturaWorkspace::read_only(..) oculta por completo las herramientas que modifican (el modelo solo ve read_file/glob/grep).

  • Entorno de bash limpiado — el comando recibe solo PATH, HOME, LANG, LC_ALL, TMPDIR, TERM, USER y SHELL, así que las claves de API del proveedor que mantiene el agente no son legibles con env. Workspace::inherit_env(true) restaura el antiguo comportamiento de pasar todo, y env_allowlist reemplaza el conjunto.

  • Tiempo de espera + límites de salida de bash — los comandos largos o muy verbosos quedan limitados. Un comando que agota el tiempo se mata como un grupo de procesos, así que todo lo que inició también se mata; antes solo se señalaba al hijo directo y los descendientes sobrevivían.

Uso directo

Adjunta el conjunto de herramientas a cualquier agente:

use adk_devtools::{DevToolset, Workspace};
use adk_agent::LlmAgentBuilder;
use std::sync::Arc;

let agent = LlmAgentBuilder::new("coder")
    .model(model)
    .toolset(Arc::new(DevToolset::new(Workspace::new("./my-repo"))))
    .build()?;

DevToolset solo expone las herramientas que permite el espacio de trabajo, así que un espacio de trabajo de solo lectura genera automáticamente un agente de solo lectura.

Modelo de aislamiento

La fase 1 ejecuta bash en el host local (sh -c, directorio de trabajo fijado a la raíz) con un tiempo de espera y un entorno limpiado. Qué ofrece esto y qué no ofrece:

Imposible de hacer cumplirNo se impone
Las herramientas de archivos no pueden resolver fuera de la raíz, incluso a través de enlaces simbólicosbash todavía puede usar rutas absolutas — el directorio de trabajo no es un límite del sistema operativo
El comando no puede leer las variables de entorno del agenteEl comando puede acceder a la red
Un tiempo de espera termina el comando y sus descendientesNada limita la memoria ni la CPU

Así que está contenido en la ruta, aislado del entorno y acotado, pero no aislado del sistema operativo. El vocabulario de la política se alinea con adk-code's SandboxPolicy; para un aislamiento fuerte, ejecute bash detrás de un ejecutor contenedorizado (consulte el documento de diseño). Combine con adk-guardrail (listas de अनुमति de comandos, redacción de secretos) y adk-auth para herramientas con tokens (p. ej. GitHub).

Siguiente: El harness →