Episode ini membahas sisi pengguna: skenario multi-user dengan perilaku per user, persona dan role-based access yang membatasi kemampuan agent, hingga pertimbangan privasi untuk data pengguna mulai dari PII, retensi data, hingga isolasi antar session.

Di episode 12 agent kalian sudah aman di infrastruktur: secrets tersimpan eksternal, traffic lewat HTTPS, dan webhook diverifikasi. Tapi siapa yang memakai agent itu? Sampai sekarang kita menganggap ada satu pemakai yang sama di setiap percakapan. Episode 13 ini menghancurkan asumsi itu: kalian akan belajar skenario multi-user, perilaku agent yang menyesuaikan pengguna, persona dan role-based access, serta privasi data pengguna.
Roadmap episode ini: memetakan skenario multi-user dan state per user, mendefinisikan persona beserta kontrol akses berbasis role, lalu menata privasi data mulai dari PII hingga retensi.
Agent produksi jarang dipakai satu orang. Bayangkan satu agent "checkout assistant" dipakai oleh semua customer, atau satu agent "ops" dipakai seluruh tim engineering. Setiap user punya konteks, preferensi, dan riwayat yang berbeda — dan kalian tidak boleh mencampurnya.
Kunci dasarnya: setiap turn harus tahu siapa pemanggilnya. Identitas ini jadi bagian dari context agent, dipakai untuk mengunci session, memory, dan hasil tool:
userId, bukan satu key global.userId dari konteks, bukan dari asumsi global.Alur autentikasi di depan agent terlihat seperti ini:
export function resolveUser(req) {
const token = req.headers.authorization?.replace("Bearer ", "");
const claims = verifyJwt(token);
return {
id: claims.sub,
role: claims.role,
sessionKey: `session:${claims.sub}`,
preferredLang: claims.locale ?? "id",
};
}Token di sini dikeluarkan oleh identity provider (Keycloak, Authentik, Authelia — lihat series SSO di blog ini), dan agent mempercayai klaim di dalamnya. Agent sendiri tidak perlu mengelola password; tugasnya hanya memetakan klaim ke perilaku.
Setelah identitas jelas, agent bisa berperilaku berbeda antar user tanpa mengubah kode. Beberapa hal yang bisa dibedakan:
Contoh implementasinya adalah menyuntikkan profil user ke system prompt saat membangun prompt setiap turn:
export function buildSystemPrompt(user, persona) {
return [
persona.systemPrompt,
`Kamu sedang melayani ${user.id} (role: ${user.role}).`,
"Gunakan bahasa Indonesia kecuali user bicara bahasa lain.",
user.frequentTopics?.length
? `User sering bertanya soal: ${user.frequentTopics.join(", ")}.`
: "User baru pertama kali; jelaskan lebih detail.",
].join("\n");
}Yang harus dijaga: personalisasi tidak boleh membuat agent membocorkan data antar user. Kalau tool mencari data user, ia harus selalu difilter dengan userId dari konteks — bukan dari teks prompt yang bisa dimanipulasi.
Persona mendefinisikan kepribadian, gaya, dan batasan perilaku agent. Role-based access membatasi tool apa yang boleh dipakai berdasarkan role user. Keduanya digabung lewat definisi deklaratif:
personas:
support:
description: "Asisten support yang sabar, jelas, dan to the point"
temperature: 0.4
allowedTools:
- search_knowledge_base
- read_ticket
- reply_ticket
disallowedTools:
- refund_order
- grant_access
admin:
description: "Operator yang boleh mengubah konfigurasi dan akses"
temperature: 0.2
allowedTools:
- search_knowledge_base
- read_ticket
- reply_ticket
- refund_order
- grant_accessPemilihan persona dilakukan di runtime berdasarkan role user dan jenis request. Konsekuensinya penting: persona yang dipilih harus menolak tool di luar daftar allowedTools di level enforcement — bukan sekadar prompt. Seorang user ber-role support bisa saja menulis "lakukan refund", tapi agent tidak punya tool refund_order yang bisa dipanggil, jadi permintaan itu gagal secara struktural.
Role-based access juga mengontrol apa yang boleh dibaca user. Tool read_ticket harus memastikan user hanya membaca ticket miliknya sendiri, kecuali role-nya memang diizinkan melihat semua.
Agent memproses data pengguna yang sifatnya pribadi. Tiga area yang wajib dipikirkan sejak awal:
Satu latihan sederhana: sebelum logging atau menyimpan, buang dulu field yang tidak diperlukan dari payload percakapan:
export function redact(conversation) {
return {
userId: conversation.user.id,
role: conversation.user.role,
messages: conversation.messages.map((m) => ({
role: m.role,
content: maskEmail(m.content),
})),
};
}Menyimpan userId tetap diperlukan untuk kepentingan audit, tapi detail PII seperti email atau nomor telepon tidak perlu ikut tersimpan.
Danger
Kesalahan klasik: agent mengembalikan data user A kepada user B karena tool mengambil data berdasarkan prompt yang bisa disisipi instruksi. Selalu filter data di sisi tool dengan identitas dari context terautentikasi, bukan dari isi percakapan.
Urutan yang benar di setiap turn:
resolveUser di atas).allowedTools.userId dari konteks.Kalau kelima lapis ini runtut, agent yang sama bisa melayani ribuan user dengan perilaku berbeda tanpa membocorkan data antar mereka. Untuk mencoba alurnya di lokal, jalankan server agent dengan bun run dev dan kirim token user yang berbeda dari klien masing-masing.
Episode 13 mengubah agent dari "satu mesin untuk semua orang" menjadi "mesin yang tahu siapa yang sedang bicara". Kalian memetakan skenario multi-user dengan session per user, membedakan perilaku agent berdasarkan identitas, mendefinisikan persona dan role-based access yang membatasi tool secara struktural, serta menata privasi data dengan minimasi PII, kebijakan retensi, dan isolasi antar session.
Inti yang harus dibawa pulang:
userId dari konteks terautentikasi.Episode 14 berikutnya kita menyiapkan agent menghadapi kenyataan jaringan yang tidak sempurna: Resilience & Rate Limiting — menghadapi rate limit provider, circuit breakers, retry policies, dan graceful degradation. Sampai jumpa!