Skip to content

4.3 Workflow State

Tujuan bab

Setelah bab ini kamu bisa:

  • Menjelaskan perbedaan antara state dan aliran input/output antar langkah
  • Mendefinisikan stateSchema di tingkat workflow maupun tingkat langkah
  • Membaca dan memperbarui state dengan state dan setState
  • Menetapkan nilai awal lewat initialState
  • Memutuskan kapan sebuah nilai layak masuk state, dan kapan sebaiknya tetap lewat schema langkah

Prasyarat

Masalah mengoper data lewat setiap langkah

Bayangkan workflow tujuh langkah. Langkah pertama menghitung ID transaksi, dan langkah ketujuh membutuhkannya untuk mencatat hasil akhir.

Dengan hanya mengandalkan aliran input/output, ID itu harus ikut menumpang di outputSchema langkah 1, lalu di inputSchema dan outputSchema langkah 2, 3, 4, 5, dan 6 — meskipun tak satu pun dari mereka memakainya.

❌ ID menumpang di seluruh rantai

step1 → { hasil, transaksiId }
step2 → { hasil, transaksiId }   ← tidak memakainya
step3 → { hasil, transaksiId }   ← tidak memakainya
...
step7 → memakai transaksiId

Hasilnya: schema yang penuh field tidak relevan, dan langkah-langkah yang kehilangan sifat mandirinya. Menghapus langkah 4 sekarang merusak langkah 7.

Workflow state menyelesaikan ini dengan menyediakan penyimpanan bersama yang bisa dibaca dan ditulis semua langkah, tanpa harus melewati schema masing-masing.

✅ ID disimpan di state

        ┌──────────── state: { transaksiId } ────────────┐
        │                                                 │
     step1 → step2 → step3 → step4 → step5 → step6 → step7
     tulis                                          baca

State versus input/output

Dokumentasi membedakan keduanya dengan tegas, dan pembedaan ini layak dihafal:

Input/output langkahState
Arah aliranBerurutan, satu langkah ke langkah berikutnyaBersama, semua langkah bisa baca-tulis
CakupanAntara dua langkah bertetanggaSeluruh eksekusi workflow
Diakses lewatinputData dan nilai kembalianstate dan setState
Bertahan saat suspend/resumeMelalui snapshotYa, ikut bertahan

Poin terakhir penting dan akan terpakai di 4.5: state bertahan melewati siklus berhenti dan lanjut.

Bentuk dasarnya

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

const step1 = createStep({
  id: 'step-1',
  inputSchema: z.object({ workflowInput: z.string() }),
  outputSchema: z.object({ step1Output: z.string() }),
  stateSchema: z.object({ sharedCounter: z.number() }),
  execute: async ({ inputData, state, setState }) => {
    // inputData berasal dari input workflow atau keluaran langkah sebelumnya
    console.log(inputData.workflowInput)

    // state adalah penyimpanan bersama seluruh workflow
    console.log(state.sharedCounter)

    // Perbarui state untuk langkah-langkah berikutnya
    await setState({ sharedCounter: state.sharedCounter + 1 })

    // Nilai kembalian mengalir ke inputData langkah berikutnya
    return { step1Output: 'processed' }
  },
})

Tiga hal baru dibanding step biasa:

  • stateSchema pada definisi step — mendeklarasikan bagian state yang dipakai step ini
  • state pada parameter execute — untuk membaca
  • setState pada parameter execute — untuk menulis, dan perlu di-await

Schema induk dan bagiannya

Ini pola yang membuat state tetap terkendali di workflow besar.

Workflow mendeklarasikan schema state lengkap; tiap langkah mendeklarasikan hanya bagian yang ia butuhkan.

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

const step1 = createStep({
  id: 'step-1',
  inputSchema: z.object({ message: z.string() }),
  outputSchema: z.object({ formatted: z.string() }),
  stateSchema: z.object({
    processedItems: z.array(z.string()),
  }),
  execute: async ({ inputData, state, setState }) => {
    const { message } = inputData
    const { processedItems } = state

    await setState({
      processedItems: [...processedItems, 'item-1', 'item-2'],
    })

    return { formatted: message.toUpperCase() }
  },
})

const step2 = createStep({
  id: 'step-2',
  inputSchema: z.object({ formatted: z.string() }),
  outputSchema: z.object({ emphasized: z.string() }),
  stateSchema: z.object({
    metadata: z.object({
      processedBy: z.string(),
    }),
  }),
  execute: async ({ inputData, state }) => {
    const { formatted } = inputData
    const { metadata } = state

    return { emphasized: `${formatted}!! ${metadata.processedBy}` }
  },
})

export const stateWorkflow = createWorkflow({
  id: 'state-workflow',
  inputSchema: z.object({ message: z.string() }),
  outputSchema: z.object({ emphasized: z.string() }),
  stateSchema: z.object({
    processedItems: z.array(z.string()),
    metadata: z.object({
      processedBy: z.string(),
    }),
  }),
})
  .then(step1)
  .then(step2)
  .commit()

Perhatikan bahwa step1 hanya mendeklarasikan processedItems dan step2 hanya metadata — meskipun workflow mendeklarasikan keduanya. Ini bukan sekadar kerapian: schema langkah menjadi dokumentasi tentang apa yang disentuh langkah itu. Saat kamu membaca step2 enam bulan kemudian, kamu langsung tahu ia tidak akan mengubah processedItems.

Nilai awal

State harus punya nilai awal, dan itu diberikan saat memulai run:

ts
// run.mjs
import { mastra } from './src/mastra/index.ts'

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

const result = await run.start({
  inputData: { message: 'halo' },
  initialState: {
    processedItems: [],
    metadata: { processedBy: 'sistem-batch' },
  },
})

initialState diberikan berdampingan dengan inputData, dan bentuknya harus sesuai stateSchema workflow.

Pola yang berguna: initialState adalah tempat yang tepat untuk konfigurasi seluruh workflow — mode operasi, identitas pemanggil, feature flag. Nilai seperti itu relevan untuk banyak langkah tapi bukan bagian dari data yang sedang diproses.

Kapan memakai state, kapan tidak

State memudahkan, dan karena itu mudah disalahgunakan. Workflow yang seluruh datanya lewat state kehilangan hal yang membuat workflow bertipe itu berharga: sambungan antar langkah yang diverifikasi compiler.

Pakai state untukTetap pakai input/output untuk
Konfigurasi seluruh workflowData yang sedang diproses
Akumulasi hasil dari beberapa langkahKeluaran satu langkah untuk langkah berikutnya
Pelacakan progresApa pun yang dipakai hanya oleh langkah tetangga
Nilai yang dipakai langkah berjauhan

Pertanyaan pemutus: apakah nilai ini dipakai oleh langkah yang tidak bertetangga? Kalau tidak, biarkan lewat input/output — sambungan yang diverifikasi tipe lebih berharga daripada kenyamanan sesaat.

Kesalahan umum

Gejala: Nilai state yang diperbarui di satu langkah terbaca sebagai nilai lama di langkah berikutnya. Penyebab: setState tidak di-await. Ia mengembalikan promise, dan tanpa await langkah bisa selesai sebelum pembaruan benar-benar tersimpan. Perbaikan: Selalu tulis await setState({ ... }).

Gejala: Error tipe saat mengakses sebuah field dari state di dalam sebuah langkah, padahal field itu jelas ada di stateSchema workflow. Penyebab: Langkah tersebut tidak mendeklarasikan field itu di stateSchema miliknya sendiri. Tiap langkah hanya melihat bagian state yang ia deklarasikan. Perbaikan: Tambahkan field yang dibutuhkan ke stateSchema langkah itu. Kalau kamu mendapati diri menambahkan hampir semua field ke hampir semua langkah, pertimbangkan apakah data itu memang seharusnya lewat state.

Coba sendiri

Tantangan: Bangun audit-workflow yang memproses berkas unggahan dan mengumpulkan jejak audit sepanjang jalan.

Alur yang diminta — lima langkah berurutan: validasi format, pindai keamanan, ekstrak metadata, kompresi, dan simpan.

Ketentuan:

  1. Definisikan stateSchema di tingkat workflow yang memuat: jejakAudit (array string), mulaiPada (string), dan mode (enum 'cepat' atau 'teliti')
  2. Setiap langkah menambahkan satu entri ke jejakAudit — tapi hanya mendeklarasikan bagian state yang benar-benar ia pakai
  3. Langkah "pindai keamanan" dan "kompresi" membaca mode dan berperilaku berbeda untuk 'cepat' dan 'teliti'
  4. mulaiPada dan mode diberikan lewat initialState, bukan lewat inputData
  5. Langkah terakhir mengembalikan seluruh jejakAudit sebagai bagian dari keluaran workflow
  6. Jalankan dua kali dengan mode berbeda dan bandingkan jejaknya

Checklist penerimaan:

  • [ ] jejakAudit di akhir berisi lima entri, satu dari tiap langkah, dengan urutan yang benar
  • [ ] Tidak ada satu pun langkah yang mendeklarasikan field state yang tidak ia pakai
  • [ ] Setiap setState di-await
  • [ ] Kedua mode menghasilkan jejak yang berbeda pada dua langkah yang relevan
  • [ ] mode dan mulaiPada tidak muncul di inputSchema satu pun langkah
  • [ ] Kamu bisa menjelaskan kenapa jejakAudit layak di state, sementara metadata hasil ekstraksi sebaiknya lewat input/output

Petunjuk: Checklist terakhir menguji pemahaman inti. jejakAudit dikumpulkan oleh semua langkah dan dipakai di akhir — itu persis kasus untuk state. Metadata hasil ekstraksi hanya diteruskan dari langkah ekstraksi ke langkah kompresi yang bertetangga — sambungan langsung lebih tepat, dan compiler akan memverifikasinya untukmu.

Ikhtisar

  • State adalah penyimpanan bersama yang bisa dibaca dan ditulis semua langkah, tanpa harus menumpang di schema tiap langkah di antaranya.
  • Diakses lewat state untuk membaca dan setState untuk menulis; setState harus di-await.
  • Workflow mendeklarasikan stateSchema lengkap; tiap langkah mendeklarasikan hanya bagian yang ia butuhkan — dan itu berfungsi sebagai dokumentasi tentang apa yang disentuh langkah tersebut.
  • Nilai awal diberikan lewat initialState saat memulai run, berdampingan dengan inputData.
  • State bertahan melewati suspend dan resume.
  • Pakai state untuk konfigurasi, akumulasi, dan nilai yang dipakai langkah berjauhan. Data yang mengalir antar langkah bertetangga tetap lewat input/output, supaya sambungannya diverifikasi compiler.

Lanjut ke mana

Semua langkah sejauh ini berisi logika biasa. Bab berikutnya memasukkan kembali kecerdasan ke dalam alur yang deterministik — memanggil agent dan tool dari dalam workflow: 4.4 Agent & Tool di Workflow.

Materi belajar mandiri. Bukan dokumentasi resmi Mastra.