Kontexttechnik in der Praxis: Ein Forschungsagent mit dem Claude Agent SDK
Entwickeln Sie einen Python-Forschungsagenten mit dem Claude Agent SDK unter Verwendung der Muster write, select, compress und isolate und vergleichen Sie ihn anschließend in getesteten Ausführungen mit einem einfachen Baseline-Modell.
Quellen: Context Engineering in Practice: Building a Production AI Agent with the Claude Agent SDK
Ein Forschungsagent stößt selten auf nur eine schwierige Anweisung. Schlimm ist vielmehr die Ansammlung solcher Anweisungen. Im Laufe einer einzigen Untersuchung füllt sich der Kontext mit Anweisungen des Benutzers, einem Plan, Suchanfragen, heruntergeladenen Seiten, Tool-Schemata, Quellmetadaten, halbfertigen Aussagen, Berichten von Hilfsprogrammen sowie früheren Schritten. Wenn all das aktiv bleibt, muss sich gutes Beweismaterial mit dem eigenen Rückstand an Aufgaben des Agents um die Aufmerksamkeit streiten.
Hier wird das Framework „Write, Select, Compress and Isolate“ zu einer funktionsfähigen Python-Architektur im Claude Agent SDK. Ein einzelner Agent wird Schritt für Schritt durch bessere Entscheidungen verbessert, und jede Grenze stellt dieselbe Frage: Was muss das Modell in diesem Moment sehen, und was darf außerhalb davon bleiben? Das Ziel ist ein betriebsfähiger Forschungsagent – nicht nur eine Auflistung der SDK-Funktionen.
Der Aufbau erfolgt in zehn Schritten:
- Definieren, was gebaut werden soll.
- Eine einfache Grundlage schaffen.
- Schreiben: Den Arbeitszustand aus dem Gespräch herausnehmen.
- Auswählen: Ein begrenztes Arbeitsset zusammenstellen.
- Komprimieren: Lange Sitzungen wiederherstellbar machen.
- Isolieren: Fokussierte Untersuchungen delegieren.
- Schwerwiegende Tool-Ausgaben im Umfeld isolieren.
- Kontext über Sitzungen hinweg mitnehmen.
- Den kontextbasiert konstruierten Agenten zusammenstellen.
- Die beiden Architekturen vergleichen.
Was wird erstellt
Angesichts einer Frage plant der Agent seine Recherche, durchsucht Primärquellen, überträgt spezifische Untersuchungen an Helfer, protokolliert Beweise und erstellt schließlich einen zitierten Bericht. Die Beispielaufgabe besteht darin, die wichtigsten Langzeitgedächtnismethoden zu vergleichen, die in produktiven KI-Agenten verwendet werden, und zu erläutern, was jede Methode kostet. Dafür sind wissenschaftliche Arbeiten, Implementierungsunterlagen, Repository-Daten sowie Zitierprüfungen erforderlich – dadurch ist die Aufgabe breit genug, um eine echte Prüfung darzustellen.
Der leitende Agent behält den Plan bei und erstellt die endgültige Zusammenfassung. Der sorgfältig verwaltete Arbeitszustand liegt in Dateien vor, wobei Beschränkungen die Menge an einfließendem Wissen begrenzen, und die Unteragenten bearbeiten kleine Fragen innerhalb ihrer eigenen Kontexte. All das ist nicht im SDK enthalten. Es stellt das Modell sowie den Tool-Loop bereit, und die Anwendung entscheidet, wo der Zustand gespeichert wird und wie viel Informationen jede Grenze passieren dürfen.
Die erste Abbildung zeigt das Ausgangssystem: einen leitenden Agenten, der mit Tools, Wissen, einem Notizblock sowie Unteragenten verbunden ist. Der Rest der Architektur verändert die Art und Weise, wie Informationen über diese Verbindungen fließen.
Beginnen Sie damit, eine Umgebung zu erstellen und eine festgelegte SDK-Version zu installieren, bevor Sie irgendein Modul ausführen.
# terminal
python3 -m venv .venv
source .venv/bin/activate
python -m pip install claude-agent-sdk==0.2.139
export ANTHROPIC_API_KEY="your-api-key"
Die Struktur trennt die Konfiguration der Agenten von den Arbeitsplatzoperationen, benutzerdefinierten Tools, Hooks und Messungen. Diese Trennung ist wichtig, da jede Aktion im jeweiligen Kontext nur eine Grenze verändert, ohne dass eine völlig neue Anwendung erforderlich ist.
# code/
research_agent/
config.py # naive and engineered ClaudeAgentOptions
hooks.py # pre-compaction checkpoint
metrics.py # message-stream and context measurements
runner.py # repeated runs and comparison
tools.py # in-process MCP tools
workspace.py # scratchpad and bounded retrieval
knowledge/
memory_approaches.json
tests/
CLAUDE.md
Der Kontextengineering beginnt mit der Zuweisung von Verantwortlichkeiten. Das Gespräch enthält die aktuelle Entscheidung, während Dateien, Tools, Abrufsysteme und Unteragenten alles enthalten, was im Moment nicht aktiv sein muss.
Die naive Grundlage
Das kleinste Agent-SDK-Programm übergibt den Steuerungslauf an query(). Claude erhält die Anfrage, entscheidet, ob ein Tool benötigt wird, beobachtet das Ergebnis und wiederholt den Vorgang, bis es eine Antwort liefert oder eine konfigurierte Grenze erreicht. Da das SDK diesen Zyklus bereitstellt, muss die Anwendung ihre eigene Parsing- und Dispatch-Logik für Toolaufrufe nicht schreiben.
# research_agent/minimal.py
import asyncio
from claude_agent_sdk import ClaudeAgentOptions, ResultMessage, query
QUESTION = "Compare approaches for long-term memory in production AI agents."
async def main() -> None:
options = ClaudeAgentOptions(
model="sonnet",
allowed_tools=["WebSearch", "WebFetch"],
permission_mode="dontAsk",
max_turns=20,
)
async for message in query(prompt=QUESTION, options=options):
if isinstance(message, ResultMessage):
print(message.result or "")
asyncio.run(main())
Dieses kurze Programm verbirgt etwas: query() führt die Schleife aus, doch die Anwendung entscheidet weiterhin darüber, was hineingegeben wird. Die ausführbare Grundversion in research_agent/config.py legt diese Auswahlmöglichkeiten detailliert dar.
# research_agent/config.py
def naive_options(*, run_root, server, model, max_budget_usd):
tools = ["Read", "Glob", "Grep", "WebSearch", "WebFetch"]
return ClaudeAgentOptions(
cwd=run_root,
model=model,
tools=tools,
allowed_tools=[*tools, "mcp__research__*"],
permission_mode="dontAsk",
mcp_servers={"research": server},
strict_mcp_config=True,
setting_sources=[],
system_prompt={
"type": "preset",
"preset": "claude_code",
"append": NAIVE_PROMPT,
},
env={"ENABLE_TOOL_SEARCH": "false"},
max_turns=20,
max_budget_usd=max_budget_usd,
)
In der Baseline gibt es kein Schreibwerkzeug, kein Agentenwerkzeug, keine agents-Einstellung, keinen Hook und kein Projektanweisungsfile. Der Lead muss die gesamte Untersuchung innerhalb eines Gesprächs abschließen. Die Einstellung ENABLE_TOOL_SEARCH=false verhindert außerdem, dass MCP-Schemata aufgeschoben werden. Die Systemanweisung vervollständigt die Richtlinie, indem sie dem Agenten mitteilt, das whole-corpus-Werkzeug aufrufen, die Erkenntnisse im Gespräch speichern und weder Arbeitsplatznotizen schreiben noch Aufgaben delegieren zu sollen.
Das whole-corpus-Werkzeug ist absichtlich sehr klein, denn das Problem liegt darin, was es zurückgibt, und nicht in der Implementierungsmethode.
# research_agent/tools.py
@tool(
"load_knowledge_corpus",
"Return the entire local memory-research collection. Intended only for the naive baseline.",
{},
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=False),
)
async def load_knowledge(_: dict[str, Any]) -> dict[str, Any]:
return _text_result(load_corpus(corpus_path))
Wenn Claude darauf aufruft, gibt das SDK das Ergebnis als UserMessage aus, der einen ToolResultBlock enthält. Der dortige „User“ ist eine Protokollrolle und keine weitere menschliche Nachricht. Dieser Block wird der Historie hinzugefügt, die an jeden nachfolgenden Modellschritt weitergegeben wird, sodass eine Websuche, drei heruntergeladene Seiten sowie das gesamte Korpus eine solche wiederkehrende Sequenz erzeugen.
UserMessage(question)
AssistantMessage(ToolUseBlock: load_knowledge_corpus)
UserMessage(ToolResultBlock: entire corpus)
AssistantMessage(ToolUseBlock: WebSearch + WebFetch)
UserMessage(ToolResultBlock: raw search and page content)
AssistantMessage(final report)
An dieser Abfolge ist an sich nichts falsch. Sie ist naiv, weil die Anwendung die Rohdaten niemals in ein sorgfältig gestaltetes externes Zustandsmodell umwandelt und sie auch niemals aus der aktiven Entwicklungsbahn entfernt.
Die zweite Abbildung zeigt den naiven Kontext, der Anweisungen, Tool-Definitionen, Rohseiten, Erkenntnisse sowie das Konversationshistorium in einem einzigen Fenster sammelt. Das Wachstum resultiert aus der Richtlinie der Anwendung und nicht aus irgendeinem Defekt im SDK-Loop.
Die Tool-Suche ist bei den meisten Agent-SDK-Einstellungen standardmäßig aktiv, wobei dokumentierte Ausnahmen existieren. Ein fairer Vergleichspunkt muss sie daher absichtlich deaktivieren, wenn das Experiment vorsieht, die gesamte Tool-Liste vorzu laden. Als Kontrollgruppe ist das naive Design nützlich, da es alle Entscheidungen zur Kontextverwaltung entfernt, die in späteren Schritten getestet werden sollen.
Bevor Zeit und Ressourcen in einen Vergleich investiert wurden, wurde ein Smoke-Test durchgeführt, bei dem eine eingeschränkte Anfrage über dieselbe Konfiguration gesendet wurde. Die erfasste Ausgabe zeigte, dass Authentifizierung, Routing sowie die Modellauflösung alle funktionierten.
# Captured output: python scripts/smoke_test.py
subtype=success
result=OPENROUTER_OK
models=claude-sonnet-5
Die vollständige Baseline wurde anschließend dreimal durchgeführt, wobei die Ergebnisse in der endgültigen Vergleichstabelle neben den modifizierten Versuchen aufgeführt sind. Der erste Versuch macht die Akkumulation konkret sichtbar.
# Captured output: python scripts/show_naive_trial.py ../measurements/comparison.json
Naive trial 1
SDK result: success
Assistant steps: 4
Tool calls: 6
WebFetch: 3
WebSearch: 1
mcp__research__load_knowledge_corpus: 1
mcp__research__search_knowledge: 1
Final active context: 16,478 tokens
Final tool-result payload: 6,851 tokens
Cumulative tree input: 138,182 tokens
Estimated cost: $0.754
Die letzten drei Werte messen unterschiedliche Dinge. Die Zahl 16.478 gibt den endgültigen aktiven Kontext an, wobei 6.851 dieser Tokens von Tool-Ergebnissen stammen. Die Zahl 138.182 repräsentiert die kumulierte Eingabe über alle vier Assistentenschritte, wobei auch die Erstellung und Lektüre von Caches mitgezählt werden; das bedeutet also nicht, dass der Agent zu einem bestimmten Zeitpunkt ein Fenster mit 138.182 Tokens hatte. Der Unterschied ist wichtig, sobald Subagenten in den Vergleich einbezogen werden.
Ein naiver Agent ist nur dann eine angemessene Kontrollgruppe, wenn Bequemlichkeiten wie die Tool-Suche ausdrücklich deaktiviert werden. Andernfalls können die aktuellen SDK-Einstellungen heimlich dazu führen, dass die Baseline intelligenter ist, als beabsichtigt.
Schreiben: Scratchpad-Dateien
Die erste Änderung fügt ein Notizbuch hinzu, das vom Dateisystem unterstützt wird. plan.md enthält die Frage, die Forschungsdimensionen sowie das, was bereits erledigt wurde und was noch aussteht. research_notes.md sammelt Behauptungen, Beweise und Widersprüche, sources.json enthält Zitatenmetadaten und open_questions.md macht offene Punkte sichtbar. Umfangreiches Quellmaterial wird in einen Ordner für Artefakte gelegt.
# research_agent/workspace.py
def initialize_workspace(root: Path, question: str) -> Path:
workspace = root / "workspace"
workspace.mkdir(parents=True, exist_ok=True)
(workspace / "artifacts").mkdir(exist_ok=True)
(workspace / "checkpoints").mkdir(exist_ok=True)
for name, template in WORKSPACE_FILES.items():
path = workspace / name
if not path.exists():
path.write_text(template.format(question=question), encoding="utf-8")
return workspace
Weil diese Dateien langlebig sind, kann der Agent sie nach jedem Forschungsschritt aktualisieren und nur die benötigte Datei lesen. Ein Absturz oder ein Komprimierungsereignis lässt den sorgfältig erstellten Plan sowie die Beweise unverändert, sodass eine Person den Zustand direkt lesen kann, ohne ihn aus einer Transkription zusammensetzen zu müssen.
Die dritte Abbildung stammt von einem Offline-Skript, das den Arbeitsbereich initialisiert und die genauen Dateien ausgibt, die der entwickelte Agent als Notizbrett verwendet.
Eine Sitzung dient einem weiteren Zweck. Die Sitzungsunterlagen beschreiben sie als Speicher für Anfragen, Toolaufrufe, Toolergebnisse sowie Antworten, die zur Fortsetzung eines Gesprächs benötigt werden. Das Notizbrett hingegen enthält den vom Anwendungsprogramm selbst erstellten, sorgfältig aufbereiteten Forschungszustand. Das Wiederaufnehmen einer Sitzung bringt eine bestimmte Vorgehensweise zurück, während das Lesen von plan.md eine absichtlich aufrechterhaltene Ansicht der Aufgabe liefert.
Diese Unterscheidung verhindert zwei häufige Fehler. Wenn die Transkripte die einzige Datenbank sind, hängt die Wiederherstellung von einer immer längeren Historie ab. Wenn das Scratchpad zu einem Dump der Transkripte wird, entsteht auf der Festplatte wieder das gleiche Kontextproblem. Daher erhält jede Datei ein eng gefasstes Schema und enthält Schlussfolgerungen, Beweise sowie unvollendete Arbeiten – nicht kopierte Gespräche. Die Sitzungshistorie dokumentiert, wie der Agent hierhergekommen ist, und das Scratchpad bewahrt den kleinen, sorgfältig ausgewählten Zustand auf, der benötigt wird, um zu entscheiden, was als Nächstes geschieht.
Selektion: ein begrenztes Arbeitsset
Der externe Zustand kann größer werden, als jede einzelne Modellaufruf-Operation aufnehmen sollte. Die Selektion behebt das Problem, indem sie je nach Phase lädt: Die Planung liest plan.md, die Synthese liest research_notes.md und die Erstellung von Zitaten liest sources.json. Auch Tools und Wissen folgen demselben Prinzip, da selbst ihre Metadaten einen erheblichen Teil der Eingabe einnehmen können.
Aus Sicht der Tools ermöglicht die konzipierte Struktur eine Suche nach Tools. In Anthropics Dokumentation heißt es, dass ein Katalog bis zu 10.000 Tools enthalten kann, wobei bei einer Suche standardmäßig bis zu fünf relevante Tools zurückgegeben werden, sofern nichts anderes eingestellt ist. Er umfasst sowohl MCP-Tools als auch benutzerdefinierte SDK-MCP-Tools, sodass ein großer Katalog außerhalb des Kontexts bleiben kann, bis Claude ein bestimmtes Schema anfordert. Bei etwa zehn oder weniger Tools sei es laut den Dokumenten oft schneller, alles im Voraus zu laden; dadurch wird die verzögerte Laden von Tools eher als Mittel zur Skalierung denn als garantierten Vorteil dargestellt.
Was das Wissen angeht, basiert das benutzerdefinierte Tool search_knowledge auf SQLite FTS5. Der Handler nimmt eine Abfrage sowie einen begrenzten Wert für top_k entgegen und gibt anstelle der gesamten Sammlung nur die zitierten Abschnitte zurück. Benutzerdefinierte Tools im SDK laufen über einen internen MCP-Server, wodurch das Beispiel zwar selbstständig bleibt, aber dennoch dieselben Grenzen überschreitet wie ein externer Dienst.
# research_agent/tools.py
@tool(
"search_knowledge",
"Search the local memory-research collection and return only the most relevant cited passages.",
{
"type": "object",
"properties": {
"query": {"type": "string", "minLength": 1},
"top_k": {"type": "integer", "minimum": 1, "maximum": 10},
},
"required": ["query", "top_k"],
"additionalProperties": False,
},
annotations=ToolAnnotations(readOnlyHint=True, openWorldHint=False),
)
async def search_knowledge(args: dict[str, Any]) -> dict[str, Any]:
try:
return _text_result(
search_corpus(corpus_path, args["query"], args["top_k"])
)
except (KeyError, TypeError, ValueError, sqlite3.Error) as error:
return _error_result(error)
Die Anfrage gibt top_k=5 an, während das Schema Werte von eins bis zehn zulässt. Auf diese Weise werden Richtlinien und feste Grenzen getrennt gehalten: Die Qualität der Datenerfassung kann später angepasst werden, ohne den Vertrag der Tool zu ändern, und eine Anfrage nach Tausenden von Einträgen wird abgelehnt, bevor sie den Kontext überfluten kann.
Die vierte Abbildung zeigt Dateiabrufe, Tool-Suche und Wissensabruf als drei Methoden zur Auswahl unterschiedlicher Arten von Kontext, die alle zum selben Ergebnis führen: ein begrenztes Arbeitsset für den aktuellen Schritt.
Ein Offline-Test führt den tatsächlichen Suchcode aus und überprüft, ob fünf als Obergrenze gilt, ob jedes zurückgegebene Element seine Quell-URL beibehält und ob schlechte top_k-Werte abgelehnt werden. Er sagt nichts über die Qualität der semantischen Suche aus, da das Testumfeld sehr klein ist. In der Produktion sollte diese Beurteilung vom Suchmechanismus selbst vorgenommen werden, gestützt auf bewertete Abfragen und Recall-Metriken – anstatt aus der Art und Weise abgeschlossen zu werden, wie der endgültige Text aussieht. Das Herausfiltern von Daten aus dem Dialog schafft Platz, doch die Auswahl bestimmt, ob der Agent weiterhin die fünf richtigen Elemente finden kann, sobald diese relevant werden.
Komprimieren: wiederherstellbare lange Sitzungen
Die Auswahl bestimmt, was eingefügt wird, doch eine lange Sitzung führt dazu, dass alte Austausche angesammelt werden. Wenn die Kontextgrenze näher rückt, komprimiert das Agent SDK den Verlauf automatisch: Ältere Nachrichten werden zu einem Zusammenfassung, während neuere Antworten sowie wichtige Zustände beibehalten werden. In den Datenstrom tritt eine SystemMessage mit dem Untertyp compact_boundary ein, wodurch der Messungskode jedes Mal zählen kann, wenn dies geschieht.
Ein allgemeines Zusammenfassung könnte genau die Fakten weglassen, auf die eine Forschungsarbeit angewiesen ist. Deshalb legt das Projekt spezifische Aufbewahrungsregeln für Forschungen in CLAUDE.md fest: Die ursprüngliche Frage, überprüfte Erkenntnisse, offene Fragen, Widersprüche, Quell-URLs, wichtige Entscheidungen sowie der aktuelle Plan müssen erhalten bleiben. Die Datei bestimmt, was im Zusammenfassung berücksichtigt wird, sodass nicht der gesamte Notizblock bei jedem Aufruf mitübertragen werden muss.
Vor jeder Kompression, sei sie automatisch oder manuell, speichert ein PreCompact-Hook einen Checkpoint. Sobald die SDK-Transkriptversion verfügbar ist, wird sie kopiert, und Metadaten geben den Auslöser, die Sitzung, das Arbeitsverzeichnis sowie die Herkunft der Transkriptversion an.
# research_agent/hooks.py
def build_precompact_hook(workspace: Path):
async def archive_before_compaction(
input_data: dict[str, Any],
tool_use_id: str | None,
context: Any,
) -> dict[str, Any]:
del tool_use_id, context
checkpoint_dir = workspace / "checkpoints"
checkpoint_dir.mkdir(parents=True, exist_ok=True)
timestamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
session_id = input_data["session_id"]
trigger = input_data["trigger"]
stem = f"{timestamp}-{session_id}-{trigger}"
transcript = Path(input_data["transcript_path"])
metadata = {
"session_id": session_id,
"trigger": trigger,
"created_at": datetime.now(timezone.utc).isoformat(),
"source_transcript": str(transcript),
"custom_instructions": input_data.get("custom_instructions"),
"archived": transcript.is_file(),
}
if transcript.is_file():
shutil.copy2(transcript, checkpoint_dir / f"{stem}.jsonl")
(checkpoint_dir / f"{stem}.json").write_text(
json.dumps(metadata, indent=2) + "\n", encoding="utf-8"
)
return {}
return archive_before_compaction
Die fünfte Abbildung zeigt, wie die Komprimierung den angesammelten Chat in einen Zusammenfassungstext umwandelt, der die Erhaltungsregeln einhält, während das Checkpoint und das Scratchpad als unabhängige Aufzeichnungen im System verbleiben.
Scratchpads und Komprimierung erfüllen unterschiedliche Zwecke. Das Scratchpad ist ein Zustand, den der Agent absichtlich im Laufe des Vorgangs schreibt und zur späteren Lesung organisiert. Die Komprimierung ist eine verlustbehaftete Zusammenfassung des angesammelten Chats, die entsteht, weil der aktive Historiebereich zu groß geworden ist. Mit beiden Mechanismen kann eine komprimierte Sitzung auf sources.json oder research_notes.md zurückgreifen, falls im Zusammenfassungstext etwas verloren gegangen ist.
In einem Offline-Test wurde dem Hook ein temporärer Transkript bereitgestellt, wodurch drei Dinge bestätigt werden konnten: Der Checkpoint erschien, das Transkript wurde kopiert und die Trigger-Metadaten blieben erhalten. Bei den sechs Live-Vergleichsabläufen tauchte niemals ein compact_boundary auf, da der größte sichtbare Kontext 34.309 Token umfasste, während ein angegebener Fensterumfang von 200.000 Token vorlag. Somit bestätigt das Experiment, dass der Hook funktioniert, liefert aber keine Informationen darüber, wie gut die Live-Kompaktion ist. Die Zusammenfassung sollte Entscheidungen sowie ungelöste Aufgaben enthalten, während das Scratchpad die Beweise in einer wiederlesbaren Form speichern sollte.
Isolieren: fokussierte Subagenten
Unabhängige Ermittlungsstränge sind einfacher zu verwalten, wenn sie nicht denselben stetig wachsenden Verlauf teilen. Nachdem der leitende Ermittler den Plan ausgearbeitet hat, übernehmen drei benannte Unterermittler die papierbasierte Analyse, die Überprüfung der Beweismittel im Archiv sowie die Überprüfung von Quellenangaben. Jede Definition begrenzt die Aufgaben, die verwendeten Tools sowie die Art der Übergabe.
# research_agent/config.py
"paper-researcher": AgentDefinition(
description="Analyzes primary research papers for memory mechanisms and trade-offs.",
prompt=(
"Investigate only the assigned paper question. Use primary sources. "
"Return at most five findings, each with a URL and an explicit limitation."
),
tools=["WebSearch", "WebFetch", "mcp__research__search_knowledge"],
model=model,
),
In Code werden Subagenten mit AgentDefinition deklariert. Laut Anthropics Dokumentation zu Subagenten beginnt jeder von ihnen ein neues Gespräch mit eigenem Kontext, speichert dort seine Zwischennutzungsrufe sowie Ergebnisse und sendet nur eine endgültige Nachricht an den Elagenten zurück. Die Übergabe stellt somit eine echte Kontextgrenze dar, nicht lediglich eine Konvention innerhalb des bestehenden Fensters des Leitagenten.
Die sechste Abbildung zeigt, wie jeder Spezialist eine fokussierte Aufgabe sowie ein neues Fenster erhält. Der Leitagent sieht lediglich die prägnante Übergabemeldung und nicht die Suchvorgänge, heruntergeladenen Seiten oder Extraktionsschritte, die dazu geführt haben.
Die Isolierung kostet etwas. Drei Unteragenten könnten dieselben Quellen erneut entdecken, mehr Token verbrauchen und auf widersprüchliche Begrifflichkeiten kommen. Um das zu verhindern, fordern die Anweisungen primäre URLs, angegebene Einschränkungen sowie maximal fünf Erkenntnisse. Der Faktenprüfer sieht nur die umstrittenen Behauptungen und nicht den gesamten Auftrag, was seine Tätigkeit überprüfbar hält und das Risiko verringert, dass er die Zusammenfassung heimlich umschreibt.
Ein Elternteil muss in der Lage sein, das Ergebnis zu bewerten. Fünf Zeilen ohne Quellenangaben sind kompakt, aber unbrauchbar, während ein vollständiger Transkript des Kindes die Grenzen verwischt. Der Vertrag liefert hier Erkenntnisse, Zitate, Versionsbeschränkungen und Einschränkungen – die Felder, die der Leiter benötigt, um die Behauptungen gegeneinander abzuwägen. Das Verbergen des Gesprächs des Kindes allein reicht nicht aus, denn die Isolierung funktioniert nur, wenn das Ergebnis Beweise sowie Unsicherheiten enthält.
Isolierung von Ausgaben aufwändiger Tools
Bestimmte Payloads sollten nur als Referenzen die Grenze des Tools überschreiten. materialize_source schreibt ein ausgewähltes Dokument in workspace/artifacts/ und gibt dessen Pfad, die Größe in Bytes sowie eine kurze Vorschau zurück. Später kann Claude mit Dateifunktionen einen relevanten Bereich öffnen, sodass das gesamte Dokument niemals in einem einzigen Tool-Ergebnis landet.
# research_agent/workspace.py (full function; use the Gist when publishing)
def materialize_source(corpus_path: Path, workspace: Path, source_id: str) -> dict:
document = next(
(item for item in load_corpus(corpus_path) if item["source_id"] == source_id),
None,
)
if document is None:
raise ValueError(f"unknown source_id: {source_id}")
path = safe_artifact_path(workspace, f"{source_id}.txt")
content = (
f"Title: {document['title']}\n"
f"URL: {document['url']}\n"
f"Published: {document['published']}\n\n"
f"{document['text']}\n"
)
path.write_text(content, encoding="utf-8")
return {
"path": str(path),
"characters": len(content),
"preview": content[:240],
}
Das SDK verfügt über ein ähnliches Sicherheitsnetz für MCP-Ergebnisse. Die MCP-Dokumentation von Anthropic markiert Ausgaben mit mehr als 10.000 Tokens und legt 25.000 Tokens als Standardobergrenze fest; zu große Texte können auf die Festplatte geschrieben und im Kontext durch eine Dateiverweisung ersetzt werden. Diese Standardwerte dienen als Notfallmaßnahme. Durch die Rückgabe von Pfaden und Metadaten wird der Anwendung bereits weit vor Erreichen der Notfallgrenze ein stabiles Konzept geboten.
Die siebte Abbildung stammt von einem absichtlichen Fehlertest, bei dem versucht wird, über ../outside.txt aus dem Artifact-Verzeichnis herauszukommen. Die Arbeitsumgebung lehnt den Pfad ab, bevor etwas geschrieben wird.
Die Isolierung der Umgebung schafft außerdem eine Sicherheitsgrenze. Der von einem Modell gesteuerte Name eines Artefakts darf niemals zu einem willkürlichen Dateisystempfad werden. safe_artifact_path akzeptiert einen konservativen Dateinamen und überprüft, ob das resultierende Ziel weiterhin innerhalb von workspace/artifacts/ liegt. Der festgestellte Fehler sah so aus.
# Captured output: python scripts/demonstrate_failure.py
Blocked artifact path: artifact name must use only letters, numbers, dots, underscores, or hyphens
Die Prüfung macht keine Angaben zu Sandboxing des Betriebssystems oder zur Isolation mehrerer Nutzer. Sie bestätigt nur eine enge Regel: Ein Tool-Argument darf keine Pfadumgehungen nutzen, um außerhalb des Artefaktverzeichnisses zu schreiben. Tatsächliche Implementierungen benötigen weiterhin Prozessberechtigungen sowie getrennte Arbeitsräume für jeden Benutzer oder jede Ausführung. Das Ersetzen eines großen Datenträgers durch einen Pfad spart lediglich Speicherplatz, wenn der Pfad eingeschränkt ist und das Tool später nur einen begrenzten Teil abrufen kann.
Kontext über Sessions hinweg bewahren
Lange Aufgaben dauern länger als Prozesse sowie Kontextfenster. Das SDK bietet drei Optionen: Die aktuellste Konversation fortsetzen, eine bestimmte Session anhand der ID wieder aufnehmen oder eine Session in einen neuen Zweig spalten. „Fortsetzen“ dient für eine laufende Aufgabe, „Wieder aufnehmen“ bedeutet eine explizite Wiederherstellung, und „Spalten“ eignet sich für Nachfolgeaufgaben, die den früheren Kontext übernehmen sollen, ohne die zukünftige Entwicklung der ursprünglichen Session zu verändern.
# examples/session_modes.py
def session_options(session_id: str) -> dict[str, ClaudeAgentOptions]:
return {
"continue": ClaudeAgentOptions(continue_conversation=True),
"resume": ClaudeAgentOptions(resume=session_id),
"fork": ClaudeAgentOptions(resume=session_id, fork_session=True),
}
Jede Option bewahrt den Konversationszustand: Anfragen, Toolaufrufe, Ergebnisse und Antworten. Bei einem Fork wird eine neue Session-ID vergeben, während die ursprüngliche beibehalten bleibt. Das eignet sich für Anfragen wie „Vergleichen Sie jetzt diese Erkenntnisse insbesondere für Coding-Agents“, da die neue Abteilung auf der bestehenden Forschungsrichtung aufbauen kann.
In der achten Abbildung werden die Modi verglichen: „Continue“ erweitert die aktuelle Forschungsrichtung, „Resume“ stellt eine ausgewählte Richtung wieder her und „Fork“ startet einen neuen Zweig von derselben Checkpoint-Position aus.
Die auf der Festplatte gespeicherten Dateien erfordern eine eigene Handhabung. Durch ein Fork erstellte Branches teilen sich einen Arbeitsverzeichnis, es sei denn, die Anwendung weist ihnen ein anderes zu – dadurch könnten beide Dateien wie plan.md oder final_report.md überschrieben werden. Der Ausführungsmechanismus vermeidet solche Kollisionen, indem er einen eindeutigen Ausführungswurzelpfad aus einem UTC-Zeitstempel und einem zufälligen Suffix erstellt. Ein Produktiv-Fork sollte das Zwischenspeicherverzeichnis vor dem Schreiben durch eine der Branches kopieren oder in einer neuen Ausführungswurzel speichern. Denken Sie daran, dass eine Sitzungs-ID die Konversationshistorie benennt. Es handelt sich dabei nicht um eine Transaktions-ID für die externen Systeme, mit denen der Agent in Kontakt kam; daher benötigen Datenbanken, Objektspeicher, Suchindizes und Arbeitsräume jeweils eigene Verlaufs- und Konkurrenzrichtlinien. Die Dateistruktur sorgt dafür, dass dies sichtbar bleibt und es nicht hinter dem Wiederaufnahmeverhalten versteckt wird. Ein Fork trennt lediglich die Konversation, während der externe Zustand weiterhin geteilt bleibt, bis auch der Arbeitsraum versioniert oder aufgeteilt wird.
Zusammenbau des kontextbasiert konstruierten Agenten
Die fertige Konfiguration vereint alle vier Operationen und fügt kein weiteres Orchestrierungsframework hinzu. „Write“ ist der strukturierte Arbeitsraum. „Select“ beinhaltet phasenbasiertes Dateilesen, verzögerte Tool-Schemata sowie begrenzte Wissensabrufmechanismen. „Compress“ kombiniert SDK-Kompaktion mit Aufbewahrungsanweisungen sowie einem Checkpoint, der unmittelbar davor erstellt wird. „Isolate“ bedeutet, dass subagentenbasierte Strukturen sowie Tool-Ausgaben in Dateien gespeichert werden.
# research_agent/config.py
tools = ["Read", "Write", "Edit", "Glob", "Grep", "WebSearch", "WebFetch", "Agent"]
return ClaudeAgentOptions(
cwd=run_root,
model=model,
tools=tools,
allowed_tools=[*tools, "mcp__research__*"],
permission_mode="dontAsk",
mcp_servers={"research": server},
strict_mcp_config=True,
setting_sources=["project"],
system_prompt={"type": "preset", "preset": "claude_code", "append": ENGINEERED_PROMPT},
env={"ENABLE_TOOL_SEARCH": "true"},
hooks={"PreCompact": [HookMatcher(hooks=[build_precompact_hook(workspace)])]},
agents=research_subagents(model),
max_turns=30,
max_budget_usd=max_budget_usd,
)
Durch die Einstellung setting_sources=[„project“] kann das für den jeweiligen Ausführungsvorgang spezifische CLAUDE.md zur Konfiguration hinzugefügt werden, und strict_mcp_config=True beschränkt das SDK auf den Server, den diese Anwendung bereitstellt. Der permission_mode ist auf dontAsk gesetzt, weil das Beispiel unüberwachte, isolierte Ausführungen vorsieht und es sich dabei nicht um eine allgemeine Empfehlung bezüglich der Berechtigungen handelt. Fortgeschrittene Genehmigungsprozesse sowie Deployment- Richtlinien werden absichtlich außer Acht gelassen.
In der neunten Abbildung regelt die fertige Architektur vier Bereiche: dauerhafte Schreibvorgänge, selektive Lesevorgänge, Komprimierung der Konversation sowie isolierte Ausführung. Das Hauptmodell erhält lediglich einen fokussierten, aktiven Kontext.
Der finale Ausführungsprozess setzt auf ClaudeSDKClient und verzichtet auf den leichteren query()-Hilfsfunktion. Beide teilen sich denselben Agenten-Loop, doch der Client bietet eine explizite Antwortgrenze sowie einen abschließenden Aufruf von get_context_usage(). Die Instrumentierung führte zu dieser Entscheidung, und die Architektur blieb unverändert.
Der erste gezielte Versuch, der ausschließlich durch Anweisungen gesteuert wurde, zeigte eine weitere Produktionsgrenze auf. Alle drei SDK-Ausführungen waren abgeschlossen, doch nur eine erzeugte final_report.md; zwei führende Agenten gaben Ergebnisse zurück, während die delegierten Aufgaben faktisch unvollendet blieben. Ein sauberer SDK-Ausstieg bedeutet daher nicht, dass die Aufgabe der Anwendung abgeschlossen ist. Die Lösung bestand darin, die Aufrufe der Unteragenten explizit synchron zu gestalten und dem Controller einen eigenen Abschlussvertrag zur Verfügung zu stellen: Dieser sucht nach einem umfassenden Bericht, der mindestens zwei URLs enthält, und erlaubt innerhalb derselben Sitzung eine einzige, gezielte Reparaturanfrage.
# research_agent/runner.py
while True:
async for message in client.receive_response():
metrics.observe(message)
report_path = workspace / "final_report.md"
if mode == "naive" or _report_meets_contract(report_path):
break
if metrics.completion_retries >= MAX_COMPLETION_RETRIES:
break
metrics.completion_retries += 1
await client.query(COMPLETION_REPAIR_PROMPT)
Das fehlgeschlagene Ergebnis mit nur Prompt bleibt in measurements/initial-prompt-only-comparison.json, da ein Controller nachhaltige Ergebnisse überprüfen sollte und sie nicht aus dem Endzustand des Modells ableiten darf.
Der Offline-Testsuite kümmert sich um alles, was kein Modell benötigt: beide Optionensätze, die Erstellung von Arbeitsräumen, begrenzte Abfragen, die Ablehnung fehlerhafter Pfade, Komprimierungs-Checkpointe sowie die Verwaltung von Nachrichten. Das ist es, was sie in der angegebenen Umgebung berichtet hat.
# Captured output: python -m unittest discover -s tests -q
----------------------------------------------------------------------
Ran 12 tests in 0.048s
OK
Zu diesem Zeitpunkt ist das Framework betriebsbereit: Jedes Verb ist mit einer Grenze verbunden, die ihren eigenen Code sowie einen Verantwortlichen für ihren Zustand besitzt und über eine Invarianz verfügt, die in Tests überprüft werden kann.
Vergleich der beiden Architekturen
Aussagen zur Architektur sollten in Zahlen umgewandelt werden. RunMetrics überwacht den Nachrichtenstrom des SDK, entfernt auf höherer Ebene doppelte Assistentenschritte mithilfe von Nachrichten-ID‑ern, zählt jede Toolaufruf sowie jede Komprimierungsgrenze, erfasst die Anzahl der Zeichen in den Tool‑Ergebnissen und fordert schließlich ein Endkontextbild an. Die kumulative Modellnutzung im gesamten Agentenbaum, einschließlich der Subagenten, stammt von ResultMessage.model_usage, während total_cost_usd eine lokale Schätzung und kein Rechnungsdokument darstellt.
# research_agent/metrics.py
if isinstance(message, ResultMessage):
self.query_results += 1
self.session_id = message.session_id
self.result_subtype = message.subtype
result = message.result or ""
self.sdk_success = (
message.subtype == "success"
and bool(result.strip())
and "not logged in" not in result.lower()
)
self.estimated_cost_usd = (
(self.estimated_cost_usd or 0.0) + (message.total_cost_usd or 0.0)
)
self.result_usage = message.usage
self.model_usage = self._merge_model_usage(self.model_usage, message.model_usage)
self.total_tree_input_tokens = self._tree_input_tokens(self.model_usage)
if isinstance(message, SystemMessage) and message.subtype == "compact_boundary":
self.compactions += 1
Jede Architektur wurde unabhängig voneinander dreimal mit derselben gemeinsamen Frage, unter Verwendung desselben SDKs, Modell-Aliases, Anbieters sowie derselben lokalen Wissenskonfiguration ausgeführt. Die Aufzeichnungen pro Ausführung sowie die daraus berechneten Werte befinden sich in measurements/comparison.json, und ein kleines Anzeigeskript erzeugt aus dieser Datei die kompakte Tabelle.
# terminal
python scripts/show_comparison.py ../measurements/comparison.json
Die zehnte Abbildung zeigt die Durchschnittswerte für drei Runs und macht einen echten Kompromiss sichtbar. Die Isolierung verringerte die finale Ergebnismenge, die der Leiter sah, doch der Subagentenbaum verbrauchte deutlich mehr Token sowie einen höheren geschätzten Kostenwert. Der erfasste Ausgabeinhalt des Anzeigeskripts folgt.
# Captured output: python scripts/show_comparison.py ../measurements/comparison.json
Measured comparison - 3 runs per architecture
Metric Naive Engineered
Artifact success 3/3 3/3
SDK success 3/3 1/3
Mean tree input 102,210 737,036
Mean peak context 17,184 32,668
Final tool-result tokens 7,351 6,202
Mean subagents 0 3
Mean estimated cost $0.663 $3.270
Compactions 0 0
Erfolg gibt es in zwei Varianten. Erfolg im Sinne eines Artefakts bedeutet, dass das Ausgabevertragsmodell der Architektur erfüllt wurde: Für den naiven Agenten bedeutet das einen zurückgegebenen Bericht mit mindestens zwei unterschiedlichen URLs, für den entwickelten Agenten hingegen ein dauerhaftes final_report.md mit 500 oder mehr Zeichen sowie zwei URLs. Alle sechs Ausführungen haben dieses Ziel erreicht.
Der Erfolg des SDK setzt voraus, dass die endgültige ResultMessage-Unterart den Wert success hat. Naive Ausführungen schafften das in drei von drei Fällen, ingeniierte Ausführungen hingegen nur in einem von drei. Zwei ingeniierte Ausführungen erzeugten berichtete Ergebnisse, endeten jedoch mit dem Fehler error_max_budget_usd, wobei eine davon die einzige Reparaturanfrage des Controllers verwendete. Das Budgetbeispiel von Anthropic erklärt, dass die Ausgaben nach einem API-Aufruf überprüft werden, sodass eine Ausführung ihr Ergebnis erstellen und den konfigurierten Schätzwert noch vor Eintreffen des endgültigen Ergebnisses erfüllen kann. Betrachten Sie die Obergrenze als Schutzmaßnahme, nicht als Signal dafür, dass die Anwendung abgeschlossen ist.
Die Zahlen:
- Der Lead erhielt weniger Informationen. Die durchschnittlichen Anzahl der für den Lead sichtbaren Tool-Ergebniszeichen lag im Baseline-Zustand bei 28.572 und sank nach Isolierung auf 23.428 – ein Rückgang von 18,0 %. Der Anteil der Tool-Ergebnisse am endgültigen Kontextbild sank von 7.351 auf 6.202 Tokens (eine Verringerung um 15,6 %). In jedem durchgeführten Test kamen genau drei Subagenten zum Einsatz, und ihre Zwischensuchen traten niemals in die Entwicklungsbahn des Leads ein.
- Insgesamt stiegen die Kosten stark an. Die Gesamtanzahl der Eingaben im gesamten System lag zunächst bei durchschnittlich 102.210 Tokens und stieg danach auf 737.036 – ein Anstieg von 621,1 %. Die maximale Menge an Eingaben für Lead und Kontext lag durchschnittlich bei 17.184 Tokens und danach bei 32.668 Tokens, was einem Anstieg von 90,1 % entspricht. Die Kostenschätzung des SDK stieg von 0,663 $ auf 3,270 $ – ein Anstieg von 393,4 %.
Eine Komprimierung wurde nie angewandt, da jede Ausführung null Grenzwerte meldete. Das zeigt nicht, dass eine auf Erhalt des Kontexts ausgerichtete Komprimierung unwirksam ist. Es zeigt vielmehr, dass diese Aufgabe niemals in die 200.000-Token-Grenze kam. Das Experiment isoliert außerdem nicht den Wert der Tool-Suche, da das MCP-Katalog klein war und die verzögerte Entdeckung kaum Raum hatte, ihre Selektionskosten auszugleichen.
Was die Ergebnisse sagen und was nicht
Hier gibt es nichts, was eine pauschale Kritik an der Kontexttechnik darstellt. Die Daten zeigen, warum die vier Operationen selektiv eingesetzt werden sollten. Das Schreiben von Dateien sowie begrenzte Abfragen erzeugen einen dauerhaften Zustand, der überprüft werden kann. Isolation lohnt sich, wenn die Entwicklung eines Kindes sonst einen bereits sehr langen Hauptkontext verschmutzen würde – und sie ist niemals kostenlos. Bei dieser Aufgabengröße ist ein einzelner Hauptkontext mit einem Scratchpad durchaus das bessere Design, es sei denn, Vorteile in Bezug auf die Spezialqualität rechtfertigen die zusätzlichen Kosten.
Was übrig bleibt, ist ein funktionsfähiges Artefakt mit einer Kontextarchitektur, einem lokalen Fehler und klaren Bewertungsgrenzen. Eine sinnvolle Nachfrage betrifft die Qualität: Ist das geschriebene, ausgewählte, komprimierte und isolierte Material tatsächlich das richtige Material? Die Beantwortung erfordert Bewertungen auf Aufgabenebene hinsichtlich der Abdeckung von Belegen, des Umgangs mit Widersprüchen, der Gültigkeit von Zitaten sowie der Wiederherstellung nach Komprimierung. Durch diese Aufgabenisolation verringerte sich zwar die Datenmenge des Tools, doch die Anzahl der Token sowie die Kosten für den gesamten Baum stiegen an; daher muss die Kontextengineering-Strategie als Ganzes gemessen werden und nicht einfach als Optimierung vorausgesetzt werden.
Verwandte Literatur
- Context Engineering für LLM-Anwendungen: Strukturierte Ausgaben, Tools und Grenzen von Agenten — Wie man gezielt Eingaben für Modelle erstellt: Kontextauswahl, Prompt-Vorlagen, JSON-Schemata, eingeschränkte Dekodierung, Toolaufrufe, Agentenschleifen und begrenzte Autonomie.
- Context Engineering für KI-Agenten: Schreiben, Auswählen, Komprimieren, Isolieren — Warum Agenten bei wachsendem Kontext an Leistung verlieren, die vier Strategien zur Bewältigung dieses Problems, vier Fehlermuster, Prompt- und Tool-Design sowie die Vorgehensweisen von Claude Code, Manus und anderen Tools.