多使用者 thread
單一 Mastra thread 可由多位使用者共用,每位使用者都有自己的名稱與功能角色。你可以在訊息內文中帶入發話者身分,讓 Agent 從單一共用 thread 讀取內容時,仍能區分不同使用者。
何時使用多使用者 thread「何時使用多使用者 thread」的直接連結
當多人透過同一個 Agent 針對相同主題協作時,請使用多使用者 thread:
- 由編輯者、審查者與核准者共同處理的協作文件
- 由一個助理服務多位參與者的群組聊天
- 不同角色具有不同權限的多方審查
讓所有參與者共用一個 resourceId「share-one-resourceid-across-all-participants」的直接連結
一個 thread 只屬於一個 resourceId,因此共用 thread 上的所有參與者都必須傳入相同的值。請不要使用使用者 ID(單一使用者應用程式的預設做法),而是依對話本身設定 resourceId;例如,共用文件可使用 doc_${docId},群組聊天則可使用 room_${roomId}。所有人都指向相同的 resourceId 後,便會讀寫同一份歷史。
使用發話者身分標記每則使用者訊息「使用發話者身分標記每則使用者訊息」的直接連結
模型需要知道每一輪是誰在發話。訊息內文是唯一會保留至歷史並重新回到脈絡中的位置,因此請將每則使用者訊息包在簡短的 <turn> 標籤中,並帶入發話者的 ID、名稱與角色。此標籤會持續附在訊息上,因此回想先前輪次時,模型仍能看出每段話是誰說的。
使用簡短的輔助函式建立標籤。以下範例是其中一種做法;請複製到專案中,再依你的使用者資料結構調整:
export type Speaker = {
id: string
name: string
role: string
}
function escapeAttr(value: string) {
return value
.replace(/&/g, '&')
.replace(/"/g, '"')
.replace(/</g, '<')
.replace(/>/g, '>')
}
export function asUserTurn(speaker: Speaker, text: string) {
const id = escapeAttr(speaker.id)
const name = escapeAttr(speaker.name)
const role = escapeAttr(speaker.role)
return {
role: 'user' as const,
content: `<turn author_id="${id}" author_name="${name}" functional_role="${role}">
${text}
</turn>`,
}
}
在 Agent 的 instructions 中教它如何讀取 <turn> 標籤。Agent 必須設定 memory,才能以 thread 與 resource 呼叫:
import { Agent } from '@mastra/core/agent'
import { Memory } from '@mastra/memory'
import { LibSQLStore } from '@mastra/libsql'
const memory = new Memory({
storage: new LibSQLStore({ id: 'collab-storage', url: 'file:./collab.db' }),
options: {
lastMessages: 20,
},
})
export const collabAgent = new Agent({
id: 'collab',
name: 'CollabAgent',
model: 'openai/gpt-5-mini',
memory,
instructions: `
You are a collaborative document assistant. Multiple users talk to you in the SAME thread.
Every user message is wrapped in a <turn> tag carrying the user's identity:
<turn author_id="u_alice" author_name="Alice" functional_role="editor">
...message text...
</turn>
Rules:
1. Address users by their author_name.
2. Respect functional_role: editors propose changes, reviewers approve.
3. When attributing past statements, read author_name from the surrounding <turn> tag.
4. Do not echo the <turn> tags back at users.
`.trim(),
})
使用包裝後的訊息呼叫 Agent。每位參與者都共用相同的 thread 與 resource:
import { asUserTurn } from './identity'
const docResourceId = 'doc_42'
const docThreadId = 'doc_42'
const alice = { id: 'u_alice', name: 'Alice', role: 'editor' }
const bob = { id: 'u_bob', name: 'Bob', role: 'reviewer' }
await collabAgent.generate([asUserTurn(alice, 'My favorite color is teal.')], {
memory: { thread: docThreadId, resource: docResourceId },
})
await collabAgent.generate([asUserTurn(bob, 'I want QA sign-off before publish.')], {
memory: { thread: docThreadId, resource: docResourceId },
})
<turn> 標籤會保留在訊息內文中,因此日後回想歷史時,模型仍能看出每段話是誰說的。
與 Memory 層搭配使用「與 Memory 層搭配使用」的直接連結
使用者標記模式可與每個 Memory 層搭配使用。請依對話需要保留每位使用者事實的時間長度選擇合適的層:
- 短對話(單次工作階段,或 thread 小到足以放入
lastMessages),或需要逐字記錄誰說了什麼時:只使用訊息歷史即可。歷史中的使用者標籤已經足夠,不需要額外的 Memory 層。 - 長時間執行的 thread(對話超出
lastMessages,而且每位使用者的事實必須在歷史遭移除後繼續保留):使用 Observational Memory。 - 需要結構化參與者清單,或儲存 adapter 不支援 OM(OM 需要 LibSQL、PG 或 MongoDB):使用 working memory。
建議使用 Observational Memory 或 working memory 其中之一,因為兩者涵蓋的需求有所重疊。同時執行只會增加延遲與 token 成本,效益不大。
只使用訊息歷史「只使用訊息歷史」的直接連結
對於短對話,或需要逐字記錄誰說了什麼的情境,歷史中的使用者標籤已經足夠。lastMessages 會將先前輪次帶回脈絡,並完整保留其歸屬資訊:
import { Memory } from '@mastra/memory'
import { LibSQLStore } from '@mastra/libsql'
const memory = new Memory({
storage: new LibSQLStore({ id: 'collab-storage', url: 'file:./collab.db' }),
options: {
lastMessages: 20,
},
})
模型會從目前訊息的 <turn> 標籤,以及透過 lastMessages 帶回的先前已標記訊息中讀取身分。
搭配 Observational Memory(建議)「搭配 Observational Memory(建議)」的直接連結
Observational Memory(OM)會將每位使用者的事實擷取至背景記錄,不會占用 Agent 的 Tool 額度。預設 Observer 模型能直接讀取 <turn> 標籤,並產生如 Alice stated her favorite color is teal. 與 Bob asked for QA sign-off before publish. 的歸屬描述。
若儲存系統支援,多使用者 thread 應優先使用 OM,而非 working memory。OM 會自動擷取事實、可擴充至任意數量的參與者,也不需要維護範本。啟用時不需覆寫任何設定:
import { Memory } from '@mastra/memory'
import { LibSQLStore } from '@mastra/libsql'
const memory = new Memory({
storage: new LibSQLStore({ id: 'collab-storage', url: 'file:./collab.db' }),
options: {
lastMessages: 20,
observationalMemory: true,
},
})
OM 需要支援它的儲存 adapter:@mastra/libsql、@mastra/pg、@mastra/mongodb 或 @mastra/oracledb。
若將 Observer 改為較弱的模型後,發現事實都被歸為泛稱的 User,請使用 observation.instruction 教 Observer 如何讀取 <turn> 標籤。
搭配 working memory「搭配 working memory」的直接連結
當 OM 不適用時,例如儲存 adapter 不支援 OM,或需要一份結構明確、具決定性且 Agent 每輪都能讀寫的參與者清單,請使用 working memory。
預設的 working memory 範本假設每個 thread 只有一位使用者(「First Name」、「Last Name」等)。若是多使用者 thread,請提供包含參與者清單的範本:
import { Memory } from '@mastra/memory'
import { LibSQLStore } from '@mastra/libsql'
const memory = new Memory({
storage: new LibSQLStore({ id: 'collab-storage', url: 'file:./collab.db' }),
options: {
lastMessages: 20,
workingMemory: {
enabled: true,
scope: 'thread',
template: `# Document Collaboration State
## Participants
<!-- One entry per known collaborator. Use author_id as the stable key. -->
<!-- - **<author_name>** (<author_id>, <functional_role>): <their position> -->
## Open Questions
## Decisions
`,
},
},
})
設定 scope: 'thread',讓參與者清單屬於文件,而不是任何個別使用者。再加入一項 instruction,要求 Agent 每當新的 author_id 出現在 <turn> 中時,便將新參與者附加至清單。
如需範本的詳細資訊,請參閱自訂範本。
安全性「安全性」的直接連結
請從已驗證的要求脈絡設定 speaker,絕不要從要求內文取得。若用戶端可以自行選擇 author_id,使用者就能冒充他人。請使用 Request Context 從驗證層讀取已驗證的使用者,並在呼叫 Agent 前於伺服器端建立 <turn> 標籤。