Memilih arsitektur loop agent bukan soal mengikuti pola yang paling kompleks, tetapi soal menempatkan kompleksitas pada saat yang tepat. Untuk sistem AI agent berbasis loop eksekusi seperti konteks trace -> execution -> feedback data, pilihan awal yang paling sehat sering kali adalah monolit modular. Pisahkan menjadi worker saat beban kerja asinkron mulai mengganggu latency request utama, lalu pertimbangkan event bus hanya ketika banyak komponen perlu bereaksi terhadap event yang sama secara independen.

Jika disederhanakan: mulai dari satu proses bila alur masih sederhana dan tim kecil, tambah worker saat perlu throughput dan isolasi eksekusi, dan tambah event bus saat orkestrasi berubah menjadi sistem reaktif multi-konsumen. Keputusan ini memengaruhi throughput, observability, idempotensi, retry, biaya operasional, isolasi kegagalan, latency, dan maintainability secara langsung.

Apa yang Dimaksud dengan Loop Agent?

Dalam sistem agent, loop eksekusi biasanya terdiri dari beberapa tahap berulang:

  1. Menerima input atau job: misalnya task dari user, scheduler, atau hasil evaluasi sebelumnya.
  2. Membangun konteks: mengambil state, tool configuration, memory, atau trace sebelumnya.
  3. Menjalankan eksekusi agent: memanggil model, tool, evaluator, planner, atau runner.
  4. Menyimpan hasil dan trace: token usage, output, status, error, intermediate steps.
  5. Mengirim feedback data: untuk evaluasi, training data, ranking, atau perbaikan kebijakan berikutnya.

Pada implementasi seperti agent-apprenticeship, masalah arsitektur muncul bukan karena satu langkah terlalu sulit, tetapi karena loop-nya terus berjalan, stateful, rawan retry, dan menghasilkan event turunan. Begitu volume naik, keputusan arsitektur mulai menentukan apakah sistem tetap mudah dipahami atau berubah menjadi kumpulan proses yang sulit ditelusuri.

Tiga Opsi Arsitektur untuk Loop Agent

1. Monolit modular

Semua komponen berjalan dalam satu aplikasi atau satu deployment, tetapi dipisah secara jelas dalam boundary modul: API, scheduler, executor, trace store, feedback pipeline, evaluator, dan adapter tool.

Ciri utama:

  • Satu codebase, satu deployment unit.
  • Pemanggilan antar modul umumnya berupa function call atau internal queue in-memory/database-backed.
  • Transaksi data lebih mudah dikendalikan.
  • Debugging lebih sederhana karena jejak eksekusi lebih pendek.

2. Worker terpisah

Aplikasi utama tetap ada, tetapi pekerjaan berat atau lama dipindahkan ke proses worker melalui queue. Biasanya API atau orchestrator membuat job, lalu worker mengeksekusi loop agent secara asinkron.

Ciri utama:

  • Memisahkan request path dari execution path.
  • Mudah menambah concurrency untuk job tertentu.
  • Retry dan backoff bisa dikelola per jenis job.
  • Masih relatif mudah dipahami dibanding sistem event-driven penuh.

3. Event bus

Setiap tahap penting mempublikasikan event, lalu beberapa consumer bereaksi secara independen. Misalnya, setelah execution selesai, event yang sama bisa dikonsumsi oleh trace ingestor, feedback writer, metrik aggregator, dan evaluator.

Ciri utama:

  • Satu event dapat dipakai banyak subscriber.
  • Coupling temporal berkurang: producer tidak harus menunggu semua consumer selesai.
  • Cocok untuk workflow yang berkembang menjadi banyak reaksi turunan.
  • Namun observability, ordering, replay, dan idempotensi menjadi lebih rumit.

Kapan Monolit Modular Sudah Cukup?

Monolit modular cukup bila mayoritas kebutuhan Anda adalah menjalankan loop agent secara konsisten, dapat diuji, dan mudah di-debug. Ini sering benar untuk tahap awal produk, sistem internal, atau platform agent yang masih sering berubah desain.

Tanda-tanda monolit adalah pilihan tepat

  • Tim masih kecil dan belum ada ownership service yang jelas.
  • Perubahan domain model masih sering terjadi.
  • Volume job belum tinggi atau belum membutuhkan autoscaling terpisah.
  • Eksekusi agent masih perlu koordinasi sinkron yang ketat.
  • Masalah terbesar Anda adalah correctness dan iteration speed, bukan puncak throughput.

Keunggulan teknis monolit modular

  • Latency lebih rendah untuk komunikasi antar modul karena tidak ada hop jaringan tambahan.
  • Observability lebih mudah karena trace request dan eksekusi berada dalam satu proses atau satu boundary runtime.
  • Maintainability awal lebih baik bila modul dipisah dengan interface yang jelas.
  • Biaya operasional rendah karena deployment, monitoring, dan incident response lebih sederhana.
  • Transaksi dan konsistensi data lebih mudah karena status job, trace, dan feedback dapat diatur dekat dengan sumbernya.

Risiko monolit

  • Satu bug pada path eksekusi berat dapat mengganggu API utama.
  • Scaling menjadi seragam, padahal beban tiap modul tidak sama.
  • Job panjang dapat memakan thread, koneksi, atau memori aplikasi utama.
  • Batas antar modul bisa kabur jika disiplin desain lemah.

Praktik yang baik: meskipun masih monolit, desainlah modul seolah-olah suatu hari bisa dipisahkan. Hindari akses database lintas modul tanpa kontrak, dan simpan payload job atau event internal dalam bentuk yang stabil.

Kapan Worker Perlu Dipisahkan?

Memisahkan worker biasanya adalah langkah evolusi paling masuk akal sebelum menuju arsitektur event bus. Di titik ini Anda belum membutuhkan banyak subscriber independen, tetapi Anda sudah membutuhkan isolasi eksekusi, retry, dan scaling asinkron.

Tanda-tanda Anda butuh worker

  • Request API harus cepat selesai, tetapi loop agent bisa berlangsung detik hingga menit.
  • Panggilan ke model, tool eksternal, browser automation, atau evaluator sering lambat atau gagal sementara.
  • Anda perlu mengatur concurrency berbeda untuk jenis job yang berbeda.
  • Spike traffic membuat API utama tidak stabil karena berbagi resource dengan executor.
  • Anda perlu retry tanpa memaksa user melakukan request ulang.

Dampak worker pada throughput dan latency

Worker biasanya meningkatkan throughput total sistem untuk job berat karena eksekusi bisa diparalelkan secara terkontrol. Namun, latency end-to-end untuk hasil final bisa bertambah sedikit karena ada antrean dan serialisasi job. Ini trade-off yang sehat bila prioritas Anda adalah stabilitas dan kapasitas, bukan hasil sinkron instan.

Dampak worker pada observability dan reliability

  • Observability sedikit lebih kompleks: Anda perlu correlation ID dari request ke job ke trace.
  • Isolasi kegagalan membaik: crash worker tidak selalu mematikan API.
  • Retry lebih natural: queue biasanya mendukung pengulangan dengan delay atau dead-letter pattern.
  • Idempotensi menjadi wajib: job bisa diproses ulang karena timeout, crash, atau visibility timeout.

Contoh alur worker untuk trace -> execution -> feedback data

Client/API
  - create task
  - simpan task status = queued
  - enqueue ExecuteAgentJob(task_id, trace_id)

Worker ExecuteAgentJob
  - load task + context
  - tulis trace event: execution_started
  - jalankan planner/model/tools
  - simpan output + trace event: execution_finished
  - enqueue PersistFeedbackJob(task_id, trace_id, result_id)
  - update task status = completed | failed

Worker PersistFeedbackJob
  - bentuk feedback record
  - simpan evaluasi / preference / reward signal
  - tulis trace event: feedback_persisted

Alur di atas sengaja sederhana. Intinya, job eksekusi tidak harus sekaligus menangani seluruh pipeline turunan. Namun pemecahan job harus tetap rasional. Jika setiap langkah kecil dijadikan worker terpisah tanpa alasan jelas, Anda hanya memindahkan kompleksitas dari function call ke distributed debugging.

Kapan Event Bus Layak Ditambahkan?

Event bus layak ketika sistem loop agent tidak lagi hanya butuh pemrosesan asinkron, tetapi mulai memiliki banyak konsumen independen untuk event yang sama. Misalnya satu event execution_finished perlu diproses oleh:

  • trace storage,
  • feedback writer,
  • cost accounting,
  • alerting,
  • analytics,
  • dataset curation,
  • evaluator terjadwal,
  • pipeline retraining.

Pada titik ini, menambahkan semua pemanggilan tersebut langsung dari worker utama akan membuat coupling tinggi. Setiap penambahan fitur baru memperpanjang chain dan memperbesar blast radius kegagalan.

Tanda-tanda event bus memang dibutuhkan

  • Satu event domain harus dikonsumsi banyak komponen yang owner-nya berbeda.
  • Anda memerlukan replay event untuk membangun ulang state turunan atau pipeline analitik.
  • Alur mulai bersifat reaktif, bukan sekadar request lalu execute.
  • Pipeline turunan berkembang lebih cepat daripada engine eksekusi utama.
  • Anda ingin mengurangi coupling langsung antara producer dan consumer.

Trade-off event bus

  • Throughput dapat meningkat karena beban distribusi dipisah ke consumer masing-masing.
  • Latency antar komponen bertambah karena event propagation tidak instan.
  • Observability jauh lebih menantang: tracing lintas producer, broker, dan consumer perlu dirancang, bukan diasumsikan.
  • Idempotensi mutlak karena event dapat dikirim ulang atau diproses lebih dari sekali.
  • Ordering tidak boleh diasumsikan kecuali benar-benar dijamin oleh desain topik/partisi dan kunci event.
  • Biaya operasional naik: broker, retention, dead-letter handling, schema evolution, dan on-call complexity.
  • Maintainability bisa membaik atau memburuk tergantung disiplin kontrak event. Tanpa schema yang jelas, event bus berubah menjadi integrasi implisit yang sulit dipetakan.

Contoh event flow yang masuk akal

Event: task.created
  - consumed by execution-orchestrator

Event: execution.started
  - consumed by tracing-service
  - consumed by monitoring

Event: execution.finished
  - consumed by trace-writer
  - consumed by feedback-pipeline
  - consumed by cost-metering
  - consumed by evaluation-scheduler

Event: feedback.persisted
  - consumed by analytics
  - consumed by dataset-builder

Perhatikan bahwa event bus bukan pengganti desain job yang baik. Ia lebih cocok untuk distribusi event domain, bukan untuk memecah semua langkah kecil secara agresif.

Matriks Keputusan: Monolit vs Worker vs Event Bus

AspekMonolit ModularWorker TerpisahEvent Bus
Kompleksitas awalRendahSedangTinggi
Latency request utamaBaik jika beban ringanSangat baik untuk API karena job dipindah ke asyncBaik untuk producer, tapi end-to-end lebih variatif
Throughput job beratTerbatas oleh satu deploymentBaik, mudah diskalakan per workerSangat baik untuk banyak consumer independen
ObservabilityPaling mudahButuh correlation ID dan trace lintas prosesPaling sulit tanpa tracing dan schema discipline
RetryManual atau internalNatural lewat queueNatural tapi lebih kompleks karena banyak consumer
IdempotensiPenting, tapi lebih mudah dikendalikanSangat pentingWajib dan harus sistematis
Isolasi kegagalanTerbatasBaik antara API dan workerBaik per consumer, tapi debugging lebih sulit
Biaya operasionalRendahSedangTinggi
Maintainability jangka pendekBaikBaik bila boundary jelasSering menurun jika event sprawl
Maintainability jangka panjangBisa menurun bila semua menumpukSering paling seimbangBaik jika kebutuhan multi-konsumen nyata dan governance kuat

Idempotensi, Retry, dan Model Kegagalan

Pada loop agent, kegagalan bukan edge case. Timeout model, rate limit tool, jaringan putus, worker restart, atau event terkirim ulang adalah kejadian normal. Karena itu, idempotensi harus dianggap sebagai requirement desain.

Prinsip idempotensi yang praktis

  • Gunakan job ID atau execution ID yang stabil.
  • Simpan status transisi secara eksplisit: queued, running, completed, failed.
  • Sebelum menulis hasil, cek apakah hasil untuk execution ID tersebut sudah ada.
  • Untuk feedback data, gunakan key unik yang mencegah duplikasi insert.
  • Bedakan retry aman dan langkah yang punya side effect eksternal.

Contoh pseudo-code idempotent worker

function executeAgentJob(job) {
  const execution = db.findExecution(job.executionId)

  if (execution.status === 'completed') {
    return
  }

  if (execution.status !== 'running') {
    db.markRunning(job.executionId)
  }

  const result = runAgent(execution.input)

  db.transaction(() => {
    if (!db.resultExists(job.executionId)) {
      db.insertResult(job.executionId, result)
    }
    db.markCompleted(job.executionId)
  })
}

Contoh ini tidak bergantung pada framework tertentu, tetapi menunjukkan inti idempotensi: jangan mengasumsikan job hanya akan dijalankan sekali.

Retry yang sehat

  • Gunakan retry untuk error sementara, bukan untuk validation error permanen.
  • Terapkan backoff agar sistem tidak menghantam dependency yang sedang bermasalah.
  • Pisahkan dead-letter queue atau bucket error untuk investigasi manual.
  • Simpan alasan gagal dan jumlah percobaan agar diagnosis lebih cepat.

Observability untuk Sistem Agent Berbasis Loop

Semakin terdistribusi arsitektur Anda, semakin penting observability yang dirancang sejak awal. Masalah umum pada sistem agent adalah developer hanya menyimpan log teks, lalu bingung saat satu task melewati banyak langkah dan banyak proses.

Minimal data yang perlu ada di setiap langkah

  • trace_id: mengikat seluruh alur task.
  • execution_id: identitas satu putaran eksekusi.
  • job_id atau event_id: identitas unit kerja spesifik.
  • parent_id: menghubungkan step turunan.
  • state/status: queued, running, completed, failed.
  • timestamp: untuk menghitung latency per tahap.
  • error type dan retry count.

Tips praktis observability

  • Gunakan structured logging, bukan string bebas.
  • Tampilkan timeline per task di dashboard internal jika memungkinkan.
  • Catat durasi antrean terpisah dari durasi eksekusi.
  • Jangan campur status bisnis dengan status infrastruktur.
  • Untuk event bus, simpan metadata event version dan producer source.

Kesalahan umum: mengukur hanya latency model, padahal bottleneck nyata sering ada di antrean, serialisasi payload, write ke storage, atau retry side-effect.

Anti-Pattern: Memecah Service Terlalu Dini

Pola yang paling sering merusak maintainability adalah membuat banyak service hanya karena alurnya terlihat cocok untuk microservices. Pada loop agent, ini berbahaya karena domain dan eksperimen biasanya masih berubah cepat.

Bentuk anti-pattern yang umum

  • Setiap langkah kecil dijadikan service sendiri: planner-service, prompt-service, tool-service, feedback-service, trace-service, evaluator-service, padahal tim belum mampu mengoperasikannya.
  • Event bus dipakai untuk semua interaksi, termasuk yang seharusnya cukup synchronous call internal.
  • Tidak ada schema event yang stabil, sehingga perubahan payload mematahkan consumer diam-diam.
  • Semua masalah performa dianggap perlu diatasi dengan pemisahan service, padahal bottleneck utamanya ada di query database atau dependency eksternal.

Mengapa ini terjadi?

Karena arsitektur terdistribusi terlihat lebih fleksibel di atas kertas. Namun pada praktiknya, Anda membayar dengan:

  • lebih banyak deployment,
  • lebih banyak alarm palsu,
  • lebih sulit local development,
  • lebih sulit testing end-to-end,
  • lebih banyak failure mode non-bisnis.

Jika Anda belum bisa menjelaskan kontrak data, model retry, ownership, dan alasan scaling untuk sebuah service baru, kemungkinan besar service itu belum perlu dipisahkan.

Checklist Migrasi Bertahap ke Arsitektur Terdistribusi

Migrasi yang sehat biasanya dilakukan dalam beberapa tahap, bukan loncat langsung ke event bus.

Tahap 1: Rapikan monolit modular

  • Pisahkan modul domain: task, execution, trace, feedback.
  • Definisikan interface antar modul.
  • Tambahkan trace ID dan execution ID yang konsisten.
  • Pastikan status state machine jelas dan terdokumentasi.
  • Buat operasi penting idempotent meski masih satu proses.

Tahap 2: Pindahkan eksekusi berat ke worker

  • Identifikasi langkah yang paling lambat atau rawan gagal.
  • Masukkan job payload minimal: referensi ID lebih baik daripada payload besar.
  • Tambahkan retry policy dan dead-letter handling.
  • Pisahkan metrik antrean dan metrik durasi eksekusi.
  • Uji failure mode: worker crash, duplicate delivery, timeout dependency.

Tahap 3: Stabilkan kontrak job dan trace

  • Pastikan format event internal konsisten.
  • Tambahkan versioning untuk payload yang berpotensi berubah.
  • Dokumentasikan producer-consumer relationship.
  • Pastikan setiap consumer aman terhadap duplicate processing.

Tahap 4: Tambahkan event bus hanya untuk event domain yang memang dipakai banyak consumer

  • Mulai dari sedikit event bernilai tinggi, misalnya execution.finished.
  • Jangan migrasikan semua komunikasi sekaligus.
  • Tambahkan monitoring backlog, consumer lag, dan dead-letter.
  • Audit apakah event benar-benar dipakai lebih dari satu atau dua consumer independen.
  • Siapkan prosedur replay dan rollback schema.

Tahap 5: Evaluasi ulang boundary service

  • Pisahkan service hanya jika ada kebutuhan scaling, ownership, atau failure isolation yang nyata.
  • Hindari membuat service baru yang hanya menjadi pass-through tipis.
  • Pastikan local test flow masih realistis dijalankan developer.

Rekomendasi Praktis untuk Banyak Tim

Untuk banyak sistem AI agent berbasis loop eksekusi, urutan keputusan yang paling aman adalah:

  1. Mulai dari monolit modular dengan boundary domain yang rapi.
  2. Tambah worker saat eksekusi mulai berat, lama, atau perlu retry dan concurrency terpisah.
  3. Tambah event bus saat event domain yang sama benar-benar punya banyak consumer independen dan Anda siap menanggung kompleksitas observability serta operasionalnya.

Dengan kata lain, worker sering menjadi titik tengah terbaik. Ia memberi isolasi kegagalan, throughput lebih baik, dan retry yang masuk akal tanpa langsung masuk ke kompleksitas penuh event-driven architecture.

Penutup

Memilih arsitektur loop agent seharusnya didorong oleh pola beban kerja dan kebutuhan operasional, bukan preferensi gaya arsitektur. Jika sistem Anda masih berfokus pada akurasi alur, eksperimen cepat, dan debugging yang mudah, monolit modular biasanya cukup. Jika bottleneck utama mulai muncul pada eksekusi asinkron dan stabilitas API, worker adalah langkah berikutnya yang paling praktis. Event bus baru layak ketika loop agent sudah berkembang menjadi sistem multi-konsumen yang reaktif dan Anda benar-benar membutuhkan pemisahan tersebut.

Prinsip sederhananya: tambahkan distribusi saat manfaatnya jelas lebih besar daripada biaya koordinasinya. Pada sistem agent, biaya koordinasi itu nyata: idempotensi, retry, tracing, ordering, schema, dan operasi harian. Semakin terlambat Anda menambah kompleksitas tanpa terlambat menangani bottleneck, semakin sehat arsitektur Anda dalam jangka panjang.