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:
| Werkzeug | Parameter | Verhalten |
|---|---|---|
read_file | path, offset?, limit? | Gibt den Dateiinhalt mit Zeilennummern zurück |
write_file | path, content | Datei erstellen/überschreiben (legt übergeordnete Verzeichnisse an) |
edit_file | path, old_string, new_string, replace_all? | Exakte Zeichenkettenersetzung |
glob | pattern, path? | Listet Dateien auf, die einem Glob entsprechen (z. B. src/**/*.rs) |
grep | pattern, path?, glob?, case_insensitive? | Regex-Inhaltssuche |
bash | command, timeout_secs? | Führt einen Shell-Befehl im Workspace-Root aus |
Zwei Sicherheitsverhalten, die man kennen sollte:
edit_fileerfordert eine vorherigeread_filedieser Datei in der Session, und standardmäßig muss der Zielstring genau einmal vorkommen (replace_allzum Ü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 Modus —
Workspace::read_only(..)blendet die verändernden Tools vollständig aus (das Modell sieht nur nochread_file/glob/grep). -
bash-Umgebung wird geleert — der Befehl erhält nurPATH,HOME,LANG,LC_ALL,TMPDIR,TERM,USERundSHELL, sodass Provider-API-Schlüssel, die der Agentenprozess hält, mitenvnicht lesbar sind.Workspace::inherit_env(true)stellt das alte Alles-durchreichen-Verhalten wieder her, undenv_allowlistersetzt 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:
| Erzwingt | Nicht erzwungen |
|---|---|
| File-Tools können außerhalb des Root-Verzeichnisses nicht auflösen, auch nicht über Symlinks | bash kann weiterhin absolute Pfade verwenden — das Arbeitsverzeichnis ist keine OS-Grenze |
| Der Befehl kann die Umgebungsvariablen des Agents nicht lesen | Der Befehl kann auf das Netzwerk zugreifen |
| Ein Timeout beendet den Befehl und seine Nachkommen | Nichts 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 →