Hai gaes! Pernah kepikiran gimana caranya bikin sekumpulan AI agent kamu bisa kerja barengan secara rapi, mandiri, dan tanpa saling tabrakan? Apalagi kalau kamu lagi bangun proyek keren dan nggak mau pusing sama infrastruktur yang rumit. Nah, siap-siap karena ada rahasia dapur developer yang mungkin belum banyak yang tahu!
Artikel ini awalnya diterbitkan di hexisteme notes, sebagai bagian dari seri tentang membangun dan menjalankan AI agent fleet. Yuk, kita bongkar!
Bayangkan kamu punya banyak AI agent, masing-masing adalah CLI independen yang sudah terinstal—ada yang jago ngumpulin informasi, ada yang ahli nulis konten, ada juga yang bisa bikin kerangka aplikasi. Lalu, kamu pengen mereka menjalankan sebuah tujuan besar yang butuh beberapa langkah berurutan dari berbagai agent ini. Biasanya, solusi ‘berat’ untuk ini adalah pakai framework dalam satu proses (kayak LangGraph atau gaya AutoGPT) atau pakai message broker (Redis, Kafka, RabbitMQ).
Tapi, jujur aja, buat ‘fleet’ AI agent yang operatornya cuma satu atau skalanya belum terlalu besar, solusi ini seringkali overkill dan bikin ribet. Framework biasanya bikin worker kamu terikat dalam satu proses dan satu bahasa pemrograman. Sedangkan broker itu infrastruktur tambahan yang harus kamu jalankan, amankan, dan terus monitor. Bikin pusing, kan?
Untungnya, ada cara yang jauh lebih ringan dan elegan yang pas banget buat skenario ini: sebuah work-bus yang terbuat dari file!
Gimana Sih Cara Kerjanya? Simpel Banget!
Jadi, ada satu proses conductor (ibarat sang dirigen orkestra) yang mengelola sebuah direktori barengan—ini dia 'bus' kita. Buat ngelaksanain sebuah tujuan, langkahnya gini:
- Uraikan tujuan utama jadi grafik subtasks yang terarah dan asiklik (sering disebut DAG, contoh: kumpulin data → nulis narasi → bikin aplikasi).
- Untuk tiap subtask yang siap, tulis sebuah Task file ke dalam 'bus', lengkap dengan capability (kemampuan) yang dibutuhkan.
- Pantau terus atau 'poll' sampai Result file yang sesuai muncul, dengan jeda waktu (backoff) yang singkat.
- Setelah hasilnya didapat, serap (absorb), validasi, lalu lepaskan subtasks berikutnya dalam grafik.
Satu aturan emas yang bikin mekanisme ini aman adalah atomic writes. Jadi, tiap data ditulis ke jalur sementara dulu, baru di-rename ke tempat aslinya. Proses rename ini bersifat atomic di POSIX filesystems, artinya pembaca cuma bakal lihat file utuh atau nggak sama sekali—nggak pernah lihat data setengah jadi. Task dan Result ini adalah 'typed records' (seperti pydantic schema kecil), dan conductor punya catatan apa saja yang lagi dalam proses.
Lihat nih contoh kodenya:
# atomic publish — a reader never sees a partial recorddef publish(path, record): tmp = path.with_suffix(".tmp") tmp.write_text(record.model_dump_json()) tmp.rename(path) # atomic on POSIX# the conductor loopfor task in topo_order(dag): publish(bus / f"{task.id}.task.json", task) result = poll(bus / f"{task.id}.result.json", backoff=...) # durable: waits for the file absorb(result)Bukan Sekadar Event Fana, Tapi 'State' yang Tahan Banting!
Mungkin ada yang bertanya, 'Ini file work-bus cuma event bus yang nyamar, ya?' Jawabannya, bukan! Dan perbedaan inilah yang bikin sistem ini jago. Event bus itu sifatnya 'push': producer ngirim event yang sifatnya sebentar, kalau nggak ada yang dengerin saat itu juga, ya ketinggalan. Nah, file work-bus ini sifatnya 'state': catatan Task dan Result adalah file yang awet di disk sampai mereka dipakai.
Jadi, kalau ada worker yang telat mulai atau restart di tengah jalan, tugasnya tetap setia menunggu. (Konsep yang sama juga berlaku buat monitoring: state is truth, events are rumors—dan ini berlaku lagi buat koordinasi!). Kenapa push cocok buat ini tapi nggak buat monitoring? Karena kamu yang bangun dan kontrol worker ini, jadi kamu bisa bikin mereka baca dan tulis ke 'bus'. Monitoring itu kebalikannya—kamu ngawasin komponen yang nggak kamu kontrol, jadi kamu pull state mereka. Koordinasi worker milikmu via file yang awet, monitoring komponen yang bukan milikmu via state scan: keduanya sama-sama mengandalkan state yang tahan lama dibanding event yang cepat hilang.
Routing Cerdas: Ngasih Tugas Berdasarkan Kemampuan, Bukan Nama!
Si conductor ini nggak kayak bos yang nentuin 'langkah 2 dikirim ke worker X'. Nggak gitu! Tiap worker itu ngumumin capabilities (kemampuan) yang mereka punya. Terus, tiap subtask ngumumin capability apa yang dia butuhkan. Nah, si conductor tinggal cocokin aja saat mau ngasih tugas, cari worker yang sehat dan punya capability yang pas.
Mau nambah atau ngurangin worker? Nggak masalah! Routing bakal otomatis menyesuaikan, nggak perlu ubah-ubah 'wiring diagram'. Ini dia kuncinya kenapa satu conductor bisa ngatur 'fleet' yang heterogen dan selalu berubah dengan satu 'contract' yang seragam.
Anti Galau! 'Worker' Nggak Ada? Sistem Tetap Jalan!
Ini fitur paling penting buat 'fleet' yang lagi dibangun: kalau ada worker yang 'missing', sistem nggak boleh langsung gagal! Kalau ada subtask yang butuh capability tapi nggak ada worker sehat yang menyediakannya, conductor tinggal menandai node itu sebagai 'skipped' (dan dicatat di log sebagai worker_absent). Habis itu, dia tetap lanjutin proses di grafik yang lain dan bakal menyusun hasilnya dari apa pun yang sudah selesai.
Jadi, di hari pertama, waktu sebagian besar worker belum ada, conductor tetap bisa jalan end-to-end dan menghasilkan output parsial. Catatan 'skip' itu bahkan bisa jadi to-do list yang jelas tentang capability mana saja yang harus dibangun selanjutnya. Keren, kan? Kekurangan dilaporkan, bukan malah crash.
Output 'Agent' Jangan Langsung Dipercaya! Harus Ada Validasi!
Output dari worker itu ibarat input yang nggak bisa langsung dipercaya karena melintasi batas, dan 'bus' kita memperlakukannya seperti itu. Tiap result bakal di-parse ke dalam schema yang ketat sebelum diserap. Kalau ada ketidaksesuaian (misalnya, beda kapitalisasi antara format di 'wire' dan enum internal), itu akan disesuaikan dan dinormalisasi.
Klaim penting yang bawa beban harus punya label provenance dan menyertakan bukti—klaim yang datang ditandai FACT tanpa ID bukti akan langsung ditolak saat di-parse, bukan malah dipercaya. Typed contract inilah yang memungkinkan worker independen, yang ditulis dalam bahasa berbeda oleh kamu di waktu yang berbeda, bisa saling beroperasi tanpa conductor harus percaya buta-buta pada salah satunya.
Oke, Tapi Ada Satu Batasan Jujur Nih...
⚠️ Hati-hati: Nggak Ada Kondisi Berhenti Buat 'Re-routing'! Routing berbasis capability ini memang keren, tapi ada satu celah: kalau sebuah node bisa di-re-route ke 'worker' mana pun yang ngiklanin capability C dan hasilnya terus gagal validasi, conductor yang naive bisa muter-muter dalam loop tanpa batas. Sebuah file work-bus butuh 'attempt budget' eksplisit per node (dan dead-letter outcome) biar nggak terus-terusan muter. Durability dan decoupling itu keuntungannya; tapi retry policy yang terbatas itu harga yang harus kamu bayar untuk bisa memakai fitur ini dengan aman.
Jadi, Kapan Dong Kita Pakai 'Message Broker' Asli?
Pola ini cocok banget buat 'fleet' kecil yang heterogen, yang menjalankan tugas berdurasi detik hingga menit, dan diatur oleh satu operator. Kalau kamu butuh high-throughput, low-latency fan-out dengan banyak producer dan consumer, nah, baru deh pakai message bus asli. Model polling dan single-conductor dari file-bus ini nggak bakal sanggup.
Pilih 'tool' sesuai dengan masalah yang bikin pusing: buat 'fleet' skala kecil, yang bikin sakit kepala itu 'operational overhead' dan 'coupling' yang rapuh. Dan percaya deh, direktori berisi atomic files bisa menghilangkan dua masalah itu!
Pengen tau lebih banyak soal membangun AI agent fleet dan tips-tips keren lainnya? Kamu bisa cek hexisteme.github.io/notes buat artikel lain tentang kenapa event bus ditolak buat monitoring, cara melabeli fakta versus inferensi, dan unit keputusan yang bisa dipakai ulang. Yuk, siapkan dirimu untuk jadi arsitek AI masa depan!