- 홈
- Documentation
- 런타임 도구
- 태스크 에이전트 탐색 및 선택
태스크 에이전트 탐색 및 선택
이 문서는 태스크 서브시스템이 에이전트 정의를 탐색하고, 여러 소스를 병합하며, 실행 시점에 요청된 에이전트를 해석하는 방법을 설명합니다.
선행 순위, 잘못된 정의 처리, 에이전트를 사실상 사용 불가 상태로 만들 수 있는 스폰/깊이 제약을 포함하여 현재 구현된 런타임 동작을 다룹니다.
구현 파일
섹션 제목: “구현 파일”src/task/discovery.tssrc/task/agents.tssrc/task/types.tssrc/task/index.tssrc/task/commands.tssrc/prompts/agents/task.mdsrc/prompts/tools/task.mdsrc/discovery/helpers.tssrc/config.tssrc/task/executor.ts
에이전트 정의 형태
섹션 제목: “에이전트 정의 형태”태스크 에이전트는 AgentDefinition으로 정규화됩니다 (src/task/types.ts):
name,description,systemPrompt(유효하게 로드된 에이전트에 필수)- 선택적
tools,spawns,model,thinkingLevel,output source:"bundled" | "user" | "project"- 선택적
filePath
파싱은 parseAgentFields()를 통해 프론트매터에서 이루어집니다 (src/discovery/helpers.ts):
name또는description누락 => 유효하지 않음 (null), 호출자는 파싱 실패로 처리tools는 CSV 또는 배열 허용; 제공된 경우submit_result가 자동 추가됨spawns는*, CSV, 또는 배열 허용- 하위 호환성 동작:
spawns가 없지만tools에task가 포함된 경우spawns는*이 됨 output은 불투명한 스키마 데이터로 그대로 전달됨
번들 에이전트
섹션 제목: “번들 에이전트”번들 에이전트는 텍스트 임포트를 사용하여 빌드 시점에 내장됩니다 (src/task/agents.ts).
EMBEDDED_AGENT_DEFS 정의:
- 프롬프트 파일에서
explore,plan,designer,reviewer - 공유
task.md본문과 주입된 프론트매터에서task및quick_task
로딩 경로:
loadBundledAgents()는parseAgent(..., "bundled", "fatal")로 내장 마크다운을 파싱- 결과는 메모리 내 캐시에 저장됨 (
bundledAgentsCache) clearBundledAgentsCache()는 테스트 전용 캐시 초기화
번들 파싱은 level: "fatal"을 사용하므로, 잘못된 번들 프론트매터는 예외를 발생시키고 탐색 전체를 실패하게 만들 수 있습니다.
파일시스템 및 플러그인 탐색
섹션 제목: “파일시스템 및 플러그인 탐색”discoverAgents(cwd, home) (src/task/discovery.ts)는 번들 정의를 추가하기 전에 여러 위치의 에이전트를 병합합니다.
탐색 입력
섹션 제목: “탐색 입력”getConfigDirs("agents", { project: false })의 사용자 설정 에이전트 디렉터리findAllNearestProjectConfigDirs("agents", cwd)의 가장 가까운 프로젝트 에이전트 디렉터리listClaudePluginRoots(home)의 Claude 플러그인 루트와agents/하위 디렉터리- 번들 에이전트 (
loadBundledAgents())
실제 소스 순서
섹션 제목: “실제 소스 순서”소스 패밀리 순서는 getConfigDirs("", { project: false })에서 가져오며, 이는 src/config.ts의 priorityList에서 파생됩니다:
.xcsh.claude.codex.gemini
각 소스 패밀리에 대해 탐색 순서는 다음과 같습니다:
- 해당 소스의 가장 가까운 프로젝트 디렉터리 (발견된 경우)
- 해당 소스의 사용자 디렉터리
모든 소스 패밀리 디렉터리 이후, 플러그인 agents/ 디렉터리가 추가됩니다 (프로젝트 범위 플러그인 먼저, 그 다음 사용자 범위).
번들 에이전트는 마지막에 추가됩니다.
중요한 주의사항: 오래된 주석 vs 현재 코드
섹션 제목: “중요한 주의사항: 오래된 주석 vs 현재 코드”discovery.ts 헤더 주석은 여전히 .pi를 언급하며 .codex/.gemini는 언급하지 않습니다. 실제 런타임 순서는 src/config.ts에 의해 결정되며 현재 .xcsh, .claude, .codex, .gemini를 사용합니다.
병합 및 충돌 규칙
섹션 제목: “병합 및 충돌 규칙”탐색은 정확한 agent.name으로 선착순 중복 제거를 사용합니다:
Set<string>이 이미 확인된 이름을 추적합니다.- 로드된 에이전트는 디렉터리 순서로 평탄화되며 이름이 처음 확인된 경우에만 유지됩니다.
- 번들 에이전트는 동일한 집합에 대해 필터링되며 아직 확인되지 않은 경우에만 추가됩니다.
함의:
- 동일한 소스 패밀리의 경우 프로젝트가 사용자를 재정의합니다.
- 더 높은 우선순위의 소스 패밀리가 더 낮은 순위를 재정의합니다 (
.xcsh가.claude보다 앞). - 번들되지 않은 에이전트가 동일한 이름의 번들 에이전트를 재정의합니다.
- 이름 매칭은 대소문자를 구분합니다 (
Task와task는 다름). - 하나의 디렉터리 내에서 마크다운 파일은 중복 제거 전에 파일 이름 사전순으로 읽힙니다.
유효하지 않은/누락된 에이전트 파일 동작
섹션 제목: “유효하지 않은/누락된 에이전트 파일 동작”디렉터리별 (loadAgentsFromDir):
- 읽을 수 없거나 누락된 디렉터리: 비어 있는 것으로 처리됨 (
readdir(...).catch(() => [])) - 파일 읽기 또는 파싱 실패: 경고 기록, 파일 건너뜀
- 파싱 경로는
parseAgent(..., level: "warn")을 사용
프론트매터 실패 동작은 parseFrontmatter에서 옵니다:
warn수준의 파싱 오류는 경고를 기록- 파서는 단순한
key: value줄 파서로 폴백 - 필수 필드가 여전히 누락된 경우
parseAgentFields가 실패하고,AgentParsingError가 발생하여 호출자에게 잡힘 (파일 건너뜀)
결과적으로: 하나의 잘못된 커스텀 에이전트 파일이 다른 파일의 탐색을 중단시키지 않습니다.
에이전트 조회 및 선택
섹션 제목: “에이전트 조회 및 선택”조회는 정확한 이름으로 선형 검색합니다:
getAgent(agents, name)=>agents.find(a => a.name === name)
태스크 실행 시 (TaskTool.execute):
- 호출 시점에 에이전트가 재탐색됨 (
discoverAgents(this.session.cwd)) - 요청된
params.agent가getAgent를 통해 해석됨 - 에이전트 누락 시 즉각적인 도구 응답 반환:
Unknown agent "...". Available: ...- 서브프로세스 실행 없음
설명 vs 실행 시점 탐색
섹션 제목: “설명 vs 실행 시점 탐색”TaskTool.create()는 초기화 시점에 탐색 결과로 도구 설명을 빌드합니다 (buildDescription).
execute()는 에이전트를 다시 탐색합니다. 따라서 세션 중간에 에이전트 파일이 변경된 경우 런타임 집합이 이전 도구 설명에 나열된 내용과 다를 수 있습니다.
구조화된 출력 가드레일 및 스키마 우선순위
섹션 제목: “구조화된 출력 가드레일 및 스키마 우선순위”TaskTool.execute의 런타임 출력 스키마 우선순위:
- 에이전트 프론트매터
output - 태스크 호출
params.schema - 부모 세션
outputSchema
(effectiveOutputSchema = effectiveAgent.output ?? outputSchema ?? this.session.outputSchema)
src/prompts/tools/task.md의 프롬프트 시점 가드레일 텍스트는 구조화 출력 에이전트(explore, reviewer)에 대한 불일치 동작에 대해 경고합니다: 산문에서의 출력 형식 지침이 내장 스키마와 충돌하여 null 출력을 생성할 수 있습니다.
이것은 discoverAgents의 하드 런타임 유효성 검사 로직이 아닌 지침입니다.
명령어 탐색 상호작용
섹션 제목: “명령어 탐색 상호작용”src/task/commands.ts는 워크플로우 명령어(에이전트 정의가 아님)를 위한 병렬 인프라이지만, 동일한 전반적인 패턴을 따릅니다:
- 먼저 기능 제공자에서 탐색
- 선착순으로 이름별 중복 제거
- 아직 확인되지 않은 경우 번들 명령어 추가
getCommand를 통한 정확한 이름 조회
src/task/index.ts에서 명령어 헬퍼는 에이전트 탐색 헬퍼와 함께 재내보내집니다. 에이전트 탐색 자체는 런타임에 명령어 탐색에 의존하지 않습니다.
탐색 이후의 가용성 제약
섹션 제목: “탐색 이후의 가용성 제약”에이전트는 탐색 가능하더라도 실행 가드레일로 인해 여전히 실행할 수 없는 상태일 수 있습니다.
부모 스폰 정책
섹션 제목: “부모 스폰 정책”TaskTool.execute는 session.getSessionSpawns()를 확인합니다:
"*"=> 모두 허용""=> 모두 거부- CSV 목록 => 나열된 이름만 허용
거부된 경우: 즉각적인 Cannot spawn '...'. Allowed: ... 응답.
차단된 자기 재귀 환경 가드
섹션 제목: “차단된 자기 재귀 환경 가드”PI_BLOCKED_AGENT는 도구 생성 시점에 읽힙니다. 요청이 일치하는 경우 실행은 재귀 방지 메시지와 함께 거부됩니다.
재귀 깊이 게이팅 (자식 세션 내 태스크 도구 가용성)
섹션 제목: “재귀 깊이 게이팅 (자식 세션 내 태스크 도구 가용성)”runSubprocess에서 (src/task/executor.ts):
- 깊이는
taskDepth에서 계산됨 task.maxRecursionDepth가 차단 기준을 제어- 최대 깊이 도달 시:
task도구가 자식 도구 목록에서 제거됨- 자식
spawns환경이 비어 있게 설정됨
따라서 에이전트 정의에 spawns가 포함되어 있더라도 더 깊은 수준에서는 추가 태스크를 스폰할 수 없습니다.
플랜 모드 주의사항 (현재 구현)
섹션 제목: “플랜 모드 주의사항 (현재 구현)”TaskTool.execute는 플랜 모드를 위한 effectiveAgent를 계산하지만 (플랜 모드 프롬프트 앞에 추가, 읽기 전용 도구 서브셋 강제, 스폰 초기화), runSubprocess는 effectiveAgent 대신 agent로 호출됩니다.
현재 효과:
- 모델 재정의 / 사고 수준 / 출력 스키마는
effectiveAgent에서 파생됨 effectiveAgent의 시스템 프롬프트 및 도구/스폰 제한은 이 호출 경로에서 전달되지 않음
이것은 플랜 모드 동작 기대치를 읽을 때 알아두어야 할 구현상의 주의사항입니다.