다중 사용자 스레드
단일 Mastra 스레드는 각각 고유한 이름과 기능적 역할을 가진 여러 사용자가 공유할 수 있습니다. Agent가 단일 공유 스레드를 읽는 동안 사용자를 구분할 수 있도록 메시지 본문에 화자 ID가 포함되어 있습니다.
다중 사용자 스레드를 사용하는 경우다중 사용자 스레드를 사용하는 경우에 대한 직접 링크
여러 사람이 하나의 Agent를 통해 동일한 주제에 대해 공동 작업하는 경우 다중 사용자 스레드를 사용하십시오.
- 편집자, 검토자, 승인자가 포함된 공동 문서
- 한 명의 보조자가 여러 참가자에게 서비스를 제공하는 그룹 채팅
- 다양한 역할이 다양한 권한을 갖는 다중 이해관계자 검토
하나 공유resourceId across all participantsshare-one-resourceid-across-all-participants에 대한 직접 링크
스레드는 정확히 하나의 resourceId에 속하므로 공유 스레드의 모든 참가자는 같은 값을 전달해야 합니다. 사용자 ID(단일 사용자 앱의 기본값) 대신 대화 자체를 기준으로 resourceId를 지정하세요. 예를 들어 공유 문서는 doc_${docId}, 그룹 채팅은 room_${roomId}를 사용할 수 있습니다. 모두가 같은 resourceId를 가리키면 동일한 기록을 읽고 씁니다.
각 사용자 메시지에 화자의 신원을 태그로 지정하세요.각 사용자 메시지에 화자의 신원을 태그로 지정하세요.에 대한 직접 링크
Model은 매 턴 누가 말하는지 알아야 합니다. 메시지 본문은 기록에 남아 컨텍스트로 다시 전달되는 유일한 위치이므로, 각 사용자 메시지를 화자의 ID, 이름 및 역할을 담은 작은 <turn> 태그로 감싸세요. 태그는 메시지에 계속 연결되어 있으므로 이전 턴을 회상할 때도 Model은 누가 무엇을 말했는지 알 수 있습니다.
작은 도우미를 사용하여 태그를 만듭니다. 아래 예는 이를 수행하는 한 가지 방법으로, 이를 프로젝트에 복사하고 사용자 데이터 형태에 맞게 조정합니다.
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의 지침에서 <turn> 태그를 읽는 방법을 알려 주세요. Agent를 thread와 resource로 호출할 수 있도록 memory가 구성되어 있어야 합니다.
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> 태그는 메시지 본문에 유지되므로 이후 턴에서 기록을 회상해도 Model은 누가 무엇을 말했는지 알 수 있습니다.
Memory 레이어와 결합Memory 레이어와 결합에 대한 직접 링크
사용자 태깅 패턴은 모든 Memory 계층으로 구성됩니다. 대화에서 사용자별 사실을 기억해야 하는 기간을 기준으로 레이어를 선택하세요.
- 짧은 대화(단일 세션 또는
lastMessages에 들어갈 만큼 작은 스레드)이거나 누가 무엇을 말했는지 축어적 기록이 필요한 경우: 메시지 기록만 사용하세요. 기록의 사용자 태그만으로 충분하며 추가 Memory 계층은 필요하지 않습니다. - 장기 실행 스레드(
lastMessages보다 커지는 대화에서 기록이 제외된 후에도 사용자별 사실을 유지해야 하는 경우): 관찰 Memory를 사용하세요. - 구조화된 참가자 목록이 필요하거나 스토리지 어댑터가 OM을 지원하지 않는 경우(OM에는 LibSQL, PG 또는 MongoDB가 필요함): working Memory를 사용하세요. 관찰 기억이나 작업 기억은 중복되는 요구 사항을 충족하므로 사용하는 것이 좋습니다. 둘 다 실행하면 큰 이점 없이 대기 시간과 토큰 비용이 추가됩니다.
메시지 기록만메시지 기록만에 대한 직접 링크
짧은 대화이거나 누가 무엇을 말했는지 축어적 기록이 필요한 경우에는 기록의 사용자 태그만으로 충분합니다. 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,
},
})
Model은 현재 메시지의 <turn> 태그와 lastMessages를 통해 다시 가져온 이전 태그 메시지에서 화자 정보를 읽습니다.
관찰 기억 포함(권장)관찰 기억 포함(권장)에 대한 직접 링크
관찰 Memory(OM)는 Agent의 Tool 예산을 소모하지 않고 사용자별 정보를 백그라운드 로그로 추출합니다. 기본 Observer Model은 <turn> 태그를 기본적으로 읽고 Alice stated her favorite color is teal. 및 Bob asked for QA sign-off before publish. 같은 화자 정보가 포함된 내용을 생성합니다.
스토리지에서 지원하는 경우 다중 사용자 스레드의 경우 작업 Memory보다 OM을 선호합니다. 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에는 @mastra/libsql, @mastra/pg, @mastra/mongodb 또는 @mastra/oracledb처럼 이를 지원하는 스토리지 어댑터가 필요합니다.
Observer를 성능이 낮은 Model로 변경했을 때 사실의 화자가 일반 User로 처리된다면 observation.instruction을 사용하여 Observer에게 <turn> 태그를 읽는 방법을 알려 주세요.
작업기억으로작업기억으로에 대한 직접 링크
OM이 옵션이 아닌 경우(예: 스토리지 어댑터가 OM을 지원하지 않는 경우 또는 Agent가 매번 읽고 쓸 수 있는 체계적이고 결정적인 참가자 목록이 필요한 경우) 작업 Memory를 사용하십시오.
기본 working Memory 템플릿은 스레드당 사용자가 한 명이라고 가정합니다("First Name", "Last Name" 등). 다중 사용자 스레드에서는 참가자 목록이 포함된 템플릿을 제공하세요.
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'를 설정하세요. <turn>에 새로운 author_id가 나타날 때마다 Agent가 참가자 목록에 새 참가자를 추가하도록 지침 하나를 추가합니다.
템플릿에 대한 자세한 내용은 다음을 참조하세요.Custom templates.
보안보안에 대한 직접 링크
speaker는 요청 본문이 아니라 인증된 요청 컨텍스트에서 설정하세요. 클라이언트가 자체 author_id를 선택할 수 있으면 한 사용자가 다른 사용자를 사칭할 수 있습니다. Request Context를 사용하여 인증 계층에서 검증된 사용자를 읽고, Agent를 호출하기 전에 서버에서 <turn> 태그를 구성하세요.