Pola arsitektur planner-worker memisahkan tugas inferensi berbiaya tinggi dari eksekusi task deterministik. Model perencana (seperti frontier LLM) bertugas menyusun dekomposisi instruksi menjadi rencana eksekusi multi-langkah (execution plan). Selanjutnya, worker yang lebih hemat sumber daya (model kompak atau code runner engine) mengeksekusi setiap langkah tersebut secara sekuensial atau paralel.

Masalah utama muncul pada batas integrasi (handoff boundary): payload drift dan risiko mutasi ganda saat retry. Tanpa validasi skema ketat di layer orkestrator, variasi output struktural dari model perencana menyebabkan worker gagal mem-parsing instruksi di tengah proses. Kegagalan jaringan atau timeout yang memicu retry tanpa mekanisme idempotensi dapat menyebabkan mutasi ganda pada database atau sistem eksternal.

Anatomi Masalah: Payload Drift & Non-Idempotent Retries

LLM bersifat probabilistik. Meskipun Anda menggunakan structured outputs atau function calling, model dapat menghasilkan deviasi kecil dalam skema JSON, seperti mengubah tipe data integer menjadi string, memodifikasi nama kunci properti, atau menambahkan nested objects tak terduga.

Worker kompak (misalnya model parameter kecil yang di-host lokal) memiliki jendela konteks dan kapasitas pemahaman semantik yang terbatas. Worker ini tidak mampu menoleransi inkonsistensi payload. Jika orkestrator meneruskan payload tanpa validasi runtime, worker akan memicu unhandled exception di tengah jalannya pipeline.

Masalah berikutnya adalah penanganan kegagalan. Ketika suatu step gagal merespons akibat latensi jaringan atau worker timeout, orkestrator biasanya mengulang (retry) step tersebut. Jika step melibatkan operasi mutasi (seperti memanggil API pihak ketiga atau memperbarui status saldo) tanpa pengenal identik (idempotency key), sistem rentan menjalankan aksi ganda.

Perancangan Kontrak Skema Ketat Menggunakan Zod

Lakukan validasi skema tepat di boundary: setelah planner menghasilkan teks/JSON dan sebelum task dimasukkan ke antrean worker. Tolak atau generate ulang plan jika validasi skema gagal, alih-alih membiarkan kegagalan terjadi di layer worker.

Skema harus menolak properti asing (strict parsing) dan membatasi aksi ke dalam discriminated unions yang eksplisit.

import { z } from 'zod';

// Kontrak langkah individual: gunakan discriminated union berdasarkan 'action'
export const StepActionSchema = z.discriminatedUnion('action', [
  z.object({
    action: z.literal('create_file'),
    params: z.object({
      path: z.string().min(1),
      content: z.string(),
    }).strict(),
  }),
  z.object({
    action: z.literal('run_command'),
    params: z.object({
      command: z.string().min(1),
      timeout_ms: z.number().int().positive().max(30000).default(5000),
    }).strict(),
  }),
  z.object({
    action: z.literal('mutate_record'),
    params: z.object({
      entity_id: z.string().uuid(),
      changes: z.record(z.unknown()),
    }).strict(),
  }),
]);

// Kontrak seluruh execution plan
export const ExecutionPlanSchema = z.object({
  plan_id: z.string().uuid(),
  context_hash: z.string().length(64), // SHA-256 state awal
  steps: z.array(StepActionSchema).nonempty(),
}).strict();

export type StepAction = z.infer<typeof StepActionSchema>;
export type ExecutionPlan = z.infer<typeof ExecutionPlanSchema>;

// Middleware validator sebelum payload dikirim ke antrean
export function validateHandoffPayload(rawOutput: unknown): ExecutionPlan {
  const result = ExecutionPlanSchema.safeParse(rawOutput);
  if (!result.success) {
    throw new Error(`Payload drift terdeteksi: ${JSON.stringify(result.error.issues)}`);
  }
  return result.data;
}

Catatan: Penggunaan .strict() pada object parameter mencegah masuknya properti tambahan yang sering dihalusinasi oleh LLM, menjamin worker menerima data sesuai kontrak.

Idempotensi Eksekusi Berbasis Step Token Deterministik

Setiap step harus memiliki identitas unik yang dapat dihitung ulang secara deterministik (content-addressable token). Token ini menggabungkan plan_id, indeks step, dan hash dari parameter eksekusi. Jika orkestrator mengulang step yang sama, token yang dihasilkan tetap identik.

Penyimpanan state (misalnya Redis) digunakan untuk melacak siklus hidup token: PENDING, COMPLETED, atau FAILED.

import crypto from 'node:crypto';
import Redis from 'ioredis';
import { StepAction } from './schema';

export class StepIdempotencyManager {
  constructor(private redis: Redis) {}

  public generateStepToken(planId: string, stepIndex: number, step: StepAction): string {
    const payloadSignature = crypto
      .createHash('sha256')
      .update(JSON.stringify(step))
      .digest('hex');

    return `step:${planId}:${stepIndex}:${payloadSignature}`;
  }

  public async executeStepIdempotent<T>(
    stepToken: string,
    ttlSeconds: number,
    executionFn: () => Promise<T>
  ): Promise<{ status: 'executed' | 'cached'; result: T }> {
    const lockKey = `${stepToken}:lock`;
    const resultKey = `${stepToken}:result`;

    // 1. Cek apakah hasil sudah tersedia
    const cachedResult = await this.redis.get(resultKey);
    if (cachedResult) {
      return { status: 'cached', result: JSON.parse(cachedResult) };
    }

    // 2. Pasang distributed lock agar retry tidak berjalan paralel
    const acquired = await this.redis.set(lockKey, 'IN_PROGRESS', 'EX', ttlSeconds, 'NX');
    if (!acquired) {
      throw new Error('Step sedang dieksekusi oleh worker lain. Silakan poll kembali.');
    }

    try {
      const output = await executionFn();
      
      // Simpan hasil eksekusi dan hapus lock
      await this.redis.pipeline()
        .set(resultKey, JSON.stringify(output), 'EX', ttlSeconds)
        .del(lockKey)
        .exec();

      return { status: 'executed', result: output };
    } catch (error) {
      // Buka lock jika gagal agar worker berikutnya dapat mencoba lagi
      await this.redis.del(lockKey);
      throw error;
    }
  }
}

Menangani Timeout: Webhook vs Polling

Eksekusi step oleh AI worker sering membutuhkan waktu beberapa detik hingga menit, sehingga API sinkron rentan mengalami HTTP 504 Gateway Timeout. Gunakan komunikasi asinkron berbasis Job Token.

1. Pendekatan Polling Terbatas

Ketika worker menerima step payload, kembalikan HTTP 202 (Accepted) disertai URL status dan step_token. Klien melakukan polling dengan interval eksponensial (exponential backoff).

  • Batas maksimum polling harus disetel tegas (misal: 60 detik).
  • Jika batas terlampaui tanpa status selesai, orkestrator menandai worker sebagai STALE dan merilis kunci idempotensi untuk dialihkan ke worker cadangan.

2. Webhook Callback dengan Signature

Untuk task yang memakan waktu lebih dari 30 detik, webhook lebih efisien dibandingkan polling terus-menerus. Orkestrator menyertakan header callback pada saat handoff:

POST /api/v1/worker/execute
X-Step-Token: step:b8f9e1:0:e3b0c442...
X-Callback-URL: https://orchestrator.internal/api/v1/steps/callback
X-Signature: sha256=4f8b9...

Worker mengirimkan status akhir ke endpoint callback tersebut. Orkestrator memvalidasi signature HMAC sebelum memperbarui state step menjadi COMPLETED.

Strategi Mitigasi dan Error Recovery

Saat validasi atau eksekusi step gagal, terapkan hierarki pemulihan berikut:

  1. Schema Drift Recovery: Jika planner menghasilkan JSON tidak valid, kirim error dari parser Zod kembali ke model perencana sebagai konteks error (reflection prompt) untuk diperbaiki secara otomatis. Batasi auto-repair ini maksimal 2 kali percobaan.
  2. Poison Pill Payload: Jika skema tetap tidak lolos setelah 2 kali perbaikan, batalkan plan dan kirim payload ke Dead Letter Queue (DLQ) untuk analisis developer. Jangan teruskan ke worker.
  3. Worker Crash Isolation: Gunakan TTL pada lock Redis. Jika worker mati mendadak saat memproses step non-idempotent, lock akan otomatis expired, dan pengawas (orchestrator supervisor) dapat memicu kompensasi rollback untuk mengembalikan state sistem.

Ringkasan Implementasi

Keandalan orkestrasi multi-model bergantung pada isolasi tanggung jawab yang kaku. Planner fokus pada penalaran tingkat tinggi, boundary layer menegakkan skema ketat untuk mencegah payload drift, dan token idempotensi deterministik memastikan proses eksekusi worker aman dari efek samping saat terjadi retry jaringan.