Agent Core 구현
왜 단일 클래스인가
섹션 제목: “왜 단일 클래스인가”에이전트 시스템을 처음 설계하면 자연스럽게 PlannerAgent, ExecutorAgent, ReviewerAgent 같은 별도 클래스를 만들고 싶어집니다. 하지만 이 접근법은 코드 중복과 실행 경로 분기 라는 문제를 낳습니다. 한 클래스에서 수정한 버그가 다른 클래스에 반영되지 않을 수 있고, 각 클래스마다 다른 동작을 추적하는 것이 점점 어려워집니다.
단일 MainAgent 클래스 패턴 은 이 문제를 해결합니다. 모든 에이전트는 동일한 실행 엔진을 사용하고, 역할의 차이는 생성 시 주입하는 파라미터 로만 표현됩니다.
AgentDependencies 인터페이스
섹션 제목: “AgentDependencies 인터페이스”메인 에이전트와 서브에이전트는 접근 가능한 리소스의 범위가 다릅니다.
// 메인 에이전트: 전체 리소스 접근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 에서 sessionStore 와 approvalManager 를 제외한 것은 실수가 아닙니다. 서브에이전트가 세션을 직접 조작하거나 승인 흐름을 우회하지 못하도록 구조적으로 막는 것입니다.
SubAgentSpec 선언
섹션 제목: “SubAgentSpec 선언”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,};MainAgent 구현
섹션 제목: “MainAgent 구현”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
섹션 제목: “SubAgentFactory”메인 에이전트가 서브에이전트를 생성할 때는 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 추가 |