지식위키

gajae-code · 서브에이전트·태스크 위임 (Role Agents)

gajae-code · 서브에이전트·태스크 위임 (Role Agents)

한 줄 요약

큰 작업을 메인 에이전트가 혼자 하지 않고, 역할이 정해진 부하 에이전트에게 쪼개 맡긴 뒤 한 장짜리 “영수증”만 받아오는 구조다. 왜 배우나 — 일을 병렬로 나누면서도 부모의 대화 토큰이 폭증하지 않고, “누가 손대도 되고 누가 보기만 하는지” 권한을 명찰처럼 못 박을 수 있기 때문이다.

그림

flowchart TD
  A[메인 에이전트가 task 도구 호출] --> G{"게이팅: 5명 이상인데 계획 없나?"}
  G -- "예: 계획 누락" --> R[거부하고 빠진 항목 보고]
  G -- "아니오: 통과" --> N["이름 부여: SwiftFalcon 같은 고유 식별자"]
  N --> O["순번 ID 부여: 0-Auth.1-Sub 같은 prefix"]
  O --> I["격리된 책상 준비: 워크트리 작업공간 분리"]
  I --> P["동시 실행: 정해진 인원 상한 안에서 병렬"]
  P --> X[각 부하가 일하고 끝날 때까지 관찰]
  X --> C[바뀐 부분만 캡처해 임시 브랜치에 보관]
  C --> M[부모 작업본에 차례로 합치기]
  M --> RC["영수증 생성: 압축 요약본"]
  RC --> S["금지 키 차단: 풀텍스트 누출 막기"]
  S --> A[부모는 요약본만 받음]

쉽게 풀기

회사 팀장(메인 에이전트)이 큰 프로젝트를 받았다고 생각하자. 혼자 다 하면 책상이 서류로 넘쳐난다(= 토큰 폭증). 그래서 신입 여러 명에게 나눠 맡긴다. 다만 그냥 맡기지 않는다.

  1. 명찰부터 붙인다 (역할 정의) — 각 부하는 마크다운 파일 한 장(prompts/agents/*.md)으로 정의된다. 파일 맨 위 “프론트매터”가 명찰이다. 이름, 할 일 한 줄, 쓸 수 있는 도구, 생각 깊이, “손대도 됨/보기만” 같은 권한이 적혀 있다. 그 아래 본문은 그 부하의 성격(시스템 프롬프트)이다.

  2. 네 종류 신입이 있다executor는 실제로 코드를 고치는 쓰기 가능 일꾼이다. architect / planner / critic은 파일을 읽고 진단·계획·심사만 하는 읽기전용 검토자다. 비유하면 한 명은 공사를 하고, 셋은 각각 설계 검토관, 작업 계획자, 최종 심사관이다.

  3. 별도 책상을 준다 (격리) — 부하들이 같은 서류를 동시에 고치면 엉킨다. 그래서 각자에게 원본을 복제한 격리 작업공간(워크트리)을 준다. 일이 끝나면 바뀐 부분만 떼어내 임시 폴더에 모았다가 부모 작업본에 차례로 합친다.

  4. 너무 많이 부르려 하면 막는다 (게이팅) — 부하를 5명 이상 동시에 부르려 하면 시스템이 “왜 병렬이어야 하지? 왜 혼자 못 하지? 서로 독립적인가?”를 적은 계획서를 먼저 요구한다. 안 적으면 거부한다.

  5. 보고는 한 장으로만 받는다 (영수증) — 부하가 토해낸 전체 출력물을 그대로 받지 않는다. 상태, 결과를 다시 찾아볼 수 있는 주소(agent://<id>), 검토 의견 정도만 담은 압축 영수증을 받는다. 풀텍스트가 필요하면 그 주소로 다시 읽는다.

핵심 정리

명찰(프론트매터)에 적히는 주요 항목 — 파싱은 parseAgentFields()가 담당한다.

필드역할메모
name / description이름과 한 줄 역할둘 중 하나라도 없으면 파싱 자체가 실패(null)
tools허용 도구 화이트리스트명시하면 결과 제출용 yield가 자동 추가됨
spawns다시 부를 수 있는 하위 에이전트toolstask만 있고 미지정이면 "*"로 추론
thinkingLevel / blocking생각 깊이 / 부모가 끝까지 기다릴지architect는 blocking: true
forkContext / bashAllowedPrefixes부모 대화 포크 여부 / 예외 bash 허용읽기전용도 gjc state 등은 예외 허용

[!note] 네 종류 신입의 계약 차이

  • executor — 쓰기 가능. 도구 전체, 생각 medium, forkContext: allowed. 산출물: 변경 파일·결정·검증 증거.
  • architect — 읽기전용. 생각 high, blocking: true. 산출물: Architectural Status(CLEAR/WATCH/BLOCK) + 리뷰 권고(APPROVE/COMMENT/REQUEST CHANGES).
  • planner — 읽기전용. 생각 medium. 산출물: scope/steps/acceptance/risks/verification 계획.
  • critic — 읽기전용. 생각 high. 산출물: OKAY / ITERATE / REJECT 판정.
  • 읽기전용 3종 공통: 본문 <constraints>에서 “never write, edit, format, commit, push, or mutate files” 명시. 예외 bash는 gjc ralplan --write(아티팩트는 파일이 아니라 인라인 마크다운)와 gjc state만.

[!note] 동작을 떠받치는 부품들

  • 병렬 실행(parallel.ts)mapWithConcurrencyLimit가 워커풀 + 동시성 상한. 중단 시 부분결과(aborted:true) 보존, 일반 에러는 fail-fast.
  • 격리(worktree.ts)ensureIsolation이 최적 백엔드(apfs/btrfs/zfs/reflink/overlayfs 등) 선택, 불가하면 폴백. baseline → 델타 패치 → gjc/task/<taskId> 브랜치 → cherry-pick으로 부모 HEAD에 순차 병합(충돌 시 멈추고 보고).
  • 이름 생성(name-generator.ts) — 형용사+명사(SwiftFalcon, 약 42만 조합). 충돌 시 Task<n> 숫자 폴백.
  • 순번 ID(output-manager.ts)0-AuthProvider 식 prefix, 중첩은 0-Parent.0-Child. agent://<id> URL 안정화.
  • 게이팅(spawn-gate.ts) — 임계값 DEFAULT_SPAWN_THRESHOLD = 4. 초과 시 SpawnPlanReceipt 5필드(whyParallel/whyNotLocal/independence/expectedReceiptShape/maxInlineTokens) 제출 필수.
  • 영수증(receipt.ts)buildTaskReceipt가 압축. BANNED_RAW_TASK_KEYS(output/stdout/resultText 등)는 assertNoRawTaskFields가 차단해 풀텍스트 표면 누출을 막는다.

[!note] await 타임아웃은 실패가 아니다 (AGENTS.md 44행 규약) 원문: “Subagent await timeouts are observation windows, not failure signals.” await가 타임아웃됐다고 부하를 취소하지 말 것 — 살펴보고, 독립 작업을 계속하다가, 실제로 실패/이탈/회복불능일 때만 취소하라. 런타임도 wall-clock 하드 리밋은 기본 비활성(task.maxRuntimeMs > 0일 때만)이라 타임아웃 자체가 자동 취소를 부르지 않는다.

실제 예시

실제 architect 명찰(프론트매터):

# /home/seunghyeong/harness-work/gajae-code/packages/coding-agent/src/prompts/agents/architect.md
---
name: architect
description: Read-only architecture and code-review agent with severity-rated findings and status verdicts
tools: read, search, find, lsp, ast_grep, web_search, bash, report_finding
thinking-level: high
blocking: true
forkContext: allowed
bashAllowedPrefixes:
  - gjc ralplan --write
  - gjc state
---

명찰을 파싱하는 규칙 — 이름/설명 없으면 실패, yield/spawns 자동 추론:

// /home/seunghyeong/harness-work/gajae-code/packages/coding-agent/src/discovery/helpers.ts
export function parseAgentFields(frontmatter: Record<string, unknown>): ParsedAgentFields | null {
	const name = typeof frontmatter.name === "string" ? frontmatter.name : undefined;
	const description = typeof frontmatter.description === "string" ? frontmatter.description : undefined;
	if (!name || !description) {
		return null;                       // name/description 없으면 파싱 실패
	}
	let tools = parseArrayOrCSV(frontmatter.tools)?.map(tool => tool.toLowerCase());
	// Subagents with explicit tool lists always need yield
	if (tools && !tools.includes("yield")) {
		tools = [...tools, "yield"];       // tools 명시되면 yield 강제 추가
	}
	// Backward compat: infer spawns: "*" when tools includes "task"
	if (spawns === undefined && tools?.includes("task")) {
		spawns = "*";
	}
	// ... model/blocking/hide/forkContext/bashAllowedPrefixes 파싱 ...
}

범용 위임 에이전트 task는 파일 대신 코드에 직접 박혀 있다:

// /home/seunghyeong/harness-work/gajae-code/packages/coding-agent/src/task/agents.ts
// task.md는 코드에 직접 프론트매터를 박아 임베드한다 (범용 위임 에이전트)
{
	fileName: "task.md",
	frontmatter: {
		name: "task",
		description: "General-purpose subagent with full capabilities for delegated multi-step tasks",
		spawns: "*",
		model: "pi/default",
		thinkingLevel: Effort.Medium,
		hide: true,
	},
	template: taskMd,
},

직접 읽기전용 검토자를 만들 때 복붙용 최소 템플릿:

---
name: my-reviewer
description: Read-only reviewer that returns PASS/FAIL with file-backed evidence
tools: read, search, find, bash
thinking-level: high
blocking: true
bashAllowedPrefixes:
  - gjc state
---
<identity>
You are MyReviewer. You inspect and judge. You are read-only.
</identity>

<constraints>
- Read-only: never write, edit, commit, or mutate files.
- Cite concrete files for every claim.
</constraints>

<output_contract>
**[PASS / FAIL]** + 근거 + 수정 제안.
</output_contract>

쓰기 가능 일꾼이면: tools 줄을 빼서 전체 도구를 주거나 명시(이 경우 yield 자동 추가), forkContext: allowed, <constraints>에 “diff는 작고 되돌릴 수 있게” 류를 넣는다.

만들 때 체크리스트:

  • name + description 둘 다 있는가 (없으면 파싱 자체가 실패)
  • 읽기전용이면 <constraints>에 mutate 금지 명시 + 필요한 bashAllowedPrefixes만 화이트리스트
  • tools 명시했다면 yield 자동 추가됨을 이해(결과 제출 경로)
  • 하위 위임 시키려면 spawns(또는 toolstask) 설정
  • 5명 이상 병렬이면 SpawnPlanReceipt 5필드 채울 준비
  • <output_contract>에 판정 라벨/영수증 형태 고정(부모가 파싱)
  • 신규 번들 에이전트면 agents.tsEMBEDDED_AGENT_DEFS.md import 추가
  • await 타임아웃을 실패로 다루지 말 것(관찰창 규약)

요약 & 셀프체크

3줄 요약:

  • 메인 에이전트는 task 도구로 역할이 정해진 부하들을 격리된 작업공간에서 병렬 실행하고, 끝나면 압축 영수증만 받는다.
  • 네 종류(executor 쓰기 / architect·planner·critic 읽기전용)는 명찰(프론트매터)로 권한과 산출 계약이 못 박혀 있다.
  • 5명 초과 병렬은 계획서 제출이 강제되고, await 타임아웃은 실패가 아니라 관찰창일 뿐이다.

스스로 답해보기:

  1. 부하 명찰에 name만 있고 description이 없으면 어떻게 되나? (힌트: 파싱 결과)
  2. 읽기전용 검토자가 그래도 실행할 수 있는 bash 명령은 무엇이고 왜 예외인가?
  3. 부모는 왜 부하의 풀텍스트 출력을 직접 받지 않고 영수증만 받나? 풀텍스트가 필요하면 어떻게 하나?

연결

GJ_개요 · _분석축_루브릭

[!tip] Codex 교차검증 보존 기존 분석 노트의 근거 파일 목록은 모두 유효하다. 핵심 소스:

  • task/agents.ts — 빌드타임 임베드, EMBEDDED_AGENT_DEFS, loadBundledAgents, task 인라인 프론트매터
  • prompts/agents/executor.md · architect.md · planner.md · critic.md — 4종 계약
  • prompts/agents/frontmatter.md — 프론트매터 직렬화 Handlebars 템플릿
  • discovery/helpers.tsparseAgentFields(yield/spawns 추론)
  • task/parallel.tsmapWithConcurrencyLimit, Semaphore
  • task/worktree.ts — 격리 백엔드/baseline/델타패치/브랜치 병합
  • task/name-generator.tsgenerateTaskName
  • task/output-manager.tsAgentOutputManager
  • task/spawn-gate.tsDEFAULT_SPAWN_THRESHOLD, SpawnPlanReceipt, 게이팅
  • task/receipt.tsbuildTaskReceipt, BANNED_RAW_TASK_KEYS, ROI/sanitize
  • task/executor.ts — 서브에이전트 실행 루프, AbortReason(signal/terminate/timeout), maxRuntimeMs 하드리밋
  • AGENTS.md(44행) — “Subagent await timeouts are observation windows, not failure signals” 모든 경로 접두사: /home/seunghyeong/harness-work/gajae-code/packages/coding-agent/src/