[{"data":1,"prerenderedAt":292},["ShallowReactive",2],{"site-header":3,"article-multi-agen-linear-atau-grafik":60},{"featuredPost":4,"navigations":24},{"id":5,"title":6,"slug":7,"excerpt":8,"published_at":9,"reading_time":10,"hidden_from_listing":11,"tags":12,"thumbnail_image":17,"og_image":18,"series":19,"authors":23},110,"Ketika Pilih Framework Cuma Biar Kelihatan Beda","ketika-pilih-framework-cuma-biar-kelihatan-beda","Terakhir kali kamu milih framework atau library baru, itu karena masalahmu butuh alat itu, atau karena alatnya kedengaran keren dan belum banyak yang pakai? Pertanyaan ini nggak enak dijawab. Soalnya keputusan teknologi sering diambil dengan alasan yang nggak pernah ditulis di dokumen teknis mana pun.","2026-10-06T15:25:21.000Z",5,false,[13,14,15,16],"karier","framework","opini","pengalaman",null,"\u002Fog\u002Fketika-pilih-framework-cuma-biar-kelihatan-beda.png",{"id":20,"title":21,"slug":22},9,"Artikel","all",[],[25,38,55],{"collection":26,"item":27},"series",{"__typename":26,"title":21,"slug":22,"children_series":28},[29,32,35],{"title":30,"slug":31},"Kecerdasan Artifisial","kecerdasan-artifisial",{"title":33,"slug":34},"Konsep Dasar Pemrograman","konsep-dasar-pemrograman",{"title":36,"slug":37},"Algoritma dan Struktur Data","algoritma-struktur-data",{"collection":26,"item":39},{"__typename":26,"title":40,"slug":41,"children_series":42},"Tutorial","tutorial",[43,46,49,52],{"title":44,"slug":45},"Belajar Python","belajar-python",{"title":47,"slug":48},"Belajar Golang","belajar-golang",{"title":50,"slug":51},"Belajar Java","belajar-java",{"title":53,"slug":54},"Belajar Pascal","belajar-pascal",{"collection":26,"item":56},{"__typename":26,"title":57,"slug":58,"children_series":59},"Lab","lab",[],{"article":61,"renderedBody":212,"renderedTakeaway":213,"latestArticles":214,"toc":267},{"id":62,"title":63,"slug":64,"excerpt":65,"published_at":66,"reading_time":67,"hidden_from_listing":11,"tags":68,"thumbnail_image":17,"og_image":75,"series":76,"authors":207,"body":210,"takeway":211,"prerequisite":17},128,"Linear atau graf: cara mengatur banyak agent tanpa loop abadi","multi-agen-linear-atau-grafik","Satu agent dengan dua puluh tool gampang kacau. Begitu beberapa agent saling revisi, kamu butuh graf yang mendukung cycle dan angka yang membuatnya berhenti.","2026-10-21T16:05:00.000Z",6,[69,70,71,72,73,74],"multi-agent","langgraph","autogen","orkestrasi","checkpoint","llm","\u002Fog\u002Fmulti-agen-linear-atau-grafik.png",{"id":77,"title":30,"slug":31,"description":17,"articles":78,"children_series":199,"parent_series":200},8,[79,94,109,122,134,146,161,176,194],{"id":80,"title":81,"slug":82,"excerpt":83,"published_at":84,"reading_time":85,"hidden_from_listing":11,"tags":86,"thumbnail_image":17,"og_image":91,"series":92,"authors":93},1,"Kenapa AI Suka 'Halu'? Kenalan sama JEPA, Otak AI yang Punya Akal Sehat","kenapa-ai-suka-halu-kenalan-sama-jepa-otak-ai-yang-punya-akal-sehat","AI generatif sering 'halu' karena cuma jago nebak pola kata. JEPA coba ngerombak itu dengan ngajarin AI cara kerja dunia nyata kayak otak manusia.","2026-06-02T08:09:05.595Z",4,[87,88,89,90],"jepa","artificial-intelligence","yann-lecun","agi","\u002Fog\u002Fkenapa-ai-suka-halu-kenalan-sama-jepa-otak-ai-yang-punya-akal-sehat.png",{"id":77,"title":30,"slug":31},[],{"id":95,"title":96,"slug":97,"excerpt":98,"published_at":99,"reading_time":85,"hidden_from_listing":11,"tags":100,"thumbnail_image":17,"og_image":101,"series":102,"authors":103},2,"LLM Bakal Mentok, Terus Gimana Dong?","llm-bakal-mentok-terus-gimana-dong","LLM kayak ChatGPT itu jago ngerangkai kata, tapi nggak beneran paham realitas. Yann LeCun bilang arsitektur ini bakal mentok dan butuh solusi baru.","2026-06-03T08:55:33.003Z",[88,74,89,87],"\u002Fog\u002Fllm-bakal-mentok-terus-gimana-dong.png",{"id":77,"title":30,"slug":31},[104],{"authors_id":105},{"id":80,"name":106,"slug":107,"role":108,"profile_picture":17},"Daffa Izzuddin","izzudd","Writer",{"id":110,"title":111,"slug":112,"excerpt":113,"published_at":114,"reading_time":85,"hidden_from_listing":11,"tags":115,"thumbnail_image":17,"og_image":117,"series":118,"authors":119},3,"Mengenal JEPA: Saat AI Mulai Belajar Punya \"Akal Sehat\"","kenapa-ai-suka-halu-dan-gimana-jepa-beresin-masalah-ini","AI sering 'halu' karena cuma nebak probabilitas data tanpa paham realita. Arsitektur JEPA mencoba mengubah itu dengan membangun model dunia di kepalanya.","2026-06-03T09:32:50.446Z",[88,87,116,89],"world-models","\u002Fog\u002Fkenapa-ai-suka-halu-dan-gimana-jepa-beresin-masalah-ini.png",{"id":77,"title":30,"slug":31},[120],{"authors_id":121},{"id":80,"name":106,"slug":107,"role":108,"profile_picture":17},{"id":85,"title":123,"slug":124,"excerpt":125,"published_at":126,"reading_time":85,"hidden_from_listing":11,"tags":127,"thumbnail_image":17,"og_image":129,"series":130,"authors":131},"Bukan Sekadar Generative AI, JEPA Coba Kejar Pemahaman Konsep ala Manusia","kenapa-ai-masih-gagal-paham-fisika-dasar-mengenal-arsitektur-jepa","AI biasanya cuma jago nebak piksel, tapi JEPA coba belajar lewat konsep abstrak di ruang laten. Ternyata, cara ini bisa sampai dipakai buat baca gelombang otak.","2026-06-03T09:34:14.840Z",[87,89,128,90],"machine-learning","\u002Fog\u002Fkenapa-ai-masih-gagal-paham-fisika-dasar-mengenal-arsitektur-jepa.png",{"id":77,"title":30,"slug":31},[132],{"authors_id":133},{"id":80,"name":106,"slug":107,"role":108,"profile_picture":17},{"id":10,"title":135,"slug":136,"excerpt":137,"published_at":138,"reading_time":85,"hidden_from_listing":11,"tags":139,"thumbnail_image":17,"og_image":141,"series":142,"authors":143},"Di Balik Dapur JEPA: Rahasia EMA, VICReg, dan Efisiensi Komputasi","di-balik-dapur-jepa-rahasia-ema-vicreg-dan-efisiensi-komputasi","Bosen sama AI yang cuma jago nebak kata atau ngafalin pixel? Kenalan sama JEPA, arsitektur revolusioner yang ngajarin mesin punya \"akal sehat\" secara super efisien. Yuk intip jeroan teknisnya!","2026-06-07T16:14:13.723Z",[87,140],"artificial-intelligent","\u002Fog\u002Fdi-balik-dapur-jepa-rahasia-ema-vicreg-dan-efisiensi-komputasi.png",{"id":77,"title":30,"slug":31},[144],{"authors_id":145},{"id":80,"name":106,"slug":107,"role":108,"profile_picture":17},{"id":147,"title":148,"slug":149,"excerpt":150,"published_at":151,"reading_time":77,"hidden_from_listing":11,"tags":152,"thumbnail_image":17,"og_image":158,"series":159,"authors":160},125,"MCP dibongkar ulang: kabar baik buat kamu yang capek nulis konektor","mcp-dibongkar-ulang-kabar-baik-buat-kamu","Kamu punya beberapa API internal dan satu agent AI, tapi kodenya harus ditulis ulang tiap kali modelnya diganti. MCP datang buat mengakhiri itu, dan tahun ini protokolnya dibongkar ulang supaya server-nya bisa jalan seperti web server biasa. Yuk lihat apa yang berubah. 🔧","2026-10-16T05:00:00.000Z",[153,154,155,74,156,157],"mcp","model-context-protocol","ai-agent","backend","python","\u002Fog\u002Fmcp-dibongkar-ulang-kabar-baik-buat-kamu.png",{"id":77,"title":30,"slug":31},[],{"id":162,"title":163,"slug":164,"excerpt":165,"published_at":166,"reading_time":167,"hidden_from_listing":11,"tags":168,"thumbnail_image":17,"og_image":171,"series":172,"authors":173},126,"Memori AI Agent: dari Vector Store ke Temporal Graph","memori-ai-agent-vector-store-ke-temporal-graph","Dua fakta yang saling bertentangan bikin agent menjawab ngawur. Ini bedanya vector store dan temporal knowledge graph dalam menjaga state jangka panjang.","2026-10-18T13:13:29.458Z",7,[169,74,156,170],"ai","system-design","\u002Fog\u002Fmemori-ai-agent-vector-store-ke-temporal-graph.png",{"id":77,"title":30,"slug":31},[174],{"authors_id":175},{"id":80,"name":106,"slug":107,"role":108,"profile_picture":17},{"id":177,"title":178,"slug":179,"excerpt":180,"published_at":181,"reading_time":67,"hidden_from_listing":11,"tags":182,"thumbnail_image":17,"og_image":189,"series":190,"authors":191},127,"Kenapa RAG yang cuma andalkan cosine similarity sering nyasar","kenapa-rag-cuma-cosine-similarity-sering-nyasar","RAG yang cuma andalkan embedding balikin chunk yang mirip maknanya, bukan yang kamu butuh. Ini penyebabnya, dan dua lapis penambalnya.","2026-10-19T02:04:06.718Z",[183,184,185,186,187,188],"rag","vector-search","bm25","hybrid-search","retrieval","embedding","\u002Fog\u002Fkenapa-rag-cuma-cosine-similarity-sering-nyasar.png",{"id":77,"title":30,"slug":31},[192],{"authors_id":193},{"id":80,"name":106,"slug":107,"role":108,"profile_picture":17},{"id":62,"title":63,"slug":64,"excerpt":65,"published_at":66,"reading_time":67,"hidden_from_listing":11,"tags":68,"thumbnail_image":17,"og_image":75,"series":195,"authors":196},{"id":77,"title":30,"slug":31},[197],{"authors_id":198},{"id":80,"name":106,"slug":107,"role":108,"profile_picture":17},[],{"id":20,"title":21,"slug":22,"parent_series":17,"children_series":201},[202,203,205],{"id":77,"title":30,"slug":31,"cover_image":17},{"id":204,"title":33,"slug":34,"cover_image":17},12,{"id":206,"title":36,"slug":37,"cover_image":17},15,[208],{"authors_id":209},{"id":80,"name":106,"slug":107,"role":108,"profile_picture":17},"\nKasih satu *agent* dua puluh *tool* dan *system prompt* sepanjang novel, hasil pertamanya biasanya kelihatan meyakinkan. Masalahnya tuh muncul belakangan: *agent* mulai salah pilih *tool*, lupa apa yang dia baca tiga langkah sebelumnya, lalu mengarang langkah yang nggak pernah ada di instruksi. Nggak ada error merah, cuma hasil yang makin lama makin ngawur.\n\nBegitu tugasnya bertambah (nulis kode backend, nyusun *unit test*, audit keamanan, memperbaiki *compile error*, minta persetujuan manusia sebelum *deploy*), satu *agent* nggak lagi cukup. Solusinya memecah pekerjaan ke beberapa *agent* dengan peran sempit: *Coder Agent*, *Reviewer Agent*, *Tester Agent*, *Deployment Agent*. *Prompt*-nya pendek, *tool*-nya sedikit, tanggung jawabnya jelas.\n\nYang belum selesai justru pertanyaan sesudahnya: siapa yang ngatur giliran? Kalau semua *agent* dilepas ngobrol bebas, yang jadi pembatas cuma tagihan *token* yang kamu bayar. Kalau diatur jadi *chain* kaku, revisi jadi mustahil.\n\n## *Chain* linear mentok di revisi pertama\n\n*Workflow engine* generasi awal memakai model *chain*: *agent* A bikin draf, *agent* B meninjau, *agent* C merilis. Apache Airflow termasuk kubu ini. Alurnya *DAG*, jadi boleh bercabang banyak, tapi nggak boleh membentuk *cycle*. Selama semuanya maju ke depan, model begini emang bersih dan gampang dilacak.\n\nProses review di dunia nyata jarang sebegitu rapi. *Reviewer* menemukan celah, kode dikembalikan ke *coder*, *coder* memperbaiki, lalu balik lagi ke *reviewer*. *Cycle* kayak gini normal, biasanya dua sampai tiga kali sebelum kode dianggap bersih.\n\nDi dalam *DAG*, *cycle* itu secara definisi nggak ada. Namanya aja asiklik. Kalau dipaksa pakai `while` di Python dengan *state* yang dioper sebagai variabel biasa, tiga hal langsung hilang: *state* gampang berubah tanpa jejak, riwayat siapa mengubah apa nggak tercatat, dan kalau *worker* mati di tengah jalan, nggak ada titik untuk *resume*.\n\n## Graf cuma butuh empat bagian\n\nParadigma yang sekarang dipakai LangGraph sebenarnya sederhana:\n\n- *Node*: unit kerja terisolasi. Bisa panggilan LLM dengan *prompt* khusus, bisa fungsi Python biasa.\n- *Edge*: jalur dari satu *node* ke *node* berikutnya.\n- *Conditional edge*: fungsi kecil yang membaca *state* dan memutuskan alurnya belok ke mana. Review `APPROVED`? Lurus ke *deploy*. Masih ada catatan? Balik ke *coder*.\n- *Shared state*: satu objek dengan skema ketat (biasanya `TypedDict` atau model Pydantic). Tiap *node* mengembalikan potongan yang dia ubah, lalu *reducer* menggabungkannya.\n\n```mermaid\nflowchart LR\n  subgraph lin[\"chain linear\"]\n    A[\"coder\"] --> B[\"reviewer\"]\n    B --> C[\"deploy\"]\n  end\n  subgraph sik[\"graf siklus\"]\n    D[\"coder\"] --> E[\"reviewer\"]\n    E -- \"revisi\" --> D\n    E -- \"lolos\" --> F[\"deploy\"]\n    E -- \"revisi lebih dari 3\" --> G[\"eskalasi\"]\n  end\n```\n\nEmpat bagian itu cukup untuk menggambarkan alur yang punya *cycle*, bercabang, dan berhenti sendiri.\n\n## Ping-pong yang nggak pernah berhenti\n\nSkenario paling mahal: *coder* mengirim kode dengan bug, *reviewer* menolak, *coder* mengubah baris yang salah, *reviewer* menolak lagi. *Cycle* ini bisa berjalan puluhan kali tanpa ada yang sadar, dan tagihannya baru kelihatan besok pagi 💸\n\nJadi gimana cara membatasinya? Mitigasinya **dua lapis**. Lapis pertama di *runtime*: `recursion_limit`. Satu hal yang sering salah kutip di tutorial: angka ini menghitung *super-step* (tiap *node* yang jalan), bukan jumlah *cycle* review. Nilai bawaannya juga bukan 25 lagi. Angkanya kok jadi 10007? Angka itu pindah tiga kali: 25 di 1.0.0, 10000 di 1.0.6 sampai 1.1.x, lalu **10007** sejak 1.2.0. Di LangGraph 1.2.16 yang kupakai di sini, `DEFAULT_RECURSION_LIMIT` isinya 10007, dan bisa di-*override* lewat *environment variable* `LANGGRAPH_DEFAULT_RECURSION_LIMIT` atau *config* saat `invoke`.\n\nWaktu dicoba pakai graf dengan *cycle* tanpa syarat berhenti, `GraphRecursionError` baru muncul setelah 10007 langkah. Graf kosong begini selesai dalam dua detik, tapi dua detik itu percuma kalau tiap langkahnya panggilan LLM. Jadi angka 25 yang dulu jadi bawaan sekarang harus kamu pasang sendiri. Itu memang yang kamu mau.\n\nLapis kedua di level desain: penghitung di dalam *state*, misalnya `iterasi \u003C= 3`. Lewat dari itu, alurnya dialihkan ke *node* eskalasi untuk manusia. Peran ini nggak bisa digantikan `recursion_limit`, karena angka itu cuma bilang \"sudah terlalu banyak langkah\", bukan \"review gagal tiga kali, berhenti\".\n\nAutoGen punya bentuk pengaman yang mirip, cuma namanya lain. *Group chat* tanpa `termination_condition` memang jalan terus, dan dokumentasinya bilang begitu apa adanya. Yang biasa dipakai `max_turns` atau `TextMentionTermination`.\n\n## Konteks yang membengkak\n\nBahaya kedua kelihatan sepele nih: tiap *agent* menambahkan seluruh riwayat obrolan ke *state*. Setelah sepuluh *cycle*, *context window* penuh *raw transcript* yang sudah nggak relevan, dan biaya tiap panggilan ikut naik terus.\n\nObatnya *pruning* di *reducer*: yang disimpan cuma artefak terakhir (file kode versi terbaru, ringkasan catatan review), bukan transkrip lengkap. Ini keputusan desain dari awal, bukan saklar yang bisa dinyalakan belakangan.\n\n## *Checkpoint*: kenapa *agent* bisa di-*pause*\n\n*Framework* graf modern menyimpan *snapshot* dari *state* tiap kali eksekusi pindah *node*. Snapshot itu diikat ke `thread_id` dan `checkpoint_id` di *database* persisten: `PostgresSaver` buat Postgres, `RedisSaver` buat Redis, `InMemorySaver` buat main-main di lokal.\n\nDari *checkpoint* itu tiga hal jadi mungkin. Kalau *container* restart di tengah kerjaan empat puluh menit, eksekusi di-*resume* dari *checkpoint* terakhir. Untuk persetujuan manusia sebelum *node* rilis, eksekusi di-*pause* lewat `interrupt`, *state* disimpan, dan graf menunggu *resume*. Untuk *debugging*, *state* lama bisa dibaca ulang, diedit, lalu dijalankan ulang dari titik itu.\n\nSatu detail yang jarang disebut: *checkpoint* disimpan di batas *super-step*, bukan di tengah fungsi. Konsekuensinya, kalau eksekusi di-*pause* lalu di-*resume*, *node* yang sedang berjalan di-*replay* dari awal fungsi. *Side effect*-nya jadi **wajib** idempoten. Kirim email dua kali, potong saldo dua kali: itu bukan teori, itu yang beneran kejadian kalau *node*-nya nggak disiapkan.\n\n## Kodenya, versi yang jalan sekarang\n\nSekarang kita lihat kodenya. Ini graf revisi dengan pengaman. *Reviewer*-nya sengaja pakai aturan tetap, bukan LLM, biar bisa diuji tanpa *API key*.\n\n```python\nfrom typing import TypedDict, Literal\nfrom langgraph.graph import StateGraph, START, END\nfrom langgraph.checkpoint.memory import InMemorySaver\n\nclass TimState(TypedDict):\n    tugas: str\n    draf: str\n    catatan_review: str\n    iterasi: int\n\nMAKS_REVISI = 3\n\ndef coder(state: TimState) -> dict:\n    iterasi = state.get(\"iterasi\", 0) + 1\n    print(f\"[coder] menulis draf ke-{iterasi}\")\n    return {\"draf\": f\"def bayar(): return True  # versi {iterasi}\", \"iterasi\": iterasi}\n\ndef reviewer(state: TimState) -> dict:\n    lolos = state[\"iterasi\"] >= 2   # di produksi ini panggilan LLM\n    print(f\"[reviewer] draf ke-{state['iterasi']} -> {'lolos' if lolos else 'ditolak'}\")\n    return {\"catatan_review\": \"APPROVED\" if lolos else \"belum ada error handling\"}\n\ndef deploy(state: TimState) -> dict:\n    print(\"[deploy] disetujui, lanjut rilis\")\n    return {}\n\ndef eskalasi(state: TimState) -> dict:\n    print(\"[eskalasi] batas revisi habis, lempar ke manusia\")\n    return {}\n\ndef arah_setelah_review(state: TimState) -> Literal[\"coder\", \"deploy\", \"eskalasi\"]:\n    if state[\"catatan_review\"] == \"APPROVED\":\n        return \"deploy\"\n    if state[\"iterasi\"] >= MAKS_REVISI:\n        return \"eskalasi\"\n    return \"coder\"\n\nbuilder = StateGraph(TimState)\nbuilder.add_node(\"coder\", coder)\nbuilder.add_node(\"reviewer\", reviewer)\nbuilder.add_node(\"deploy\", deploy)\nbuilder.add_node(\"eskalasi\", eskalasi)\n\nbuilder.add_edge(START, \"coder\")\nbuilder.add_edge(\"coder\", \"reviewer\")\nbuilder.add_conditional_edges(\"reviewer\", arah_setelah_review, [\"coder\", \"deploy\", \"eskalasi\"])\nbuilder.add_edge(\"deploy\", END)\nbuilder.add_edge(\"eskalasi\", END)\n\napp = builder.compile(checkpointer=InMemorySaver())\n\nconfig = {\"configurable\": {\"thread_id\": \"tugas-1\"}}\napp.invoke({\"tugas\": \"tulis endpoint pembayaran\"}, config)\nprint(\"state akhir:\", app.get_state(config).values)\n```\n\nOutputnya, dijalankan pakai LangGraph 1.2.16 dan Python 3.12:\n\n```\n[coder] menulis draf ke-1\n[reviewer] draf ke-1 -> ditolak\n[coder] menulis draf ke-2\n[reviewer] draf ke-2 -> lolos\n[deploy] disetujui, lanjut rilis\nstate akhir: {'tugas': 'tulis endpoint pembayaran', 'draf': 'def bayar(): return True  # versi 2', 'catatan_review': 'APPROVED', 'iterasi': 2}\n```\n\nKalau *reviewer* kamu buat selalu menolak, *cycle*-nya berhenti di iterasi ketiga dan alurnya pindah ke *node* eskalasi. Itu inti pengamannya: graf tetap boleh punya *cycle*, tapi ada angka yang menentukan kapan harus berhenti.\n\nSatu catatan biar nggak bingung waktu baca tutorial lama: `MemorySaver` masih bisa diimpor, tapi sekarang cuma **alias** dari `InMemorySaver`. Dua-duanya jalan, yang satunya ketinggalan zaman.\n\n## Terus AutoGen?\n\nAutoGen sering disebut sebaris dengan LangGraph, padahal bentuknya beda. AutoGen lebih dekat ke percakapan terstruktur: beberapa *agent* dengan peran, satu *termination condition* yang menentukan kapan berhenti. Kamu nggak mendefinisikan grafnya sendiri.\n\nStatusnya yang perlu kamu tahu: sejak 16 Oktober 2026 proyeknya menyatakan diri diarsipkan. Ini lebih tegas daripada sekadar berhenti dapat fitur baru, karena Microsoft menyatakan nggak menyediakan perbaikan bug, perbaikan keamanan, apalagi dukungan untuk proyek ini. Rilis terakhirnya, 0.7.5, masih bisa di-*install* dari PyPI, tapi jangan harap ada tambalan sesudahnya.\n\nPenggantinya Microsoft Agent Framework, dibangun tim inti AutoGen dan Semantic Kernel. Bagian *workflow*-nya menyediakan dua gaya sekaligus, fungsional dan berbasis graf, dan per Oktober 2026 paket Python-nya sudah rilis stabil (`agent-framework` 1.22.0).\n\nSatu catatan biar presisi: yang diarsipkan itu proyeknya, bukan repo GitHub-nya. Halamannya masih bisa dibuka dan masih bisa di-*star* sampai sekarang. Dan ini klaim yang paling cepat basi dari seluruh artikel ini, jadi cek tanggalnya sendiri sebelum mengutip.\n\n## Jadi linear atau graf?\n\nKalau alurnya memang lurus dan nggak ada yang perlu dikembalikan, *chain* linear cukup, dan graf cuma menambah bagian yang harus dirawat. Graf baru bernilai begitu ada revisi, percabangan, dan manusia yang harus menyetujui di tengah jalan.\n\nJadi, semua harus pindah ke graf? Nggak juga sih. Sistem multi-*agent* yang waras bukan berarti beberapa model dibiarkan ngobrol bebas. Yang bikin dia waras: peran yang sempit, *state* dengan skema, batas *cycle* yang eksplisit (kamu yang pilih angkanya), dan *checkpoint* yang bikin kerjanya bisa di-*resume* kapan saja ⚙️\n\nKalau *agent*-mu mulai jalan di *loop* tanpa ujung, yang pertama dicek bukan modelnya, tapi apakah ada angka yang membatasi *cycle*-nya. Sisanya cuma soal rapi-rapi *state* dan sabar baca *trace*. Semoga tagihanmu nggak bikin kaget di akhir bulan 😄\n","- Graf asiklik nggak punya tempat buat revisi balik, jadi alur yang butuh *cycle* review harus pakai graf siklus dengan *conditional edge*.\n- `recursion_limit` menghitung *super-step*, **bukan** jumlah *cycle* review, dan nilai bawaannya **10007** di LangGraph 1.2.16. Batas revisi yang kamu mau tetap harus dipasang sendiri di dalam *state*.\n- *Checkpoint* disimpan di batas *super-step*, jadi saat eksekusi di-*resume*, *node* yang sedang berjalan di-*replay* dari awal fungsi dan *side effect*-nya wajib idempoten.\n- AutoGen dinyatakan diarsipkan per 16 Oktober 2026 tanpa perbaikan bug maupun keamanan; penerusnya Microsoft Agent Framework, yang *workflow*-nya tersedia dalam gaya fungsional maupun berbasis graf.","\u003Cp>Kasih satu \u003Cem>agent\u003C\u002Fem> dua puluh \u003Cem>tool\u003C\u002Fem> dan \u003Cem>system prompt\u003C\u002Fem> sepanjang novel, hasil pertamanya biasanya kelihatan meyakinkan. Masalahnya tuh muncul belakangan: \u003Cem>agent\u003C\u002Fem> mulai salah pilih \u003Cem>tool\u003C\u002Fem>, lupa apa yang dia baca tiga langkah sebelumnya, lalu mengarang langkah yang nggak pernah ada di instruksi. Nggak ada error merah, cuma hasil yang makin lama makin ngawur.\u003C\u002Fp>\n\u003Cp>Begitu tugasnya bertambah (nulis kode backend, nyusun \u003Cem>unit test\u003C\u002Fem>, audit keamanan, memperbaiki \u003Cem>compile error\u003C\u002Fem>, minta persetujuan manusia sebelum \u003Cem>deploy\u003C\u002Fem>), satu \u003Cem>agent\u003C\u002Fem> nggak lagi cukup. Solusinya memecah pekerjaan ke beberapa \u003Cem>agent\u003C\u002Fem> dengan peran sempit: \u003Cem>Coder Agent\u003C\u002Fem>, \u003Cem>Reviewer Agent\u003C\u002Fem>, \u003Cem>Tester Agent\u003C\u002Fem>, \u003Cem>Deployment Agent\u003C\u002Fem>. \u003Cem>Prompt\u003C\u002Fem>-nya pendek, \u003Cem>tool\u003C\u002Fem>-nya sedikit, tanggung jawabnya jelas.\u003C\u002Fp>\n\u003Cp>Yang belum selesai justru pertanyaan sesudahnya: siapa yang ngatur giliran? Kalau semua \u003Cem>agent\u003C\u002Fem> dilepas ngobrol bebas, yang jadi pembatas cuma tagihan \u003Cem>token\u003C\u002Fem> yang kamu bayar. Kalau diatur jadi \u003Cem>chain\u003C\u002Fem> kaku, revisi jadi mustahil.\u003C\u002Fp>\n\u003Ch2 id=\"chain-linear-mentok-di-revisi-pertama\">\u003Cem>Chain\u003C\u002Fem> linear mentok di revisi pertama\u003C\u002Fh2>\n\u003Cp>\u003Cem>Workflow engine\u003C\u002Fem> generasi awal memakai model \u003Cem>chain\u003C\u002Fem>: \u003Cem>agent\u003C\u002Fem> A bikin draf, \u003Cem>agent\u003C\u002Fem> B meninjau, \u003Cem>agent\u003C\u002Fem> C merilis. Apache Airflow termasuk kubu ini. Alurnya \u003Cem>DAG\u003C\u002Fem>, jadi boleh bercabang banyak, tapi nggak boleh membentuk \u003Cem>cycle\u003C\u002Fem>. Selama semuanya maju ke depan, model begini emang bersih dan gampang dilacak.\u003C\u002Fp>\n\u003Cp>Proses review di dunia nyata jarang sebegitu rapi. \u003Cem>Reviewer\u003C\u002Fem> menemukan celah, kode dikembalikan ke \u003Cem>coder\u003C\u002Fem>, \u003Cem>coder\u003C\u002Fem> memperbaiki, lalu balik lagi ke \u003Cem>reviewer\u003C\u002Fem>. \u003Cem>Cycle\u003C\u002Fem> kayak gini normal, biasanya dua sampai tiga kali sebelum kode dianggap bersih.\u003C\u002Fp>\n\u003Cp>Di dalam \u003Cem>DAG\u003C\u002Fem>, \u003Cem>cycle\u003C\u002Fem> itu secara definisi nggak ada. Namanya aja asiklik. Kalau dipaksa pakai \u003Ccode>while\u003C\u002Fcode> di Python dengan \u003Cem>state\u003C\u002Fem> yang dioper sebagai variabel biasa, tiga hal langsung hilang: \u003Cem>state\u003C\u002Fem> gampang berubah tanpa jejak, riwayat siapa mengubah apa nggak tercatat, dan kalau \u003Cem>worker\u003C\u002Fem> mati di tengah jalan, nggak ada titik untuk \u003Cem>resume\u003C\u002Fem>.\u003C\u002Fp>\n\u003Ch2 id=\"graf-cuma-butuh-empat-bagian\">Graf cuma butuh empat bagian\u003C\u002Fh2>\n\u003Cp>Paradigma yang sekarang dipakai LangGraph sebenarnya sederhana:\u003C\u002Fp>\n\u003Cul>\n\u003Cli>\u003Cem>Node\u003C\u002Fem>: unit kerja terisolasi. Bisa panggilan LLM dengan \u003Cem>prompt\u003C\u002Fem> khusus, bisa fungsi Python biasa.\u003C\u002Fli>\n\u003Cli>\u003Cem>Edge\u003C\u002Fem>: jalur dari satu \u003Cem>node\u003C\u002Fem> ke \u003Cem>node\u003C\u002Fem> berikutnya.\u003C\u002Fli>\n\u003Cli>\u003Cem>Conditional edge\u003C\u002Fem>: fungsi kecil yang membaca \u003Cem>state\u003C\u002Fem> dan memutuskan alurnya belok ke mana. Review \u003Ccode>APPROVED\u003C\u002Fcode>? Lurus ke \u003Cem>deploy\u003C\u002Fem>. Masih ada catatan? Balik ke \u003Cem>coder\u003C\u002Fem>.\u003C\u002Fli>\n\u003Cli>\u003Cem>Shared state\u003C\u002Fem>: satu objek dengan skema ketat (biasanya \u003Ccode>TypedDict\u003C\u002Fcode> atau model Pydantic). Tiap \u003Cem>node\u003C\u002Fem> mengembalikan potongan yang dia ubah, lalu \u003Cem>reducer\u003C\u002Fem> menggabungkannya.\u003C\u002Fli>\n\u003C\u002Ful>\n\n\u003Cdiv class=\"mermaid-block my-6 not-prose rounded-xl border-2 border-black dark:border-gray-600 shadow-neo overflow-hidden bg-[#24292e]\" data-mermaid-diagram>\n  \u003Cdiv class=\"mermaid-header flex items-center justify-between px-4 py-2.5 bg-[#1f2428] border-b-2 border-black dark:border-gray-600 font-mono text-xs font-bold text-gray-300 select-none\">\n    \u003Cspan class=\"uppercase tracking-wider\">Diagram\u003C\u002Fspan>\n    \u003Cbutton type=\"button\" class=\"copy-code-btn flex items-center gap-1.5 px-3 py-1 text-xs font-bold bg-[#2f363d] text-gray-200 border-2 border-black dark:border-gray-500 rounded-lg shadow-neo-sm hover:translate-x-0.5 hover:translate-y-0.5 hover:shadow-none hover:bg-gray-700 transition-all cursor-pointer\" data-code=\"flowchart%20LR%0A%20%20subgraph%20lin%5B%22chain%20linear%22%5D%0A%20%20%20%20A%5B%22coder%22%5D%20--%3E%20B%5B%22reviewer%22%5D%0A%20%20%20%20B%20--%3E%20C%5B%22deploy%22%5D%0A%20%20end%0A%20%20subgraph%20sik%5B%22graf%20siklus%22%5D%0A%20%20%20%20D%5B%22coder%22%5D%20--%3E%20E%5B%22reviewer%22%5D%0A%20%20%20%20E%20--%20%22revisi%22%20--%3E%20D%0A%20%20%20%20E%20--%20%22lolos%22%20--%3E%20F%5B%22deploy%22%5D%0A%20%20%20%20E%20--%20%22revisi%20lebih%20dari%203%22%20--%3E%20G%5B%22eskalasi%22%5D%0A%20%20end\" title=\"Salin kode diagram\">\n      \u003Cspan class=\"copy-text\">Copy\u003C\u002Fspan>\n    \u003C\u002Fbutton>\n  \u003C\u002Fdiv>\n  \u003Cdiv class=\"mermaid-canvas p-4 overflow-x-auto\">\n    \u003Cdiv class=\"mermaid-source text-sm font-mono leading-relaxed text-[#e1e4e8]\" data-mermaid-src=\"flowchart%20LR%0A%20%20subgraph%20lin%5B%22chain%20linear%22%5D%0A%20%20%20%20A%5B%22coder%22%5D%20--%3E%20B%5B%22reviewer%22%5D%0A%20%20%20%20B%20--%3E%20C%5B%22deploy%22%5D%0A%20%20end%0A%20%20subgraph%20sik%5B%22graf%20siklus%22%5D%0A%20%20%20%20D%5B%22coder%22%5D%20--%3E%20E%5B%22reviewer%22%5D%0A%20%20%20%20E%20--%20%22revisi%22%20--%3E%20D%0A%20%20%20%20E%20--%20%22lolos%22%20--%3E%20F%5B%22deploy%22%5D%0A%20%20%20%20E%20--%20%22revisi%20lebih%20dari%203%22%20--%3E%20G%5B%22eskalasi%22%5D%0A%20%20end\">flowchart LR\n  subgraph lin[\"chain linear\"]\n    A[\"coder\"] --&gt; B[\"reviewer\"]\n    B --&gt; C[\"deploy\"]\n  end\n  subgraph sik[\"graf siklus\"]\n    D[\"coder\"] --&gt; E[\"reviewer\"]\n    E -- \"revisi\" --&gt; D\n    E -- \"lolos\" --&gt; F[\"deploy\"]\n    E -- \"revisi lebih dari 3\" --&gt; G[\"eskalasi\"]\n  end\u003C\u002Fdiv>\n  \u003C\u002Fdiv>\n\u003C\u002Fdiv>\u003Cp>Empat bagian itu cukup untuk menggambarkan alur yang punya \u003Cem>cycle\u003C\u002Fem>, bercabang, dan berhenti sendiri.\u003C\u002Fp>\n\u003Ch2 id=\"ping-pong-yang-nggak-pernah-berhenti\">Ping-pong yang nggak pernah berhenti\u003C\u002Fh2>\n\u003Cp>Skenario paling mahal: \u003Cem>coder\u003C\u002Fem> mengirim kode dengan bug, \u003Cem>reviewer\u003C\u002Fem> menolak, \u003Cem>coder\u003C\u002Fem> mengubah baris yang salah, \u003Cem>reviewer\u003C\u002Fem> menolak lagi. \u003Cem>Cycle\u003C\u002Fem> ini bisa berjalan puluhan kali tanpa ada yang sadar, dan tagihannya baru kelihatan besok pagi 💸\u003C\u002Fp>\n\u003Cp>Jadi gimana cara membatasinya? Mitigasinya \u003Cstrong>dua lapis\u003C\u002Fstrong>. Lapis pertama di \u003Cem>runtime\u003C\u002Fem>: \u003Ccode>recursion_limit\u003C\u002Fcode>. Satu hal yang sering salah kutip di tutorial: angka ini menghitung \u003Cem>super-step\u003C\u002Fem> (tiap \u003Cem>node\u003C\u002Fem> yang jalan), bukan jumlah \u003Cem>cycle\u003C\u002Fem> review. Nilai bawaannya juga bukan 25 lagi. Angkanya kok jadi 10007? Angka itu pindah tiga kali: 25 di 1.0.0, 10000 di 1.0.6 sampai 1.1.x, lalu \u003Cstrong>10007\u003C\u002Fstrong> sejak 1.2.0. Di LangGraph 1.2.16 yang kupakai di sini, \u003Ccode>DEFAULT_RECURSION_LIMIT\u003C\u002Fcode> isinya 10007, dan bisa di-\u003Cem>override\u003C\u002Fem> lewat \u003Cem>environment variable\u003C\u002Fem> \u003Ccode>LANGGRAPH_DEFAULT_RECURSION_LIMIT\u003C\u002Fcode> atau \u003Cem>config\u003C\u002Fem> saat \u003Ccode>invoke\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Cp>Waktu dicoba pakai graf dengan \u003Cem>cycle\u003C\u002Fem> tanpa syarat berhenti, \u003Ccode>GraphRecursionError\u003C\u002Fcode> baru muncul setelah 10007 langkah. Graf kosong begini selesai dalam dua detik, tapi dua detik itu percuma kalau tiap langkahnya panggilan LLM. Jadi angka 25 yang dulu jadi bawaan sekarang harus kamu pasang sendiri. Itu memang yang kamu mau.\u003C\u002Fp>\n\u003Cp>Lapis kedua di level desain: penghitung di dalam \u003Cem>state\u003C\u002Fem>, misalnya \u003Ccode>iterasi &lt;= 3\u003C\u002Fcode>. Lewat dari itu, alurnya dialihkan ke \u003Cem>node\u003C\u002Fem> eskalasi untuk manusia. Peran ini nggak bisa digantikan \u003Ccode>recursion_limit\u003C\u002Fcode>, karena angka itu cuma bilang &quot;sudah terlalu banyak langkah&quot;, bukan &quot;review gagal tiga kali, berhenti&quot;.\u003C\u002Fp>\n\u003Cp>AutoGen punya bentuk pengaman yang mirip, cuma namanya lain. \u003Cem>Group chat\u003C\u002Fem> tanpa \u003Ccode>termination_condition\u003C\u002Fcode> memang jalan terus, dan dokumentasinya bilang begitu apa adanya. Yang biasa dipakai \u003Ccode>max_turns\u003C\u002Fcode> atau \u003Ccode>TextMentionTermination\u003C\u002Fcode>.\u003C\u002Fp>\n\u003Ch2 id=\"konteks-yang-membengkak\">Konteks yang membengkak\u003C\u002Fh2>\n\u003Cp>Bahaya kedua kelihatan sepele nih: tiap \u003Cem>agent\u003C\u002Fem> menambahkan seluruh riwayat obrolan ke \u003Cem>state\u003C\u002Fem>. Setelah sepuluh \u003Cem>cycle\u003C\u002Fem>, \u003Cem>context window\u003C\u002Fem> penuh \u003Cem>raw transcript\u003C\u002Fem> yang sudah nggak relevan, dan biaya tiap panggilan ikut naik terus.\u003C\u002Fp>\n\u003Cp>Obatnya \u003Cem>pruning\u003C\u002Fem> di \u003Cem>reducer\u003C\u002Fem>: yang disimpan cuma artefak terakhir (file kode versi terbaru, ringkasan catatan review), bukan transkrip lengkap. Ini keputusan desain dari awal, bukan saklar yang bisa dinyalakan belakangan.\u003C\u002Fp>\n\u003Ch2 id=\"checkpoint-kenapa-agent-bisa-di-pause\">\u003Cem>Checkpoint\u003C\u002Fem>: kenapa \u003Cem>agent\u003C\u002Fem> bisa di-\u003Cem>pause\u003C\u002Fem>\u003C\u002Fh2>\n\u003Cp>\u003Cem>Framework\u003C\u002Fem> graf modern menyimpan \u003Cem>snapshot\u003C\u002Fem> dari \u003Cem>state\u003C\u002Fem> tiap kali eksekusi pindah \u003Cem>node\u003C\u002Fem>. Snapshot itu diikat ke \u003Ccode>thread_id\u003C\u002Fcode> dan \u003Ccode>checkpoint_id\u003C\u002Fcode> di \u003Cem>database\u003C\u002Fem> persisten: \u003Ccode>PostgresSaver\u003C\u002Fcode> buat Postgres, \u003Ccode>RedisSaver\u003C\u002Fcode> buat Redis, \u003Ccode>InMemorySaver\u003C\u002Fcode> buat main-main di lokal.\u003C\u002Fp>\n\u003Cp>Dari \u003Cem>checkpoint\u003C\u002Fem> itu tiga hal jadi mungkin. Kalau \u003Cem>container\u003C\u002Fem> restart di tengah kerjaan empat puluh menit, eksekusi di-\u003Cem>resume\u003C\u002Fem> dari \u003Cem>checkpoint\u003C\u002Fem> terakhir. Untuk persetujuan manusia sebelum \u003Cem>node\u003C\u002Fem> rilis, eksekusi di-\u003Cem>pause\u003C\u002Fem> lewat \u003Ccode>interrupt\u003C\u002Fcode>, \u003Cem>state\u003C\u002Fem> disimpan, dan graf menunggu \u003Cem>resume\u003C\u002Fem>. Untuk \u003Cem>debugging\u003C\u002Fem>, \u003Cem>state\u003C\u002Fem> lama bisa dibaca ulang, diedit, lalu dijalankan ulang dari titik itu.\u003C\u002Fp>\n\u003Cp>Satu detail yang jarang disebut: \u003Cem>checkpoint\u003C\u002Fem> disimpan di batas \u003Cem>super-step\u003C\u002Fem>, bukan di tengah fungsi. Konsekuensinya, kalau eksekusi di-\u003Cem>pause\u003C\u002Fem> lalu di-\u003Cem>resume\u003C\u002Fem>, \u003Cem>node\u003C\u002Fem> yang sedang berjalan di-\u003Cem>replay\u003C\u002Fem> dari awal fungsi. \u003Cem>Side effect\u003C\u002Fem>-nya jadi \u003Cstrong>wajib\u003C\u002Fstrong> idempoten. Kirim email dua kali, potong saldo dua kali: itu bukan teori, itu yang beneran kejadian kalau \u003Cem>node\u003C\u002Fem>-nya nggak disiapkan.\u003C\u002Fp>\n\u003Ch2 id=\"kodenya-versi-yang-jalan-sekarang\">Kodenya, versi yang jalan sekarang\u003C\u002Fh2>\n\u003Cp>Sekarang kita lihat kodenya. Ini graf revisi dengan pengaman. \u003Cem>Reviewer\u003C\u002Fem>-nya sengaja pakai aturan tetap, bukan LLM, biar bisa diuji tanpa \u003Cem>API key\u003C\u002Fem>.\u003C\u002Fp>\n\n\u003Cdiv class=\"code-block-wrapper my-6 not-prose rounded-xl border-2 border-black dark:border-gray-600 shadow-neo overflow-hidden bg-[#24292e] text-[#e1e4e8] transition-all\">\n  \u003Cdiv class=\"code-block-header flex items-center justify-between px-4 py-2.5 bg-[#1f2428] border-b-2 border-black dark:border-gray-600 font-mono text-xs font-bold text-gray-300 select-none\">\n    \u003Cdiv class=\"flex items-center gap-2\">\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#ff5f56] border border-black\u002F40 inline-block\">\u003C\u002Fspan>\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#ffbd2e] border border-black\u002F40 inline-block\">\u003C\u002Fspan>\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#27c93f] border border-black\u002F40 inline-block\">\u003C\u002Fspan>\n      \u003Cspan class=\"ml-2 font-mono font-bold text-xs uppercase tracking-wider text-gray-300\">PYTHON\u003C\u002Fspan>\n    \u003C\u002Fdiv>\n    \u003Cbutton type=\"button\" class=\"copy-code-btn flex items-center gap-1.5 px-3 py-1 text-xs font-bold bg-[#2f363d] text-gray-200 border-2 border-black dark:border-gray-500 rounded-lg shadow-neo-sm hover:translate-x-0.5 hover:translate-y-0.5 hover:shadow-none hover:bg-gray-700 transition-all cursor-pointer\" data-code=\"from%20typing%20import%20TypedDict%2C%20Literal%0Afrom%20langgraph.graph%20import%20StateGraph%2C%20START%2C%20END%0Afrom%20langgraph.checkpoint.memory%20import%20InMemorySaver%0A%0Aclass%20TimState(TypedDict)%3A%0A%20%20%20%20tugas%3A%20str%0A%20%20%20%20draf%3A%20str%0A%20%20%20%20catatan_review%3A%20str%0A%20%20%20%20iterasi%3A%20int%0A%0AMAKS_REVISI%20%3D%203%0A%0Adef%20coder(state%3A%20TimState)%20-%3E%20dict%3A%0A%20%20%20%20iterasi%20%3D%20state.get(%22iterasi%22%2C%200)%20%2B%201%0A%20%20%20%20print(f%22%5Bcoder%5D%20menulis%20draf%20ke-%7Biterasi%7D%22)%0A%20%20%20%20return%20%7B%22draf%22%3A%20f%22def%20bayar()%3A%20return%20True%20%20%23%20versi%20%7Biterasi%7D%22%2C%20%22iterasi%22%3A%20iterasi%7D%0A%0Adef%20reviewer(state%3A%20TimState)%20-%3E%20dict%3A%0A%20%20%20%20lolos%20%3D%20state%5B%22iterasi%22%5D%20%3E%3D%202%20%20%20%23%20di%20produksi%20ini%20panggilan%20LLM%0A%20%20%20%20print(f%22%5Breviewer%5D%20draf%20ke-%7Bstate%5B'iterasi'%5D%7D%20-%3E%20%7B'lolos'%20if%20lolos%20else%20'ditolak'%7D%22)%0A%20%20%20%20return%20%7B%22catatan_review%22%3A%20%22APPROVED%22%20if%20lolos%20else%20%22belum%20ada%20error%20handling%22%7D%0A%0Adef%20deploy(state%3A%20TimState)%20-%3E%20dict%3A%0A%20%20%20%20print(%22%5Bdeploy%5D%20disetujui%2C%20lanjut%20rilis%22)%0A%20%20%20%20return%20%7B%7D%0A%0Adef%20eskalasi(state%3A%20TimState)%20-%3E%20dict%3A%0A%20%20%20%20print(%22%5Beskalasi%5D%20batas%20revisi%20habis%2C%20lempar%20ke%20manusia%22)%0A%20%20%20%20return%20%7B%7D%0A%0Adef%20arah_setelah_review(state%3A%20TimState)%20-%3E%20Literal%5B%22coder%22%2C%20%22deploy%22%2C%20%22eskalasi%22%5D%3A%0A%20%20%20%20if%20state%5B%22catatan_review%22%5D%20%3D%3D%20%22APPROVED%22%3A%0A%20%20%20%20%20%20%20%20return%20%22deploy%22%0A%20%20%20%20if%20state%5B%22iterasi%22%5D%20%3E%3D%20MAKS_REVISI%3A%0A%20%20%20%20%20%20%20%20return%20%22eskalasi%22%0A%20%20%20%20return%20%22coder%22%0A%0Abuilder%20%3D%20StateGraph(TimState)%0Abuilder.add_node(%22coder%22%2C%20coder)%0Abuilder.add_node(%22reviewer%22%2C%20reviewer)%0Abuilder.add_node(%22deploy%22%2C%20deploy)%0Abuilder.add_node(%22eskalasi%22%2C%20eskalasi)%0A%0Abuilder.add_edge(START%2C%20%22coder%22)%0Abuilder.add_edge(%22coder%22%2C%20%22reviewer%22)%0Abuilder.add_conditional_edges(%22reviewer%22%2C%20arah_setelah_review%2C%20%5B%22coder%22%2C%20%22deploy%22%2C%20%22eskalasi%22%5D)%0Abuilder.add_edge(%22deploy%22%2C%20END)%0Abuilder.add_edge(%22eskalasi%22%2C%20END)%0A%0Aapp%20%3D%20builder.compile(checkpointer%3DInMemorySaver())%0A%0Aconfig%20%3D%20%7B%22configurable%22%3A%20%7B%22thread_id%22%3A%20%22tugas-1%22%7D%7D%0Aapp.invoke(%7B%22tugas%22%3A%20%22tulis%20endpoint%20pembayaran%22%7D%2C%20config)%0Aprint(%22state%20akhir%3A%22%2C%20app.get_state(config).values)\" title=\"Salin kode\">\n      \u003Csvg class=\"w-3.5 h-3.5 copy-icon-svg\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n        \u003Cpath stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z\">\u003C\u002Fpath>\n      \u003C\u002Fsvg>\n      \u003Cspan class=\"copy-text\">Copy\u003C\u002Fspan>\n    \u003C\u002Fbutton>\n  \u003C\u002Fdiv>\n  \u003Cdiv class=\"code-block-content p-4 overflow-x-auto text-sm font-mono leading-relaxed bg-[#24292e]\">\n    \u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\" tabindex=\"0\">\u003Ccode>\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">from\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> typing \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">import\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> TypedDict, Literal\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">from\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> langgraph.graph \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">import\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> StateGraph, \u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">START\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">END\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">from\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> langgraph.checkpoint.memory \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">import\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> InMemorySaver\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">class\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> TimState\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\">TypedDict\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">):\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    tugas: \u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">str\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    draf: \u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">str\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    catatan_review: \u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">str\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    iterasi: \u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">int\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">MAKS_REVISI\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\"> =\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\"> 3\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">def\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> coder\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(state: TimState) -> \u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">dict\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    iterasi \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> state.get(\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"iterasi\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">0\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">) \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">+\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\"> 1\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">    print\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">f\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"[coder] menulis draf ke-\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">{\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">iterasi\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">}\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    return\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> {\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"draf\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">: \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">f\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"def bayar(): return True  # versi \u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">{\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">iterasi\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">}\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"iterasi\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">: iterasi}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">def\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> reviewer\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(state: TimState) -> \u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">dict\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">    lolos \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> state[\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"iterasi\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">] \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">>=\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\"> 2\u003C\u002Fspan>\u003Cspan style=\"color:#6A737D\">   # di produksi ini panggilan LLM\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">    print\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">f\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"[reviewer] draf ke-\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">{\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">state[\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">'iterasi'\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">]\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">}\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\"> -> \u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">{\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">'lolos'\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\"> if\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> lolos \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">else\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\"> 'ditolak'\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">}\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    return\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> {\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"catatan_review\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">: \u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"APPROVED\"\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\"> if\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> lolos \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">else\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\"> \"belum ada error handling\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">def\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> deploy\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(state: TimState) -> \u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">dict\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">    print\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"[deploy] disetujui, lanjut rilis\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    return\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> {}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">def\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> eskalasi\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(state: TimState) -> \u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">dict\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">    print\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"[eskalasi] batas revisi habis, lempar ke manusia\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    return\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> {}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">def\u003C\u002Fspan>\u003Cspan style=\"color:#B392F0\"> arah_setelah_review\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(state: TimState) -> Literal[\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"coder\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"deploy\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"eskalasi\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">]:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    if\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> state[\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"catatan_review\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">] \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">==\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\"> \"APPROVED\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\"> \"deploy\"\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    if\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> state[\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"iterasi\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">] \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">>=\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\"> MAKS_REVISI\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">:\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">        return\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\"> \"eskalasi\"\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#F97583\">    return\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\"> \"coder\"\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">builder \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> StateGraph(TimState)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">builder.add_node(\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"coder\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, coder)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">builder.add_node(\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"reviewer\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, reviewer)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">builder.add_node(\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"deploy\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, deploy)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">builder.add_node(\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"eskalasi\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, eskalasi)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">builder.add_edge(\u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">START\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"coder\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">builder.add_edge(\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"coder\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"reviewer\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">builder.add_conditional_edges(\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"reviewer\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, arah_setelah_review, [\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"coder\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"deploy\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"eskalasi\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">])\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">builder.add_edge(\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"deploy\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">END\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">builder.add_edge(\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"eskalasi\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, \u003C\u002Fspan>\u003Cspan style=\"color:#79B8FF\">END\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">app \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> builder.compile(\u003C\u002Fspan>\u003Cspan style=\"color:#FFAB70\">checkpointer\u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">InMemorySaver())\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">config \u003C\u002Fspan>\u003Cspan style=\"color:#F97583\">=\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\"> {\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"configurable\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">: {\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"thread_id\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">: \u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"tugas-1\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">}}\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#E1E4E8\">app.invoke({\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"tugas\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">: \u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"tulis endpoint pembayaran\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">}, config)\u003C\u002Fspan>\u003C\u002Fspan>\n\u003Cspan class=\"line\">\u003Cspan style=\"color:#79B8FF\">print\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">(\u003C\u002Fspan>\u003Cspan style=\"color:#9ECBFF\">\"state akhir:\"\u003C\u002Fspan>\u003Cspan style=\"color:#E1E4E8\">, app.get_state(config).values)\u003C\u002Fspan>\u003C\u002Fspan>\u003C\u002Fcode>\u003C\u002Fpre>\n  \u003C\u002Fdiv>\n\u003C\u002Fdiv>\u003Cp>Outputnya, dijalankan pakai LangGraph 1.2.16 dan Python 3.12:\u003C\u002Fp>\n\n\u003Cdiv class=\"code-block-wrapper my-6 not-prose rounded-xl border-2 border-black dark:border-gray-600 shadow-neo overflow-hidden bg-[#24292e] text-[#e1e4e8] transition-all\">\n  \u003Cdiv class=\"code-block-header flex items-center justify-between px-4 py-2.5 bg-[#1f2428] border-b-2 border-black dark:border-gray-600 font-mono text-xs font-bold text-gray-300 select-none\">\n    \u003Cdiv class=\"flex items-center gap-2\">\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#ff5f56] border border-black\u002F40 inline-block\">\u003C\u002Fspan>\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#ffbd2e] border border-black\u002F40 inline-block\">\u003C\u002Fspan>\n      \u003Cspan class=\"w-3 h-3 rounded-full bg-[#27c93f] border border-black\u002F40 inline-block\">\u003C\u002Fspan>\n      \u003Cspan class=\"ml-2 font-mono font-bold text-xs uppercase tracking-wider text-gray-300\">TEXT\u003C\u002Fspan>\n    \u003C\u002Fdiv>\n    \u003Cbutton type=\"button\" class=\"copy-code-btn flex items-center gap-1.5 px-3 py-1 text-xs font-bold bg-[#2f363d] text-gray-200 border-2 border-black dark:border-gray-500 rounded-lg shadow-neo-sm hover:translate-x-0.5 hover:translate-y-0.5 hover:shadow-none hover:bg-gray-700 transition-all cursor-pointer\" data-code=\"%5Bcoder%5D%20menulis%20draf%20ke-1%0A%5Breviewer%5D%20draf%20ke-1%20-%3E%20ditolak%0A%5Bcoder%5D%20menulis%20draf%20ke-2%0A%5Breviewer%5D%20draf%20ke-2%20-%3E%20lolos%0A%5Bdeploy%5D%20disetujui%2C%20lanjut%20rilis%0Astate%20akhir%3A%20%7B'tugas'%3A%20'tulis%20endpoint%20pembayaran'%2C%20'draf'%3A%20'def%20bayar()%3A%20return%20True%20%20%23%20versi%202'%2C%20'catatan_review'%3A%20'APPROVED'%2C%20'iterasi'%3A%202%7D\" title=\"Salin kode\">\n      \u003Csvg class=\"w-3.5 h-3.5 copy-icon-svg\" fill=\"none\" stroke=\"currentColor\" viewBox=\"0 0 24 24\">\n        \u003Cpath stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z\">\u003C\u002Fpath>\n      \u003C\u002Fsvg>\n      \u003Cspan class=\"copy-text\">Copy\u003C\u002Fspan>\n    \u003C\u002Fbutton>\n  \u003C\u002Fdiv>\n  \u003Cdiv class=\"code-block-content p-4 overflow-x-auto text-sm font-mono leading-relaxed bg-[#24292e]\">\n    \u003Cpre class=\"shiki github-dark\" style=\"background-color:#24292e;color:#e1e4e8\">\u003Ccode>[coder] menulis draf ke-1\n[reviewer] draf ke-1 -&gt; ditolak\n[coder] menulis draf ke-2\n[reviewer] draf ke-2 -&gt; lolos\n[deploy] disetujui, lanjut rilis\nstate akhir: {'tugas': 'tulis endpoint pembayaran', 'draf': 'def bayar(): return True  # versi 2', 'catatan_review': 'APPROVED', 'iterasi': 2}\u003C\u002Fcode>\u003C\u002Fpre>\n  \u003C\u002Fdiv>\n\u003C\u002Fdiv>\u003Cp>Kalau \u003Cem>reviewer\u003C\u002Fem> kamu buat selalu menolak, \u003Cem>cycle\u003C\u002Fem>-nya berhenti di iterasi ketiga dan alurnya pindah ke \u003Cem>node\u003C\u002Fem> eskalasi. Itu inti pengamannya: graf tetap boleh punya \u003Cem>cycle\u003C\u002Fem>, tapi ada angka yang menentukan kapan harus berhenti.\u003C\u002Fp>\n\u003Cp>Satu catatan biar nggak bingung waktu baca tutorial lama: \u003Ccode>MemorySaver\u003C\u002Fcode> masih bisa diimpor, tapi sekarang cuma \u003Cstrong>alias\u003C\u002Fstrong> dari \u003Ccode>InMemorySaver\u003C\u002Fcode>. Dua-duanya jalan, yang satunya ketinggalan zaman.\u003C\u002Fp>\n\u003Ch2 id=\"terus-autogen\">Terus AutoGen?\u003C\u002Fh2>\n\u003Cp>AutoGen sering disebut sebaris dengan LangGraph, padahal bentuknya beda. AutoGen lebih dekat ke percakapan terstruktur: beberapa \u003Cem>agent\u003C\u002Fem> dengan peran, satu \u003Cem>termination condition\u003C\u002Fem> yang menentukan kapan berhenti. Kamu nggak mendefinisikan grafnya sendiri.\u003C\u002Fp>\n\u003Cp>Statusnya yang perlu kamu tahu: sejak 16 Oktober 2026 proyeknya menyatakan diri diarsipkan. Ini lebih tegas daripada sekadar berhenti dapat fitur baru, karena Microsoft menyatakan nggak menyediakan perbaikan bug, perbaikan keamanan, apalagi dukungan untuk proyek ini. Rilis terakhirnya, 0.7.5, masih bisa di-\u003Cem>install\u003C\u002Fem> dari PyPI, tapi jangan harap ada tambalan sesudahnya.\u003C\u002Fp>\n\u003Cp>Penggantinya Microsoft Agent Framework, dibangun tim inti AutoGen dan Semantic Kernel. Bagian \u003Cem>workflow\u003C\u002Fem>-nya menyediakan dua gaya sekaligus, fungsional dan berbasis graf, dan per Oktober 2026 paket Python-nya sudah rilis stabil (\u003Ccode>agent-framework\u003C\u002Fcode> 1.22.0).\u003C\u002Fp>\n\u003Cp>Satu catatan biar presisi: yang diarsipkan itu proyeknya, bukan repo GitHub-nya. Halamannya masih bisa dibuka dan masih bisa di-\u003Cem>star\u003C\u002Fem> sampai sekarang. Dan ini klaim yang paling cepat basi dari seluruh artikel ini, jadi cek tanggalnya sendiri sebelum mengutip.\u003C\u002Fp>\n\u003Ch2 id=\"jadi-linear-atau-graf\">Jadi linear atau graf?\u003C\u002Fh2>\n\u003Cp>Kalau alurnya memang lurus dan nggak ada yang perlu dikembalikan, \u003Cem>chain\u003C\u002Fem> linear cukup, dan graf cuma menambah bagian yang harus dirawat. Graf baru bernilai begitu ada revisi, percabangan, dan manusia yang harus menyetujui di tengah jalan.\u003C\u002Fp>\n\u003Cp>Jadi, semua harus pindah ke graf? Nggak juga sih. Sistem multi-\u003Cem>agent\u003C\u002Fem> yang waras bukan berarti beberapa model dibiarkan ngobrol bebas. Yang bikin dia waras: peran yang sempit, \u003Cem>state\u003C\u002Fem> dengan skema, batas \u003Cem>cycle\u003C\u002Fem> yang eksplisit (kamu yang pilih angkanya), dan \u003Cem>checkpoint\u003C\u002Fem> yang bikin kerjanya bisa di-\u003Cem>resume\u003C\u002Fem> kapan saja ⚙️\u003C\u002Fp>\n\u003Cp>Kalau \u003Cem>agent\u003C\u002Fem>-mu mulai jalan di \u003Cem>loop\u003C\u002Fem> tanpa ujung, yang pertama dicek bukan modelnya, tapi apakah ada angka yang membatasi \u003Cem>cycle\u003C\u002Fem>-nya. Sisanya cuma soal rapi-rapi \u003Cem>state\u003C\u002Fem> dan sabar baca \u003Cem>trace\u003C\u002Fem>. Semoga tagihanmu nggak bikin kaget di akhir bulan 😄\u003C\u002Fp>\n","\u003Cul>\n\u003Cli>Graf asiklik nggak punya tempat buat revisi balik, jadi alur yang butuh \u003Cem>cycle\u003C\u002Fem> review harus pakai graf siklus dengan \u003Cem>conditional edge\u003C\u002Fem>.\u003C\u002Fli>\n\u003Cli>\u003Ccode>recursion_limit\u003C\u002Fcode> menghitung \u003Cem>super-step\u003C\u002Fem>, \u003Cstrong>bukan\u003C\u002Fstrong> jumlah \u003Cem>cycle\u003C\u002Fem> review, dan nilai bawaannya \u003Cstrong>10007\u003C\u002Fstrong> di LangGraph 1.2.16. Batas revisi yang kamu mau tetap harus dipasang sendiri di dalam \u003Cem>state\u003C\u002Fem>.\u003C\u002Fli>\n\u003Cli>\u003Cem>Checkpoint\u003C\u002Fem> disimpan di batas \u003Cem>super-step\u003C\u002Fem>, jadi saat eksekusi di-\u003Cem>resume\u003C\u002Fem>, \u003Cem>node\u003C\u002Fem> yang sedang berjalan di-\u003Cem>replay\u003C\u002Fem> dari awal fungsi dan \u003Cem>side effect\u003C\u002Fem>-nya wajib idempoten.\u003C\u002Fli>\n\u003Cli>AutoGen dinyatakan diarsipkan per 16 Oktober 2026 tanpa perbaikan bug maupun keamanan; penerusnya Microsoft Agent Framework, yang \u003Cem>workflow\u003C\u002Fem>-nya tersedia dalam gaya fungsional maupun berbasis graf.\u003C\u002Fli>\n\u003C\u002Ful>\n",[215,220,225,230,233,252],{"id":62,"title":63,"slug":64,"excerpt":65,"published_at":66,"reading_time":67,"hidden_from_listing":11,"tags":68,"thumbnail_image":17,"og_image":75,"series":216,"authors":217},{"id":77,"title":30,"slug":31},[218],{"authors_id":219},{"id":80,"name":106,"slug":107,"role":108,"profile_picture":17},{"id":177,"title":178,"slug":179,"excerpt":180,"published_at":181,"reading_time":67,"hidden_from_listing":11,"tags":182,"thumbnail_image":17,"og_image":189,"series":221,"authors":222},{"id":77,"title":30,"slug":31},[223],{"authors_id":224},{"id":80,"name":106,"slug":107,"role":108,"profile_picture":17},{"id":162,"title":163,"slug":164,"excerpt":165,"published_at":166,"reading_time":167,"hidden_from_listing":11,"tags":168,"thumbnail_image":17,"og_image":171,"series":226,"authors":227},{"id":77,"title":30,"slug":31},[228],{"authors_id":229},{"id":80,"name":106,"slug":107,"role":108,"profile_picture":17},{"id":147,"title":148,"slug":149,"excerpt":150,"published_at":151,"reading_time":77,"hidden_from_listing":11,"tags":152,"thumbnail_image":17,"og_image":158,"series":231,"authors":232},{"id":77,"title":30,"slug":31},[],{"id":234,"title":235,"slug":236,"excerpt":237,"published_at":238,"reading_time":67,"hidden_from_listing":11,"tags":239,"thumbnail_image":17,"og_image":245,"series":246,"authors":247},124,"Rahasia Zero-Cost Abstraction di Rust, Dibedah dari Assembly","rahasia-zero-cost-abstraction-di-rust","\"Abstraksi tinggi selalu lebih lambat\" itu anggapan lama. Iterator Rust bisa menyamai loop manual, dan assembly-nya bisa kita baca sendiri. Yuk, kita bongkar!","2026-10-13T05:19:00.000Z",[240,241,242,243,244],"rust","performance","system-programming","compiler","optimasi","\u002Fog\u002Frahasia-zero-cost-abstraction-di-rust.png",{"id":20,"title":21,"slug":22},[248,250],{"authors_id":249},{"id":80,"name":106,"slug":107,"role":108,"profile_picture":17},{"authors_id":251},{"id":80,"name":106,"slug":107,"role":108,"profile_picture":17},{"id":253,"title":254,"slug":255,"excerpt":256,"published_at":257,"reading_time":85,"hidden_from_listing":11,"tags":258,"thumbnail_image":17,"og_image":261,"series":262,"authors":264},122,"Script yang Nggak Bisa Bilang di Mana Salahnya","script-yang-tidak-bilang-di-mana-salahnya","Script yang berhenti di baris jelek pertama cuma melaporkan sebagian masalah yang ada. Ini yang muncul di layar sekarang.","2026-10-11T23:00:15.000Z",[157,259,260,58],"error-handling","validasi","\u002Fog\u002Fscript-yang-tidak-bilang-di-mana-salahnya.png",{"id":263,"title":57,"slug":58},29,[265],{"authors_id":266},{"id":80,"name":106,"slug":107,"role":108,"profile_picture":17},[268,271,274,277,280,283,286,289],{"text":269,"depth":95,"id":270},"Chain linear mentok di revisi pertama","chain-linear-mentok-di-revisi-pertama",{"text":272,"depth":95,"id":273},"Graf cuma butuh empat bagian","graf-cuma-butuh-empat-bagian",{"text":275,"depth":95,"id":276},"Ping-pong yang nggak pernah berhenti","ping-pong-yang-nggak-pernah-berhenti",{"text":278,"depth":95,"id":279},"Konteks yang membengkak","konteks-yang-membengkak",{"text":281,"depth":95,"id":282},"Checkpoint: kenapa agent bisa di-pause","checkpoint-kenapa-agent-bisa-di-pause",{"text":284,"depth":95,"id":285},"Kodenya, versi yang jalan sekarang","kodenya-versi-yang-jalan-sekarang",{"text":287,"depth":95,"id":288},"Terus AutoGen?","terus-autogen",{"text":290,"depth":95,"id":291},"Jadi linear atau graf?","jadi-linear-atau-graf",1792604183184]