1. Sebelum memulai
Sebagian besar situs penjualan tiket menempatkan pembeli dalam antrean, baik untuk mencegah server error maupun untuk mengelola permintaan yang tinggi. Anda memulai pembelian dan mendapati diri Anda berada di belakang 14.000 orang. Antrean bergerak selama empat puluh menit. Saat giliran Anda tiba, Anda memiliki waktu singkat untuk memilih tempat duduk dan membayar. Jika Anda melewatkan periode tersebut, giliran Anda dalam antrean akan diberikan kepada orang lain.
Banyak tugas dunia nyata mengikuti pola ini: tugas yang membutuhkan waktu lebih lama daripada satu percakapan, yang berjalan melawan sistem eksternal yang tidak Anda kendalikan. Contohnya termasuk menunggu pemasok mengisi ulang stok, menunggu persetujuan klaim, atau menunggu dalam antrean.
Agen AI standar berada dalam satu giliran chat, yang berlangsung selama beberapa detik dan bersifat reaktif. Mereka tidak dapat menunggu selama 40 menit atau bertindak secara mandiri. Untuk mengatasi hal ini, Anda memerlukan agen yang berjalan lama: sistem yang tetap tidak aktif selama menunggu lama, mempertahankan konteks di seluruh proses dimulai ulang, dan dilanjutkan secara otomatis saat diaktifkan oleh pemicu eksternal.
Workshop ini akan memandu Anda membangun agen antrean tiket konser, yang mencakup pola arsitektur inti yang diperlukan untuk agen yang berjalan lama di Google Cloud.
Yang akan Anda build

Dalam workshop ini, Anda akan membangun agen pemesanan tiket yang berjalan lama sambil mempelajari pola software agentik utama:
- Penangguhan & Lanjutan Proses: Menghentikan sementara pemanggilan agen selama periode tunggu yang lama dan melanjutkan eksekusi melalui pemicu eksternal.
- Penyimpanan Status yang Andal: Mempertahankan status dan artefak percakapan di seluruh proses dimulai ulang menggunakan SQLite/Cloud SQL dan Cloud Storage.
- Perlindungan Terhadap Data Usang & Idempotensi: Verifikasi data dinamis (seperti ketersediaan kursi) sebelum bertindak dan gunakan kunci idempotensi untuk mencegah transaksi duplikat.
- Grafik Alur Kerja ADK: Struktur eksekusi menggunakan node kode deterministik untuk aturan prosedural dan node agen untuk pengambilan keputusan kontekstual.
- Persetujuan Human-in-the-Loop: Menjeda eksekusi untuk mengumpulkan dan mempertahankan batas pembelanjaan eksplisit dari pengguna.
- Deployment Cloud: Masukkan agen ke dalam container dan deploy ke Cloud Run dengan pemicu Cloud Scheduler dan Pub/Sub.
Memulai persiapan
Pertama, siapkan lingkungan Anda dan pastikan Anda memiliki akses ke Google Cloud. Sebaiknya jalankan perintah ini di Cloud Shell, yang sudah memiliki setiap alat yang Anda butuhkan. Cukup periksa apakah Anda memiliki ruang yang cukup, dan apakah akun Anda dapat membuat project.
👉💻 Clone repo dan jalankan penyiapan:
cd ~
git clone https://github.com/gca-americas/monstertix
cd ~/monstertix
./setup.sh
Anda akan ditanya sekali:
Which Google Cloud project should this workshop use?
Find yours at https://console.cloud.google.com (top-left picker)
Project id [YOUR_PROJECT_ID]:
Kemudian, secara berurutan, tindakan berikut akan dilakukan:
virtualenv | membuat | |
login |
| |
project | menunjuk | |
API | mengaktifkan sepuluh workshop yang diperlukan. | |
| menulis project, region, dan ID model Anda, sehingga setiap skrip dan | |
model check | melakukan satu panggilan nyata ke Gemini, sehingga ID project atau model yang salah akan gagal di sini | |
benih | membangun sesi yang sudah berusia dua hari, yang dibuka oleh langkah 3 | |
Cloud SQL | memulai instance Postgres di latar belakang | |
venue | men-deploy penjual tiket tiruan Anda sendiri ke Cloud Run | |
👉💻 Aktifkan lingkungan:
cd ~/monstertix
source .venv/bin/activate
Perintah Anda sekarang dimulai dengan (.venv).
Ringkasan Skenario
Dalam skenario workshop ini, agen Anda memesan dua tiket konser ("The Midnight Signal" di Amsterdam). Preferensi pengguna yang disimpan dalam memori historis meliputi:
- Harus berupa pertunjukan akhir pekan (Sam tidak dapat menghadiri pertunjukan pada malam hari).
- Hindari tempat duduk di bagian atas Ziggo Dome (visibilitas buruk).
- Harga target sekitar $200 per tiket, dengan batas maksimum $250.

Memverifikasi penginstalan Anda
👉💻 Satu perintah, delapan tanda:
./verify.sh
Checking the rig
✓ adk installed adk, version 2.6.2
✓ adk 2 apis App + ResumabilityConfig
✓ workflow graphs Workflow + node
✓ async sqlite drivers sqlalchemy + aiosqlite + greenlet
✓ project your-project-id
✓ vertex ai no API keys anywhere
✓ seeded session 13 events, two days old
✓ venue https://venue-you-xxxx.us-central1.run.app
Ready. Cloud SQL is still building in the background and nothing
before step 10 needs it.
👉 Buka tab browser kedua:
Di mana | Penjelasan |
| Panel kontrol, tombol Anda. |
2. Mendorong jalan Anda menuju otonomi?
Perintah Tidak Dapat Dimulai Sendirinya
Perintah hanyalah teks yang diteruskan ke model AI saat panggilan terjadi. Ponsel tidak dapat berjalan sendiri, mengaktifkan diri sendiri, atau memulai panggilan baru.
Jika Anda memberi tahu agen dalam perintahnya untuk "kembali lagi dalam 10 menit", tidak ada yang terjadi setelah giliran saat ini berakhir. Model tidak memiliki loop atau jam internal. Untuk membuat agen otonom, sesuatu di luar agen harus mengaktifkannya dan memanggilnya.

Mulai rig
Kita akan mulai dengan menguji alat developer. adk web memulai UI web ADK, antarmuka pengembangan yang disertakan dengan ADK. Dengan alat ini, Anda dapat berbicara dengan agen yang sedang Anda buat tanpa menulis frontend terlebih dahulu, dan alat ini akan menunjukkan hal-hal yang tidak dapat ditampilkan oleh jendela chat, seperti setiap panggilan alat dan argumennya, status sesi saat berubah, artefak yang disimpan agen, dan peristiwa mentah di bawahnya. Anda tidak akan mengeksposnya dalam produksi. Ini adalah jendela terbaik untuk melihat agen yang sedang berjalan saat Anda belajar. Pada langkah 3, Anda membangun frontend yang akan diekspos, dan pada langkah terakhir, Anda men-deploy-nya.
👉🔴 Di panel tempat, tekan RESET TEMPAT. Setiap langkah dimulai dari dunia yang sama: 8 pertunjukan, semua kursi tersedia, jam di 1×.
👉💻 Terminal 1 — muat kode langkah ini dan mulai UI web ADK:
cd ~/monstertix
. ./set_env.sh
./use-solution.sh 2 --force
adk web agent --port 8000 --allow_origins "*"
use-solution.sh menyalin kode langkah yang telah selesai ke agent/, tempat Anda bekerja. Setiap langkah dimulai dengan file ini dan mencetak file yang diubah sehingga Anda tahu apa yang harus dibaca.
Yang Anda mulai dengan:
File | Penjelasan |
| agen — nama, model, petunjuk, daftar alat |
|
|
| klien HTTP untuk tempat. Tidak ada yang menarik |
| model mana yang akan digunakan, dan pemeriksaan bahwa kredensial Anda berfungsi |
. ./set_env.sh mengaktifkan virtualenv, memuat project dan model Anda, serta mengekspor $WORKSHOP — jalur absolut ke ~/monstertix. Jalankan di setiap terminal yang Anda buka. Mencetak apa yang ditetapkan:
folder /home/you/monstertix
project your project id
model gemini-2.5-flash (We'll avoid using 3.x for now to avoid high usage demands)
venue https://venue-yourname-xxxx.run.app
👉 Buka tab browser pertama:
Di mana | Penjelasan |
UI web ADK |
|
Bicaralah dengannya
👉✨ Di UI web ADK, ketik:
I want to see The Midnight Signal. I'm in Amsterdam, going with Sam, budget around $200 each.
Sistem ini menelusuri tur, membaca peta tempat duduk, dan melakukan penalaran tentang anggaran dan perusahaan Anda. Ini adalah asisten yang benar-benar kompeten. Namun, saat prapenjualan dibuka pada pukul 10.00, agen tidak akan dieksekusi secara otomatis karena tidak ada proses atau pemicu latar belakang yang berjalan.
Baca apa yang baru saja terjadi
Bagian tengah layar bukan hanya log chat. Setiap langkah giliran diberi nomor, termasuk panggilan alat:
▸ I want to see The Midnight Signal. I'm in Amsterdam... ← user input
⚡ search_events({"city": "Amsterdam"}) ← tool execution
✓ search_events ← venue API response
Two Amsterdam shows: Tuesday 10 Nov and Saturday 14 ... ← model response
👉 Klik baris ⚡. Panel kiri menampilkan argumen yang dipilih model. Klik ✓ di bawahnya untuk melihat JSON yang ditampilkan.
Informasi tur tidak ada dalam data pelatihan atau perintah model. Agen mengambil data ini dengan menjalankan alat search_events, yang mengkueri API tempat.
Buka kode yang baru saja Anda jalankan
Anda telah melihat cara kerjanya dan melihat rekaman aktivitas. Sekarang lihat kodenya.
agent/concert/
├── agent.py the agent — about twenty lines
├── tools.py what it can do
├── venue.py an httpx wrapper, nothing interesting
└── config.py which model, and a credentials check
👉💻 Buka agent/concert/agent.py. Seluruh agen berada di bagian bawah file:
INSTRUCTION = """
You help someone plan a trip to see a band on tour.
You can search tour dates and read seat maps. Be concrete: name the show, the
city, the section, and the price...
"""
root_agent = Agent(
name="concert",
model=MODEL,
instruction=INSTRUCTION,
tools=[search_events, get_seatmap],
)
Nama, model, beberapa deskripsi tentang perilaku agen, dan daftar fungsi Python. Itulah yang dimaksud dengan Agent. adk web menemukannya karena file tersebut bernama agent.py dan variabelnya bernama root_agent.
👉💻 Sekarang agent/concert/tools.py:
def search_events(city: str = "", weekday: str = "") -> dict:
"""List tour dates. Call with no arguments to see the whole tour.
This venue sells one artist and one tour, so there is nothing to search by
name — never ask the user which artist they mean.
Args:
city: Optional. Filter to one city, e.g. "Amsterdam".
weekday: Optional. Filter to one weekday, e.g. "Saturday".
"""
return venue.get("/events", city=city, weekday=weekday)
Fungsi biasa. Tidak ada dekorator, tidak ada pendaftaran, tidak ada skema untuk ditulis. ADK membaca tanda tangan dan docstring, lalu membuat definisi alat yang dilihat model. Itulah sebabnya docstring tersebut sangat penting, dan mengapa menghapus satu argumen mengubah perilaku model.
Model tidak pernah menjalankan kode Anda. Saat Anda melihat search_events({"city": "Amsterdam"}), model meminta panggilan tersebut. ADK menjalankan fungsi, mengembalikan hasilnya, dan model menulis kalimat tentangnya. Loopnya adalah:
👉 Ikon di sepanjang tepi kiri beralih menampilkan apa yang ada di panel. Yang kedua adalah grafik agen — coba dan Anda akan melihat dua alat:

Jadi, beri tahu agar tidak tidur
Apa yang akan Anda lakukan untuk memperbaikinya? Coba hal yang paling banyak dicari orang terlebih dahulu. Ingat INSTRUCTION dari langkah 1, prosa yang diberikan ke model di setiap giliran, dan tulis ulang.
👉💻 Buka agent/concert/agent.py. Di bawah INSTRUCTION ada email kedua, yang sudah ditulis untuk Anda. Coba ubah sendiri jika Anda mau:
PROACTIVE_INSTRUCTION = """
You are a proactive ticket-buying assistant.
Monitor the presale for The Midnight Signal. The moment it opens at 10:00 on
Tuesday, buy two tickets for the Amsterdam Saturday show — do not wait to be
asked, and do not wait for me to say anything. Act on your own.
Keep checking until the tickets are bought.
"""
# agent/concert/agent.py
root_agent = Agent(
name="concert",
model=MODEL,
instruction=PROACTIVE_INSTRUCTION, # ← was INSTRUCTION
tools=[search_events, get_seatmap],
)
👉💻 Di terminal 1, mulai ulang:
cd ~/monstertix
adk web agent --port 8000 --allow_origins "*"
👉✨ Beri tahu rencana tersebut, persis seperti Anda memberi tahu orang lain:
I want to see The Midnight Signal in Amsterdam with Sam. The presale opens at 10:00 on Tuesday. Grab us two tickets the moment it opens.
Model akan setuju, dan akan terdengar seperti sungguh-sungguh.
👉 Sekarang tunggu. Jangan ketik apa pun lagi. Tonton panelnya.
Tunggu sebentar. TIDAK TERJADI APA-APA!
Alasan perintah tersebut tidak akan berhasil
Baca lagi dengan sudut pandang baru. Monitor. Saat dibuka. Terus periksa. Setiap kata tersebut menggambarkan sesuatu yang terjadi seiring waktu.
Sekarang, ingatlah apa itu instruction=. adalah string, yang diberikan ke model sebagai bagian dari permintaan, pada saat seseorang mengirim pesan. Tidak ada yang membacanya di antara giliran.
# agent/concert/agent.py
root_agent = Agent(
instruction=PROACTIVE_INSTRUCTION, # read on invocation
... # ...and there was no invocation
)
Petunjuk tidak dapat memanggil fungsi yang ditulis di dalamnya.
Variasi Perintah yang Gagal
Mencoba menerapkan eksekusi otonom melalui petunjuk perintah gagal terlepas dari kata-katanya. Upaya perintah umum—seperti:
- "Setel timer untuk pukul 10.00 hari Selasa dan beli tiketnya saat itu."
- "Periksa setiap lima menit apakah prapenjualan sudah dibuka."
- "Bertindak secara mandiri tanpa menunggu input pengguna."
Semua gagal karena alasan yang sama: perintah hanya dapat dibaca saat pemanggilan terjadi. Tanpa pemanggil eksternal, tidak ada kode yang dieksekusi di antara giliran.
3. Dormansi Berbasis Peristiwa
Agen Tidak Akan Selalu Aktif
Membiarkan proses berjalan secara polling terus-menerus akan membuang-buang memori, menghabiskan uang, dan menyebabkan error jika laptop Anda dalam mode tidur atau dimulai ulang.
Daripada membuat agen tetap berjalan tanpa henti saat menunggu, agen yang berjalan lama harus benar-benar tidak aktif. Peristiwa eksternal (seperti timer, webhook, atau pesan) mengaktifkan agen hanya jika ada pekerjaan yang harus dilakukan.
👉🔴 Di panel tempat, tekan RESET TEMPAT. Setiap langkah dimulai dari dunia yang sama: 8 pertunjukan, semua kursi tersedia, jam di 1×.
👉💻 Muat kode langkah ini:
cd ~/monstertix
. ./set_env.sh
./use-solution.sh 3 --force
Perhatikan apa yang diinstal: monstertix/, yang memegang server, jam, dan satu hal lagi yang belum Anda lihat. Periksa apakah agent/ tidak berubah, karena tidak ada yang berubah pada agen dalam langkah ini. Tahan itu, itu intinya.
File | Penjelasan |
| baru. Server web dengan Runner di belakangnya |
| baru. Sesuatu yang mengetahui waktu dan memanggil server |
File ini memiliki dua bagian, dan kedua bagian tersebut tetap berada dalam dua file hingga ke cloud: 
menjadi, pada langkah 10 | |||
pemicu | endpoint yang dapat menjalankan agen | Cloud Run | |
pemicu | sesuatu dengan jam, yang memanggilnya | Cloud Scheduler | |
Pemicu — server web Anda sendiri
Ya, server web. Sesuatu harus memantau sebelum ada yang dapat memanggilnya. UI web ADK dari langkah sebelumnya adalah antarmuka pengembangan yang dapat menjalankan agen.
👉💻 Buka monstertix/server.py:
from google.adk.runners import Runner
from google.adk.sessions import InMemorySessionService
from concert.agent import root_agent
session_service = InMemorySessionService()
runner = Runner(
agent=root_agent,
app_name="concert",
session_service=session_service,
)
@app.post("/wake")
async def wake(payload: dict = Body(default={})):
session_id = f"wake-{uuid.uuid4().hex[:8]}" # brand new, every time
await session_service.create_session(...)
async for event in runner.run_async(
user_id=USER_ID, session_id=session_id,
new_message=types.Content(role="user",
parts=[types.Part(text=payload["message"])]),
):
...
Agent tidak berjalan dengan sendirinya. Deskripsi: nama, model, petunjuk, daftar alat. Sesuatu harus mengambil pesan masuk, menemukan sesi yang tepat, menyerahkan percakapan sejauh ini ke model, menjalankan alat apa pun yang diminta, mengirimkan kembali hasilnya, melakukan loop hingga berhenti memanggil alat, dan menulis setiap langkah tersebut ke penyimpanan. Hal tersebut adalah Runner.

UI web ADK memiliki satu default untuk Anda. Di sini Anda membangunnya sendiri, dan itulah seluruh perbedaan antara UI web ADK dan sesuatu yang dapat Anda deploy.
👉💻 Terminal 2 — mulai:
Mulai Runner,
cd ~/monstertix
. ./set_env.sh
python -m monstertix.server
[server] agent concert · gemini-x-flash
[server] listening on http://xxxx_URL/wake
[server] nothing will happen until something calls it. Ctrl-C to stop.
Baca baris terakhir itu. Pemicu sedang berjalan dan agen dimuat, tetapi tidak ada yang terjadi. Pisahkan kedua ide: dapat dipanggil dan dipanggil.
Dan pintu depan, saat kita berada di sini
Meskipun adk web adalah alat pengembangan yang hebat, Anda tidak boleh mengirimkannya. Anda tidak boleh mengaktifkannya saat men-deploy ke produksi, dan Anda tidak akan mengarahkannya kepada pelanggan jika hal itu terjadi. Jadi, server yang sama juga menayangkan halaman.
👉 Buka http://127.0.0.1:8090 di browser — alamat yang sama.
Perkenalkan MonsterTix. Jendela chat, nama di sudut, dua tombol. Aplikasi ini memposting ke /wake dan merender apa yang ditampilkan, dan aplikasi ini akan berfungsi tanpa perubahan terhadap endpoint apa pun yang merespons dengan cara yang sama.
Di halaman | Apa yang ada di bawahnya |
percakapan |
|
+ Sesi baru | ID sesi baru — percakapan baru, dan selanjutnya, anggaran baru |
Reset memori | menghapus setiap sesi dan file memori. Pertama-tama, aplikasi akan bertanya dan menunjukkan lokasi status tersebut secara tepat |
👉✨ Ucapkan sesuatu kepadanya — What Amsterdam dates are there? — dan lihat terminal 2. Runner yang sama, agen yang sama, baris log yang sama. Hanya penelepon yang berubah.
Pemicu — sesuatu yang mengetahui waktu
👉💻 Buka monstertix/clock.py. Lihat kodenya.
time.sleep(args.delay) # wait
while True:
client.post(f"{TRIGGER_URL}/wake", # knock
json={"message": args.message})
if not args.every:
return # once, unless told otherwise
time.sleep(args.every) # or again, forever
Mekanismenya sangat sederhana: tidur, POST, mungkin berulang. Setiap penjadwal yang telah Anda gunakan melakukan hal yang sama, ditambah percobaan ulang, zona waktu, dan jaminan bahwa penjadwal tersebut akan tetap berfungsi setelah dimulai ulang.
👉💻 Terminal 3 — aktifkan sekali, 10 detik dari sekarang:
cd ~/monstertix
. ./set_env.sh
python -m monstertix.clock --in 10
[clock] target http://127.0.0.1:8090/wake
[clock] first fire in 60s, once only
[clock] go and watch the other terminal — do not touch the keyboard
👉 Sekarang berhenti mengetik. Tidak diragukan lagi. Tonton terminal 2.
Satu menit kemudian, tanpa ada orang di keyboard:
[clock] firing → POST /wake
[server] woken · session wake-23930983
[server] called search_events
[server] called purchase
[server] said I've purchased 2 tickets for The Midnight Signal in
Amsterdam on Saturday, section A. Your total is 420.
[clock] done. the agent ran and you were not involved.
Itulah jawaban untuk langkah 2. Sesuatu di luar agen memanggilnya, dan sesuatu itu adalah sleep dan permintaan HTTP.
👉💻 Ctrl-C saat Anda telah menontonnya beberapa kali. Gunakan --every 30 terlebih dahulu jika Anda ingin melihatnya berulang.
Sekarang baca apa yang dikatakannya
"Saya telah membeli dua tiket untuk The Midnight Signal di Amsterdam, bagian A, $420."
👉🔴 Periksa panel tempat acara dan Anda akan melihat bahwa tiket telah dibeli.
Jadi, apa yang masih salah?
Ia membeli sesuatu. Lihat apa yang harus ditebaknya. Sistem memilih Amsterdam dan bagian A karena merupakan hal pertama yang ditemukan, dan Anda memberi tahu agen untuk membelinya secara langsung dan agen tidak tahu bahwa Sam tidak bisa melakukannya pada malam hari. Agen yang bertindak berdasarkan tebakan pada pukul 03.00 lebih buruk daripada agen yang tidak melakukan apa pun.
Bangun tidur adalah bagian yang mudah. Agen yang aktif tanpa apa pun — tanpa histori, tanpa preferensi, tidak ada yang mendengarkan — belum layak diaktifkan.
4. Mengelola Masa Aktif Konteks
Informasi Tersimpan di Tempat yang Berbeda untuk Waktu yang Berbeda
Agen menyimpan fakta di beberapa tempat: dalam percakapan chat, dalam status sesi, dalam file (artefak), atau dalam memori pengguna jangka panjang. Setiap tempat memiliki durasi yang berbeda.
Jika Anda memasukkan informasi di tempat yang salah:
- Percakapan menghilang terlalu cepat: Detail penting akan dihapus saat percakapan berakhir.
- Terlalu lama disimpan: Fakta sementara mencemari memori jangka panjang dan mengacaukan percakapan di masa mendatang.
Anda harus mencocokkan setiap informasi dengan tempat penyimpanan dan masa aktif yang tepat.
Memuat kode
👉🔴 Di panel tempat, tekan RESET TEMPAT.
👉💻 Di terminal 1, jalankan:
cd ~/monstertix
. ./set_env.sh
./use-solution.sh 4 --force
Yang berubah sejak langkah terakhir:
File | Apa yang berubah |
|
|
| baru. Penyimpanan memori kami sendiri |
| tiga alat lainnya dalam daftar, dan petunjuk memberi tahu agen untuk menggunakannya |
👉💻 Mulai ulang agen di terminal 1:
cd ~/monstertix
. ./set_env.sh
adk web agent --port 8000 --allow_origins "*" \
--session_service_uri="sqlite+aiosqlite:///$WORKSHOP/sessions.db" \
--artifact_service_uri="file://$WORKSHOP/artifacts"
Dua tanda yang belum pernah Anda gunakan sebelumnya:
Bendera | Penjelasan |
| ADK menulis setiap pesan, panggilan alat, dan perubahan status ke dalamnya sebagai baris (database SQLite). |
| File yang disimpan agen.(File) |
Tanpa ADK, semuanya akan disimpan di memori dan dihapus saat Anda menghentikan proses. Kedua setelan ini membuat fitur tersebut tetap aktif.
Database sessions.db berisi percakapan sebelumnya yang di-seed (two-days-ago) dengan 13 peristiwa yang direkam:
you We're thinking about seeing The Midnight Signal. Me, Sam, and maybe Priya.
you Sam can't do weeknights though, bailed on every single one.
you Priya's out, she's away that weekend. Just me and Sam.
agent Two it is. Still Saturday the 14th, still Amsterdam.
you You said the upper bowl at Ziggo was bad last time?
File memori dari tiga pemesanan sebelumnya, memory/userx.md, yang dapat Anda buka di editor:
- Sam bails on weeknights. Every weeknight show we booked, Sam cancelled.
- Hates the upper bowl at Ziggo Dome, "couldn't see a thing" from section B.
- Comfortable spend is around $200 per ticket. $250 is the hard ceiling.
- Always books two seats, always together.
Itulah sebabnya baris malam hari berwarna kuning penting. Pertunjukan pada malam hari kerja lebih murah Rp150.000 per kursi, memiliki sisa kursi yang lebih baik, dan berada di bagian atas daftar, tetapi ini adalah jawaban yang salah bagi orang ini. Satu-satunya alasan agen dapat mengetahui hal itu adalah file yang ditulisnya setelah pemesanan terakhir. Apakah fakta tersebut masih ada atau tidak adalah langkah 4 dan 5.
Tiga alat yang kami tambahkan

note_companion mencatat siapa yang datang dan apa yang membatasi mereka:
# agent/concert/tools.py
def note_companion(name: str, constraint: str, tool_context: ToolContext) -> dict:
prefs = dict(tool_context.state.get("user:prefs", {}))
prefs[name] = constraint
tool_context.state["user:prefs"] = prefs
return {"companions": prefs}
Tambahkan karena "Sam tidak bisa di malam hari" harus tetap berlaku minggu depan. Tulis ke dalam user:prefs untuk mewujudkannya, dan lihat bagian user: dari nama tersebut melakukan tugasnya. Kita akan kembali ke hal itu di bawah.
recall
dan
remember membaca dan menulis file teks, memory/userx.md. Tahan posisi tersebut selama beberapa saat.
👉 Sekarang lihat dua tempat agen menyimpan konteksnya:
lokasi | Yang dipegangnya |
Status | memori sesi ini, sebagai key-value pair — Anda berada di sini pada langkah 4 |
Artefak | file yang disimpan agen, yang tidak pernah dimasukkan ke dalam perintah |
Melihat perubahan yang terjadi di
get_seatmap
. Sebelumnya, API ini menampilkan seluruh peta tempat duduk. Sekarang, peta kursi akan disimpan ke file dan ringkasan singkat akan ditampilkan:
# agent/concert/tools.py
async def get_seatmap(event_id: str, tool_context: ToolContext) -> dict:
seatmap = venue.get(f"/events/{event_id}/seatmap") # about 6 KB of JSON
part = types.Part(inline_data=types.Blob(
mime_type="application/json",
data=json.dumps(seatmap, indent=2).encode()))
await tool_context.save_artifact(f"seatmap_{event_id}.json", part)
summary = {"event_id": ..., "sections": [...], "captured_at": ...}
tool_context.state["temp:seatmap"] = summary
return summary
Berikut alasan perubahan tersebut penting.
Setiap payload respons alat ditambahkan ke histori percakapan dan dikirim ulang ke model pada semua giliran berikutnya. Menampilkan objek JSON besar (seperti peta tempat duduk 6 KB) akan meningkatkan konsumsi token pada setiap giliran berikutnya.
Simpan ke file dan bayar satu nama file. Biarkan agen meminta detail kembali dengan load_artifact jika diperlukan, dan perkirakan bahwa sebagian besar waktu agen tidak akan pernah melakukannya.
Tujuan file: layanan artefak
Lihat lagi garis di get_seatmap, di agent/concert/tools.py:
await tool_context.save_artifact(f"seatmap_{event_id}.json", part)
tool_context berasal dari ADK, yang meneruskannya ke alat Anda sebagai argumen, dan save_artifact adalah metode di dalamnya. Baris tersebut adalah tempat alat Anda menyerahkan byte ke ADK.
Kemudian, ADK akan menuliskannya menggunakan layanan artefak yang digunakan saat memulai. Anda memilihnya dengan tanda:
--artifact_service_uri="file://$WORKSHOP/artifacts"
ADK memiliki tiga lokasi untuk menyimpannya:
URI | Tujuan byte |
| RAM. Hilang saat Anda menghentikan proses. Ini adalah nilai default jika Anda tidak meneruskan tanda |
| folder di disk. Yang Anda gunakan sekarang |
| memindahkan data ke Cloud Storage. Apa yang digunakan agen yang di-deploy pada langkah terakhir |
👉💻 Di sini kita memilih file:
ls -R ~/monstertix/artifacts
Untuk memindahkan dari laptop Anda ke Cloud Storage, ubah file:// menjadi gs://. (Kita akan melihatnya di Langkah 10)
Penyimpanan memori kami

ADK memberi Anda antarmuka untuk memori jangka panjang yang disebut BaseMemoryService. Objek ini memiliki dua metode:
# google.adk.memory.BaseMemoryService — ADK's interface, not your file
async def add_session_to_memory(session) # take a conversation in
async def search_memory(*, app_name, user_id, query) # hand memories back
ADK mengirimkan tiga implementasinya:
Kelas | URI | Yang diperlukan |
|
| Tidak terjadi apa-apa. Kehilangan semuanya saat Anda menghentikan proses |
|
| korpus RAG Vertex AI |
|
| resource Agent Engine |
Kita akan menulisnya sendiri untuk workshop ini. Singkirkan InMemoryMemoryService, yang melupakan semuanya saat Anda menghentikan proses, dan singkirkan dua lainnya, yang memerlukan resource cloud yang membutuhkan waktu beberapa menit untuk dibuat. Pilih file saja, karena Anda dapat menyemainya terlebih dahulu dan membacanya di editor. Jadi, anggap memory.py sebagai penerapan Anda sendiri, kita akan menyimpan semuanya dalam file Markdown:
class MarkdownMemoryService(BaseMemoryService):
async def search_memory(self, *, app_name, user_id, query) -> SearchMemoryResponse:
path = self._path(user_id) # memory/userx.md
return SearchMemoryResponse(memories=[
MemoryEntry(content=types.Content(
role="user", parts=[types.Part(text=path.read_text())]))])
SearchMemoryResponse dan MemoryEntry adalah jenis ADK, dan Memory Bank menampilkan objek yang sama. Salah satunya memiliki layanan terkelola di belakangnya. Kita memiliki file.
Memilih file karena satu alasan: Anda dapat membukanya dan membacanya. Seluruh memori jangka panjang agen Anda adalah halaman teks.
Jadi, ke mana Anda mengarahkannya?
Untuk meringkas penerapan dalam workshop ini, dua dari tiga layanan dipilih di command line. Yang ketiga, kami melakukan implementasi sendiri karena alasan di atas:
Layanan | Tempat Anda menyetelnya | Workshop ini |
sesi |
|
|
artefak |
|
|
memori | tidak ada di command line |
|
Jadi, memory.py membuat layanan itu sendiri, dari variabel lingkungan:
# agent/concert/memory.py
MEMORY_DIR = pathlib.Path(os.environ.get("MEMORY_DIR", "./memory"))
MEMORY_USER = os.environ.get("MEMORY_USER", "userx")
memory_service = MarkdownMemoryService(MEMORY_DIR)
. ./set_env.sh mengekspor keduanya. Get MEMORY_DIR menetapkan tempat agen membaca memorinya dan MEMORY_USER menentukan file.
Pergi dan temukan lokasi barang
Sekarang Anda memiliki empat tempat yang dapat menyimpan fakta. Lihat keempatnya.
Pertama, sesi Anda sendiri
👉 Klik New Session di panel atas.
👉✨ Ucapkan tiga hal, satu per satu:
Sam can't do weeknights, by the way.

Show me the seat map for the Amsterdam Saturday show.

Actually, hold that thought — what else is on in Amsterdam?
Pesan ketiga itu penting. Tindakan ini mengakhiri giliran yang membaca peta tempat duduk, dan mengakhiri giliran adalah tindakan yang membuat temp: menghilang.
Tiga benda disimpan di tiga tempat yang berbeda:
Data yang disimpan | Alat mana yang melakukannya | Ke mana perginya | |
1 | Sam tidak bisa pada malam hari |
|
|
2 | peta tempat duduk lengkap, 6 KB |
| layanan artefak |
3 | ringkasan peta tempat duduk satu baris |
| tidak ada |
👉 Sekarang periksa. Tab State dan tab Artifacts berada di sebelah kiri adk web.


Di tab State, perhatikan bahwa temp:seatmap tidak ada. Kunci status dengan awalan temp: hanya ada selama pemanggilan aktif dan otomatis dihapus oleh ADK sebelum mempertahankan status sesi ke database.
Kemudian, percakapan lama
👉 Beralih ke two-days-ago di dropdown sesi di bagian atas.
Sesi ini berisi histori percakapan yang telah diisi sebelumnya dalam database.

👉 Lihat aliran peristiwa (kolom tengah). Perhatikan bahwa giliran yang lebih lama telah diganti dengan blok peristiwa yang diringkas.

👉 Lihat tab State. Tiga jenis kunci ada di sana bersama-sama:
target_event_id ms-ams-01 ← plain: this booking only
party_size 2 ← plain: this booking only
user:prefs {"excluded_weekdays": [...]} ← the person, every session
Awalan user: (misalnya, user:prefs) mencakup status di semua sesi untuk pengguna tertentu. Sebaliknya, kunci tanpa awalan (
target_event_id
,
party_size
) merepresentasikan status sesi biasa, yang dicakup secara ketat ke sesi aktif dan dihapus saat sesi berakhir.
👉 Sekarang, bandingkan kedua tab State. Meskipun user:prefs dan artefak tersimpan tetap ada, temp:seatmap tidak ada di kedua sesi. Kunci status dengan awalan temp: bersifat efemeral dan tidak pernah dipertahankan ke database.
Kemudian file
👉💻 Buka di editor:
cloudshell edit ~/monstertix/memory/userx.md
Tiga pemesanan, kembali ke bulan Januari. Agen membacanya saat memanggil recall(), dan akan tetap ada meskipun kita kehilangan data sesi.
Empat tempat
Tempatnya berada | Bertahan dari pesan berikutnya? | Bertahan saat dimulai ulang? | Bertahan setelah menghapus sesi? |
| no | tidak | tidak |
| ya | ya | tidak |
| ya | ya | ya |
| ya | ya | ya, dan tetap ada setelah menghapus database |
artefak | ya | ya | bergantung pada layanan |
Baca tabel tersebut sebagai tabel tentang penampung, bukan tentang isinya.memory/userx.md bertahan dari segalanya, dan hanya berisi apa yang dipilih remember() untuk dimasukkan ke dalamnya. Tidak ada yang menulis percakapan Anda saat ini ke dalamnya. Jadi, file yang bertahan lebih lama dari sesi tidak sama dengan agen yang mengingat apa yang baru saja Anda bicarakan, dan langkah 5 adalah tempat perbedaan itu terlihat.

Apa itu status
Anggap status sebagai kamus yang berjalan bersama sesi. Baca dan tulis dari alat Anda melalui tool_context.state, dan biarkan ADK menyimpannya bersama percakapan.
ADK menetapkan arti awalan. Anda tidak menentukannya di mana pun. ADK membaca bagian pertama nama kunci dan menggunakannya untuk memutuskan ke mana nilai akan masuk dan berapa lama nilainya bertahan:
Kunci yang disebut | ADK menyimpannya | Berlangsung |
| dengan sesi | hingga sesi dihapus |
| terhadap pengguna | di setiap sesi yang dimiliki pengguna tersebut |
| terhadap aplikasi | di setiap pengguna |
| tidak di mana pun | satu pemanggilan |
Pemanggilan adalah siklus permintaan-respons tunggal, termasuk semua penalaran model, eksekusi alat, dan respons akhir.
Saat pemanggilan berakhir, ADK menulis status baru ke database dan menghapus setiap kunci temp: di sepanjang proses.
Satu hal yang belum dijelaskan
Di two-days-ago, Anda melihat ringkasan yang berada di tempat beberapa giliran pertama seseorang berada. ADK menuliskannya secara otomatis setelah percakapan cukup panjang.
Setiap fakta memiliki masa berlaku
5. Degradasi & Pemadatan Konteks
Ringkasan Melupakan Detail
Jika percakapan terlalu panjang, sistem akan meringkas pesan lama untuk menghemat ruang dan token.
Ringkasan berguna untuk memahami intisari umum, yang berguna untuk menghemat penggunaan token.

👉🔴 Di panel tempat, tekan RESET TEMPAT. Setiap langkah dimulai dari dunia yang sama: 8 pertunjukan, semua kursi tersedia, jam di 1×.
👉💻 Buka kode langkah ini:
cd ~/monstertix
. ./set_env.sh
./use-solution.sh 5 --force
Yang berubah sejak langkah terakhir:
File | Apa yang berubah |
| agen digabungkan dalam |
hal lainnya | tidak diubah |
agent.py tidak lagi diakhiri dengan root_agent. Kode ini diakhiri dengan:
app = App(
name="concert",
root_agent=root_agent,
events_compaction_config=EventsCompactionConfig(
compaction_interval=3, # summarise after every 3 turns
overlap_size=1, # re-read 1 turn either side when summarising
),
)
Ketahuilah bahwa adk web mencari variabel bernama app terlebih dahulu dan kembali ke root_agent. Bungkus agen dalam App setiap kali Anda memerlukan sesuatu yang berlaku untuk seluruh aplikasi, bukan untuk satu agen: pemadatan di sini, dan kemampuan melanjutkan nanti pada langkah-langkah berikutnya.
compaction_interval=3 berarti ADK meringkas setelah setiap tiga giliran. Satu giliran adalah Anda mengucapkan sesuatu dan agen menyelesaikan jawabannya, berapa pun alat yang dipanggilnya. Tiga adalah angka kecil sehingga akan diaktifkan selama workshop. Aplikasi sebenarnya akan menggunakan angka yang jauh lebih besar.
overlap_size=1 berarti setiap ringkasan dimulai satu giliran lebih awal dari yang sebenarnya diperlukan.
ADK merangkum giliran 1, 2, dan 3. Tiga giliran kemudian, model akan meringkas batch berikutnya. Dengan overlap_size=1, ringkasan kedua dimulai lagi pada giliran 3, bukan giliran 4:

Perhatikan bahwa giliran 3 diringkas dua kali. Hal itu disengaja. Jika Anda mengajukan pertanyaan pada giliran 3 dan agen menjawab pada giliran 4, pemisahan yang jelas akan menempatkan pertanyaan dalam satu ringkasan dan jawaban dalam ringkasan lainnya, sehingga keduanya membingungkan. Mengulangi giliran adalah jaminan murah agar tidak memotong pemikiran di tengah-tengah.
Tetapkan kedua kolom. ADK tidak memiliki default untuk keduanya.
Pemadatan dan layanan memori Anda tidak pernah bertemu
Perlu dijelaskan secara eksplisit, karena kedua kata tersebut terdengar terkait dan sistemnya tidak.
EventsCompactionConfig disetel di App di agent.py, di samping root_agent. Ini adalah properti aplikasi, bukan properti layanan apa pun:
# agent/concert/agent.py
app = App(
name="concert",
root_agent=root_agent,
events_compaction_config=EventsCompactionConfig(
compaction_interval=3,
overlap_size=1,
),
)
Ringkasan menulis ulang percakapan. Fitur ini tidak menulis ulang file.
Itulah alasan remember() ada sebagai panggilan yang disengaja. Apa pun yang tersisa dalam transkrip pada akhirnya akan dibaca sebagai parafrasa yang ditulis orang lain; apa pun yang ditulis ke file akan dibaca persis seperti yang disimpan, besok dan minggu depan. Di akhir langkah ini, Anda akan melihat perbedaan tersebut.
Tempat penyimpanan ringkasan
Di sessions.db, dalam tabel events, catatan pemadatan ditulis ke baris peristiwa oleh agen, dengan stempel waktu seperti peristiwa giliran lainnya.
Peristiwa biasa memiliki kolom content dan ringkasan berada di actions.compaction.
👉💻 Lihat di database Anda sendiri:
cd ~/monstertix
. ./set_env.sh
sqlite3 sessions.db \
"select json_extract(event_data,'\$.actions.compaction.compacted_content.parts[0].text')
from events where event_data like '%compaction%';"
Berikut tampilan bentuk acara:
events row
└── event_data (JSON)
├── id, author, timestamp, invocation_id ← like every other event
├── content ← ABSENT on this one
└── actions
└── compaction
├── start_timestamp ← the window it replaced
├── end_timestamp
└── compacted_content.parts[0].text ← the summary itself
Aliran peristiwa menarik setiap peristiwa dari kontennya. Ringkasan dalam database dan dikirim ke model pada setiap giliran.
Agen kedua
budget_split adalah agen yang sepenuhnya terpisah, bukan alat:
# agent/concert/agent.py
budget_split = Agent(
name="budget_split",
model=MODEL,
include_contents="none", # sees none of the conversation
instruction="You do ticket arithmetic and nothing else...", #just the budget
output_key="budget_plan", # its answer goes into state
)
root_agent = Agent(
...,
tools=[..., AgentTool(agent=budget_split)],
)
Mengapa agen, bukan fungsi biasa? Karena pekerjaan ini memerlukan model. Baca "bagian mana yang sesuai dengan anggaran Rp3.000.000 untuk empat orang, dan berapa totalnya" sebagai aritmatika yang dibungkus dalam penilaian, dengan jawaban yang harus dapat dibaca oleh seseorang. Biarkan fungsi Python mengalikan, dan jangan minta fungsi tersebut memutuskan opsi mana yang layak disebutkan.
Lalu, mengapa tidak membiarkan agen utama melakukannya? Lihat baris berikutnya include_contents="none" berarti agen ini tidak diberi satu pun percakapan. Tidak ada chat grup, tidak ada preferensi, tidak ada empat puluh tanggal tur. Metode ini mendapatkan permintaan dan tidak ada yang lain. Flag tersebut hanya ada di Agent, yang merupakan alasan sebenarnya mengapa ini adalah agen, bukan fungsi.

Lebih memilih perintah singkat: lebih murah, lebih cepat, dan jauh lebih sulit untuk dibelokkan. Mengandalkan aritmatika yang tidak terpengaruh oleh sesuatu yang dikatakan dua puluh giliran yang lalu, karena tidak pernah melihatnya.
Mengapa
AgentTool
? Gabungkan agen dengan alat ini agar agen utama dapat memanggilnya seperti alat lainnya. Dari sisi model, budget_split dibaca sama dengan search_events.
Dan
output_key="budget_plan" menempatkan jawaban ke dalam status sesi dengan nama tersebut, bukan membiarkannya tidak terikat dalam percakapan. Tonton video tentang mengapa hal itu penting.
Menjalankan aplikasi
👉💻 Di terminal 1, tekan Ctrl-C dan mulai ulang dengan tanda yang sama:
cd ~/monstertix
adk web agent --port 8000 --allow_origins "*" \
--session_service_uri="sqlite+aiosqlite:///$WORKSHOP/sessions.db" \
--artifact_service_uri="file://$WORKSHOP/artifacts"
👉 Mulai Sesi Baru dan lakukan percakapan yang nyata. Lima atau enam putaran. Yang ini berfungsi:
hello
what's on in Amsterdam?
Sam is coming with us, and he can't do weeknights
there are 4 of us and I can spend about $200 a head. What fits?
👉 Buka tab State.
Temukan budget_plan di sana, yang berisi jawaban lengkap agen aritmetika.

👉✨ Sekarang ubah angka di dalamnya:
what if 10 people come and I can only do $120 each?
👉 Lihat budget_plan lagi. Ditimpa, bukan ditambahkan. Hal ini karena output_key menyimpan yang terbaru.

Yang tidak dapat Anda lihat: ringkasan
Di suatu tempat dalam putaran tersebut, ADK merangkum putaran sebelumnya.
👉💻 Di Terminal baru, jalankan untuk membacanya secara langsung:
cd ~/monstertix
. ./set_env.sh
./show-compaction.sh
Membaca apa yang ditulisnya
Berikut adalah contoh nyata yang diringkas:
── summary 1 ──────────────────────────────────────────────────────
Conversation Language: English
**User Request:**
The user initially wanted to know what concerts are playing in Amsterdam.
Subsequently, the user provided a constraint regarding a companion, Sam.
**Context Summary:**
The user is planning a concert trip. The agent identified two events in
Amsterdam: "The Midnight Signal" at the Ziggo Dome on Tuesday, November
10 and Saturday, November 14, 2026. The user then informed the agent that a
companion named "Sam" will be joining, but "he can't do weeknights." The
agent has successfully noted this constraint for Sam.
**Key Decisions/Information Obtained:**
* **City of interest:** Amsterdam
* **Events found:** "The Midnight Signal" at the Ziggo Dome, Tuesday
10 November and Saturday 14 November 2026.
* **Companion:** Sam
* **Companion constraint:** Sam cannot attend concerts on weeknights.
**Unresolved Questions/Tasks:**
* The agent has not explicitly confirmed if the found event (which is
on a Saturday) is suitable given Sam's constraint, nor has it asked the
user for the next steps regarding this event or further event searches.
**Tools Used:**
* `recall`
* `search_events`
* `note_companion`
Bot tersebut mempertahankan harga, bagian, kota, dan preferensi, serta mencatat dengan benar bahwa belum ada keputusan yang diambil.
Tiga hal berikut ini:
Ini adalah parafrasa. Tidak ada yang dikatakan siapa pun di sini. Ini adalah catatan tentang apa yang dikatakan, yang dihasilkan oleh jenis proses yang sama yang terkadang menghasilkan kesalahan pada hal lain yang didapatkan model.
Catatan: String ringkasan disimpan dalam rekaman peristiwa sesi internal dan tidak ditampilkan secara langsung sebagai tab UI terpisah.
Setiap tiga giliran, dalam percakapan yang kini sebagian terdiri dari ringkasan sebelumnya.
Coba memesan tanpa mengulangi diri Anda
Dalam sesi baru Jangan memulai sesi baru — ringkasan yang baru saja Anda baca ada di peristiwa sesi ini, dan itulah satu-satunya alasan agen masih mengetahui sesuatu tentang Amsterdam.
👉✨ Tidak ada yang baru:
Book us something.
Tonton agen menanyakan acara, bagian, dan jumlah orang. Tandai sebagai benar: ringkasan itu sendiri mengatakan bahwa pengguna tidak pernah mengonfirmasi apa pun, jadi agen bertanya. Berikan kredit untuk pemadatan.
Meskipun pemadatan berhasil, meringkas giliran sebelumnya dapat menimbulkan ambiguitas yang mengharuskan agen mengonfirmasi ulang batasan dengan pengguna.
👉✨ Jawab dan izinkan pemesanan:
Amsterdam Saturday, section C, 8 people
Fitur ini membacakan kembali pesanan sebelum membelanjakan apa pun:
"Anda ingin memesan 8 tiket di Bagian C untuk The Midnight Signal di Amsterdam pada hari Sabtu, 14 November. Total biaya adalah $760. Haruskah saya membeli tiket ini sekarang?"
👉✨ Konfirmasi:
yes
Sekarang, Anda akan melihat bahwa perintah tersebut berhasil:
"Pembelian 8 tiket Anda di Bagian C untuk The Midnight Signal di Amsterdam pada Sabtu, 14 November, dengan total $760, telah selesai. ID pesanan Anda adalah ord_227877fe."
Buka dan periksa apakah sudah dilakukan atau belum
👉 Beralihlah ke panel tempat acara. Jangan langsung memercayai kata-kata agen.

Di panel | Yang akan Anda lihat |
Tiket yang dibeli |
|
pesanan di bawahnya |
|
The tour, Amsterdam Saturday | Bagian C turun 8 |
Aktivitas |
|
Periksa panel untuk mengetahui penjualan tiket.
Mengapa user:prefs adalah pilihan yang tepat
Ingatlah penulisan batasan Sam note_companion ke dalam user:prefs pada langkah terakhir, dan keberatan yang jelas: mengapa repot-repot menggunakan alat jika agen bisa mengingatnya.
Berikut alasannya. Jangan sertakan status dalam percakapan dan tidak ada ringkasan yang menyentuhnya. Periksa user:prefs dan budget_plan terhadap cara mereka masuk, dan temukan bahwa keduanya tidak berubah. Kemudian, periksa transkripnya.
Ringkasan mempertahankan bentuk umum dan menghilangkan detail. Simpan semua hal yang tidak boleh hilang di tempat lain selain percakapan.
6. Melanjutkan Proses
Menunggu 40 Menit Tanpa Tetap Online
Jika agen harus menunggu dalam antrean tiket selama 40 menit, menjaga proses Python tetap aktif dan menunggu akan sulit dan mahal.
Untuk menangani penantian yang lama dengan benar:
- Menyimpan status ke database: Menyimpan tempat agen dijeda sehingga proses dapat dimatikan dengan aman.
- Aktifkan sesuai permintaan: Saat menunggu selesai, sinyal eksternal akan memulai ulang agen dan melanjutkan eksekusi tepat di tempat terakhir kali agen berhenti.
👉🔴 Di panel tempat, tekan RESET TEMPAT. Setiap langkah dimulai dari dunia yang sama: 8 pertunjukan, semua kursi tersedia, jam di 1×.
👉💻 Buka kode langkah ini:
cd ~/monstertix
. ./set_env.sh
./use-solution.sh 6 --force
Menjalankan aplikasi
Yang berubah sejak langkah terakhir:
File | Apa yang berubah |
| menambahkan |
|
|
join_queue langsung menampilkan tiket, bukan memblokir, dan ResumabilityConfig memungkinkan jeda eksekusi dan dilanjutkan setelahnya.

👉💻 Terminal 1 — agen, tidak berubah:
cd ~/monstertix
. ./set_env.sh
adk web agent --port 8000 --allow_origins "*" \
--session_service_uri="sqlite+aiosqlite:///$WORKSHOP/sessions.db" \
--artifact_service_uri="file://$WORKSHOP/artifacts"
👉✨ Minta agen untuk membeli:
Get us two tickets to the Amsterdam show.
Fungsi ini memanggil recall, lalu search_events, lalu join_queue, dan menjawab dengan sesuatu yang mirip dengan ini:
You are in the queue for The Midnight Signal at Ziggo Dome, Amsterdam, on Saturday, November 14. Your position is 14203.
Perhatikan bahwa Sabtu dipilih tanpa menanyakan kepada Anda. Ada dua pertunjukan di Amsterdam, tanggal 17 adalah hari Selasa, dan file memori dari langkah terakhir mengatakan bahwa Sam tidak tampil pada malam hari kerja. Itu adalah fakta dari pemesanan sebelumnya yang memutuskan pertanyaan dalam pemesanan ini.
Perhatikan juga bahwa ia bergabung dalam antrean sebelum melihat satu kursi atau harga, dan tidak mengatakan apa pun tentang keduanya. Itu adalah aturan tempat acara, Anda membeli tiket hingga mencapai bagian depan, jadi satu-satunya hal yang layak didapatkan lebih awal adalah tempat dalam antrean. Kursi mana yang harus diambil adalah pertanyaan untuk empat puluh menit dari sekarang.
👉 Lihat panel kontrol. Banner tersebut bertuliskan:

Agent is waiting in line — #14,203
About 2400s at 1×. Safe to kill the agent right now — it will still be here.
Antrean benar-benar berdurasi empat puluh menit dan tidak akan hilang saat Anda bekerja.
👉💻 Hentikan agen.:
Ctrl-C
👉💻 Mulai lagi di terminal 1:
cd ~/monstertix
. ./set_env.sh
adk web agent --port 8000 --allow_origins "*" \
--session_service_uri="sqlite+aiosqlite:///$WORKSHOP/sessions.db" \
--artifact_service_uri="file://$WORKSHOP/artifacts"
👉✨ Dalam sesi yang sama, tanyakan kepada agen di mana posisinya dalam antrean:
Where am I in line?
You are currently at position 12982 in the queue. It is not your turn yet.

Bandingkan tiket, dan lihat posisi terus menurun saat tidak ada yang berjalan. Perhatikan bahwa agen tidak tahu bahwa agen telah dihentikan. Proses ini memanggil check_queue dengan tiket yang didapat sebelum dimulai ulang, karena tiket tersebut ada di disk.
👉💻 Lihat alasannya:
cd ~/monstertix
sqlite3 sessions.db "select name from pragma_table_info('events');"
id
app_name
user_id
session_id
invocation_id
timestamp
event_data
Perhatikan bahwa setiap giliran dalam setiap percakapan adalah satu baris, dan giliran itu sendiri, siapa yang mengatakannya, alat mana yang dipanggil, apa yang dikembalikan, dan perhatikan bahwa itu adalah JSON di dalam event_data.
👉💻 Temukan tiket antrean Anda di sana:
sqlite3 sessions.db "select event_data from events;" \
| grep -o '"ticket": "q_[a-z0-9]*", "position": [0-9]*' | tail -1
"ticket": "q_8cc2c6f9", "position": 14203
Saat join_queue ditampilkan, tiketnya akan disimpan langsung ke database. Setelah dimulai ulang, ADK memuat ulang hasil alat dan giliran sebelumnya dari disk, sehingga agen dapat melanjutkan dari tempat terakhir ia berhenti.
Perhatikan apa yang tidak dimulai ulang. Agen melanjutkan secara langsung, bukan memutar ulang alur kerja dari awal. Karena LongRunningFunctionTool dan ResumabilityConfig mempertahankan status eksekusi ke disk, ADK mengetahui bahwa agen sudah menunggu dalam antrean, sehingga mencegahnya mencari ulang tanggal tur atau meminta tiket duplikat.

Mengapa join_queue, bukan check_queue
Kedua fungsi ditampilkan dalam milidetik. Perbedaannya adalah apakah tugas pokok selesai saat fungsi ditampilkan.
Hasil yang ditampilkan | Apakah pekerjaan sudah selesai? | |
| Tiket antrean dan posisi awal | Tidak. Pemrosesan dilanjutkan secara asinkron di sistem eksternal. |
| Status antrean saat ini | Bagus. Menampilkan jawaban langsung dan point-in-time. |
LongRunningFunctionTool dirancang untuk operasi yang menampilkan handle ke pekerjaan yang tertunda. Pembungkusan join_queue memungkinkan agen menangguhkan eksekusi saat antrean eksternal berlanjut.
Sebaliknya, check_queue bersifat sinkron dan lengkap. Membungkusnya akan menyebabkan agen menangguhkan eksekusi secara salah setiap kali ia memeriksa statusnya—bahkan saat ia mencapai bagian depan antrean dan perlu menyelesaikan pembelian dengan segera.

Faktor yang membuat hal itu berhasil
Tiga komponen memungkinkan penangguhan dan pemulihan eksekusi:
# agent/concert/agent.py
LongRunningFunctionTool(func=join_queue) # the tool returns without finishing
ResumabilityConfig(is_resumable=True) # the run may pause and be picked up
--session_service_uri="sqlite+aiosqlite:///$WORKSHOP/sessions.db"
LongRunningFunctionTool: Menandai alat sebagai tidak memblokir, menampilkan handle aktif sehingga agen dapat menunggu tanpa menghentikan thread.ResumabilityConfig: Menginstruksikan runtime untuk menjeda siklus proses eksekusi pada tugas yang belum selesai dan melanjutkan dari status terakhir yang tercatat.Session Storage URI (--session_service_uri): Mengarahkan persistensi status ke penyimpanan eksternal (misalnya, SQLite, Cloud SQL) daripada memori proses, sehingga mencegah kehilangan data saat dimulai ulang.
7. Menindaklanjuti Data Tidak Berlaku
Informasi menjadi tidak berlaku saat Anda menunggu
Histori percakapan yang dipertahankan bukanlah status real-time. Selama interval menunggu yang lama, data eksternal—seperti ketersediaan atau harga tiket—dapat berubah tanpa diketahui agen.
Agen yang tidak memiliki informasi akan berhenti sejenak untuk meminta klarifikasi, tetapi agen yang memiliki data usang akan langsung bertindak berdasarkan asumsi yang sudah tidak berlaku. Untuk mencegah tindakan yang tidak valid, data yang sensitif terhadap waktu harus diverifikasi ulang segera sebelum menjalankan operasi penting.
Jangan muat kode baru dulu. Tetap menggunakan agen langkah sebelumnya (dapat membeli tiket, tetapi saat ini tidak memiliki pengamanan untuk keusangan atau idempotensi).
👉🔴 Tekan RESET TEMPAT, lalu mulai sesi baru di adk web — tombol + Sesi Baru di atas chat.
Mulai sesi baru untuk menghapus tiket antrean sebelumnya dan status pembelian tertunda. Langkah ini memerlukan konteks eksekusi yang bersih untuk mendemonstrasikan penanganan data basi dengan benar.
Snapshot tersebut berbohong
👉✨ Minta acara yang sama seperti sebelumnya:
Get us two tickets to the Amsterdam show.
👉✨ Sekarang, saat sedang mengantre, tanyakan tentang tempat duduk:
How much are the seats, and what's still available?
Section A: $210 per ticket, with 400 seats available.
Section B: $145 per ticket, with 900 seats available.
Section C: $95 per ticket, with 1200 seats available.
👉🔴 Tekan JUAL TIKET BAGUS. Bagian A turun menjadi nol.
👉🔴 Tekan LEWATI ANTRIAN untuk memindahkan agen ke depan antrean.

👉✨ Perintahkan untuk membeli:
Buy the two section A seats.
Hal ini menjalankan purchase(section="A") tanpa memverifikasi ulang ketersediaan kursi, sehingga menyebabkan API tempat acara menampilkan respons error:
{"error": "sold_out", "available": 0,
"message": "those seats are gone — re-fetch the seatmap before buying"}
Oh no! It looks like the Section A tickets just sold out. Let me get an
updated seat map to see what's still available.

Perhatikan bahwa agen hanya meminta ulang peta tempat duduk setelah menerima respons error API. Penyebab utamanya adalah konteks yang tidak valid: sebelumnya dalam sesi, agen mencatat "bagian A: 400 tersedia", dan giliran tersebut tetap ada dalam histori percakapan yang diteruskan ke model.
Jelaskan dengan jelas tempat informasi lama disimpan: informasi tersebut tetap ada di histori percakapan, yang tidak dapat digantikan oleh awalan status seperti temp:.

Perbaikan
👉🔴 Di panel tempat, tekan RESET TEMPAT. Setiap langkah dimulai dari dunia yang sama: 8 pertunjukan, semua kursi tersedia, jam di 1×.
👉💻 Sekarang muat kode langkah ini:
cd ~/monstertix
. ./set_env.sh
./use-solution.sh 7 --force
👉💻 Di terminal 1, Ctrl-C dan mulai ulang:
cd ~/monstertix
. ./set_env.sh
adk web agent --port 8000 --allow_origins "*" \
--session_service_uri="sqlite+aiosqlite:///$WORKSHOP/sessions.db" \
--artifact_service_uri="file://$WORKSHOP/artifacts"
Yang berubah:
File | Apa yang berubah |
| baru. Langsung berjalan sebelum pembelian dan dapat memblokirnya |
|
|
|
|
fence.py adalah callback yang dijalankan ADK sesaat sebelum panggilan alat:
root_agent = Agent(
...,
before_tool_callback=refresh_before_purchase, # re-read inventory, then decide
)
Buktikan
👉🔴 RESET TEMPAT. Antre, tekan JUAL TEMPAT DUDUK BAGUS di tengah antrean, lalu minta untuk membeli bagian A.
Kali ini, proses akan berhenti dengan sendirinya. Perhatikan agen mengucapkan sesuatu seperti:
"Sepertinya Bagian A baru saja terjual habis. Saya perlu membaca ulang peta tempat duduk."
Kemudian, perhatikan ia memanggil get_seatmap lagi tanpa diminta, dan kembali menawarkan bagian C.
👉🔴 Baca feed aktivitas tempat dengan cermat, karena feed tersebut menampilkan kedua bagian:
error purchase failed: Section A has 0 seats left, 2 requested. Re-read the
seat map and pick again.
agent calling purchase

Di dasbor, Anda melihat pembelian panggilan, tetapi penghitung tiket tetap nol. Hal ini terjadi karena fence.py bertindak sebagai penjaga sebelum alat berjalan:
- Agen mencoba memanggil alat pembelian.
fence.pymenangkap permintaan terlebih dahulu dan memeriksa ketersediaan kursi live.- Melihat bahwa tiket sudah habis,
fence.pylangsung menampilkan error, membatalkan pembelian sebelum API dipanggil.
Petunjuk perintah tidak dapat menjamin bahwa LLM tidak akan mencoba membeli kursi yang sudah habis terjual. Pengamanan kode (before_tool_callback) menjamin bahwa pembelian yang buruk tidak akan pernah terjadi di dunia nyata.
👉🔴 Tekan RESET TEMPAT, lalu BATALKAN PEMBELIAN BERIKUTNYA
👉✨ Minta Gemini untuk membeli, dan biarkan Gemini mencoba lagi sebanyak yang diinginkannya.

Jumlah pesanan tetap 1 karena tempat menerima kunci keidempotensian yang sama pada percobaan ulang dan menampilkan konfirmasi asli, bukan membuat pesanan duplikat.
8. Alur Kerja Otonom
LLM Tidak Mampu Mengikuti Aturan Ketat Setiap Saat
LLM sangat bagus dalam membuat keputusan (seperti memilih acara berdasarkan preferensi pengguna). Namun, mengandalkan LLM untuk mengikuti aturan langkah demi langkah yang ketat (seperti "selalu bergabung ke antrean sebelum memeriksa kursi") berarti LLM terkadang akan melewati langkah-langkah atau mengacaukan urutannya.
Untuk memperbaikinya, bagi alur kerja menjadi dua jenis langkah:
- Aturan Tetap (Fungsi Kode): Langkah-langkah yang harus terjadi dengan cara yang sama persis setiap saat (mengambil tiket antrean, memeriksa posisi) ditulis sebagai node kode biasa.
- Keputusan (Node Agen): Langkah-langkah yang memerlukan pertimbangan (memilih bagian tempat duduk, memeriksa preferensi pengguna) diberikan kepada agen AI.
Lihat apa yang sebenarnya ada dalam alur ini. Beberapa di antaranya tidak mengandung penilaian sama sekali:
Langkah | Penilaian? |
mengambil tepat satu tiket antrean | tidak. Selalu satu, selalu sebelum membeli |
memeriksa apakah giliran Anda | tidak. Ajukan, baca jawabannya |
menulis rangkuman untuk pembeli | tidak. Kalimat yang sama, bentuk yang sama, setiap saat |
yang menampilkan bagian mana | ya. Harga, hari, siapa yang datang, apa yang mereka benci |
preferensi dan batasan pengguna | ya. |
Prosedur yang ketat tidak boleh mengandalkan LLM karena model probabilistik pada akhirnya akan gagal mengikuti urutan yang kaku jika tidak dipantau.
Workflow ADK memisahkan eksekusi deterministik dari penalaran AI:
- Node fungsi menerapkan aturan tetap (seperti bergabung dalam antrean atau memeriksa posisi) secara konsisten setiap saat.
- Node agen menangani keputusan kontekstual (seperti memilih tempat duduk berdasarkan preferensi pengguna).
- Node tunggu menangguhkan eksekusi tanpa menjaga proses tetap aktif dalam memori.
Agen asli tidak diganti. Node ini menjadi node akhir dalam grafik alur kerja, dengan mempertahankan alat, pemeriksaan keusangan, dan perlindungan idempotensi. Grafik mengontrol urutan eksekusi, sementara agen mengeksekusi keputusan pembelian.
👉🔴 Di panel tempat, tekan RESET TEMPAT.
👉💻 Buka kode langkah ini:
cd ~/monstertix
. ./set_env.sh
./use-solution.sh 8 --force
Menjalankan aplikasi
👉💻 Di terminal 1, tekan Ctrl-C dan mulai ulang. Perintah yang sama seperti biasanya:
cd ~/monstertix
. ./set_env.sh
adk web agent --port 8000 --allow_origins "*" \
--session_service_uri="sqlite+aiosqlite:///$WORKSHOP/sessions.db" \
--artifact_service_uri="file://$WORKSHOP/artifacts"
Yang berubah sejak langkah terakhir:
File | Apa yang berubah |
| baru. Jalankan tanpa pengawasan sebagai grafik ADK 2 |
|
|
| juga kehilangan |
Jalankan grafik — tiga pesan, secara berurutan
Jalankan semua tindakan berikut di python harian (Di sinilah grafik berada). Kirim input yang identik pada setiap giliran untuk mengamati transisi grafik alur kerja melalui statusnya.
1. Perhatikan siapa yang sedang Anda ajak bicara
👉 Muat ulang UI web ADK.
# concert/agent.py, at the bottom
buyer_agent = Agent(name="concert", ...) # ← everything you built, renamed
from .nightly import nightly
root_agent = nightly # ← what adk web actually runs
Perhatikan bahwa adk web menjalankan objek mana pun yang ditetapkan ke root_agent. Di sini, root_agent mengarah ke grafik Alur kerja nightly, dengan buyer_agent dikonfigurasi sebagai node akhir dalam grafik.
2. Mulai
👉✨ Ketik ini, lalu tekan enter:
go
Catatan: Dalam grafik alur kerja otomatis, eksekusi awal dimulai tanpa menunggu input pengguna. Mengirim pesan di adk web akan memicu proses awal.
👉 Sekarang lihat browser web. Panel kiri menggambar grafik dan mewarnai node saat dijalankan. Panel kanan adalah aliran peristiwa, yang mengamati bagaimana keputusan dibuat:

Pantau queue_uptulisqueue_ticket dan queue_event_id ke dalam status sesi.

Anda akan melihat panggilan fungsi bernama adk_request_input, yang menunggu jawaban. Kemudian, aplikasi akan berhenti dan sesi lari akan berakhir. Grafik mencapai check_front, menemukan Anda di urutan ke-14.203, dan berhenti.
👉 Periksa panel tempat acara: satu tiket antrean, dan tidak ada yang berjalan.

3. Aktifkan — dan perhatikan kotak yang Anda ketik
Scroll aliran peristiwa ke peristiwa adk_request_input dan temukan kotak input yang terkait dengan peristiwa tertentu tersebut:

Sekarang ada dua tempat yang dapat Anda gunakan untuk mengetik, dan keduanya melakukan hal yang berbeda.
Tempat Anda mengetik | Yang terjadi |
kotak | menjawab interupsi. Jalur yang di-parkir berlanjut dari |
kotak chat utama di bagian bawah | memulai pemanggilan baru. Grafik diputar ulang dari |
👉✨ Ketik apa saja di kotak Enter your response..., lalu tekan panah.
👉 Terminal 1:
[check_front] #14,197 — not yet. Pausing.
Satu baris. Anda tidak memilih pertunjukan kedua dan tidak mengambil tiket antrean kedua, yang akan menempatkan Anda di urutan belakang antrean yang berisi 14.203 orang.
Jika Anda melihat [open] dan [pick_show] berjalan lagi, berarti Anda mengetik di kotak chat. Tekan RESET TEMPAT dan mulai langkah dari awal.
👉 Periksa panel tempat acara lagi: masih satu tiket, dan posisi telah turun sedikit karena waktu di tempat acara terus berjalan sementara waktu di perangkat Anda tidak.

4. Pindahkan ke depan, lalu selesaikan
👉🔴 Tekan LEWATI ANTRIAN di panel tempat.

👉✨ Jawab peristiwa adk_request_input terbaru untuk terakhir kalinya — sekali lagi di kotak -nya, bukan di kotak chat. 
Kali ini, lihat check_front menemukan Anda di depan dan jatuh. Perhatikan panel kiri yang menyala brief dan concert, dua node terakhir.

👉 Lalu lihat browser. Ini adalah akhir operasi, dan ada empat hal di dalamnya yang layak dibaca.

Grafik berwarna hijau sepenuhnya, dan tepi dihitung. brief dan concert telah menyala dan tepi ke pick_show, queue_up, dan check_front kini memiliki badge.
2x
adalah jumlah berapa kali grafik melewati tepi tersebut, sekali per aktivasi. Angka 2 di bagian atas adalah harga untuk melanjutkan: penjadwal akan masuk kembali ke grafik dari awal setiap kali. Hal ini tidak berarti pekerjaan dilakukan tiga kali, dan panel tempat adalah buktinya: satu tiket antrean, bukan 2. @node(rerun_on_resume=False) adalah alasannya.
Grafik

# agent/concert/nightly.py
nightly = Workflow(
name="concert_nightly",
edges=[(START, open_the_night, pick_show, queue_up,
check_front, brief, buyer_agent)],
)
Perhatikan bahwa dua node adalah agen dan dua node adalah fungsi, dan perlakukan mana yang mana sebagai pertanyaan desain secara keseluruhan.
Node | Alasan bentuk tersebut |
| agent. Memilih acara dari histori seseorang adalah penilaian. Fungsi ini memanggil |
| function. Bergabung ke antrean adalah aturan. Aturan tidak boleh berupa pelemparan koin pada pukul 03.00 |
| function. "Apakah saya di depan" memiliki satu jawaban yang benar dan tempatnya memiliki jawaban tersebut |
| agent. Ini adalah |
Lihat node terakhir dan temukan agen yang Anda buat. Bukan salinan, bukan penulisan ulang:
# agent/concert/nightly.py
from .agent import buyer_agent
Semuanya disertakan: setiap alat yang dimilikinya, pembacaan ulang peta tempat duduk di fence.py, kunci keidempotenan di purchase. Hanya ada satu.
Mengapa berhenti, bukan menunggu
Berikut adalah versi yang tidak boleh ditulis oleh siapa pun:
# what NOT to write — this is nobody's file
while not ready: # never do this
time.sleep(1)
Proses ini membuat proses tetap aktif selama empat puluh menit tanpa melakukan apa pun, berhenti dengan terminal, dan tidak dapat dilanjutkan. Berikut yang dilakukan check_front:
# agent/concert/nightly.py — check_front
if not status["ready"]:
return RequestInput(message=f"Still at #{position}. Wake me and I'll check.")
RequestInput adalah interupsi. Grafik berhenti di tempatnya, pemanggilan masuk ke penyimpanan sesi, dan proses bebas keluar.
Run tidak dikenai biaya saat menunggu, dan berakhir di satu tempat: tiket yang dibeli. Mungkin perlu waktu semalaman dan beberapa kali bangun untuk mencapainya.
9. Bekerja dengan Manusia
Jangan Membelanjakan Uang Tanpa Memeriksa Ulang dengan Orang Lain
Agen otonom yang berjalan semalaman tidak boleh membuat pilihan keuangan yang mahal berdasarkan preferensi yang ditebak atau ambigu dari histori chat.
Sebelum melakukan tindakan yang berisiko tinggi (seperti membelanjakan uang untuk tiket), agen harus menjeda, meminta pengguna untuk mengonfirmasi anggaran mereka secara lisan, dan menyimpan konfirmasi eksplisit tersebut sebelum melakukan pembelian apa pun.
👉🔴 Di panel tempat, tekan RESET TEMPAT. Setiap langkah dimulai dari dunia yang sama: 8 pertunjukan, semua kursi tersedia, jam di 1×.
👉💻 Buka kode langkah ini:
cd ~/monstertix
. ./set_env.sh
./use-solution.sh 9 --force
Menjalankan aplikasi
👉💻 Di terminal 1, tekan Ctrl-C dan mulai ulang. Perintah yang sama seperti sebelumnya:
cd ~/monstertix
. ./set_env.sh
adk web agent --port 8000 --allow_origins "*" \
--session_service_uri="sqlite+aiosqlite:///$WORKSHOP/sessions.db" \
--artifact_service_uri="file://$WORKSHOP/artifacts"
Yang berubah sejak langkah terakhir:
File | Apa yang berubah |
| Menyimpan apa yang disetujui orang tersebut, sebagai kalimat yang dia ucapkan. |
| ditambahkan |
| node pertama baru, |
Jangan memodelkannya. Simpan kalimat.
Buka agent/concert/budget.py dan baca semuanya:
def load(state) -> str:
"""What they agreed, verbatim. Empty string means nobody has said."""
return str(state.get("budget") or "")
state["budget"] = "up to $100 for the upper bowl or general admission, up to
$250 for the lower bowl, and up to $300 for a Saturday show"
Node ini memeriksa dua hal... bahwa orang tersebut mengatakannya, dan bahwa dia mengonfirmasinya.
Kemudian, kalimat tersebut disimpan dalam status sesi, bukan di bawah user:, dengan sengaja. Anggaran disepakati untuk satu pemesanan. "$250 jika mereka yang bagus" diucapkan tentang band itu, pada malam itu, dan tidak ada hubungannya dengan pemesanan tiket.
Hal yang sama berlaku untuk semua hal lain yang mereka katakan
Jangan berhenti pada anggaran. Orang dapat berubah pikiran saat mereka memutuskan, jadi catat semua yang mereka minta di sini, bukan hanya mengandalkan apa yang diingat agen.
"I live in NYC, I can only do weekends, I have 10 people coming with me"
# agent/concert/nightly.py
def what_they_asked_for(ctx) -> str:
"""Everything this person typed, joined, in their own words."""
said = []
for event in (ctx.session.events or []):
if event.author != "user":
continue
for part in (event.content.parts if event.content else []) or []:
text = (getattr(part, "text", None) or "").strip()
if text and text not in said:
said.append(text)
return "\n".join(said)
Hanya peristiwa yang dibuat pengguna yang dilihatnya Hanya
text
bagian dari function_response, sehingga pertanyaan dan jawaban anggaran tidak dikembalikan di sini. Tidak ada duplikat.
Perhatikan bahwa agree_budget memanggilnya pada setiap kali dijalankan, bukan hanya yang pertama, sehingga koreksi yang diketik saat agen mengantre juga akan diterapkan. Kemudian, open_the_night menyerahkan kedua kalimat, permintaan, dan anggaran kepada pemilih, lalu pemilih membacanya.
Grafik, satu node lebih panjang
Mari kita mulai dengan percakapan:
# agent/concert/nightly.py
nightly = Workflow(
name="concert_nightly",
edges=[(START, agree_budget, open_the_night, pick_show, queue_up,
check_front, brief, buyer_agent)],
)

Dua node berhenti dan menunggu, dan keduanya adalah mekanisme yang sama. check_front menunggu lokasi dan dijawab oleh jam. agree_budget menunggu seseorang dan hanya dapat dijawab oleh orang. Perbedaan tersebut adalah keseluruhan langkah 10.
Jalankan — langkah demi langkah
1. Mulai
👉 Muat ulang UI web ADK dan pilih agen concert.
👉✨ Minta apa yang sebenarnya Anda inginkan:
Get us two tickets to the Amsterdam show.

Pada langkah 8, grafik tidak memiliki pengguna, jadi apa pun yang Anda ketik akan dibuang dan go akan berfungsi sama baiknya dengan apa pun. Di sini, agree_budget memanggil what_they_asked_for(ctx) di baris pertamanya, yang mengumpulkan semua yang telah Anda ketik dan menempatkannya di state["request"]. Kemudian, open_the_night menyerahkannya ke pemilih bersama dengan anggaran.
Anda tidak menyebutkan hari, dan Amsterdam memiliki dua pertunjukan. Jadi, perhatikan pemilih kembali ke recall() untuk itu, temukan "Sam bails on weeknights", dan pilih hari Sabtu.
Dan di browser, proses berjalan berhenti pada peristiwa adk_request_input:
Grafik baru saja mengajukan pertanyaan kepada Anda. Interupsi yang sama seperti menunggu dalam antrean di langkah 8, tetapi sekarang mengarahkan ke seseorang untuk menjawab pertanyaan.
2. Jawab seperti orang sungguhan menjawab
👉✨ Di kotak Enter your response... — bukan kotak chat di bagian bawah:
About a 100 for the cheap seats. 250 if they're the good ones.
Perhatikan saat berhenti dan konfirmasi:
[agree_budget] reading back: About a hundred for the cheap seats. Two-fifty
if they're the good ones.
⚡ adk_request_input
So: About a hundred for the cheap seats. Two-fifty if they're the good
ones. Have I got that right?

3. Konfirmasi
👉✨ Kotak yang sama:
Yes, that's right.
[agree_budget] agreed: About a hundred for the cheap seats. Two-fifty if
they're the good ones.
[open] Budget: About a hundred for the cheap seats. Two-fifty if
they're the good ones.
[pick_show] → {"section": "A", "reason": "..."}
[queue_up] ms-ams-01 section A — ticket q_5924edc2 at #14,203
[check_front] #14,203 — not yet. Pausing.
Empat node dijalankan dari satu kata. 👉 Buka tab State dan temukan budget — kalimat Anda, kata demi kata.

Tanpa awalan. Itulah status sesi, dan keputusan ini layak dibuat dengan sengaja jika Anda memulai Sesi Baru dan anggaran akan habis, sehingga Anda akan ditanya lagi.
4. Maju ke depan dan selesaikan
👉🔴 Tekan LEWATI ANTRIAN di panel tempat.
👉✨ Jawab adk_request_input terbaru — sekali lagi di kotak -nya:
go

👉 Periksa panel tempat acara: satu pesanan, dengan harga yang sesuai dengan harga yang Anda setujui untuk jenis tempat duduk tersebut.

Cara grafik mengajukan pertanyaan kepada seseorang
Buka nightly.py dan baca agree_budget. Buat tiga hal menjadi benar secara bersamaan, dan harapkan kegagalan yang sama saat salah satunya tidak ada: ia akan terus meminta.
@node(rerun_on_resume=True) # 1. run again when woken
def agree_budget(node_input, ctx):
answers = ctx.resume_inputs or {} # 3. where the answer arrives
said = answers.get(ASK_BUDGET)
if not said:
return RequestInput(
interrupt_id=ASK_BUDGET, # 2. STABLE id, not the default
message="What are you willing to spend?",
)
...
| tanpa itu, node dianggap selesai dan dilewati | |
stabil | Defaultnya adalah UUID baru per eksekusi, jadi tidak ada yang dapat digunakan untuk mencari jawaban | |
| tempat jawaban berada. Bukan nilai yang ditampilkan | |
Grafik
Ini adalah grafik langkah 8 dengan satu node tambahan di depannya. 
Batasan yang dikonfirmasi pengguna memiliki cakupan sesi dan akan berakhir saat percakapan berakhir. Mempertahankan batas sementara di seluruh sesi berisiko mengubah keputusan sekali pakai menjadi default permanen yang tidak diinginkan.
10. Cloud Stack
Laptop dalam Mode Tidur dan File Lokal Menghilang
Di laptop Anda, agen bergantung pada komputer Anda yang tetap aktif, database SQLite lokal, dan file lokal. Jika Anda menutup tutup laptop atau men-deploy ke container cloud yang dimulai ulang, file dan memori lokal akan hilang.
Agar dapat berjalan dengan andal dalam produksi:
- Cloud Compute: Jalankan agen dalam container (seperti Cloud Run) yang tetap online.
- Durable Cloud Databases: Ganti file lokal dan SQLite dengan layanan cloud (Cloud SQL untuk data sesi, Cloud Storage untuk file).
- Timer Cloud: Gunakan penjadwal terkelola (seperti Cloud Scheduler dan Pub/Sub) agar agen aktif meskipun laptop Anda dimatikan.
👉🔴 Di panel tempat, tekan RESET TEMPAT. Setiap langkah dimulai dari dunia yang sama: 8 pertunjukan, semua kursi tersedia, jam di 1×.
👉💻 Pindah ke kode akhir:
cd ~/monstertix
. ./set_env.sh
./use-solution.sh 10 --force
File | Apa yang berubah |
| baru. Satu FastAPI yang melayani halaman chat dan endpoint yang dipanggil scheduler |
| baru. Python-slim dan uvicorn biasa. Tidak ada yang khusus untuk ADK |
| disalin, sehingga halaman dikirim dalam penampung yang sama dengan agen |
| tidak berubah. Byte demi byte folder yang Anda selesaikan di langkah 9 |
Halaman yang sudah Anda buat
** UI web ADK** adalah antarmuka debug lokal untuk memeriksa status, aliran peristiwa, dan grafik alur kerja selama pengembangan. Aplikasi ini tidak dipaketkan untuk deployment produksi.
Untuk produksi, gunakan frontend chat mandiri yang dibuat di Langkah 3. Class ini menangani gangguan pesan dan interaksi pengguna melalui endpoint /wake tanpa mengekspos kontrol proses debug internal.
Memigrasikan Status Lokal ke Layanan Cloud
Hentikan server dan lihat apa yang ditulisnya:
sessions.db a SQLite file next to the code
memory/userx.md a markdown file you can open in an editor
artifacts/ seat maps written to a folder
Meskipun file lokal berfungsi dengan baik untuk pengembangan, file tersebut gagal dalam produksi karena container Cloud Run menggunakan penyimpanan ephemeral dan lokal per instance.
Saat berjalan di Cloud Run:
- Instance diskalakan ke nol: Saat traffic berhenti, container akan dihentikan dan sistem file lokalnya akan dihapus.
- Instance diskalakan secara independen: Beberapa instance serentak tidak berbagi ruang disk, masing-masing berjalan dengan sistem file terisolasi sendiri.
Jika agen menyimpan tiket antrean atau status sesi ke file SQLite lokal (sessions.db), permintaan berikutnya yang dirutekan ke instance container baru atau berbeda akan menemukan database kosong. Untuk mempertahankan status yang tahan lama di seluruh penskalaan dan mulai ulang, file lokal harus diganti dengan layanan eksternal terkelola.
Yang Anda butuhkan adalah status yang bertahan lebih lama daripada satu penampung. Tiga layanan, satu untuk setiap file:
Di laptop Anda | Di cloud | Penjelasan |
| Cloud SQL | database Postgres yang dikelola. Proses ini ada terlepas dari apakah ada yang berjalan atau tidak |
| Cloud Storage | bucket. File sebagai objek, dapat diakses dari setiap instance sekaligus |
| Cloud Storage | bucket yang sama |
| Cloud Scheduler | cron yang dijalankan Google. Terminal Anda bukan jam alarm |
Buat
Sediakan database PostgreSQL, pengguna database, dan bucket Cloud Storage. Perhatikan bahwa pembuatan instance Cloud SQL memerlukan waktu beberapa menit dan dimulai di latar belakang selama setup.sh.
👉💻 Kumpulkan, dan buat sisanya:
cd ~/monstertix
./setup-cloud-state.sh
→ instance RUNNABLE
→ database adk created
→ user adk created
→ bucket created
→ memory memory/userx.md → gs://your-project-agent-you/memory/
✓ durable state ready. Written to .env
Skrip tersebut membuat empat hal, satu per baris tabel di atas, dan menulis dua URI ke dalam .env:
CLOUD_SESSION_SERVICE_URI=postgresql+asyncpg://adk:...@/adk?host=/cloudsql/proj:region:instance
CLOUD_ARTIFACT_SERVICE_URI=gs://your-project-agent-you/artifacts
Men-deploy Agen dengan Pemicu Terjadwal Terkelola
Setelah layanan siap, berikan rumah permanen untuk agen.
👉💻 Tiga atau empat menit. Mulai dan lanjutkan membaca:
cd ~/monstertix
. ./set_env.sh
./deploy-agent.sh
Skrip ini membangun image container dari agent/, men-deploy-nya ke Cloud Run, dan menyediakan infrastruktur berbasis peristiwa pendukung.
Deployment lengkap terdiri dari lima komponen berbeda:
1. gcloud run deploy the container: your agent, the graph, and the page
2. a Pub/Sub topic a queue for messages
3. a service account an identity, so the message is allowed in
4. a push subscription topic ──► /trigger/wake
5. a Cloud Scheduler job the alarm clock, on a cron
Cloud Scheduler sebagai Sumber Pemicu
Cloud Scheduler menggantikan timer eksekusi lokal (clock.py) dengan layanan cron terkelola yang tetap ada secara independen dari sesi terminal lokal, menangani percobaan ulang otomatis, dan mendukung penjadwalan zona waktu.
Pub/Sub sebagai Lapisan Penghubung Pesan
Menempatkan topik Pub/Sub antara Cloud Scheduler dan Cloud Run memberikan tiga keuntungan arsitektur:
- Ketahanan Pesan: Mengantrekan peristiwa pemicu yang masuk jika layanan Cloud Run melakukan start dingin, di-deploy ulang, atau tidak tersedia untuk sementara.
- Kebijakan Coba Lagi Bawaan: Otomatis mencoba lagi kegagalan pengiriman sementara tanpa memerlukan logika coba lagi tingkat aplikasi kustom.
- Skalabilitas Fan-Out: Memungkinkan beberapa layanan hilir berlangganan ke peristiwa jadwal yang sama tanpa mengonfigurasi ulang penjadwal.

Mengonfigurasi trigger_sources=["pubsub"] akan mendaftarkan endpoint push yang kompatibel dengan payload envelope Pub/Sub standar, yang menerjemahkan pesan masuk secara langsung ke dalam peristiwa pemanggilan agen.
→ agent https://concert-you-xxxx.run.app
→ topic presale-you created
→ iam concert-trigger@your-project... may invoke concert-you
→ push → https://concert-you-xxxx.run.app/trigger/wake
→ schedule 0 3 * * *
Bicaralah dengannya
👉 Buka URL agen di browser. 👉✨ Minta untuk memesan:
Book me 3 tickets for The Midnight Signal in Tokyo. Best seats in the house,
I don't want the cheap ones.
Aplikasi ini akan menanyakan jumlah yang ingin Anda belanjakan
👉✨ Jawab dengan cara Anda berbicara, lalu konfirmasi:
up to 250 a seat for the good ones
Saat ini, proses berjalan diantrekan.
👉🔴 Tekan LEWATI ANTRIAN di panel tempat, sehingga antrean siap saat pesan tiba.
Untuk menjalankannya sesuai jadwal yang sebenarnya, tugas harus diaktifkan — jobs run menolak tugas yang dijeda dengan Job.state must be ENABLED:
gcloud scheduler jobs resume presale-$(gcloud config get-value account | cut -d@ -f1) \
--location=$GOOGLE_CLOUD_REGION
👉 Dalam beberapa detik, agen akan melakukan pembelian dalam batas anggaran yang telah dikonfirmasi dan mengembalikan konfirmasi pesanan secara otomatis.
👉🔴 Periksa panel tempat. Satu pesanan.
Berikut cara membuatnya berjalan setiap 5 menit:
gcloud scheduler jobs update pubsub presale-$(gcloud config get-value account | cut -d@ -f1) \
--location=$GOOGLE_CLOUD_REGION --schedule="*/5 * * * *"
Pembersihan
Hitung dua hal yang di-deploy: tempat, dan agen di sampingnya. Keduanya diskalakan hingga nol, sehingga yang tidak ada aktivitas tidak dikenai biaya. Tetap hapus.
./destroy-agent.sh # service, topic, subscription, scheduler job
./destroy-venue.sh
Perhatikan bahwa perintah tersebut sengaja membiarkan Cloud SQL dan bucket apa adanya, karena keduanya menyimpan data Anda dan tetap menagih. Setelah Anda yakin sudah selesai:
./destroy-agent.sh --all
Semua hal lainnya bersifat lokal. Hapus folder setelah selesai, atau simpan dan jalankan terhadap project Anda sendiri selama yang Anda inginkan.
Agen, alat, callback, dan anggarannya tidak berubah. Dua URI dan satu variabel lingkungan.
11. Panduan Pemecahan Masalah dan Ringkasan Sistem
Perbedaan Lingkungan: Arsitektur Lokal vs. Cloud
Laptop Anda | Di cloud | Apa yang berubah |
| Cloud SQL | string koneksi |
|
| URI |
|
| jalur |
| satu layanan Cloud Run |
|
| Cloud Scheduler → Pub/Sub |
|
Anda, di keyboard |
| default, diganti per permintaan |
Implementasi agen inti, termasuk root_agent, definisi alat, penjaga callback, dan penerapan anggaran tetap tidak berubah sama sekali. Membandingkan solutions/step10_deploy/concert/ dengan Langkah 9 menunjukkan kecocokan byte-demi-byte yang persis, yang memvalidasi cara abstraksi layanan ADK memisahkan konfigurasi infrastruktur dari logika agen.
Jika Anda mengalami masalah
Dapatkan kode kerja lengkap di setiap langkah:
./use-solution.sh N --force # any step. resets memory + sessions + artifacts
./use-solution.sh N --force --keep # code only, leave state alone
Saat ada yang rusak
Gejala | Perbaiki |
| ID salah. |
Model menampilkan error 404 | Alias |
| Sesuatu sudah menggunakan port 8000 atau 8090. |
Agen tidak dapat mencapai lokasi |
|
Antrean tidak pernah maju | Tekan LEWATI ANTRIAN. Pada 1×, antrean tidak akan mencapai bagian depan dengan sendirinya |
Tiket antrean kedua akan muncul di panel | Anda menjawab di kotak chat, bukan di kotak |
Dropdown kosong, atau aplikasi tidak akan dimuat |
|
| Diharapkan. |
Semuanya aneh | Tekan RESET TEMPAT, lalu |
File memori penuh dengan pemesanan duplikat |
|
|
|
Sesi Cloud Shell berakhir | Mulai |