[{"data":1,"prerenderedAt":359},["ShallowReactive",2],{"tag-labels":3,"site-header":4,"article-multi-agen-linear-atau-grafik-id":72},{},{"featuredPost":5,"navigations":29},{"id":6,"title":7,"title_en":8,"slug":9,"excerpt":10,"excerpt_en":11,"has_translation":12,"published_at":13,"reading_time":14,"hidden_from_listing":15,"tags":16,"thumbnail_image":21,"og_image":22,"series":23,"authors":28},110,"Ketika Pilih Framework Cuma Biar Kelihatan Beda","When You Pick a Framework Just to Look Different","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.","The last time you chose a new framework or library, was it because your problem needed that tool, or because it sounded cool and few people around you were using it? That question is uncomfortable. Technology decisions are often made for reasons that never appear in any technical document.",true,"2026-10-06T15:25:21.000Z",5,false,[17,18,19,20],"karier","framework","opini","pengalaman",null,"\u002Fog\u002Fketika-pilih-framework-cuma-biar-kelihatan-beda.png",{"id":24,"title":25,"title_en":26,"slug":27},9,"Artikel","Articles","all",[],[30,46,67],{"collection":31,"item":32},"series",{"__typename":31,"title":25,"title_en":26,"slug":27,"children_series":33},[34,38,42],{"title":35,"title_en":36,"slug":37},"Kecerdasan Artifisial","Artificial Intelligence","kecerdasan-artifisial",{"title":39,"title_en":40,"slug":41},"Konsep Dasar Pemrograman","Programming Fundamentals","konsep-dasar-pemrograman",{"title":43,"title_en":44,"slug":45},"Algoritma dan Struktur Data","Algorithms and Data Structures","algoritma-struktur-data",{"collection":31,"item":47},{"__typename":31,"title":48,"title_en":48,"slug":49,"children_series":50},"Tutorial","tutorial",[51,55,59,63],{"title":52,"title_en":53,"slug":54},"Belajar Python","Learning Python","belajar-python",{"title":56,"title_en":57,"slug":58},"Belajar Golang","Learning Go","belajar-golang",{"title":60,"title_en":61,"slug":62},"Belajar Java","Learning Java","belajar-java",{"title":64,"title_en":65,"slug":66},"Belajar Pascal","Learning Pascal","belajar-pascal",{"collection":31,"item":68},{"__typename":31,"title":69,"title_en":69,"slug":70,"children_series":71},"Lab","lab",[],{"article":73,"renderedBody":265,"renderedTakeaway":266,"latestArticles":267,"toc":334},{"id":74,"title":75,"title_en":76,"slug":77,"excerpt":78,"excerpt_en":79,"has_translation":12,"published_at":80,"reading_time":81,"hidden_from_listing":15,"tags":82,"thumbnail_image":21,"og_image":89,"series":90,"authors":257,"body_locale":260,"available_locales":261,"body":263,"takeway":264,"prerequisite":21},128,"Linear atau graf: cara mengatur banyak agent tanpa loop abadi","Linear or Graph: How to Run Many Agents Without an Eternal Loop","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.","One agent with twenty tools gets messy fast. Once several agents revise each other, you need a graph that allows cycles and a number that makes it stop.","2026-10-21T16:05:00.000Z",6,[83,84,85,86,87,88],"multi-agent","langgraph","autogen","orkestrasi","checkpoint","llm","\u002Fog\u002Fmulti-agen-linear-atau-grafik.png",{"id":91,"title":35,"title_en":36,"slug":37,"description":21,"description_en":92,"articles":93,"children_series":249,"parent_series":250},8,"Notes on AI that go past the demo: how the models work, where they break, and what changes once you put them in production.",[94,111,128,143,157,171,188,205,225,230],{"id":95,"title":96,"title_en":97,"slug":98,"excerpt":99,"excerpt_en":100,"has_translation":12,"published_at":101,"reading_time":102,"hidden_from_listing":15,"tags":103,"thumbnail_image":21,"og_image":108,"series":109,"authors":110},1,"Kenapa AI Suka 'Halu'? Kenalan sama JEPA, Otak AI yang Punya Akal Sehat","Why Does AI Keep Hallucinating? Meet JEPA, the AI Brain With Common Sense","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.","Generative AI hallucinates because all it does is guess word patterns. JEPA tries to change that by teaching machines how the real world works, the way a human brain does.","2026-06-02T08:09:05.595Z",4,[104,105,106,107],"jepa","artificial-intelligence","yann-lecun","agi","\u002Fog\u002Fkenapa-ai-suka-halu-kenalan-sama-jepa-otak-ai-yang-punya-akal-sehat.png",{"id":91,"title":35,"title_en":36,"slug":37},[],{"id":112,"title":113,"title_en":114,"slug":115,"excerpt":116,"excerpt_en":117,"has_translation":12,"published_at":118,"reading_time":102,"hidden_from_listing":15,"tags":119,"thumbnail_image":21,"og_image":120,"series":121,"authors":122},2,"LLM Bakal Mentok, Terus Gimana Dong?","LLMs Are Going to Hit a Wall. What Then?","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.","LLMs like ChatGPT are great at stringing words together, but they never really grasp reality. Yann LeCun says this architecture will hit a wall and needs a new answer.","2026-06-03T08:55:33.003Z",[105,88,106,104],"\u002Fog\u002Fllm-bakal-mentok-terus-gimana-dong.png",{"id":91,"title":35,"title_en":36,"slug":37},[123],{"authors_id":124},{"id":95,"name":125,"slug":126,"role":127,"profile_picture":21},"Daffa Izzuddin","izzudd","Writer",{"id":129,"title":130,"title_en":131,"slug":132,"excerpt":133,"excerpt_en":134,"has_translation":12,"published_at":135,"reading_time":102,"hidden_from_listing":15,"tags":136,"thumbnail_image":21,"og_image":138,"series":139,"authors":140},3,"Mengenal JEPA: Saat AI Mulai Belajar Punya \"Akal Sehat\"","Meet JEPA: Where AI Starts Learning Common Sense","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.","AI hallucinates because it only guesses data probabilities without understanding reality. The JEPA architecture tries to change that by building a world model in its head.","2026-06-03T09:32:50.446Z",[105,104,137,106],"world-models","\u002Fog\u002Fkenapa-ai-suka-halu-dan-gimana-jepa-beresin-masalah-ini.png",{"id":91,"title":35,"title_en":36,"slug":37},[141],{"authors_id":142},{"id":95,"name":125,"slug":126,"role":127,"profile_picture":21},{"id":102,"title":144,"title_en":145,"slug":146,"excerpt":147,"excerpt_en":148,"has_translation":12,"published_at":149,"reading_time":102,"hidden_from_listing":15,"tags":150,"thumbnail_image":21,"og_image":152,"series":153,"authors":154},"Bukan Sekadar Generative AI, JEPA Coba Kejar Pemahaman Konsep ala Manusia","Beyond Generative AI: JEPA Chases Human-Style Conceptual Understanding","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.","AI usually only guesses pixels, while JEPA tries to learn from abstract concepts in latent space. That approach turns out to reach as far as reading brain waves.","2026-06-03T09:34:14.840Z",[104,106,151,107],"machine-learning","\u002Fog\u002Fkenapa-ai-masih-gagal-paham-fisika-dasar-mengenal-arsitektur-jepa.png",{"id":91,"title":35,"title_en":36,"slug":37},[155],{"authors_id":156},{"id":95,"name":125,"slug":126,"role":127,"profile_picture":21},{"id":14,"title":158,"title_en":159,"slug":160,"excerpt":161,"excerpt_en":162,"has_translation":12,"published_at":163,"reading_time":102,"hidden_from_listing":15,"tags":164,"thumbnail_image":21,"og_image":166,"series":167,"authors":168},"Di Balik Dapur JEPA: Rahasia EMA, VICReg, dan Efisiensi Komputasi","Inside JEPA's Kitchen: The Secrets of EMA, VICReg, and Compute Efficiency","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!","Bored of AI that only guesses words or memorizes pixels? Meet JEPA, an architecture that teaches machines common sense very cheaply. Here is a look at its technical insides.","2026-06-07T16:14:13.723Z",[104,165],"artificial-intelligent","\u002Fog\u002Fdi-balik-dapur-jepa-rahasia-ema-vicreg-dan-efisiensi-komputasi.png",{"id":91,"title":35,"title_en":36,"slug":37},[169],{"authors_id":170},{"id":95,"name":125,"slug":126,"role":127,"profile_picture":21},{"id":172,"title":173,"title_en":174,"slug":175,"excerpt":176,"excerpt_en":177,"has_translation":12,"published_at":178,"reading_time":91,"hidden_from_listing":15,"tags":179,"thumbnail_image":21,"og_image":185,"series":186,"authors":187},125,"MCP dibongkar ulang: kabar baik buat kamu yang capek nulis konektor","MCP, Rebuilt: Good News If You're Tired of Writing Connectors","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. 🔧","You have a few internal APIs and one AI agent, but the code has to be rewritten every time the model changes. MCP ends that, and this year the protocol was rebuilt so servers can run like ordinary web servers. Here's what changed. 🔧","2026-10-16T05:00:00.000Z",[180,181,182,88,183,184],"mcp","model-context-protocol","ai-agent","backend","python","\u002Fog\u002Fmcp-dibongkar-ulang-kabar-baik-buat-kamu.png",{"id":91,"title":35,"title_en":36,"slug":37},[],{"id":189,"title":190,"title_en":191,"slug":192,"excerpt":193,"excerpt_en":194,"has_translation":12,"published_at":195,"reading_time":196,"hidden_from_listing":15,"tags":197,"thumbnail_image":21,"og_image":200,"series":201,"authors":202},126,"Memori AI Agent: dari Vector Store ke Temporal Graph","AI Agent Memory: From Vector Store to 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.","Two contradictory facts make an agent answer nonsense. Here is how a vector store and a temporal knowledge graph differ at keeping long-term state.","2026-10-18T13:13:29.458Z",7,[198,88,183,199],"ai","system-design","\u002Fog\u002Fmemori-ai-agent-vector-store-ke-temporal-graph.png",{"id":91,"title":35,"title_en":36,"slug":37},[203],{"authors_id":204},{"id":95,"name":125,"slug":126,"role":127,"profile_picture":21},{"id":206,"title":207,"title_en":208,"slug":209,"excerpt":210,"excerpt_en":211,"has_translation":12,"published_at":212,"reading_time":81,"hidden_from_listing":15,"tags":213,"thumbnail_image":21,"og_image":220,"series":221,"authors":222},127,"Kenapa RAG yang cuma andalkan cosine similarity sering nyasar","Why RAG That Leans on Cosine Similarity Alone Keeps Missing","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.","RAG that leans only on embeddings returns chunks that look close in meaning, not the ones you need. Here is why, and the two layers that patch it.","2026-10-19T02:04:06.718Z",[214,215,216,217,218,219],"rag","vector-search","bm25","hybrid-search","retrieval","embedding","\u002Fog\u002Fkenapa-rag-cuma-cosine-similarity-sering-nyasar.png",{"id":91,"title":35,"title_en":36,"slug":37},[223],{"authors_id":224},{"id":95,"name":125,"slug":126,"role":127,"profile_picture":21},{"id":74,"title":75,"title_en":76,"slug":77,"excerpt":78,"excerpt_en":79,"has_translation":12,"published_at":80,"reading_time":81,"hidden_from_listing":15,"tags":82,"thumbnail_image":21,"og_image":89,"series":226,"authors":227},{"id":91,"title":35,"title_en":36,"slug":37},[228],{"authors_id":229},{"id":95,"name":125,"slug":126,"role":127,"profile_picture":21},{"id":231,"title":232,"title_en":233,"slug":234,"excerpt":235,"excerpt_en":236,"has_translation":12,"published_at":237,"reading_time":14,"hidden_from_listing":15,"tags":238,"thumbnail_image":21,"og_image":244,"series":245,"authors":246},129,"Biar RAG nggak kena 504: pisahin jalur query dan ingestion","So Your RAG Stops Hitting 504: Split the Query Path From Ingestion","rag-pisah-jalur-query-dan-ingestion","Satu service RAG yang merangkap parsing dan menjawab kueri bakal tumbang pas ada yang upload PDF 200 halaman. Obatnya dua jalur dan satu queue.","One RAG service that both parses uploads and answers queries will fall over the moment someone uploads a 200-page PDF. The cure is two paths and one queue.","2026-10-22T01:30:00.000Z",[214,239,240,241,242,243],"cloud-run","cloud-tasks","gcp","serverless","arsitektur","\u002Fog\u002Frag-pisah-jalur-query-dan-ingestion.png",{"id":91,"title":35,"title_en":36,"slug":37},[247],{"authors_id":248},{"id":95,"name":125,"slug":126,"role":127,"profile_picture":21},[],{"id":24,"title":25,"title_en":26,"slug":27,"parent_series":21,"children_series":251},[252,253,255],{"id":91,"title":35,"title_en":36,"slug":37,"cover_image":21},{"id":254,"title":39,"title_en":40,"slug":41,"cover_image":21},12,{"id":256,"title":43,"title_en":44,"slug":45,"cover_image":21},15,[258],{"authors_id":259},{"id":95,"name":125,"slug":126,"role":127,"profile_picture":21},"id",[262,260],"en","\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",[268,285,298,311,324,329],{"id":269,"title":270,"title_en":21,"slug":271,"excerpt":272,"excerpt_en":21,"has_translation":15,"published_at":273,"reading_time":81,"hidden_from_listing":15,"tags":274,"thumbnail_image":21,"og_image":276,"series":277,"authors":282},133,"Python Strings: the Badge That Only Worked for One Name","python-strings","The badge script printed the right name for the person who wrote it and the wrong name for everybody else. Slicing counts characters, and a name is words.","2026-10-28T13:00:00.000Z",[184,275],"string","\u002Fog\u002Fpython-strings.png",{"id":278,"title":279,"title_en":280,"slug":281},30,"Koleksi dan String","Collections and Strings","python-collections",[283],{"authors_id":284},{"id":95,"name":125,"slug":126,"role":127,"profile_picture":21},{"id":286,"title":287,"title_en":21,"slug":288,"excerpt":289,"excerpt_en":21,"has_translation":15,"published_at":290,"reading_time":81,"hidden_from_listing":15,"tags":291,"thumbnail_image":21,"og_image":293,"series":294,"authors":295},132,"Python Tuples: Why All Three Points Are the Same Point","python-tuples","Three points walk into a loop and come out identical. The list was never copied three times; the name was. A tuple makes that bug impossible.","2026-10-28T12:00:00.000Z",[184,292],"tuple","\u002Fog\u002Fpython-tuples.png",{"id":278,"title":279,"title_en":280,"slug":281},[296],{"authors_id":297},{"id":95,"name":125,"slug":126,"role":127,"profile_picture":21},{"id":299,"title":300,"title_en":21,"slug":301,"excerpt":302,"excerpt_en":21,"has_translation":15,"published_at":303,"reading_time":81,"hidden_from_listing":15,"tags":304,"thumbnail_image":21,"og_image":306,"series":307,"authors":308},131,"Python Sets: the Duplicate That Keeps Coming Back","python-sets","A de-duplication loop that works on clean data leaves four tags where there should be two. The bug is case, and the fix is a `set`.","2026-10-28T11:00:00.000Z",[184,305],"set","\u002Fog\u002Fpython-sets.png",{"id":278,"title":279,"title_en":280,"slug":281},[309],{"authors_id":310},{"id":95,"name":125,"slug":126,"role":127,"profile_picture":21},{"id":312,"title":313,"title_en":21,"slug":314,"excerpt":315,"excerpt_en":21,"has_translation":15,"published_at":316,"reading_time":81,"hidden_from_listing":15,"tags":317,"thumbnail_image":21,"og_image":319,"series":320,"authors":321},130,"Python Dictionaries: the Key That Isn't There","python-dictionary","A word counter dies the first time it meets a word twice. The fix is one line; what takes longer is knowing which of a dict's three lookups to reach for.","2026-10-28T10:00:00.000Z",[184,318],"dictionary","\u002Fog\u002Fpython-dictionary.png",{"id":278,"title":279,"title_en":280,"slug":281},[322],{"authors_id":323},{"id":95,"name":125,"slug":126,"role":127,"profile_picture":21},{"id":231,"title":232,"title_en":233,"slug":234,"excerpt":235,"excerpt_en":236,"has_translation":12,"published_at":237,"reading_time":14,"hidden_from_listing":15,"tags":238,"thumbnail_image":21,"og_image":244,"series":325,"authors":326},{"id":91,"title":35,"title_en":36,"slug":37},[327],{"authors_id":328},{"id":95,"name":125,"slug":126,"role":127,"profile_picture":21},{"id":74,"title":75,"title_en":76,"slug":77,"excerpt":78,"excerpt_en":79,"has_translation":12,"published_at":80,"reading_time":81,"hidden_from_listing":15,"tags":82,"thumbnail_image":21,"og_image":89,"series":330,"authors":331},{"id":91,"title":35,"title_en":36,"slug":37},[332],{"authors_id":333},{"id":95,"name":125,"slug":126,"role":127,"profile_picture":21},[335,338,341,344,347,350,353,356],{"text":336,"depth":112,"id":337},"Chain linear mentok di revisi pertama","chain-linear-mentok-di-revisi-pertama",{"text":339,"depth":112,"id":340},"Graf cuma butuh empat bagian","graf-cuma-butuh-empat-bagian",{"text":342,"depth":112,"id":343},"Ping-pong yang nggak pernah berhenti","ping-pong-yang-nggak-pernah-berhenti",{"text":345,"depth":112,"id":346},"Konteks yang membengkak","konteks-yang-membengkak",{"text":348,"depth":112,"id":349},"Checkpoint: kenapa agent bisa di-pause","checkpoint-kenapa-agent-bisa-di-pause",{"text":351,"depth":112,"id":352},"Kodenya, versi yang jalan sekarang","kodenya-versi-yang-jalan-sekarang",{"text":354,"depth":112,"id":355},"Terus AutoGen?","terus-autogen",{"text":357,"depth":112,"id":358},"Jadi linear atau graf?","jadi-linear-atau-graf",1793295834817]