Skip to content

3.2 Working Memory

Tujuan bab

Setelah bab ini kamu bisa:

  • Menjelaskan perbedaan working memory dan message history, serta kapan masing-masing dipakai
  • Mengaktifkan working memory dan merancang template yang berguna
  • Memilih antara lingkup resource dan thread, serta memahami akibat memilih salah
  • Menjelaskan kenapa working memory berupa teks Markdown, bukan struktur data
  • Menghindari kebocoran data antar pengguna akibat lingkup yang salah

Prasyarat

Masalah yang tidak diselesaikan message history

Message history menyimpan sepuluh, dua puluh, atau lima puluh pesan terakhir. Sekarang bayangkan percakapan ini:

Pesan 1   : "Halo, saya Sari. Saya alergi kacang."
Pesan 2-40: (percakapan panjang soal hal lain)
Pesan 41  : "Rekomendasikan restoran untuk makan malam."

Dengan lastMessages: 20, pesan pertama sudah lama keluar dari jendela. Agent tidak lagi tahu soal alergi itu, dan bisa merekomendasikan restoran Thailand dengan senang hati.

Menaikkan lastMessages menjadi 50 memang menyelesaikan kasus ini — tapi dengan cara yang salah. Kamu membayar 50 pesan di setiap panggilan, hanya untuk mempertahankan satu fakta penting yang muncul sekali.

Working memory menyelesaikannya dengan memisahkan fakta dari percakapan. Alih-alih berharap fakta penting kebetulan masih ada di jendela pesan terakhir, agent memelihara catatan terpisah yang selalu ikut serta.

┌─────────────────────────────────────┐
│  Message history                    │  ← bergulir, yang lama hilang
│  20 pesan terakhir                  │
└─────────────────────────────────────┘
┌─────────────────────────────────────┐
│  Working memory                     │  ← bertahan, selalu ikut
│  # Profil Pengguna                  │
│  - Nama: Sari                       │
│  - Alergi: kacang                   │
│  - Preferensi: masakan Jepang       │
└─────────────────────────────────────┘

Mengaktifkannya

Bentuk paling minimal:

ts
// src/mastra/agents/asisten-agent.ts
import { Agent } from '@mastra/core/agent'
import { Memory } from '@mastra/memory'

export const asistenAgent = new Agent({
  id: 'personal-assistant',
  name: 'PersonalAssistant',
  instructions: 'Kamu asisten pribadi yang membantu.',
  model: 'openai/gpt-5.6-sol',
  memory: new Memory({
    options: {
      workingMemory: {
        enabled: true,
      },
    },
  }),
})

Dengan itu, agent mulai memelihara catatannya sendiri. Tapi tanpa arahan, ia akan memutuskan sendiri apa yang layak dicatat — dan hasilnya tidak konsisten. Untuk itu ada template.

Template: memberi bentuk pada ingatan

Working memory adalah blok teks Markdown yang bisa diperbarui agent dari waktu ke waktu. template memberi kerangka awalnya:

ts
// src/mastra/agents/asisten-agent.ts
import { Agent } from '@mastra/core/agent'
import { Memory } from '@mastra/memory'

export const asistenAgent = new Agent({
  id: 'personal-assistant',
  name: 'PersonalAssistant',
  instructions: 'Kamu asisten pribadi yang membantu.',
  model: 'openai/gpt-5.6-sol',
  memory: new Memory({
    options: {
      workingMemory: {
        enabled: true,
        scope: 'resource',
        template: `# Profil Pengguna
- **Nama**:
- **Lokasi**:
- **Minat**:
- **Preferensi**:
- **Tujuan jangka panjang**:
`,
      },
    },
  }),
})

Template bukan sekadar tampilan. Ia memberitahu agent apa yang layak diperhatikan. Field kosong berfungsi seperti formulir: agent cenderung mengisinya saat informasi itu muncul dalam percakapan, dan cenderung tidak mencatat hal di luar kerangka.

Kenapa Markdown, bukan JSON?

Pertanyaan yang wajar, mengingat Mastra memakai Zod di mana-mana. Alasannya: working memory ditulis dan dibaca oleh model, bukan oleh kodemu.

Model bekerja dengan teks. Format Markdown longgar dan memaafkan — kalau seorang pengguna menyebutkan sesuatu yang tidak ada field-nya, agent bisa menambahkan baris baru tanpa merusak apa pun. Struktur kaku justru akan membuat agent membuang informasi yang tidak muat.

Konsekuensinya untuk desainmu: rancang template untuk dibaca model, bukan untuk diurai program. Kalau kamu butuh data terstruktur yang dijamin bentuknya, tempatnya bukan di sini — pakai database biasa, dan berikan aksesnya lewat tool.

Merancang template yang berguna

PrinsipContoh burukContoh baik
Spesifik pada domain- Catatan:- Alergi & pantangan makanan:
Field yang benar-benar dipakai15 field "untuk jaga-jaga"5 field yang memang mempengaruhi jawaban
Nama yang tidak ambigu- Level:- Tingkat keahlian memasak (pemula/mahir):
Cukup ringkasTiga halamanSatu layar

Prinsip terakhir penting karena alasan yang sama seperti instructions: working memory ikut dikirim di setiap panggilan model.

Lingkup: resource atau thread

Ini keputusan terpenting di bab ini, dan yang paling berisiko kalau salah.

LingkupIngatan bertahanCocok untuk
resource (default)Lintas semua thread milik pengguna yang samaAsisten pribadi, bot layanan pelanggan, aplikasi edukasi
threadHanya di dalam satu threadSesi yang harus terisolasi, konteks per kasus
scope: 'resource'                      scope: 'thread'

user-8842                              user-8842
  ├── thread A ─┐                        ├── thread A ── [ingatan A]
  ├── thread B ─┼─ [ingatan bersama]     ├── thread B ── [ingatan B]
  └── thread C ─┘                        └── thread C ── [ingatan C]

Dokumentasi memberi peringatan yang layak digarisbawahi: berpindah antar lingkup berarti agent tidak akan melihat ingatan dari lingkup yang lain. Ingatan berlingkup thread benar-benar terpisah dari ingatan berlingkup resource. Kalau kamu mengubah scope pada aplikasi yang sudah berjalan, ingatan yang sudah terkumpul tidak ikut berpindah.

Kapan memilih thread

Default resource cocok untuk sebagian besar kasus, tapi ada situasi di mana justru berbahaya:

  • Kios atau perangkat bersama — satu resource dipakai banyak orang bergantian, sehingga preferensi orang sebelumnya terbawa
  • Konteks per kasus — agent support di mana tiap tiket harus dinilai sendiri tanpa terpengaruh tiket lain
  • Data sensitif yang tidak boleh menyeberang sesi — misalnya konsultasi yang harus dianggap selesai saat sesi berakhir

Lingkup resource memperbesar akibat kesalahan resource

Di 3.1 kita sudah menyebut bahaya memakai nilai resource yang sama untuk semua pengguna. Dengan working memory berlingkup resource, akibatnya jauh lebih parah: profil, preferensi, dan fakta pribadi satu pengguna akan langsung terbaca oleh pengguna lain.

Pastikan resource berasal dari sesi terautentikasi sebelum mengaktifkan working memory di aplikasi yang dipakai lebih dari satu orang.

Melihatnya bekerja

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

const agent = mastra.getAgentById('personal-assistant')

await agent.generate('Halo, saya Sari. Saya alergi kacang dan suka masakan Jepang.', {
  memory: { resource: 'user-8842', thread: 'sesi-1' },
})

// Thread berbeda, resource sama
const hasil = await agent.generate('Rekomendasikan menu makan malam untuk saya.', {
  memory: { resource: 'user-8842', thread: 'sesi-2' },
})

console.log(hasil.text)

Dengan scope: 'resource', panggilan kedua tetap memperhitungkan alergi dan preferensi — meskipun berada di thread yang berbeda. Inilah perbedaan yang tidak bisa dicapai message history, berapa pun besar lastMessages-nya.

Di Studio, isi working memory bisa dilihat di panel samping saat percakapan berlangsung. Ini cara tercepat memeriksa apakah agent benar-benar mencatat apa yang kamu harapkan.

Working memory bersama observational memory

Satu hal yang berguna diketahui sekarang dan akan masuk akal penuh di 3.4: kalau observational memory aktif, opsi observationalMemory.observation.manageWorkingMemory memungkinkan sistem itu yang memperbarui working memory untuk agent.

Artinya kedua mekanisme tidak bersaing — yang satu bisa memelihara yang lain.

Kesalahan umum

Gejala: Working memory aktif tapi isinya tetap kosong, atau terisi hal-hal yang tidak relevan. Penyebab: Tidak ada template, sehingga agent tidak punya acuan tentang apa yang layak dicatat. Atau template-nya memakai nama field yang ambigu, sehingga agent tidak yakin informasi mana yang masuk ke mana. Perbaikan: Tulis template dengan field yang spesifik pada domainmu dan namanya tidak bisa ditafsirkan dua cara. Kalau sebuah field tetap kosong padahal informasinya jelas disebutkan pengguna, perjelas nama field-nya.

Gejala: Pengguna melaporkan bahwa agent menyebut preferensi yang bukan miliknya. Penyebab: Nilai resource tidak unik per pengguna — misalnya diisi nilai tetap, atau diambil dari input klien yang bisa diubah. Dengan lingkup resource (default), semua yang berbagi nilai itu berbagi ingatan. Perbaikan: Pastikan resource berasal dari ID pengguna pada sesi terautentikasi di sisi server. Ini masalah keamanan, bukan sekadar bug fungsional.

Coba sendiri

Tantangan: Bangun tutor-agent, asisten belajar bahasa Inggris yang menyesuaikan diri dengan tiap murid dari waktu ke waktu.

Ketentuan:

  1. Aktifkan working memory dengan scope: 'resource' dan template yang memuat minimal: nama, tingkat kemampuan, topik yang sudah dikuasai, jenis kesalahan yang sering diulang, dan tujuan belajar
  2. Setiap field punya nama yang tidak ambigu — bayangkan model harus mengisinya tanpa bertanya
  3. Jalankan percakapan empat giliran di thread sesi-1, di mana murid menyebutkan tujuannya dan membuat setidaknya dua kesalahan tata bahasa yang serupa
  4. Buka thread baru sesi-2 dengan resource yang sama, lalu tanyakan "menurutmu apa yang harus saya latih hari ini?"
  5. Buat versi kedua agent yang identik tapi scope: 'thread', ulangi langkah 3 dan 4, lalu bandingkan
  6. Periksa isi working memory di Studio setelah langkah 3

Checklist penerimaan:

  • [ ] Setelah langkah 3, working memory di Studio berisi nama murid, tujuan, dan pola kesalahannya — bukan sekadar ringkasan percakapan
  • [ ] Pada versi scope: 'resource', jawaban di langkah 4 merujuk pada kesalahan yang dibuat di sesi-1
  • [ ] Pada versi scope: 'thread', jawaban di langkah 4 tidak merujuk apa pun dari sesi-1
  • [ ] Kamu bisa menjelaskan kapan scope: 'thread' justru pilihan yang benar untuk aplikasi semacam ini
  • [ ] Tidak ada field template yang tetap kosong padahal informasinya jelas muncul dalam percakapan

Petunjuk: Untuk poin terakhir, field "jenis kesalahan yang sering diulang" biasanya yang paling sering gagal terisi. Model perlu tahu bahwa ia diharapkan menyimpulkan pola, bukan menunggu murid menyebutkannya. Nama field yang lebih eksplisit membantu — misalnya "Pola kesalahan yang kamu amati (isi sendiri berdasarkan jawaban murid)".

Ikhtisar

  • Working memory menyimpan fakta yang bertahan, terpisah dari percakapan yang bergulir. Ia menyelesaikan masalah yang tidak bisa diselesaikan dengan menaikkan lastMessages.
  • Isinya adalah blok teks Markdown yang diperbarui agent, bukan struktur data — karena pembaca dan penulisnya adalah model.
  • template memberi kerangka dan menentukan apa yang layak dicatat. Tanpa itu, hasilnya tidak konsisten.
  • Dua lingkup: resource (default, lintas semua thread pengguna) dan thread (terisolasi per percakapan). Keduanya tidak saling melihat, dan berpindah lingkup tidak memindahkan ingatan yang sudah ada.
  • Lingkup resource memperbesar akibat kesalahan pada nilai resource — profil pribadi bisa terbaca pengguna lain. Ambil dari sesi terautentikasi.
  • Working memory ikut dikirim di setiap panggilan model, jadi jaga tetap ringkas.

Lanjut ke mana

Sekarang agent mengingat percakapan terkini dan fakta penting tentang penggunanya. Yang belum: kemampuan menemukan kembali sesuatu yang dibicarakan jauh di masa lalu, saat topiknya muncul lagi — 3.3 Semantic Recall.

Materi belajar mandiri. Bukan dokumentasi resmi Mastra.