본문으로 건너뛰기

AgentController

:::실험적

그만큼AgentController 기능은 beta 단계이며 beta 상태를 벗어나기 전까지 minor 버전에서 호환성을 깨는 변경이 발생할 수 있습니다.

:::

AgentController대화형 Agent 애플리케이션을 위한 공유 런타임 호스트입니다. 모드, Model, 스토리지, 작업 공간, Tool 승인, 하위 Agent 및 채널을 조정합니다. 각 사용자 또는 활성 작업은 격리된 작업을 통해 작동합니다.Session.

마스트라 코드주력 AgentController 구현입니다. 다중 Model 지원, 지속적인 대화, 계획 후 실행 Workflow를 갖춘 터미널 기반 코딩 Agent입니다. 읽다Building a coding agent for a step-by-step guide.

AgentController를 사용하는 경우
AgentController를 사용하는 경우에 대한 직접 링크

애플리케이션에 다음이 필요할 때 AgentController를 사용하세요.

  • 하나의 대화 스레드를 공유하는 다중 Agent 모드(예: 계획 → 구축 → 검토)
  • UI와 Agent 루프 사이의 제어 계층(Model 전환, 상태 지속성, 스레드 관리)
  • Human-In-The-Loop 게이팅을 위한 Tool 승인 흐름 및 권한 정책
  • 제한된 Tool을 사용하여 집중된 하위 작업을 위임하는 하위 Agent 오케스트레이션
  • 각 세션에 대해 격리된 라이브 상태를 포함하여 다시 시작 시 지속 스레드 및 선택된 스레드 설정

이 모든 것을 직접 조립할 수도 있습니다.Agent class, 전체 Agent 루프, Tools, Memory를 노출합니다. AgentController는 Agent가 일회성 endpoint가 아닌 협업자로 동작하는 지속적인 Session을 위해 명확한 기본값을 제공합니다. 완전한 제어 또는 request-response 호출이 필요하면 Agent 클래스를 직접 사용하세요. 주변 runtime을 직접 구축하지 않고 협업 Session Model을 사용하려면 AgentController를 사용하세요.

빠른 시작
빠른 시작에 대한 직접 링크

지지대 만들기Agent, storage, and Workspace. Call controller.init() once, then use controller.createSession() to create a Session. Subscribe with session.subscribe() and send work with session.sendMessage():

src/mastra/agent-controller.ts
import { Agent } from '@mastra/core/agent'
import { AgentController } from '@mastra/core/agent-controller'
import { LocalFilesystem, Workspace } from '@mastra/core/workspace'
import { LibSQLStore } from '@mastra/libsql'

const agent = new Agent({
id: 'assistant',
name: 'Assistant',
instructions: 'Help the user plan and complete tasks.',
model: 'openai/gpt-5.6-sol',
})

const controller = new AgentController({
id: 'assistant-controller',
agent,
storage: new LibSQLStore({
id: 'agent-controller-storage',
url: 'file:./mastra.db',
}),
workspace: new Workspace({
id: 'assistant-workspace',
filesystem: new LocalFilesystem({ basePath: './workspace' }),
}),
modes: [
{
id: 'plan',
name: 'Plan',
metadata: { default: true },
instructions: 'Reason about the task before making changes.',
},
{
id: 'build',
name: 'Build',
instructions: 'Implement the approved plan.',
},
],
})

await controller.init()

const session = await controller.createSession({
resourceId: 'user-123',
})

const unsubscribe = session.subscribe(event => {
if (event.type === 'message_update') {
console.log(event.message)
}
})

await session.sendMessage({ content: 'Plan a small TypeScript CLI.' })
unsubscribe()

많은 세션에 동일한 컨트롤러를 사용합니다. 컨트롤러에 현재 세션을 저장하거나 컨트롤러 수준 메시지 방법을 통해 작업을 라우팅하지 마세요.

런타임 Model 이해
런타임 Model 이해에 대한 직접 링크

컨트롤러, 세션 및 스레드의 수명은 다릅니다.

  • 제어 장치: 구성 및 런타임 서비스를 위한 공유 호스트입니다. 한 번 초기화하고 다시 사용하세요.
  • 세션: 하나의 사용자, 작업 또는 동시 작업 범위에 대한 격리된 라이브 런타임입니다. 활성 모드, Model, 상태, 이벤트 버스, 실행 상태, 권한 부여 및 현재 스레드 바인딩을 소유합니다.
  • : 메시지와 스레드 설정이 포함된 저장된 대화입니다. 스레드는 스토리지를 구성할 때 컨트롤러 및 프로세스 재생성을 유지할 수 있습니다.

세션은 라이브 상태입니다. 임의session.state, 권한 부여, 대기 중인 승인, 활성 실행은 프로세스를 다시 생성해도 자동으로 유지되지 않습니다. Mode 및 Mode별 Model 선택을 비롯한 thread 메시지와 선택된 thread 설정은 storage를 통해 유지할 수 있습니다.

세션 및 스레드
세션 및 스레드에 대한 직접 링크

createSession()다음으로 가져오거나 생성합니다.resourceId and optional scope:

const webSession = await controller.createSession({
resourceId: 'user-123',
scope: 'web',
})

const sameWebSession = await controller.createSession({
resourceId: 'user-123',
scope: 'web',
})

const workerSession = await controller.createSession({
resourceId: 'user-123',
scope: 'background-worker',
})

console.log(webSession === sameWebSession) // true
console.log(webSession === workerSession) // false

범위가 서로 다른 세션에는 별도의 이벤트 버스, 실행 루프, 상태, 모드 및 Model 선택, 현재 스레드 바인딩이 있습니다. 저장된 스레드는 여전히 공유 스레드에 속합니다.resourceId.

통과하다threadId host가 Session을 정확한 thread에 바인딩해야 할 때 사용합니다. controller는 기존 thread로 전환하거나, 해당 ID의 thread가 없으면 새로 생성합니다. 이 동작은 다음 경우에도 적용됩니다: createSession() returns a cached Session:

const session = await controller.createSession({
resourceId: 'user-123',
scope: 'web',
threadId: 'support-ticket-42',
})

사용session.thread.create() and session.thread.switch() to move one live Session between conversations.

모드 및 Model 전환
모드 및 Model 전환에 대한 직접 링크

모드는 세션이나 스레드를 교체하지 않고 공유 지원 Agent에서 사용하는 지침과 Tool을 변경합니다. 컨트롤러에서 모드별 Tool 및 가시성을 구성합니다.

const modes = [
{
id: 'plan',
name: 'Plan',
metadata: { default: true },
instructions: 'Investigate the task and propose a plan.',
additionalTools: { searchDocs },
availableTools: ['searchDocs', 'submit_plan'],
transitionsTo: 'build',
},
{
id: 'build',
name: 'Build',
instructions: 'Implement the approved plan.',
},
]

tools그리고additionalTools 는 Mode별 Tools를 추가하기 위한 상호 배타적인 입력입니다. controller에 공유 기반 Agent가 있으면 어느 입력을 사용하든 해당 Tools가 Agent의 Tools 위에 추가됩니다. 다음을 사용하여 availableTools Mode에서 최종적으로 노출되는 Tool 이름을 제한하세요. 권한 거부가 이 allowlist보다 계속 우선합니다.

다음으로 라이브 세션을 전환하세요.session.mode.switch(). Read the active mode with session.mode.get() or session.mode.resolve():

await session.mode.switch({ modeId: 'build' })

console.log(session.mode.get()) // "build"
console.log(session.mode.resolve().instructions)

다음을 사용하여 독립적으로 Model 전환session.model.switch(), then read the active selection with session.model.get(). Thread 범위의 선택 사항은 Mode별로 저장되며 Session이 해당 Mode로 돌아오면 복원됩니다:

await session.model.switch({
modelId: 'anthropic/claude-sonnet-4-6',
scope: 'thread',
})

console.log(session.model.get())

사용scope: 'global' thread 설정에 기록하지 않아야 하는 메모리 내 선택에 사용합니다.

스레드 및 상태 관리
스레드 및 상태 관리에 대한 직접 링크

다음 사람과의 저장된 대화 목록을 나열합니다.session.thread.list():

const thread = await session.thread.create({ title: 'Release planning' })
const threads = await session.thread.list()

await session.thread.switch({ threadId: thread.id })
console.log(threads.length)

사용session.state Session과 연결된 구조화된 실시간 상태에 사용합니다. 다음을 사용해 읽으세요: session.state.get() and write updates with session.state.set(). Define stateSchema and initialState 유효성 검사와 기본값이 필요할 때 controller에서 사용합니다:

console.log(session.state.get())

await session.state.set({ activeProject: 'docs-site' })

session.state.get()스냅샷을 반환합니다.set() 업데이트의 유효성을 검사하고 Session 상태에 병합합니다. host가 이 상태를 명시적으로 유지하고 복원하지 않는 한, 실시간 Session 데이터로 취급하세요.

Tool 승인 및 정지 재개
Tool 승인 및 정지 재개에 대한 직접 링크

권한 정책은 Tool을 허용할지, 거부할지 또는 승인을 위해 UI로 보낼지 여부를 결정합니다. 다음을 사용하여 사용자 정의 Tool을 카테고리에 매핑합니다.toolCategoryResolver on the controller:

const controller = new AgentController({
toolCategoryResolver: toolName => {
if (toolName === 'delete_project') return 'execute'
return null
},
})

다음을 사용하여 카테고리 정책을 구성합니다.session.permissions.setForCategory() and tool policies with session.permissions.setForTool():

await session.permissions.setForCategory({
category: 'execute',
policy: 'ask',
})

await session.permissions.setForTool({
toolName: 'delete_project',
policy: 'deny',
})

정책이 다음과 같이 해결되면ask, 승인 event를 구독하고 다음을 사용해 사용자의 결정을 반환하세요: session.respondToToolApproval():

session.subscribe(event => {
if (event.type === 'tool_approval_required') {
session.respondToToolApproval({
toolCallId: event.toolCallId,
decision: 'approve',
})
}
})

그만큼always_allow_category 결정은 현재 실시간 Session의 나머지 기간 동안 해당 Tool category를 허용합니다. Session 권한 부여는 영구적인 process-level 권한이 아닙니다.

다음과 같은 대화형 Toolask_user and submit_plan 대신 재개 가능한 Tool suspensions를 사용하세요. 다음을 사용해 재개합니다: session.respondToToolSuspension():

session.subscribe(event => {
if (event.type === 'tool_suspended' && event.toolName === 'ask_user') {
void session.respondToToolSuspension({
toolCallId: event.toolCallId,
resumeData: 'Use SQLite.',
})
}
})

을 위한submit_plan, resume with { action: 'approved' } or { action: 'rejected', feedback }. 승인된 plan은 다음으로 구성된 Mode로 전환할 수 있습니다: transitionsTo before the run continues.

하위 Agent에 위임
하위 Agent에 위임에 대한 직접 링크

컨트롤러에서 사용 가능한 하위 Agent 유형을 구성합니다. 내장subagent Tool은 이후 해당 정의를 사용해 범위가 명확한 작업을 위임할 수 있습니다:

const controller = new AgentController({
tools: {
searchDocs,
},
subagents: [
{
id: 'code-reviewer',
name: 'Code reviewer',
description: 'Review a change for correctness and regressions.',
instructions: 'Inspect the change and report actionable findings.',
allowedControllerTools: ['searchDocs'],
allowedWorkspaceTools: ['view', 'find_files'],
defaultModelId: 'openai/gpt-5-mini',
},
],
})

일반 하위 Agent는 구성된 지침과 제한된 Tool 세트로 시작됩니다. 세트forked: true 자식이 상위 thread를 복제하고 상위 Agent의 instructions 및 Tools로 실행되어야 할 때 사용합니다. 분기된 하위 Agent는 상위 Prompt 접두사를 유지하고, definition의 instructions, Tools, allowlists, 기본 Model을 무시하며, controller에 Memory가 있어야 합니다.

사용session.subagents.model.set() 하나의 기본 subagent Model 또는 특정 Agent type의 Model을 저장할 때 사용합니다. 다음을 사용해 선택 사항을 읽으세요: session.subagents.model.get():

await session.subagents.model.set({
modelId: 'openai/gpt-5-mini',
})

await session.subagents.model.set({
agentType: 'code-reviewer',
modelId: 'anthropic/claude-sonnet-4-6',
})

const reviewerModel = session.subagents.model.get({
agentType: 'code-reviewer',
})

이러한 선택 사항은 스레드 설정에 기록됩니다. Agent 유형 선택은 세션의 기본 하위 Agent Model보다 우선합니다.

채팅 채널 연결
채팅 채널 연결에 대한 직접 링크

채널 어댑터를 컨트롤러에 전달하고 이를 컨트롤러에 등록합니다.Mastra instance:

src/mastra/index.ts
import { Mastra } from '@mastra/core'
import { AgentController } from '@mastra/core/agent-controller'
import { createSlackAdapter } from '@chat-adapter/slack'

const controller = new AgentController({
id: 'support-controller',
agent,
storage,
workspace,
modes,
channels: {
adapters: {
slack: createSlackAdapter(),
},
resolveResourceId: ({ thread, message, defaultResourceId }) => {
if (thread.isDM) return message.author.userId
return defaultResourceId
},
onSessionStart: async ({ session, thread }) => {
const plan = await billing.planFor(thread.resourceId)
await session.model.switch({ modelId: plan.modelId })
},
},
})

export const mastra = new Mastra({
agentControllers: { controller },
storage,
})

컨트롤러별 경로에서 각 플랫폼 웹후크를 가리킵니다.

/api/agent-controllers/<CONTROLLER_ID>/channels/<PLATFORM>/webhook

각 외부 채팅 스레드는 하나의 컨트롤러 세션 및 Mastra 스레드에 매핑됩니다. 기본적으로 새 세션은 어댑터의 채팅 스레드 ID에서 파생된 리소스 ID를 사용합니다.channel:. Use resolveResourceId 직접 메시지를 기존 애플리케이션 사용자에게 매핑하거나 다른 Memory 소유자를 선택할 때 사용합니다. callback은 새 thread에만 영향을 주며, 기존 thread는 저장된 resource ID를 유지합니다.

채널 세션은 코드가 아닌 컨트롤러에 의해 생성되므로onSessionStart 에서 이를 구성합니다. Session이 매핑된 thread에 바인딩된 후 첫 번째 메시지가 처리되기 전에 Session당 한 번 실행됩니다. channel Session이라면 누락될 수 있는 Model, Memory 설정 또는 Session 상태를 적용할 때 사용하세요. 동일한 thread의 이후 메시지는 Session을 재사용하며 이를 다시 호출하지 않습니다. 오류는 기록된 후 무시되므로 구성할 수 없는 Session도 메시지에 계속 응답합니다.

컨트롤러 채널 세션과 자동 승인 상태는 Memory에 보관되므로 수명이 긴 서버를 사용하세요. 보류 중인 승인 및 라이브 세션 상태는 프로세스를 다시 시작해도 유지되지 않습니다. 승인 제어를 렌더링할 수 없는 어댑터는 승인 메시지 없이 자동으로 Tool을 실행하므로 실행이 일시 중단된 상태로 유지되지 않습니다.

보다Channels for adapter setup and platform-specific webhook configuration.

UI 연결
UI 연결에 대한 직접 링크

증분 업데이트를 위해 세션 이벤트를 구독하세요. 축소된 표시 상태를 읽으십시오.session.displayState.get() UI에 완전한 rendering snapshot이 필요할 때 사용합니다:

const unsubscribe = session.subscribe(event => {
if (event.type === 'display_state_changed') {
render(event.displayState)
}
})

render(session.displayState.get())

// Call when the UI disconnects.
unsubscribe()

구독은 세션별로 격리됩니다. 동일한 컨트롤러에 있는 다른 세션의 이벤트는 이 리스너로 전달되지 않습니다. 읽기Building a coding agent guide for a complete TUI example.