콘텐츠로 이동

Agent Core 구현

에이전트 시스템을 처음 설계하면 자연스럽게 PlannerAgent, ExecutorAgent, ReviewerAgent 같은 별도 클래스를 만들고 싶어집니다. 하지만 이 접근법은 코드 중복과 실행 경로 분기 라는 문제를 낳습니다. 한 클래스에서 수정한 버그가 다른 클래스에 반영되지 않을 수 있고, 각 클래스마다 다른 동작을 추적하는 것이 점점 어려워집니다.

단일 MainAgent 클래스 패턴 은 이 문제를 해결합니다. 모든 에이전트는 동일한 실행 엔진을 사용하고, 역할의 차이는 생성 시 주입하는 파라미터 로만 표현됩니다.

메인 에이전트와 서브에이전트는 접근 가능한 리소스의 범위가 다릅니다.

// 메인 에이전트: 전체 리소스 접근
interface AgentDependencies {
llmClient: LLMClient;
toolRegistry: ToolRegistry;
sessionStore: SessionStore;
promptComposer: PromptComposer;
approvalManager: ApprovalManager;
subAgentFactory: SubAgentFactory;
}
// 서브에이전트: 제한된 접근 (격리 보장)
interface SubAgentDeps {
llmClient: LLMClient;
toolRegistry: RestrictedToolRegistry; // 허용 툴만 노출
promptComposer: PromptComposer;
// sessionStore, approvalManager 없음 — 의도적 제외
}

SubAgentDeps 에서 sessionStoreapprovalManager 를 제외한 것은 실수가 아닙니다. 서브에이전트가 세션을 직접 조작하거나 승인 흐름을 우회하지 못하도록 구조적으로 막는 것입니다.

SubAgentSpec 은 서브에이전트의 역할과 제약을 선언적으로 정의합니다.

interface SubAgentSpec {
name: string;
description: string;
systemPromptOverride?: string; // 기본 프롬프트 대신 사용할 프롬프트
systemPromptSuffix?: string; // 기본 프롬프트 뒤에 추가할 내용
allowedTools: string[]; // 사용 가능한 툴 이름 목록
model?: string; // 기본 모델 오버라이드 (선택)
maxTurns?: number; // 최대 실행 턴 수 제한
}
// 실제 사용 예시
const plannerSpec: SubAgentSpec = {
name: 'planner',
description: '작업을 분석하고 실행 계획을 수립하는 에이전트',
systemPromptSuffix: `
당신은 계획 수립 전문가입니다.
- 작업을 원자적 단계로 분해하세요
- 각 단계의 의존성을 명시하세요
- 실행 계획을 JSON으로 반환하세요
`,
allowedTools: ['read_file', 'list_directory', 'search_code'],
maxTurns: 10,
};
const executorSpec: SubAgentSpec = {
name: 'executor',
description: '계획을 실제로 실행하는 에이전트',
systemPromptSuffix: `
당신은 실행 전문가입니다.
- 주어진 계획의 단계를 순서대로 실행하세요
- 각 단계 완료 후 결과를 보고하세요
`,
allowedTools: ['read_file', 'write_file', 'run_command', 'search_code'],
maxTurns: 30,
};
class MainAgent {
private conversationHistory: Message[] = [];
constructor(
private deps: AgentDependencies,
private spec?: SubAgentSpec, // undefined이면 메인 에이전트
) {}
async run(task: string): Promise<AgentResult> {
const systemPrompt = this.buildSystemPrompt();
this.conversationHistory = [{ role: 'user', content: task }];
let turns = 0;
const maxTurns = this.spec?.maxTurns ?? 50;
while (turns < maxTurns) {
turns++;
const response = await this.deps.llmClient.complete({
system: systemPrompt,
messages: this.conversationHistory,
tools: this.getAvailableTools(),
});
this.conversationHistory.push({ role: 'assistant', content: response });
if (response.stopReason === 'end_turn') {
return { success: true, output: response.content, turns };
}
if (response.stopReason === 'tool_use') {
const toolResults = await this.executeToolCalls(response.toolCalls);
this.conversationHistory.push({ role: 'user', content: toolResults });
}
}
return { success: false, output: 'Max turns exceeded', turns };
}
private buildSystemPrompt(): string {
return this.deps.promptComposer.compose({
base: this.spec?.systemPromptOverride,
suffix: this.spec?.systemPromptSuffix,
});
}
private getAvailableTools() {
const allowedTools = this.spec?.allowedTools;
return this.deps.toolRegistry.getTools(allowedTools);
}
private async executeToolCalls(toolCalls: ToolCall[]): Promise<ToolResult[]> {
return Promise.all(
toolCalls.map(call => this.deps.toolRegistry.dispatch(call))
);
}
}

메인 에이전트가 서브에이전트를 생성할 때는 SubAgentFactory 를 통합니다. 직접 new MainAgent() 를 호출하지 않음으로써, 서브에이전트 생성 로직을 한 곳에서 관리합니다.

class SubAgentFactory {
constructor(
private mainDeps: AgentDependencies,
) {}
create(spec: SubAgentSpec): MainAgent {
// 서브에이전트용 제한된 의존성 생성
const subDeps: SubAgentDeps = {
llmClient: this.mainDeps.llmClient,
toolRegistry: this.mainDeps.toolRegistry.restricted(spec.allowedTools),
promptComposer: this.mainDeps.promptComposer,
};
return new MainAgent(subDeps as unknown as AgentDependencies, spec);
}
}
// 의존성 조립
const deps: AgentDependencies = {
llmClient: new AnthropicClient({ model: 'claude-opus-4-5' }),
toolRegistry: new ToolRegistry([
new ReadFileTool(),
new WriteFileTool(),
new RunCommandTool(),
]),
sessionStore: new JsonSessionStore('./sessions'),
promptComposer: new PromptComposer('./prompts/base.md'),
approvalManager: new ApprovalManager({ requireApproval: ['write_file'] }),
subAgentFactory: null!, // 아래에서 주입
};
const factory = new SubAgentFactory(deps);
deps.subAgentFactory = factory;
// 메인 에이전트 실행
const agent = new MainAgent(deps);
const result = await agent.run('src/utils.ts의 모든 함수에 JSDoc을 추가하세요');

메인 에이전트 vs 서브에이전트 비교

섹션 제목: “메인 에이전트 vs 서브에이전트 비교”
항목메인 에이전트서브에이전트
생성 방식new MainAgent(deps)factory.create(spec)
세션 접근전체 접근없음
툴 접근전체 레지스트리allowedTools 로 제한
승인 관리직접 처리없음 (메인이 위임)
최대 턴 수50 (기본)spec에서 정의
프롬프트전체 기본 프롬프트override 또는 suffix 추가