Ihr Agent hat den Zahlungs-Endpunkt aufgerufen. Die Anfrage wurde verarbeitet, die Abbuchung erfolgte, und dann kam es auf dem Rückweg zu einem Timeout der Antwort. Der Agent sah keinen 200-Status und wiederholte den Aufruf. Dem Kunden wurde zweimal etwas berechnet, obwohl in den Protokollen kein offensichtlicher Fehler erscheint.
Das ist ein typischer Agenten-Fehlermodus: Wiederholungen machen Integrationen robuster, erhöhen bei nicht-idempotenten Schreibvorgängen aber das Risiko doppelter Effekte. Die Lösung ist Idempotenz: Mehrere identische Anfragen müssen denselben Serverzustand erzeugen wie eine einzelne Anfrage.
Dieser Beitrag zeigt, wie Sie Idempotenz für Agenten implementieren, testen und nachvollziehbar machen. Doppelte Schreibvorgänge sind oft die eigentliche Ursache hinter Meldungen wie „Der Agent hat es zweimal getan“ – ein Thema, das auch der Beitrag Warum KI-Agenten in der Produktion versagen behandelt.
Warum Agenten besonders anfällig für Duplikate sind
Agenten erzeugen häufiger doppelte Schreibvorgänge als Menschen:
- Aggressive Retries: Frameworks wiederholen Netzwerkfehler standardmäßig. Backoff und Circuit Breaker verbessern die Verfügbarkeit, erhöhen aber auch die Zahl der Anfragen an Ihren Server. Mehr dazu im Leitfaden zur Fehlerbehebung bei Agenten.
-
Timeouts sind mehrdeutig: Ein
504kann bedeuten, dass der Schreibvorgang nie stattfand – oder dass er erfolgreich war und nur die Antwort verloren ging. - Aufgaben-Neustarts: Scheitert Schritt vier eines mehrstufigen Agentenablaufs, kann ein naiver Neustart auch Schritt eins erneut ausführen und beispielsweise eine zweite Bestellung erstellen.
Das Problem sind also nicht fehlerhafte Anfragen. Agenten senden häufig korrekte Anfragen mehrfach.
Was Idempotenz garantiert
Eine Operation ist idempotent, wenn ihre wiederholte Ausführung denselben Effekt hat wie eine einmalige Ausführung.
GET, PUT und DELETE sind laut RFC 9110 idempotent. POST ist es nicht – deshalb sind besonders riskante Vorgänge meist POST-Aufrufe:
- Bestellung erstellen
- Zahlung auslösen
- Nachricht senden
- Überweisung starten
Wichtig:
-
Idempotent bedeutet nicht sicher.
DELETEist idempotent, aber destruktiv. - Idempotent bedeutet nicht zwingend identische HTTP-Antworten. Entscheidend ist, dass der Serverzustand unverändert bleibt: eine Abbuchung, eine Bestellung, eine E-Mail.
Für gefährliche Agenten-Tools brauchen Sie Idempotenz zusätzlich zu restriktiven Berechtigungen, etwa nach dem Prinzip der API-Schlüssel mit geringsten Berechtigungen für Agenten.
POST mit Idempotenzschlüsseln absichern
Das Standardmuster ist ein clientseitig erzeugter Schlüssel im Header. Der Server speichert Schlüssel, Anfrage-Fingerabdruck und Ergebnis. Wiederholt ein Client dieselbe Anfrage mit demselben Schlüssel, gibt der Server das gespeicherte Ergebnis zurück, statt die Arbeit erneut auszuführen.
Stripe hat dieses Muster populär gemacht. Außerdem gibt es einen IETF-Entwurf für das Header-Feld Idempotency-Key.
POST /v1/payments HTTP/1.1
Host: api.yourservice.com
[REDACTED CREDENTIAL] [REDACTED]...
Idempotency-Key: 9f2b7c14-6d3a-4b18-9d55-1e2a7c0b4f31
Content-Type: application/json
{
"amount": 4900,
"currency": "usd",
"customer_id": "cus_8812",
"description": "Pro plan, August"
}
Der Schlüssel hat nur eine Bedeutung: Dies ist dieselbe logische Operation.
Schlüssel so erzeugen, dass Agenten sie wiederverwenden
Der häufigste Implementierungsfehler: Der Tool-Wrapper generiert bei jedem HTTP-Versuch eine neue UUID. Dann unterscheiden sich Retries technisch voneinander, und Idempotenz greift nicht.
Generieren Sie den Schlüssel, wenn der Agent die Aktion entscheidet – nicht bei jedem Request-Versuch.
import uuid
class PaymentTool:
def __init__(self, client):
self.client = client
self._keys = {}
def charge(self, task_id, step_id, amount, customer_id):
# Ein Schlüssel pro (Aufgabe, Schritt).
# Retries desselben Schritts verwenden denselben Schlüssel.
op = f"{task_id}:{step_id}"
if op not in self._keys:
self._keys[op] = str(uuid.uuid4())
return self.client.post(
"/v1/payments",
headers={"Idempotency-Key": self._keys[op]},
json={"amount": amount, "customer_id": customer_id},
)
Für Prozessneustarts ist ein deterministischer Schlüssel besser:
import hashlib
def idempotency_key(task_id: str, step_id: str, payload: dict) -> str:
raw = f"{task_id}|{step_id}|{sorted(payload.items())}"
return hashlib.sha256(raw.encode()).hexdigest()[:32]
Leiten Sie den Schlüssel aus Task-ID und Schritt-ID ab – niemals aus Zeitstempeln oder Zufallswerten, die pro Retry neu entstehen. Startet der Agent eine neue Aufgabe, entsteht mit der neuen Task-ID auch bewusst ein neuer Schlüssel.
Was der Server speichern muss
Eine robuste Implementierung braucht vier Regeln:
- Schlüssel vor der Arbeit beanspruchen. Legen Sie den Eintrag per Unique Constraint an, bevor der Vorgang ausgeführt wird.
-
Payload-Konflikte ablehnen. Existiert derselbe Schlüssel mit anderem Anfrage-Fingerabdruck, antworten Sie mit
422 Unprocessable Entity. -
Gleichzeitige Versuche stoppen. Läuft der erste Request noch, antworten Sie mit
409 Conflict, damit der Client zurückweicht. - Ergebnis wiedergeben. Speichern Sie Statuscode und Response-Body nach Abschluss und liefern Sie sie bei späteren Treffern erneut aus.
CREATE TABLE idempotency_records (
key TEXT PRIMARY KEY,
request_hash TEXT NOT NULL,
state TEXT NOT NULL, -- in_Bearbeitung | abgeschlossen
response_status INT,
response_body JSONB,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
expires_at TIMESTAMPTZ NOT NULL
);
Setzen Sie ein Ablaufdatum. 24 Stunden decken praktisch jedes Retry-Fenster ab und verhindern, dass die Tabelle unbegrenzt wächst. Auch Stripe verwendet dieses Zeitfenster.
Beweisen, dass der zweite Aufruf nichts ändert
Eine identische 200-Antwort beweist keine Idempotenz. Zwei separate, erfolgreiche Abbuchungen können beide 200 zurückgeben.
Testen Sie deshalb den Zustand:
- Der zweite Response-Body enthält dieselbe Ressourcen-ID wie der erste.
- Ein nachfolgender
GETauf die Sammlung liefert genau einen Datensatz. - Zähler, Salden oder andere Seiteneffekte wurden genau einmal geändert.
In Apidog können Sie dafür ein gespeichertes Testszenario erstellen:
- Senden Sie einen
POSTmit festemIdempotency-Key. - Wiederholen Sie exakt denselben Request.
- Prüfen Sie, ob beide Antworten dieselbe Ressourcen-ID enthalten.
- Listen Sie die Ressource auf und prüfen Sie, ob genau ein Datensatz existiert.
Speichern Sie dieses Szenario und führen Sie es in der CI aus. Das ergänzt API-Vertragstests, wie im API-Vertragstest-Leitfaden beschrieben.
Testen Sie außerdem diese zwei Fehlerfälle:
-
Gleicher Schlüssel, anderer Body: Erwartet wird
422, nicht stiller Erfolg. - Gleichzeitige Duplikate: Senden Sie beide Requests parallel. Genau einer darf die Arbeit ausführen.
Wenn die echte Zahlungs-API noch nicht verfügbar ist, verwenden Sie einen Mock mit denselben Schlüssel-Semantiken. Warum das für Agenten wichtig ist, erklärt Warum Agenten Mock-APIs statt der Produktion verwenden sollten.
Wenn die externe API keine Schlüssel unterstützt
Wenn Sie die Ziel-API nicht kontrollieren und sie keine Idempotenzschlüssel anbietet, gehen Sie in dieser Reihenfolge vor:
1. Operation von Natur aus idempotent machen
Verwenden Sie einen clientseitig bestimmten Ressourcenpfad:
PUT /orders/{client_order_id}
PUT ist per Konstruktion idempotent und benötigt keine separate Schlüssel-Tabelle.
2. Vor dem Schreiben prüfen
Suchen Sie vor dem Erstellen nach einem bestehenden Datensatz mit demselben natürlichen Schlüssel. Das hilft gegen viele Timeout-Fälle, löst aber keine Race Condition zwischen Prüfung und Schreiben.
3. Downstream deduplizieren
Bei Nachrichten oder Events: Fügen Sie eine stabile Nachrichten-ID hinzu und lassen Sie Consumer Wiederholungen verwerfen. Das ist ein zentrales Muster für zuverlässige ereignisgesteuerte Systeme und zuverlässige Webhooks.
4. Menschliche Freigabe verlangen
Ist eine Aktion irreversibel und nicht idempotent machbar, setzen Sie ein Approval Gate davor. Für hochriskante Agenten-Aktionen ist das die richtige Schutzmaßnahme, wie im Beitrag zu KI-Agenten-Schutzmaßnahmen beschrieben.
Ausführungen nachvollziehbar machen
Idempotenz verhindert Duplikate, beantwortet aber nicht automatisch die Incident-Frage: Welcher Lauf hat den Datensatz erstellt?
Protokollieren Sie mindestens:
- Task-ID
- Schritt-ID
- Idempotenzschlüssel
- Request-Hash
- Ergebnisstatus
- erzeugte Ressourcen-ID
Bei verwalteten Laufzeitumgebungen sollte diese Zuordnung Teil der Plattform sein. In Sharkly ist beispielsweise jeder Lauf mit seiner Task, dem Ausführungsstatus und dem Ergebnis verknüpft. Dadurch lässt sich ein wiederholter Schreibvorgang auf einen konkreten Lauf zurückführen.
Checkliste vor dem Versand
- [ ] Jedes nicht-idempotente Agenten-Tool verlangt einen Idempotenzschlüssel.
- [ ] Schlüssel stammen aus Aufgabe und Schritt, nicht aus einem einzelnen Request-Versuch.
- [ ] Der Server beansprucht den Schlüssel vor der Arbeit.
- [ ] Derselbe Schlüssel mit anderer Payload führt zu einem Fehler.
- [ ] Gleichzeitige Duplikate werden durch eine Datenbank-Constraint behandelt.
- [ ] Ein gespeicherter CI-Test beweist, dass der zweite Aufruf keinen weiteren Effekt erzeugt.
- [ ] Schlüssel laufen ab, und abgelaufene Datensätze werden bereinigt.
Idempotenz erlaubt aggressivere Wiederholungen, ohne Agenten gefährlicher zu machen. Das ist der eigentliche Gewinn: Ihr Agent wird widerstandsfähiger, ohne doppelte Zahlungen, Bestellungen oder Nachrichten zu erzeugen.
Häufig gestellte Fragen
Brauche ich Idempotenzschlüssel für schreibgeschützte Tools?
Nein. GET ist bereits idempotent und sicher. Schlüssel gehören zu Aufrufen, die Zustand erstellen, belasten, senden oder ändern.
Wo wird der Schlüssel erzeugt?
Im Tool-Wrapper, basierend auf Task- und Schritt-Identifikatoren. Lassen Sie das Modell den Schlüssel nicht selbst generieren: Bei Retries entstehen neue Werte, und taskübergreifende Kollisionen sind möglich.
Welchen Statuscode soll ein Retry zurückgeben?
Geben Sie den gespeicherten Status des ursprünglichen Aufrufs zurück. Hat der erste POST mit 201 geantwortet, sollte auch der Retry 201 mit demselben Body erhalten. Ein optionaler Header wie Idempotent-Replay: true erleichtert das Debugging.
Wie lange sollten Schlüssel gespeichert werden?
24 Stunden reichen für nahezu jedes Retry-Fenster. Danach behandeln Sie einen erneuten Request als neue Operation.
Ersetzt Idempotenz Transaktionen?
Nein. Idempotenz verhindert doppelte Effekte durch wiederholte Requests. Transaktionen machen einen einzelnen Request atomar. Verwenden Sie beides und schreiben Sie Schlüsselbeanspruchung und Geschäftsoperation nach Möglichkeit in derselben Transaktion.
Wie teste ich das ohne echten Zahlungsanbieter?
Richten Sie den Agenten auf einen Mock, der Schlüssel-Semantik einschließlich 422 bei abweichender Payload implementiert. Mit Apidog herunterladen können Sie Mock und Wiederholungstest im selben Projekt verwalten.


Top comments (0)