Dev Tools (adk-devtools)

adk-devtools ist das Toolset für den inneren Loop, das ein Coding-Agent benötigt — lesen, bearbeiten, suchen und ausführen —, wobei jede Operation auf ein Workspace-Verzeichnis beschränkt ist. Es ist ein eigenständiges, veröffentlichbares Crate, das nur von adk-core abhängt, sodass es sich mit jedem LlmAgent kombinieren lässt (der CodingAgent-Harness bindet es für dich ein).

Die Tools

DevToolset ist ein Toolset, das sechs Tools bündelt:

WerkzeugParameterVerhalten
read_filepath, offset?, limit?Gibt den Dateiinhalt mit Zeilennummern zurück
write_filepath, contentDatei erstellen/überschreiben (legt übergeordnete Verzeichnisse an)
edit_filepath, old_string, new_string, replace_all?Exakte Zeichenkettenersetzung
globpattern, path?Listet Dateien auf, die einem Glob entsprechen (z. B. src/**/*.rs)
greppattern, path?, glob?, case_insensitive?Regex-Inhaltssuche
bashcommand, timeout_secs?Führt einen Shell-Befehl im Workspace-Root aus

Zwei Sicherheitsverhalten, die man kennen sollte:

  • edit_file erfordert eine vorherige read_file dieser Datei in der Session, und standardmäßig muss der Zielstring genau einmal vorkommen (replace_all zum Überschreiben). Das schützt vor blinden Überschreibungen.
  • grep überspringt häufige Build-/VCS-Verzeichnisse (target, .git, node_modules, …) sowie Binär- und übergroße Dateien.

Das bash-Tool streamt stdout/stderr zeilenweise über ToolContext::emit_progress, während der Befehl läuft, sodass UIs ein Live-Terminal anzeigen können. Jedes Chunk trifft als partielles Event auf dem EventStream des Agents ein (erkennbar mit event.tool_progress_stream()); die vollständige Ausgabe wird weiterhin als finales Ergebnis des Tools zurückgegeben. Siehe das streaming_bash-Beispiel und Streaming-Fortschritt von einem Tool.

Die Workspace

Eine Workspace verankert jede Operation in einem Verzeichnis und erzwingt eine kleine Richtlinie:

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);
  • Pfad-Einschluss — jeder Pfad, der außerhalb des Roots aufgelöst wird, wird abgelehnt, sodass der Agent nicht ../../etc/... lesen oder schreiben kann. Der Einschluss wird gegen den aufgelösten Pfad erzwungen, nicht nur gegen den literalen: Ein Symlink, der nach außerhalb des Roots zeigt, wird abgelehnt, auch wenn er lexikalisch innerhalb liegt. Das deckt eine symlinkte letzte Komponente und ein symlinktes Elternverzeichnis ab, sodass auch die Erstellung über ein umgeleitetes Verzeichnis verweigert wird. Ein Symlink, dessen Ziel innerhalb des Workspaces bleibt, funktioniert weiterhin, da Repositories legitimerweise interne Links enthalten.

    Die Prüfung ist keine Sperre. Ein Symlink, der zwischen der Prüfung und dem anschließenden open platziert wird, würde dennoch verfolgt werden; dieses Zeitfenster zu schließen, erfordert descriptor-relative Traversierung mit plattformseitiger no-follow-Semantik. Behandle die Dateitools als Einschluss gegenüber einem Agenten, der sich verirrt, nicht als Isolation gegenüber einem Gegner, der gleichzeitig in den Workspace schreiben kann.

  • Schreibgeschützter ModusWorkspace::read_only(..) blendet die verändernden Tools vollständig aus (das Modell sieht nur noch read_file/glob/grep).

  • bash-Umgebung wird geleert — der Befehl erhält nur PATH, HOME, LANG, LC_ALL, TMPDIR, TERM, USER und SHELL, sodass Provider-API-Schlüssel, die der Agentenprozess hält, mit env nicht lesbar sind. Workspace::inherit_env(true) stellt das alte Alles-durchreichen-Verhalten wieder her, und env_allowlist ersetzt die Menge.

  • bash-Timeout + Ausgabebegrenzungen — lange oder geschwätzige Befehle werden begrenzt. Ein abgelaufener Befehl wird als Prozessgruppe beendet, sodass auch alles, was er gestartet hat, beendet wird; zuvor wurde nur das direkte Kind signalisiert und Nachkommen überlebten.

Direkte Verwendung

Binde den Toolsatz an jeden Agenten an:

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 zeigt nur die Tools an, die der Workspace erlaubt, sodass ein schreibgeschützter Workspace automatisch einen schreibgeschützten Agenten ergibt.

Sandbox-Modell

Phase 1 läuft bash host-lokal (sh -c, Arbeitsverzeichnis auf den Root festgelegt) mit einem Timeout und geleerter Umgebung. Was das dir gibt und was nicht:

ErzwingtNicht erzwungen
File-Tools können außerhalb des Root-Verzeichnisses nicht auflösen, auch nicht über Symlinksbash kann weiterhin absolute Pfade verwenden — das Arbeitsverzeichnis ist keine OS-Grenze
Der Befehl kann die Umgebungsvariablen des Agents nicht lesenDer Befehl kann auf das Netzwerk zugreifen
Ein Timeout beendet den Befehl und seine NachkommenNichts begrenzt Speicher oder CPU

Also ist es pfad-begrenzt, umgebungsisoliert und begrenzt, aber nicht betriebssystemisoliert. Das Policy-Vokabular stimmt mit adk-codes SandboxPolicy überein; für starke Isolation führe bash hinter einem containerisierten Executor aus (siehe das Design-Dokument). Kombiniere es mit adk-guardrail (Command-Allowlists, Secret-Redaction) und adk-auth für tokenisierte Tools (z. B. GitHub).

Weiter: Der Harness →

Dev Tools (`adk-devtools`) - ADK-Rust Dokumentation | ADK-Rust