Skip to content

4.5 Suspend & Resume

Tujuan bab

Setelah bab ini kamu bisa:

  • Menghentikan workflow di sebuah langkah dengan suspend()
  • Melanjutkannya dengan resume() dan resumeData
  • Menjelaskan peran resumeSchema dan pola "cek dulu, baru suspend"
  • Melanjutkan run dari proses lain hanya dengan runId
  • Menjelaskan kenapa fitur ini memerlukan storage dan apa yang disimpan

Prasyarat

Kemampuan yang membedakan workflow

Semua yang dibahas sampai sini — percabangan, paralel, state — bisa kamu tulis sendiri dengan fungsi TypeScript biasa kalau mau bersusah payah. Yang berikut ini tidak.

Sebuah workflow bisa berhenti di tengah langkah, dan dilanjutkan tiga hari kemudian, dari proses yang berbeda, setelah server sempat di-restart dua kali — dan melanjutkan persis dari titik ia berhenti.

Kegunaannya menurut dokumentasi: mengumpulkan data tambahan, menunggu callback API, meredam operasi yang mahal, atau meminta masukan manusia.

Yang membuatnya mungkin: saat berhenti, Mastra menyimpan keadaan eksekusi sebagai snapshot di storage yang kamu konfigurasi. Snapshot itu bertahan melewati deployment dan restart aplikasi.

Menghentikan dengan suspend()

ts
// src/mastra/workflows/persetujuan-workflow.ts
import { createWorkflow, createStep } from '@mastra/core/workflows'
import { z } from 'zod'

export const step1 = createStep({
  id: 'step-1',
  inputSchema: z.object({
    userEmail: z.string(),
  }),
  outputSchema: z.object({
    output: z.string(),
  }),
  resumeSchema: z.object({
    approved: z.boolean(),
  }),
  execute: async ({ inputData, resumeData, suspend }) => {
    const { userEmail } = inputData
    const { approved } = resumeData ?? {}

    if (!approved) {
      return await suspend({})
    }

    return {
      output: `Email terkirim ke ${userEmail}`,
    }
  },
})

export const persetujuanWorkflow = createWorkflow({
  id: 'persetujuan-workflow',
  inputSchema: z.object({ userEmail: z.string() }),
  outputSchema: z.object({ output: z.string() }),
})
  .then(step1)
  .commit()

Ada satu pola di sini yang harus benar-benar dipahami, karena bentuknya tidak langsung intuitif.

Pola "cek dulu, baru suspend"

Perhatikan bahwa execute dijalankan dua kali: sekali saat workflow pertama kali sampai di langkah ini, dan sekali lagi saat dilanjutkan.

Eksekusi pertama:
  resumeData = undefined
  → approved = undefined
  → !approved bernilai true
  → suspend()  ← berhenti di sini

... menunggu ...

Eksekusi kedua (setelah resume):
  resumeData = { approved: true }
  → approved = true
  → !approved bernilai false
  → lanjut ke baris berikutnya

Karena itu resumeData ?? {} diperlukan — pada eksekusi pertama, resumeData belum ada.

Dokumentasi merangkumnya begini: kalau kondisinya belum terpenuhi, workflow berhenti dan mengembalikan suspend(); kalau sudah terpenuhi, ia melanjutkan sisa logika di langkah tersebut.

Cara memikirkan ini yang membantu: langkah yang bisa dijeda adalah langkah yang menanyakan "apakah saya sudah punya yang saya butuhkan?" di baris pertamanya. Kalau belum, ia tidur. Kalau sudah, ia bekerja.

resumeSchema

resumeSchema mendeklarasikan bentuk data yang diharapkan saat workflow dilanjutkan — mirip inputSchema, tapi untuk masukan yang datang belakangan. Ia memberi tipe pada resumeData dan memvalidasi apa yang dikirim pemanggil.

Melanjutkan dengan resume()

ts
// run.mjs
import { mastra } from './src/mastra/index.ts'
import { step1 } from './src/mastra/workflows/persetujuan-workflow.ts'

const workflow = mastra.getWorkflow('persetujuanWorkflow')
const run = await workflow.createRun()

await run.start({
  inputData: {
    userEmail: 'alex@example.com',
  },
})

// ... nanti, setelah manusia menyetujui ...

const result = await run.resume({
  step: step1,
  resumeData: { approved: true },
})

Tiga cara menyebut langkah yang dilanjutkan

Dokumentasi menyediakan tiga bentuk, masing-masing dengan kompromi berbeda:

ts
// 1. Objek step — paling aman secara tipe
await run.resume({ step: step1, resumeData: { approved: true } })

// 2. ID step sebagai string — lebih luwes
await run.resume({ step: 'step-1', resumeData: { approved: true } })

// 3. Tanpa step sama sekali
await run.resume({ resumeData: { approved: true } })

Bentuk pertama memberi keamanan tipe penuh untuk resumeData — editor tahu persis bentuk yang diharapkan.

Bentuk kedua berguna saat ID-nya datang dari masukan pengguna atau dari basis data, misalnya pada endpoint HTTP yang menerima ID langkah sebagai parameter.

Bentuk ketiga hanya boleh dipakai kalau hanya satu langkah yang sedang tertunda — Mastra akan melanjutkan langkah terakhir yang tersuspend.

Melanjutkan dari proses lain

Ini bagian yang membuat fitur ini benar-benar berguna di produksi. Manusia yang menyetujui tidak akan menekan tombol di proses Node yang sama yang memulai workflow — ia akan mengklik tautan di email, yang memanggil endpoint HTTP di server yang mungkin sudah di-restart.

Yang dibutuhkan hanyalah runId:

ts
const workflow = mastra.getWorkflow('persetujuanWorkflow')
const run = await workflow.createRun({ runId: '123' })

const stream = run.resume({
  resumeData: { approved: true },
})

createRun({ runId }) tidak membuat eksekusi baru — ia mengambil kembali eksekusi yang sudah ada. Snapshot-nya dimuat dari storage, dan workflow melanjutkan dari titik ia berhenti.

Konsekuensi praktis untuk desain aplikasimu: simpan runId di tempat yang bisa kamu ambil lagi. Di baris basis data pengajuan, di tautan email, di mana pun yang masuk akal. Tanpa runId, workflow yang tertunda tidak bisa ditemukan lagi.

Apa yang bertahan

Yang tersimpan dalam snapshot mencakup keadaan eksekusi — langkah mana yang sudah selesai, hasilnya apa, dan workflow state seperti yang dibahas di 4.3.

Snapshot disimpan di storage provider yang kamu konfigurasi. Tanpa storage, fitur ini tidak bisa bekerja — sama seperti persetujuan tool di 2.3, yang memang mekanisme bersaudara.

Hubungannya dengan persetujuan tool

Kamu sudah bertemu konsep serupa dua kali. Ini perbandingannya:

Tool approval (2.3)Workflow suspend
Yang berhentiSatu pemanggilan toolSatu langkah workflow
Dipicu olehrequireApproval / requireToolApprovalKode suspend() di dalam step
Yang dilanjutkanapproveToolCall() / declineToolCall()resume() dengan resumeData
Data yang bisa dikirim balikSetuju atau tolakObjek apa pun sesuai resumeSchema
Cocok untukMenjaga tindakan berisikoAlur persetujuan yang punya tahapan

Perbedaan yang paling menentukan pilihan: workflow suspend bisa menerima data, bukan hanya keputusan biner. Kalau manusia perlu mengisi formulir sebelum proses lanjut, itu pekerjaan workflow suspend.

Kesalahan umum

Gejala: Workflow langsung berjalan sampai selesai tanpa pernah berhenti, padahal suspend() sudah ditulis. Penyebab: Kondisi sebelum suspend() sudah terpenuhi sejak eksekusi pertama. Misalnya resumeSchema punya field dengan .default(true), sehingga approved bernilai true bahkan sebelum ada yang menyetujui. Perbaikan: Pastikan kondisi jaganya bernilai "belum siap" saat resumeData masih undefined. Pola const { approved } = resumeData ?? {} diikuti if (!approved) bekerja karena undefined bersifat falsy.

Gejala: resume() gagal karena run tidak ditemukan, padahal workflow jelas tertunda. Penyebab: Salah satu dari dua. runId tidak disimpan, sehingga proses yang melanjutkan membuat run baru alih-alih mengambil yang lama. Atau storage tidak terkonfigurasi, sehingga snapshot tidak pernah benar-benar tersimpan. Perbaikan: Simpan runId saat workflow dimulai, dan pakai createRun({ runId }) — bukan createRun() — di sisi yang melanjutkan. Pastikan storage terpasang di instance Mastra.

Coba sendiri

Tantangan: Bangun cuti-workflow, alur pengajuan cuti karyawan dengan dua titik persetujuan berurutan.

Alur yang diminta:

  1. validasi — periksa sisa kuota cuti karyawan
  2. persetujuan-atasan — berhenti, tunggu keputusan atasan langsung
  3. persetujuan-hr — berhenti lagi, tapi hanya kalau cutinya lebih dari 5 hari
  4. finalisasi — catat hasil dan susun pesan konfirmasi

Ketentuan:

  1. Kedua langkah persetujuan memakai resumeSchema yang menerima lebih dari sekadar boolean — sertakan catatan bertipe string dan disetujuiOleh
  2. Langkah 3 memakai .branch() supaya hanya berjalan untuk pengajuan panjang
  3. Simpan seluruh keputusan ke workflow state, dan pastikan state itu masih utuh setelah kedua suspend
  4. Tulis dua skrip terpisah: satu yang memulai workflow dan mencetak runId, satu lagi yang melanjutkannya hanya dengan runId itu
  5. Antara menjalankan kedua skrip, matikan proses Node sepenuhnya
  6. Uji tiga skenario: cuti 3 hari yang disetujui, cuti 10 hari yang disetujui keduanya, dan cuti 10 hari yang ditolak HR

Checklist penerimaan:

  • [ ] Skrip kedua berhasil melanjutkan workflow setelah proses pertama dimatikan — ini bukti utama bahwa snapshot benar-benar persisten
  • [ ] Cuti 3 hari hanya berhenti satu kali, bukan dua
  • [ ] catatan dan disetujuiOleh dari persetujuan pertama masih terbaca saat finalisasi
  • [ ] Workflow state utuh setelah kedua siklus suspend/resume
  • [ ] Kamu bisa menjelaskan kenapa kasus ini lebih tepat memakai workflow suspend daripada tool approval
  • [ ] Kamu menyimpan runId di suatu tempat yang realistis, bukan menyalinnya manual dari terminal

Petunjuk: Ketentuan 5 adalah inti latihan ini, dan sering "dilewati" tanpa sadar karena orang menjalankan kedua skrip dalam satu sesi terminal yang sama. Matikan benar-benar prosesnya. Kalau workflow tetap bisa dilanjutkan setelah itu, kamu baru membuktikan bahwa snapshot bekerja seperti yang dijanjikan — dan itu kemampuan yang tidak dimiliki fungsi TypeScript biasa.

Ikhtisar

  • suspend() menghentikan workflow di sebuah langkah dan menyimpan keadaannya sebagai snapshot di storage — bertahan melewati restart dan deployment.
  • Pola yang dipakai: cek dulu, baru suspend. execute dijalankan dua kali, dan resumeData ?? {} menangani eksekusi pertama saat data itu belum ada.
  • resumeSchema mendeklarasikan bentuk data yang datang belakangan, dan memberi tipe pada resumeData.
  • resume() menerima step sebagai objek (paling aman secara tipe), sebagai string ID (paling luwes), atau tanpa step sama sekali bila hanya satu langkah yang tertunda.
  • createRun({ runId }) mengambil kembali eksekusi yang sudah ada — inilah cara melanjutkan dari proses lain. Simpan runId di tempat yang bisa ditemukan lagi.
  • Workflow state ikut bertahan melewati siklus suspend dan resume.
  • Dibanding tool approval, workflow suspend bisa menerima data, bukan hanya keputusan setuju atau tolak.

Lanjut ke mana

Bagian 4 selesai — dan dengan itu, seluruh fondasi Mastra. Agent-mu bisa berpikir, bertindak, mengingat, dan kini bekerja dalam alur yang kamu kendalikan. Lima bagian berikutnya memperluas jangkauannya, dimulai dengan memberinya pengetahuan yang tidak ada di dalam model: Bagian 5 — RAG.

Materi belajar mandiri. Bukan dokumentasi resmi Mastra.