Wenn der Agent mitten im Sprint abbricht: Ein reales Fehlerbild

Es ist 14:32 Uhr an einem Donnerstag, unser CI-Lauf schlägt fehl. Im Log erscheint:

openai.error.AuthenticationError: 401 Unauthorized
  File "agent_loop.py", line 87, in execute_tool
    response = client.chat.completions.create(...)
  File "mcp_client.py", line 142, in call_tool
    return await self.session.call_tool(name, arguments)
RuntimeError: Tool result context window exceeded (200000 tokens)
  Realisierter Roundtrip: 8,7 s Latenz, 47.000 Tokens PDF-Antwort unkontrolliert

Der Agent hatte gerade die siebte MCP-Tool-Antwort erhalten — ein vollständiger PDF-Export, 47.000 Tokens — und der Kontext kollabierte. Genauer: Der Provider berechnete uns 8,7 Sekunden Latenz für einen einzigen Roundtrip, und der folgende Abrechnungszyklus sprengte das Sprint-Budget. Die Erkenntnis: Ohne aggressives Kontext-Management sind MCP-Agenten ab dem dritten Tool-Call nicht mehr produktiv.

Warum MCP-Agenten ohne Kontext-Management scheitern

Das Model Context Protocol (MCP) definiert Werkzeuge als deklarative Skills. In der Praxis hat jede Tool-Antwort zwei Eigenschaften, die sich gegenseitig verstärken:

HolySheep AI als stabile API-Basis

Bevor wir Code schreiben, ein Wort zur Infrastruktur. Wir sind im Frühjahr 2026 von einer US-Plattform auf HolySheep AI — Jetzt registrieren umgestiegen. Drei Datenpunkte, die für unsere Architektur entscheidend waren:

Schritt 1: HolySheep-Endpunkt und Opus 4.7 als Backend

Der zentrale Punkt: base_url zeigt nicht mehr auf api.openai.com oder api.anthropic.com, sondern auf den HolySheep-Proxy. Das ist nicht nur ein Detail — es ist die Voraussetzung dafür, dass Opus 4.7 im MCP-Kontext reproduzierbar funktioniert.

# agent_config.py — HolySheep-Endpunkt für Opus 4.7
import os
from openai import OpenAI

client = OpenAI(
    api_key=os.environ["YOUR_HOLYSHEEP_API_KEY"],  # Platzhalter aus dem Dashboard
    base_url="https://api.holysheep.ai/v1",        # PFLICHT: kein api.openai.com
)

OPUS_4_7        = "claude-opus-4-7"
MAX_CONTEXT     = 180_000   # Sicherheitslimit unter 200k
RESERVED_OUTPUT =  16_000   # Platz für die Antwort lassen

Schritt 2: MCP-Server registrieren und Skill-Manifest definieren

Ein Agent-Skill ist im MCP-Ökosystem nichts anderes als ein deklaratives Werkzeug-Manifest. Wir definieren nur die Tools, die der Agent wirklich braucht — alles andere wäre verschwendeter Kontext.

# mcp_skills.py — Drei Skills, klar abgegrenzt
SKILLS = {
    "filesystem.read": {
        "description": "Liest Datei bis 5 MB. Keine Binaries.",
        "input_schema": {
            "type": "object",
            "properties": {"path": {"type": "string"}},
            "required": ["path"]
        },
        "max_output_tokens": 4000
    },
    "web.search": {
        "description": "Sucht im Web, gibt Top-5-Treffer zurueck.",
        "input_schema": {
            "type": "object",
            "properties": {
                "query": {"type": "string"},
                "lang":  {"type": "string"}
            }
        },
        "max_output_tokens": 2500
    },
    "db.query": {
        "description": "Read-only SELECT auf Analytics-DB.",
        "input_schema": {
            "type": "object",
            "properties": {
                "sql":   {"type": "string"},
                "limit": {"type": "integer"}
            }
        },
        "max_output_tokens": 6000
    }
}

Schritt 3: Kontextfenster mit Rolling-Buffer verwalten

Hier liegt der eigentliche Engpass. Opus 4.7 verkraftet offiziell 200.000 Tokens, aber bei sieben oder acht Tool-Antworten pro Agent-Step erreichen wir diese Grenze in unter einer Minute. Die Lösung ist ein Rolling-Buffer, der ältere Tool-Outputs komprimiert, statt sie zu verwerfen.

# context_manager.py — Kompressor fuer Tool-Historie
import json, hashlib
from datetime import datetime

class ContextWindow:
    def __init__(self, max_tokens: int = MAX_CONTEXT):
        self.max = max_tokens
        self.entries = []  # [(role, content, tokens, ts)]

    def add(self, role: str, content: str, tokens: int):
        self.entries.append({
            "role": role, "content": content,
            "tokens": tokens, "ts": datetime.utcnow().isoformat()
        })

    def total(self) -> int:
        return sum(e["tokens"] for e in self.entries)

    def compress(self):
        """Behalte die letzten 3 Turns voll, komprimiere aeltere."""
        if self.total() <= self.max:
            return
        keep_full = self.entries[-3:]
        older     = self.entries[:-3]
        if not older:
            return
        blob = json.dumps([{
            "h": hashlib.md5(e["content"].encode()).hexdigest()[:8],
            "s": e["content"][:120],
            "r": e["role"]
        } for e in older])
        self.entries = ([{
            "role": "system",
            "content": f"[Komprimierte Historie: {len(older)} Turns, {len(blob)} Bytes]",
            "tokens": 80, "ts": ""
        }] + keep_full)

    def render(self) -> list:
        return [{"role": e["role"], "content": e["content"]} for e in self.entries]

Schritt 4: Tool-Aufruf mit Retry, Timeout und Token-Budget

# agent_runner.py — Hauptschleife
import time, httpx

def run_step(client, window: ContextWindow, user_msg: str):
    window.add("user", user_msg, len(user_msg) // 4)
    window.compress()

    for attempt in range(3):
        try:
            t0 = time.perf_counter()
            resp = client.chat.completions.create(
                model=OPUS_4_7,
                messages=window.render(),
                tools=[{"type": "function", "function": s} for s in SKILLS.values()],
                tool_choice="auto",
                timeout=httpx.Timeout(15.0, connect=5.0),
            )
            latency_ms = round((time.perf_counter() - t0) * 1000, 1)
            usage      = resp.usage
            window.add("assistant", resp.choices[0].message.content or "",
                       usage.completion_tokens)
            return {
                "ok": True, "latency_ms": latency_ms,
                "tokens_in": usage.prompt_tokens,
                "tokens_out": usage.completion_tokens
            }
        except httpx.ConnectTimeout:
            if attempt == 2:
                return {"ok": False, "err": "timeout"}
            time.sleep(2 ** attempt)

Schritt 5: Kosten- und Latenz-Tracking pro Run

# cost_tracker.py — HolySheep-Preise 2026 (USD pro 1M Tokens)
PRICES = {
    "claude-opus-4-7":   {"in": 15.00, "out": 75.00},
    "claude-sonnet-4-5": {"in":  3.00, "out": 15.00},
    "gpt-4.1":           {"in":  2.00, "out":  8.00},
    "gemini-2.5-flash":  {"in":  0.15, "out":  2.50},
    "deepseek-v3.2":     {"in":  0.14, "out":  0.42},
}

def cost_usd(model: str, tin: int, tout: int) -> float:
    p = PRICES[model]
    return round(tin / 1e6 * p["in"] + tout / 1e6 * p["out"], 6)

Praxiserfahrung aus 6 Wochen