AI coding agent mengeksekusi instruksi backend melalui pemanggilan alat eksternal (tool calling). Pola ini rentan terhadap kegagalan jaringan, timeout, atau interupsi proses lokal runner (misalnya crash pada runtime CLI agent atau penghentian sesi oleh pengguna). Ketika interupsi terjadi sebelum agent mencatat audit eksekusi lokal (context audit log atau ctx), mekanisme recovery otomatis agent akan melakukan replay pada tool call yang sama. Tanpa proteksi idempotensi, backend akan mengeksekusi mutasi data berulang kali.

Akar Masalah: Replay dan Retry Pasca Interupsi Sesi

Siklus eksekusi tool calling pada autonomous agent umumnya mengikuti alur:

  1. Agent runtime mengevaluasi state konteks lokal dan menentukan tool yang perlu dipanggil.
  2. Runtime mengirimkan mutasi HTTP (seperti POST /api/tools/v1/provision-infra).
  3. Backend memproses request dan merubah state database.
  4. Koneksi terputus atau proses agent mati mendadak sebelum response diterima dan diverifikasi ke dalam file konteks lokal (ctx.json atau memory buffer).
  5. Agent direstart; karena log transaksi terakhir kosong, runtime mengeksekusi ulang request mutasi tersebut.

Masalah ini menciptakan efek samping permanen seperti alokasi resource ganda, pembayaran berulang, atau korupsi file state. Solusinya adalah memindahkan validasi status eksekusi ke layer API backend menggunakan kontrak idempotensi.

Spesifikasi Kontrak Idempotency-Key Deterministik

Penggunaan UUID acak yang dibuat oleh agent per-request gagal memitigasi interupsi jika sesi diulang dari awal crash checkpoint. Backend harus menuntut header Idempotency-Key yang diturunkan secara deterministik.

Format penentuan key yang disarankan adalah hash kriptografis dari kombinasi identitas sesi, nomor urut langkah (step index), nama fungsi, dan representasi kanonikal dari argumen payload:

Idempotency-Key = SHA256(session_id + ":" + step_index + ":" + tool_name + ":" + canonical_json(payload))

Dengan format ini, apabila agent mengalami crash pada langkah ke-4 lalu mengirim ulang payload yang identik untuk langkah tersebut, nilai Idempotency-Key yang dihasilkan akan selalu sama.

Lifecycle State Idempotensi dan Mitigasi Race Condition

Backend membutuhkan sistem penyimpanan terdistribusi (seperti Redis) untuk mengontrol state eksekusi request. Terdapat tiga kondisi utama:

  • Acquire Lock (IN_PROGRESS): Backend menggunakan atomic command (SETNX) dengan TTL pendek (misal 30–60 detik) untuk menandai bahwa operasi sedang berjalan.
  • Concurrent Replay (Race Condition): Jika request kedua dengan key yang sama masuk ketika status masih IN_PROGRESS, backend harus segera menghentikan eksekusi dan mengembalikan status HTTP 409 Conflict (disertai header Retry-After). Langkah ini mencegah dua thread mengeksekusi mutasi paralel.
  • Cached Replay (COMPLETED): Setelah eksekusi sukses, state diubah menjadi payload response terenkapsulasi dengan TTL lebih panjang (misal 24 jam). Request berikutnya yang membawa key ini langsung menerima response tersimpan tanpa menjalankan business logic.

Implementasi Handler Idempotensi (TypeScript & Redis)

Berikut adalah implementasi handler middleware minimal di TypeScript menggunakan driver ioredis:

import { Request, Response, NextFunction } from "express";
import Redis from "ioredis";

const redis = new Redis(process.env.REDIS_URL || "redis://localhost:6379");

interface CachedResponse {
  status: number;
  body: unknown;
}

export async function idempotencyMiddleware(req: Request, res: Response, next: NextFunction) {
  const idempotencyKey = req.header("Idempotency-Key");
  if (!idempotencyKey) {
    return res.status(400).json({ error: "Header Idempotency-Key wajib disertakan." });
  }

  const lockKey = `idemp:lock:${idempotencyKey}`;
  const responseKey = `idemp:resp:${idempotencyKey}`;

  // 1. Cek apakah response hasil eksekusi sebelumnya sudah tersimpan
  const cached = await redis.get(responseKey);
  if (cached) {
    const parsed: CachedResponse = JSON.parse(cached);
    res.setHeader("X-Cache-Lookup", "HIT");
    return res.status(parsed.status).json(parsed.body);
  }

  // 2. Akuisisi lock atomic untuk mencegah race condition (TTL 30 detik)
  const acquired = await redis.set(lockKey, "IN_PROGRESS", "EX", 30, "NX");
  if (!acquired) {
    res.setHeader("Retry-After", "3");
    return res.status(409).json({
      error: "Operasi sedang diproses oleh permintaan paralel lain. Silakan coba sesaat lagi."
    });
  }

  // 3. Intersepsi fungsi response untuk menyimpan hasil secara atomic
  const originalJson = res.json.bind(res);
  res.json = (body: unknown) => {
    // Kembalikan method asli
    res.json = originalJson;

    // Simpan response hanya jika status berhasil (2xx / 4xx valid)
    if (res.statusCode >= 200 && res.statusCode < 500) {
      const cachePayload: CachedResponse = {
        status: res.statusCode,
        body
      };
      // Simpan respons selama 24 jam dan lepaskan lock
      redis.pipeline()
        .set(responseKey, JSON.stringify(cachePayload), "EX", 86400)
        .del(lockKey)
        .exec()
        .catch((err) => console.error("Gagal menyimpan cache idempotensi:", err));
    } else {
      // Jika terjadi error 500 (internal failure), buka lock agar request dapat diretry
      redis.del(lockKey).catch((err) => console.error("Gagal menghapus lock:", err));
    }

    return originalJson(body);
  };

  next();
}

Menangani Kegagalan Infrastruktur dan Rollback

Idempotensi tidak boleh mengunci endpoint secara permanen jika terjadi kesalahan internal di sisi backend. Perhatikan aturan mitigasi berikut:

  • Pembersihan Lock Saat Internal Server Error (5xx): Jika database atau resource hilir mengalami timeout lokal, middleware wajib menghapus lockKey sebelum mengirimkan response error. Hal ini memungkinkan agent melakukan retry otomatis pada kegagalan transient.
  • Payload Mismatch Detection: Jika hash payload tidak dimasukkan ke dalam Idempotency-Key, backend harus menyimpan fingerprint argumen saat lock dibuat. Jika request baru datang membawa key yang sama namun parameter berubah, kembalikan status 422 Unprocessable Entity untuk mendeteksi anomali agent.
  • Batas TTL Replay: Batasi penyimpanan cache response hingga horizon log audit sesi agen (misalnya 24 jam hingga 7 hari tergantung kebutuhan compliance audit eksekusi lokal).