# Giver 아키텍처에서 얻은 인사이트

**작성**: 2026-05  
**근거**: 모놀리식~v3.6.6, 28체인, 동일과제 실측, 9개 버전 진화

---

## 1. 문제의 원인이 보이는 곳에 없다

v3.6.1에서 Worker 토큰이 너무 많이 나왔을 때, 원인이 'Worker가 `read()`로 너무 많이 읽어서'일 거라고 생각했다. 이건 절반은 맞았다. v3.6.2에서 reads:auto-inject로 과다 읽기(over-reading)를 줄이니 tk/byte가 171× → 63×로 개선됐다. 하지만 여전히 63×였다.

v3.6.3에서 알게 된 진자 원인: Worker 토큰의 약 75%가 테스트 실행 출력이었다. 각 Worker가 `npx vitest run`(44개 전체 테스트)을 돌릴 때마다 테스트 출력 수만 토큰이 컨텍스트에 쌓였다. `read()` 호출이 아니라 **실행 출력**이 토큰이 많이 나온 원인이었다. v3.6.3은 Target Verification로 Worker 검증 범위를 제한했지만, Planner가 전체 스위트를 복사하는 회귀를 일으켰다. v3.6.6는 검증 섹션 자체를 T₀에서 제거하여 구조적으로 방지.

```
겉으로 보이는 증상:   "Worker 토큰이 과다하다"
첫 가설:       "read() 과다" → v3.6.2 해소 → 여전히 63×
진짜 원인:     "검증 과다(over-verification)"  → v3.6.3 해소 → 12× (Planner 순응 시)
구조적 해결:   "검증 명령 자체를 T₀에서 제거" → v3.6.6 → Planner 복사 회귀 불가능
```

**핵심**: 겉으로 보이는 증상과 실제 원인이 다를 수 있다. tk/byte 비율이 이걸 분리해줬다 — 바이트(우리가 준 정보)와 토큰(LLM이 실제로 처리한 양) 사이 갭이 프롬프트 말고 어디서 토큰이 나왔는지를 보여줬다. 토큰만 보면 '많이 읽는구나' 하기 쉽다, 바이트와 토큰을 같이 봐야 read()와 실행 출력을 구분할 수 있다.

어떤 지표를 쓰느냐에 따라 문제가 안 보일 수 있다. 토큰만 측정하면 과다 읽기와 과다 검증을 구분할 수 없다. 바이트+토큰+tk/byte 세 축이 있어야 각 원인을 따로 분리할 수 있다.

---

## 2. 직교(Orthogonal) 개선이 곱해진다

너무 많이 읽는 것과 너무 많이 검증하는 건 전혀 다른 문제다.

- **과다 읽기**: `read()` 도구로 대형 파일 반복 호출 → 파일 내용이 프롬프트 밖에서 컨텍스트에 들어옴
- **과다 검증**: `npx vitest run` 전체 스위트 실행 → 테스트 출력이 컨텍스트에 들어옴

하나를 고쳐도 나머지는 그대로다. 그래서 개선이 곱해져서 떨어진다:

```
v3.6.1 → v3.6.2: 과다 읽기 해소    → tk/byte 171× × 0.37 = 63×
v3.6.2 → v3.6.3: 과다 검증 해소    → tk/byte  63× × 0.19 = 12×
누적 효과:                              171× × 0.37 × 0.19 = 12×
```

독립적인 원인을 각각 고치니 효과가 171×에서 12×까지 떨어졌다. 하나의 원인이라고 하면 나눌 수 없고, 직교 축으로 분해하면 각 축의 개선이 서로 간섭하지 않고 누적된다.

**핵심**: "토큰이 많다"를 하나의 원인으로 다루면 각 원인을 따로 분리할 수 없다. 증상은 하나인데 원인이 여럿일 수 있다. 각 원인을 따로 식별하고 고치면 개선 효과가 곱해진다.

---

## 3. 턴 증가는 예측 불가하다 — 그래서 애초에 격리한다

어려운 구현은 턴이 많아진다. 이건 미리 알 수 없다. W4가 18턴이나 20개 도구를 쓸 줄은 미리 알 수 없다.

모놀리식은 턴이 늘어나면 I/O가 누적되고, 스티어링(목표, 제약)이 컨텍스트 맨 아래로 밀려난다. 턴이 갈수록 소스 코드, 테스트 출력, 에러 로그 같은 코딩 I/O가 스티어링을 밀어낸다.

```
모놀리식 (턴 누적):
  턴 1:  스티어링 상단, I/O 작음
  턴 5:  스티어링 중간, I/O 증가
  턴 18: 스티어링 하단, I/O 지배 → 방향 상실 → 턴 더 증가 → 악순환

Giver Worker (fresh):
  턴 1: 스티어링 상단, I/O 0
  턴 6: 스티어링 상단 유지, 이 Worker의 I/O만 누적
  종료: 이 Worker의 I/O만, 다른 Worker I/O는 격리됨
```

핵심: **턴이 늘어나는 걸 자체로는 막을 수 없다.** 구현이 얼마나 어려운지는 미리 알 수 없다. 하지만 턴이 늘어났을 때 어떻게 되는지는 통제할 수 있다. 격리는 각 Worker의 턴 누적을 다른 Worker와 분리한다.

```
모놀리식: 턴 N의 I/O가 턴 1의 스티어링을 잠식 → 수정 루프에 빠지면 복구 불가
Giver:    턴 N의 I/O가 이 Worker 내에만 누적 → 다른 Worker 스티어링은 보존됨
```

격리는 턴 증가를 막는 게 아니라, 턴이 늘어난 게 **옆으로 퍼지지 않도록** 한다. W4가 18턴이나 돌아도 W1~W3의 결과는 그대로고, W5는 W4의 I/O 없이 fresh로 시작한다.

실패 경로에서도 같다:
```
모놀리식 전체 실패: 115K 버림 + 처음부터 다시 (스티어링 다시 퇴화)
v3.6.3   W4만 실패: W1~W3 결과 보존 + W4만 재실행 (격리 유지)
```

**핵심**: 턴 증가는 예측 불가하므로, 애초에 격리해서 전파를 차단한다. 이게 '최소 침투'와 '관심사 격리' 원칙의 실체다 — 각 Worker의 턴 증가가 다른 Worker에 영향을 주지 않도록 벽을 세우는 것. 재시도 비용 O(1)은 이 벽 덕분에 얻는 효과지, 벽 자체의 목적은 아니다.

---

## 4. 컨텍스트는 자원(Resource)이다, 제약(Constraint)만이 아니다

다들 '컨텍스트 윈도우 한계'를 이야기하지만, 이 프로젝트가 보여준 건 컨텍스트가 **할당해야 하는 자원**이라는 것이다.

컨텍스트를 다루는 세 가지 방식이 있다:

| 모드 | 할당량 | 효과 |
|------|--------|------|
| Fresh | 최소 (T₀만) | 격리. 부모 노이즈 0. 하지만 처음부터 시작해야 함 |
| Fork | 최대 (부모 전체) | 축적. 하지만 v1에서 Worker 평균 1.9M토큰 — 노이즈가 스티어링을 압도함 |
| T₀ 압축 | 선택적 전달 (7섹션) | 대화 전체를 ~5KB로 손실 압축. 정확도는 Giver의 압축 품질에 의존 |

Giver는 결국 컨텍스트를 어디에 얼마나 줄까를 결정하는 시스템이다. 각 경계마다 "이 에이전트에게 지금 필요한 정보가 무엇인가?"를 결정해서 그것만 전달하고 나머지는 격리한다:

```
G→P:  격리율 99%  (Giver 대화 ~500K토큰 중 T₀ ~5KB만 전달)
P→Wₖ: 격리율 83~93%  (다른 Worker 태스크 격리)
Wₖ→G: 격리율 98~99%  (Worker 실행 전체 중 RESULT만 회수)
```

**핵심**: "컨텍스트가 부족하다"가 아니라 "컨텍스트가 너무 많이 들어온다"가 진짜 문제다. 각 에이전트에 꼭 필요한 만큼만 컨텍스트를 할당하는 게 핵심 설계 결정이다. GGON 원칙 3(인지 부하 관리)의 본질이 바로 이거다.

인사이트 #3과 연결: 턴이 늘어나는 걸 막을 수 없으니 격리로 전파를 차단한다. 그럼 격리해서 무엇을 보존할까? 그게 바로 컨텍스트 할당의 문제다. T₀의 7섹션이 보존할 스티어링의 목록이고, 격리가 버릴 것(I/O 노이즈)의 차단막이다.

---

## 5. T₀의 품질이 병목이다

v1~v3.5의 W/P 분석에서 명확한 패턴이 나타난다:

```
P_input < 100K → W/P > 10× (71%의 체인)
P_input > 1M  → W/P < 2×  (0%의 체인)
```

T₀가 부실하면 Worker가 직접 파일을 읽으러 간다. T₀가 충실하면 Worker가 T₀만으로도 작동한다.

Giver의 T₀는 대화 내용(수십만 토큰)을 6개 섹션 약 5KB로 압축하는 함수다. 이 압축의 질이 하류 전체를 결정한다. Planner와 Worker는 T₀ 말고 정보가 없으니 T₀가 정확해야 제대로 일한다.

하류 최적화(reads:auto-inject)는 T₀의 불완전성으로 인한 2차 문제를 해결하는 거다. Worker가 과다 읽는 건 T₀에 구현 패턴이 없으니까. v3.6.3은 T₀에 검증 범위(Target Verification)를 넣었지만 Planner가 전체 스위트를 복사하는 회귀를 일으켰다. v3.6.6는 T₀에서 검증 섹션 자체를 제거하고 Worker에게 "각자 자기 범위만"이라는 합리적 설명으로 대체. 구조적 방지가 지시보다 강하다.

**핵심**: 파이프라인에서 가장 중요한 결정 하나: **Giver가 T₀에 뭘 넣고 뭘 빼는가**. 6섹션 구조가 이 결정의 틀이고, 섹션이 빠지면 하류에서 문제가 터진다.

---

## 6. 설계 원칙은 발견된 것이지 발명된 게 아니다

GGON 5원칙은 처음부터 있던 게 아니다. 하나하나 실제 실패에서 나왔다:

| 원칙 | 발견된 실패 | 증거 체인 |
|------|------------|----------|
| 최소 침투 | Worker가 Target Files 밖 파일 수정 | v3.5 이전 체인에서 빈번 |
| 중앙 제어 존중 | Worker가 아키텍처 결정 자의적 내림 | "follow existing patterns" → Worker 81턴 루프 |
| 인지 부하 관리 | T₀가 너무 장황하거나 너무 빈약 | P_input < 100K → W/P > 10× |
| 관심사 격리 | Worker가 참조 파일까지 수정 | v3.5 초기 Signatures 불명확 |
| 리팩터 가치 | "코드를 더 낫게"라며 무분별 리팩터링 | Worker가 "개선한다"며 범위를 벗어남 |

좋은 원칙은 '이런 실패가 자꾸 일어난다'는 관찰에서 나온다. 규범에서 나온 게 아니다.

**핵심**: 설계 원칙은 사후에 만들어진다. 반복되는 실패 패턴을 정리한 게 원칙이지, 원칙을 먼저 정해놓고 실패를 막은 게 아니다. "원칙부터 정하고 설계하는" 접근은 대강 맞을 뿐이고, "실패에서 원칙을 추출하는" 접근이 실제로 작동하는 원칙을 만든다. Giver의 9개 버전 진화가 이것의 증거다.

---

## 7. 메트릭 진화가 인사이트를 만든다

이 프로젝트에서 쓴 지표는 진화했고, 진화할 때마다 새 인사이트가 나왔다:

| 메트릭 | 질문 | 보이는 것 | 안 보이는 것 |
|--------|------|----------|---------|
| 성공/실패 | "됐나?" | 작동 여부 | 왜 안 되는지, 왜 비싼지 |
| W/P 비율 | "Planner 큐레이션이 충분한가?" | Planner 부실 → Worker 과다 | 토큰의 원인 분해 불가 |
| bytes vs tokens | "프롬프트 외부에서 무엇을 했나?" | 읽기 vs 실행 분리 | Worker 간 비교 불가 |
| tk/byte | "Worker가 과도하게 처리했나?" | 과다 읽기/과다 검증 식별 | 동일과제 비교 불가 |
| 동일과제 비교 | "구조가 효율 vs 효과에 미치는 영향?" | 총토큰 ≠ 실제비용 | — |

새 메트릭은 이전 메트릭이 보지 못한 문제를 드러냈다. W/P 비율은 "Planner가 충분한가?"를 보여줬지만 Worker 토큰의 원인을 분해하지 못했다. tk/byte는 원인을 나눠서 볼 수 있었지만 다른 과제와 비교할 기준이 없었다. 동일과제 A/B 비교는 구조적 가치(격리 vs 총토큰)를 숫자로 보여줬다.

**핵심**: 새 인사이트는 새 메트릭에서 온다. 같은 메트릭으로 계속 보면 같은 결론에 머문다. 메트릭 자체를 진화시키는 것이 분석 능력을 확장하는 핵심이다. "무엇을 측정할 것인가"가 "무엇을 개선할 것인가"보다 먼저다.

---

## 8. 소설 은유가 아키텍처를 예측했다

로이스 로리의 《기억 전달자》는 처음에 그냥 비슷해 보이는 정도였다. 하지만 분석이 진행될수록 이 비슷함이 구조적으로 정확하다는 걸 알게 됐다:

| 소설 | 아키텍처 | 정확도 |
|------|----------|--------|
| 기억 전달자가 모든 기억 보유 | Giver가 모든 대화 컨텍스트 보유 | 맞음 — Giver만 전체 맥락을 가짐 |
| 수령자는 전달받은 것만 받음 | Worker는 T₀/태스크만 수신 | 정확 — Worker는 fresh, 컨텍스트 0에서 시작 |
| 고통의 전달 (giving of pain) | Past failures 섹션 | 정확 — 실패 기억을 선택적으로 주입 |
| 전달은 선택적이고 의도적 | T₀의 7섹션 구조 | 정확 — 포함/제외가 명시적 설계 결정 |
| 기억이 아래로 새지 않음 | 격리율 99% | 정확 — 하류로 컨텍스트 역류 차단 |

핵심: **기억을 가진 에이전트와 작업을 실행하는 에이전트는 분리해야 한다.** 소설에서 기억 전달자와 공동체를 분리한 이유와 같다 — 축적된 기억(컨텍스트)이 실행(작업)에 섞이면 방향을 잃는다.

**핵심**: 좋은 은유는 구조를 예측한다

---

## 9. 명령 충돌 시 더 강한 신호가 이긴다

v3.6.7에서 Worker에게 "echo {previous}"와 "Write a brief RESULT"를 동시에 지시했다. 33588327 체인에서 모든 Worker가 echo를 건너뛰었다. "brief"가 "echo"보다 우선한 것이다.

이건 #7 "지시보다 정체성"의 확장이다. 정체성이 규칙보다 강하고, 규칙 중에서도 충돌하면 더 강한 신호가 이긴다:

```
정체성 > 강한 규칙 > 약한 규칙
"you own your scope" > "brief" > "echo previous result"
```

"brief"는 출력 길이에 대한 직접적 지시다. "echo"는 기술적 요구사항이다. 모델은 간결함을 우선하여 기술적 요구사항을 희생한다.

해결: 충돌을 제거하라. "brief"를 제거하고 "Reproduce"를 첫 지시어로. 충돌이 없으면 모델이 둘 다 따른다.

**핵심**: 충돌하는 두 지시가 있으면 모델이 하나를 선택한다. 이 선택은 예측 불가하다. 충돌 자체를 제거하는 게 지시를 강화하는 것보다 낫다. '이게 저거랑 비슷하네'가 아니라 '이게 저거랑 구조가 같네'일 때, 원본에서 검증된 패턴을 가져올 수 있다. 기억 전달자의 '선택적 전달'이 T₀의 6섹션 압축 함수가 됐고, '고통의 전달'이 Past failures 섹션이 됐다.

---

## 요약

Giver 프로젝트가 주는 가장 큰 인사이트 하나로 말하면:

**턴 증가는 예측 불가하므로, 애초에 격리로 전파를 차단한다.** 각 Worker의 턴 증가가 다른 Worker에 영향을 주지 않도록 벽을 세우는 것 — 이게 최소 침투와 관심사 격리의 실체다. 재시도 O(1)과 스티어링 보존은 이 벽의 결과물이다.

격리를 구현하려면:
1. **컨텍스트를 자원으로 인식** — 넣는 것만큼 빼는 것도 설계 결정이다
2. **T₀ 압축 품질** — 파이프라인의 병목은 Giver의 압축 함수다
3. **직교 원인 분해** — 과다의 원인을 독립 축으로 나누면 개선이 곱해진다
4. **메트릭 진화** — 새 질문이 새 메트릭을 만들고, 새 메트릭이 새 인사이트를 만든다
5. **원칙은 사후적** — 실패에서 추출한 원칙만이 작동한다
6. **지시보다 구조** — "이 검증만 실행하라"는 어기지만, 검증 명령이 없으면 어길 수도 없다
7. **지시보다 정체성** — 규칙("verify only your changes")은 어기지만, 정체성("you own your scope")은 행동을 바꾼다. 페르소나가 절차적 지시보다 강하다. b91d3d73에서 "각자 자기 범위만"이라도 템플릿 예시(`npx vitest run`)가 더 강한 신호로 작동했다. 예시를 제거하고 정체성을 부여하면 모델이 스스로 판단한다.
---

## 10. 통신 채널은 구조로 보장한다

v3.6.7에서 "Echo the previous Worker result" 지시를 추가했다. 33588327에서 모든 Worker가 echo를 무시했다. v3.6.8에서 "Reproduce"로 지시어를 강화했다. 3f1c72d0에서도 모든 Worker가 무시했다. 지시로는 안 된다.

v3.7.0에서 results.md로 교체했다. W2+의 reads에 results.md를 넣으니 pi-subagents가 파일을 자동 주입한다. Worker가 안 읽을 수 없다. append도 소스 코드 쓰기와 같은 문맥이라 잘 지켜진다.

패턴이 같다:
- v3.6.4: 검증 명령 제거 → Planner 복사 회귀 구조적 방지
- v3.7.0: echo 지시 제거 → results.md reads 자동 주입 구조적 보장

**지시보다 구조** (#6의 확장): 불만족스러운 지시를 더 강한 지시로 대치하는 게 아니라, 지시가 필요 없는 구조로 바꾼다.

v3.7.1 실측 (67df5f65): W1~W5 전부 output에 RESULT 포맷 작성, results.md에 5개 RESULT 누적. W2+ reads=['taskN.md', 'results.md'] 자동 주입. {previous} 없이도 구조적으로 정보 전달 보장. W평균tk/t 19K (v3.6.8과 동급).

### #11 같은 파일을 수정하는 Worker는 하나로 묶어라

v3.7.1 체인(67df5f65): Planner가 arg-parsers.ts를 수정하는 5개 태스크를 5개 Worker에 분리. 각 Worker가 전체 1338 테스트를 실행 → 총 6,690 테스트 실행. 같은 파일을 순차 수정하면 직렬 실행이 강제되고, 검증 비용이 Worker 수만큼 배가된다.

해결: 같은 파일을 수정하는 modification group은 하나의 Worker에 병합. 병렬 실행 가능한 group만 분리.

### Insight #12: 통신 채널은 구조로 보장한다 — NOOP도 마찬가지 (v3.7.5)
"지시로는 NOOP Worker가 write를 피하게 할 수 없었다. completionGuard라는 구조적 메커니즘을 활용하면, NOOP가 write를 안 하면 자동으로 exitCode=1이 되어 chain이 break된다. 구조가 보장하면 지시는 필요 없다."

### Insight #13: [CHAIN COMPLETED] — 시그널은 대괄호로 (v3.7.5)
"NOOP Worker가 [CHAIN COMPLETED]를 출력하면 Giver가 즉시 '성공 종료'로 인식. 대괄호 패턴은 일반 텍스트와 구분되어 파싱이 쉽다. W2~W10은 results.md를 reads로 받으므로, 시그널과 함께 완료된 작업 결과도 전달 가능. completionGuard는 write 툴콜만 체크하므로 text 출력에는 반응하지 않는다."

### Insight #14: completionGuard 에러 메시지가 시그널이다 (v3.7.5)
"Worker가 '[CHAIN COMPLETED]'를 출력해도 completionGuard가 가로채서 에러 메시지로 대체한다. 따라서 Giver는 Worker의 출력이 아닌 completionGuard의 시스템 에러 메시지를 인식해야 한다. 'Subagent completed without making edits for an implementation task.'가 바로 '모든 작업 완료' 시그널이다. Worker 지시 준수 여부와 무관하게 구조적으로 보장된다."

### Insight #15: completionGuard 메시지는 하드코딩이다 (v3.7.5)
"pi-subagents의 completionGuard 에러 메시지는 소스에 하드코딩되어 있다. step.completionGuard는 boolean(true/false)만 지원하고 메시지 커스터마이징은 불가능하다. 에러 메시지: 'Subagent completed without making edits for an implementation task. It appears to have returned planning or scratchpad output instead of applying changes.' Giver는 이 메시지를 인식하여 성공 종료로 해석한다."
