autoresearch 분석: 쓸만한 테크닉 베끼기
오토리서치에서 따올만한 테크닉. 루프 수행 / 각 루프의 평가전략 / 자연어 결과에 대한 평가 를 다룹니다.
autoresearch 분석: 쓸만한 테크닉 베끼기
최근 테스트 작업 계획 수립, 번역 문서 시리즈 생성을 autoresearch를 통해 하고 있다. 만족도가 높고, 특히 반복시행-평가 루프와 그를 유지하기 위한 주변 테크닉이 잘 되어 있어서 정리해보려 한다.
원본은 uditgoenka/autoresearch에 있다.
여기의 모든 테크닉과 원본 소스를 다루기보다는, 스킬/에이전트 동작을 구현할 때 차용할 수 있는 기술을 나열한다.
차용 테크닉 목록
1. 루프 구조 — 반복 개선 루프를 어떻게 짜는가
1-1. 에이전트 동작 명시
1-2. 1회 1변경 규칙
1-3. 루프 종료 조건 설계 (바운드, 정체 탐지)
2. 스코어링 — 성공/실패를 어떻게 판정하는가
2-1. 숫자 지표가 있을 때 (Verify 커맨드)
2-2. 숫자 지표가 없을 때 (블라인드 저지)
2-3. keep/discard 판정 로직
3. Git 상태 관리 — 루프 안에서 실험을 안전하게 추적/복원하는 방법
3-1. 검증 전 커밋, 실패시 revert
3-2. git log를 학습 기억으로 읽기
3-3. 사전 조건 검사 (Phase 0)
4. 기타 안전장치 — 깨지지 않게 하는 장치들
4-1. Guard (회귀 방지)
4-2. TSV 결과 로깅
4-3. 노이즈 핸들링
4-4. 크래시 복구
autoresearch의 기능을 도식화하면 아래와 같다.
PLAN LOOP DEBUG FIX SECURE SHIP
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Goal │ │ Modify │ │ Find │ │ Fix │ │ STRIDE │ │ Stage │
│ Metric │────▶│ Verify │────▶│ Bugs │────▶│ Errors │────▶│ OWASP │────▶│ Deploy │
│ Scope │ │ Keep/ │ │ Trace │ │ Repair │ │ Red │ │ Release │
└──────────┘ │ Discard │ └──────────┘ └──────────┘ │ Team │ └──────────┘
/autoresearch: └──────────┘ /autoresearch: /autoresearch: └──────────┘ /autoresearch:
plan /autoresearch debug fix /autoresearch: ship
security
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Scenario │ │ Predict │ │ Learn │ │ Reason │
│ Edge │ │ 5-Expert │ │ Docs │ │ Debate │
│ Cases │ │ Swarm │ │ Gen │ │ Converge │
└──────────┘ └──────────┘ └──────────┘ └──────────┘
/autoresearch: /autoresearch: /autoresearch: /autoresearch:
scenario predict learn reason
1. 루프 구조
이 툴은 "XX 목표를 달성하라"라고 명시했을 때, 단일 워크플로 혹은 에이전트 지침을 만들지 않는다.
그 대신, "XX 달성을 위한 루프 제작"을 만들고 루프를 실행함을 목표로 한다.
이 장은 루프 동작에 대한 규칙을 다룬다.
autoresearch의 루프는 아래 9단계로 고정되어 있다. 각 세부 지침은 생략하고 순서만 나열한다.
LOOP (FOREVER or N times):
1. Review: Read current state + git history + results log
2. Ideate: Pick next change based on goal, past results, what hasn't been tried
3. Modify: Make ONE focused change to in-scope files
4. Commit: Git commit the change (before verification)
5. Verify: Run the mechanical metric (tests, build, benchmark, etc.)
6. Guard: If guard is set, run the guard command
7. Decide:
- IMPROVED + guard passed → Keep commit
- IMPROVED + guard FAILED → Revert, rework (max 2 attempts)
- SAME/WORSE → Git revert, log "discard"
- CRASHED → Try to fix (max 3 attempts), else log "crash"
8. Log: Record result in results log
9. Repeat: Go to step 1.
각 단계의 상세 규칙은 .claude/skills/autoresearch/references/autonomous-loop-protocol.md에 있다.
이 스킬셋을 착안해서 다른 에이전틱한 기능을 만드려면, 이 md를 레퍼런스로 주고 약간의 변형만 해서 실행 루프를 만들라고 하는 것이 도움이 될 수 있다.
1-1. 에이전트 동작 명시
루프를 만들고 실행하려면, 스킬 진입점에서 agentic한 동작임을 명시하는 것이 유리하다. autoresearch 뿐 아니라, 이런 프롬프트 기법은 다른 자료에서도 보인다.
적용 내용
파일 위치: .claude/skills/autoresearch/SKILL.md
트리거: /autoresearch 스킬 실행시 SKILL.md 전문이 로드된다.
**Core idea:** You are an autonomous agent. Modify → Verify → Keep/Discard → Repeat.
EXECUTE IMMEDIATELY — do not deliberate, do not ask clarifying questions before reading the protocol.
- **DO NOT** ask "should I keep going?" — keep iterating unless a halt condition fires
- **DO NOT** summarize after each iteration — just log and continue
- **DO** print a brief one-line status every ~5 iterations
- **DO** alert if you discover something surprising or game-changing
- **DO** print a final summary when bounded loop completes
1-2. 1회 1변경 규칙
루프의 3단계(Modify)에서 에이전트가 한 번에 여러 가지를 바꾸지 않도록 제한하는 규칙이다.
1변경 = 1커밋이 되어, 메트릭 변화의 원인이 명확해지고 실패시 revert가 깔끔하다.
"변경이 한 가지에 대해서만 수행되었는가?"의 판단을 "변경 내용이 한 문장으로 서술되는가?"로 판단하는 휴리스틱한 방법을 가지고 있다.
이 방법이 베스트라는 보장은 없지만, 현재 돌려본 결과 러프하게 좋은 타협안이 만들어진다. 추후에 에이전트 루프를 만들 때 차용할 수 있을 것 같아서 기록.
적용 내용
파일 위치: .claude/skills/autoresearch/references/autonomous-loop-protocol.md (Phase 3)
- Make ONE focused change to in-scope files
- The change should be explainable in one sentence
- Write the description BEFORE making the change (forces clarity)
**The one-sentence test:** If you need "and" to describe it, it's two changes. Split them.
| One Change (OK) | Two Changes (Split) |
|-----------------|---------------------|
| Change port 3000→8080 in Dockerfile + compose + nginx | Change port AND add new service |
| Add Redis in compose + app config + env vars | Add Redis AND refactor auth module |
1-3. 루프 종료 조건 설계
종료 조건은 두 가지가 있다.
1) 바운드 모드 — 횟수 지정
파일 위치: references/autonomous-loop-protocol.md (Phase 8)
IF current_iteration < max_iterations:
Go to Phase 1
ELSE:
Print final summary
STOP
2) 정체 탐지 — 개선이 멈추면 멈춤
같은 파일, Plateau Detection 섹션:
best_metric = baseline metric from iteration 0
iterations_since_best = 0
plateau_patience = 15
When iterations_since_best >= plateau_patience:
PRINT "Plateau detected — best metric has not improved in {plateau_patience} iterations"
AskUserQuestion:
options:
- "Stop here"
- "Continue with reset patience"
- "Change strategy"
번역: 최고 메트릭이 15회 연속 갱신되지 않으면 정체로 판단한다. 멈출지, 계속할지, 전략을 바꿀지 유저에게 묻는다.
추가로 "stuck" 조건도 있다 (같은 파일, When Stuck 섹션):
### When Stuck (>5 consecutive discards)
1. Re-read ALL in-scope files from scratch
2. Re-read the original goal/direction
3. Review entire results log for patterns
4. Try combining 2-3 previously successful changes
5. Try the OPPOSITE of what hasn't been working
6. Try a radical architectural change
번역: 5회 연속 discard되면, 파일을 처음부터 다시 읽고, 이전 성공을 조합하거나, 반대 방향을 시도하거나, 급진적 변경을 시도한다. 정체 탐지는 "루프를 멈추는" 것이고, stuck은 "루프 안에서 방향을 바꾸는" 것이다.
2. 스코어링
위의 루프에서, 각 이터레이션에서 "이것이 올바르게 개선되고 있는가?"를 조건으로 탈출 조건과 다음 시행에서의 목적을 수립했다. 그러면 각 단계에서 "좋아짐/나빠짐"의 조건을 어떻게 설정해야 하는 것이 권장되는지, 내부적인 규칙과 "숫자로 나타낼 수 없는 평가"를 어떻게 다루는지에 대해 다룬다.
2-1. 숫자 지표가 있을 때 (Verify 커맨드)
목적
루프의 각 이터레이션에서 "좋아졌는가/나빠졌는가"를 판정하려면 숫자가 필요하다. 숫자로 결과가 나와야 하기 때문에, 빌드, 테스트, 실행 커맨드를 수행하고 로그를 보는 식으로 동작함을 가정한다.
주요 포인트
- Verify 커맨드의 최종 출력은 숫자 하나여야 한다 (빌드 로그의 속도, 혹은 테스트의 커버리지 등)
- 추출된 값이 숫자가 아니면 metric-error로 처리하고 revert한다
- 연속 2회 metric-error면 파이프라인 자체가 깨진 것으로 보고 루프를 중단한다
실제 레퍼런스
파일 위치: references/autonomous-loop-protocol.md (Phase 5), references/core-principles.md (원칙 3)
If you can't verify with a command, you can't iterate autonomously.
**Anti-pattern:** "Looks better", "probably improved", "seems cleaner"
→ these KILL autonomous loops because there's no decision function.
IF extracted_value does NOT match pattern: ^-?[0-9]+\.?[0-9]*$
STATUS = "metric-error"
IF previous_iteration.status == "metric-error":
STOP (even in unbounded mode)
2-2. 숫자 지표가 없을 때 (블라인드 저지)
목적
기획, 아키텍처 제안, 글쓰기 등 숫자 메트릭이 없는 주관적 영역에서는 "좋아졌는가?"를 커맨드로 판정할 수 없다. autoresearch는 이 경우 :reason 서브스킬에서 블라인드 저지 패널을 숫자 메트릭 대신 사용한다.
주요 포인트
흐름:
후보A 생성 → 비판 → 후보B 생성 → AB 합성 → 블라인드 저지가 승자 선택
→ 승자가 다음 라운드의 기준이 됨 → 수렴까지 반복
각 단계의 역할 분리:
- Author-A: 태스크만 보고 첫 후보를 생성한다
- Critic: 후보A만 보고 공격한다. 태스크 설명을 안 준다 (task-anchoring 방지). 수정안을 제시하지 않고 약점만 찾는다
- Author-B: 태스크 + 후보A + 비판을 보고, 비판을 반영한 후보B를 생성한다
- Synthesizer: 태스크 + 후보A + 후보B만 보고 (비판은 안 봄), 둘의 장점을 합친 AB를 생성한다
- Judge (N명): 후보들에 랜덤 라벨(X/Y/Z)을 붙여서, 누가 원본이고 누가 합성인지 모르는 상태에서 승자를 고른다
저지 규칙:
- 반드시 승자를 골라야 한다 (동점 불가)
- 길이나 스타일이 아니라 정확성, 완전성, 논리로 평가
- 후보의 구체적 텍스트를 인용해야 한다
- 수렴 조건: 같은 승자가 3회 연속 이기면 종료
주의: 현재 구현은 같은 컨텍스트에서 역할극으로 수행된다. Claude가 Author-A로 쓴 내용을 기억한 채로 Critic을 수행하므로, 실제 컨텍스트 격리는 되지 않는다. Agent tool로 서브에이전트를 띄우면 진짜 격리가 가능하지만, autoresearch는 그렇게 하지 않는다.
예시: "대시보드 페이지를 SSR로 할지 CSR로 할지" 결정
라운드 1:
[Author-A] (태스크만 받음)
→ "SSR을 추천한다. 초기 로딩 속도가 빠르고, SEO에 유리하고,
서버에서 인증 처리가 간단하다."
[Critic] (후보A만 받음, 태스크 설명 없음)
→ WEAKNESS-1 [MAJOR]: "초기 로딩 속도가 빠르다"고 했지만,
대시보드는 인증 뒤에 있어서 SEO가 무의미하다.
→ WEAKNESS-2 [FATAL]: 실시간 데이터가 많은 대시보드에서 SSR은
매 요청마다 서버 부하가 걸리는데, 이에 대한 언급이 없다.
→ WEAKNESS-3 [MINOR]: "인증 처리가 간단하다"의 근거가 없다.
→ VERDICT: 서버 부하 문제를 완전히 무시했다.
[Author-B] (태스크 + 후보A + 비판을 받음)
→ "CSR + lazy loading을 추천한다. 대시보드는 로그인 뒤에만
접근하므로 SEO 불필요. 실시간 데이터는 WebSocket으로 처리하고,
초기 번들은 코드 스플리팅으로 경량화한다."
[Synthesizer] (태스크 + 후보A + 후보B만 받음, 비판 안 봄)
→ "하이브리드를 추천한다. 초기 셸은 SSR로 빠르게 보여주고,
대시보드 위젯은 CSR로 로드한다. 실시간 데이터는 WebSocket."
저지 단계 (3명):
라벨 셔플: X = AB(하이브리드), Y = B(CSR), Z = A(SSR)
저지는 X/Y/Z만 보고 누가 원본인지 모름.
Judge 1: WINNER: X — "서버 부하와 초기 로딩을 둘 다 다룬다"
Judge 2: WINNER: Y — "하이브리드는 복잡도 대비 이점이 불명확하다"
Judge 3: WINNER: X — "구체적인 트레이드오프 분석이 가장 낫다"
결과: X(=AB, 하이브리드) 2표, Y(=B, CSR) 1표
→ 승자: AB(하이브리드)
라운드 2~3:
AB가 incumbent(현 챔피언)가 됨.
→ 새로운 Author-A가 AB를 개선 시도
→ 새로운 Critic이 공격
→ 새로운 Author-B가 대안 제시
→ 새로운 Synthesizer가 합성
→ 저지 투표
만약 AB가 또 이기면 → consecutive_wins = 2
라운드 3에서도 이기면 → consecutive_wins = 3 → 수렴. 종료.
실제 레퍼런스
파일 위치: references/reason-workflow.md
Critic 프롬프트:
You are an adversarial critic. Your job is to ATTACK the following candidate ruthlessly.
RULES:
1. Find MINIMUM 3 distinct weaknesses (more is better)
2. Each weakness must be SPECIFIC — quote or reference the exact claim
3. Weaknesses must be SUBSTANTIVE — not stylistic nitpicks
4. Do NOT offer fixes — only attack
5. Rate each weakness by impact: FATAL | MAJOR | MINOR
라벨 랜덤화:
1. Generate a random permutation: shuffle([A, B, AB]) → [AB, A, B]
2. Assign display labels: X = AB, Y = A, Z = B
3. Judges see only X, Y, Z — never A, B, AB
저지 프롬프트:
EVALUATION RULES:
1. You MUST pick a winner. "Tie" is not acceptable
2. Evaluate on: accuracy/correctness, completeness, reasoning quality, practical applicability
3. Your reasoning must cite SPECIFIC text from the candidates
4. DO NOT pick based on length — longer is not better
5. DO NOT pick based on style — substance wins
2-3. keep/discard 판정 로직
목적
2-1에서 숫자를 뽑고, 2-2에서 저지가 승자를 골랐으면, 그 결과로 "이번 변경을 유지할지 버릴지"를 결정해야 한다. 이 판정에 모호함이 없어야 루프가 멈추지 않는다.
주요 포인트
숫자 지표가 있을 때:
- 메트릭이 올랐고 가드도 통과하면 keep. 그 외는 전부 revert
- 가드 실패시 테스트를 고치는 게 아니라 구현을 고친다 (최대 2회 시도)
- 판정 경로가 4가지로 고정되어 있어서 에이전트가 "어떡하지?"로 멈출일이 없다
이터레이션 6:
커버리지 86% → 88% (올랐음) + npm test 통과 → keep
이터레이션 7:
커버리지 88% → 90% (올랐음) + npm test 실패 (기존 테스트 깨짐)
→ revert 후, 같은 방향으로 다시 시도하되 테스트가 안 깨지게 구현을 바꿈 (최대 2회)
→ 그래도 실패하면 discard
이터레이션 8:
커버리지 88% → 87% (떨어짐) → 무조건 discard, revert
숫자 지표가 없을 때 (블라인드 저지):
- 저지가 현 챔피언을 이기면 챔피언 교체, 못 이기면 유지
- 같은 챔피언이 3회 연속 이기면 수렴 종료
- 챔피언이 5번 이상 교체되는데 연속 승리가 없으면 → 진동으로 판단, "이 태스크 자체가 본질적으로 모호할 수 있다"고 판정하고 강제 종료
실제 레퍼런스
파일 위치: references/autonomous-loop-protocol.md (Phase 6), references/reason-workflow.md (Phase 7)
숫자 지표:
IF metric_improved AND (no guard OR guard_passed):
STATUS = "keep"
ELIF metric_improved AND guard_failed:
safe_revert()
FOR attempt IN 1..2:
Analyze guard output → rework implementation (NOT tests)
ELIF metric_same_or_worse:
STATUS = "discard"
safe_revert()
ELIF crashed:
STATUS = "crash"
safe_revert()
블라인드 저지:
1. Update consecutive_wins counter:
- If round_winner == incumbent → consecutive_wins += 1
- If round_winner != incumbent → consecutive_wins = 1, update incumbent = round_winner
2. Check stop conditions:
- Bounded mode: if round >= N → STOP
- Convergence: if consecutive_wins >= convergence_threshold → STOP
- Max oscillation guard: if incumbent has changed 5+ times
with no consecutive wins → STOP and flag oscillation
3. Git 상태 관리
루프가 돌면서 코드를 반복적으로 수정하면, "어디까지 성공했고 어디서 실패했는가"를 추적해야 한다. autoresearch는 별도 DB 없이 Git을 상태 관리 도구로 쓴다.
루프에서의 Git 사용 전략(검증 전 커밋, revert 전략, git log 학습, 사전 조건 검사)은 별도 문서에서 다룬다.
4. 기타 안전장치
루프가 돌다 보면 다양한 방식으로 깨질 수 있다. 최적화하다 다른 걸 망가뜨리거나, 노이즈에 속아서 잘못된 판정을 하거나, 에이전트 자체가 죽거나. 이 장은 그런 상황을 방지/복구하는 장치들을 다룬다.
4-1. Guard (회귀 방지)
목적
메트릭을 올리는 데 집중하다 다른 것을 깨뜨리는 걸 방지한다. Verify는 "올리려는 것", Guard는 "깨지면 안 되는 것"이다.
주요 포인트
- Guard는 유저가 설정한다 (선택사항, 안 쓸 수도 있음)
- 메트릭이 올라도 Guard가 실패하면 revert
- Guard 실패시 테스트를 고치지 않고 구현을 고침 (최대 2회)
Goal: 커버리지 90%로 올리기
Verify: npx jest --coverage → 커버리지 % 추출 ← 올리려는 것
Guard: npm run typecheck ← 깨지면 안 되는 것
실제 레퍼런스
파일 위치: references/autonomous-loop-protocol.md (Phase 5.5)
Guard: npm test
Guard: npx esbuild src/index.ts --bundle --minify | wc -c
Guard-Direction: lower is better
Guard-Threshold: 5%
- Only run if a guard was defined (it's optional)
- Run AFTER verify — no point checking guard if the metric didn't improve
- If guard fails, revert the optimization and try to rework it (max 2 attempts)
- NEVER modify guard/test files — always adapt the implementation instead
번역: 가드는 선택사항이다. verify 후에 실행한다 (메트릭이 안 올랐으면 가드 체크할 필요 없음). 가드 실패시 최적화를 revert하고 재시도한다 (최대 2회). 가드/테스트 파일은 절대 수정하지 않고 구현을 고친다.
4-2. TSV 결과 로깅
목적
매 이터레이션의 결과를 파일로 기록해서, 에이전트가 다음 이터레이션에서 패턴을 읽을 수 있게 한다. "어떤 종류의 변경이 성공하는가?"를 인식하는 데 쓰인다.
주요 포인트
- 매 이터레이션 후 TSV 파일에 한 줄 추가
- 이터레이션 시작시 최근 10~20줄을 읽어서 패턴 인식
- 5회 연속 discard 감지 → stuck 프로토콜 발동
- git에 커밋하지 않는다 (.gitignore)
실제 레퍼런스
파일 위치: references/results-logging.md
iteration commit metric delta guard guard-metric status description
0 a1b2c3d 85.2 0.0 pass - baseline initial state — coverage 85.2%
1 b2c3d4e 87.1 +1.9 pass - keep add tests for auth middleware
2 - 86.5 -0.6 - - discard refactor test helpers (broke 2 tests)
3 - 0.0 0.0 - - crash add integration tests (DB connection failed)
4 - 88.9 +1.8 fail - discard inline hot-path functions (guard: 3 tests broke)
# Phase 1 (Review): Read recent entries for pattern recognition
tail -20 autoresearch-results.tsv
# Detect stuck state: >5 consecutive discards triggers recovery
LAST_5=$(tail -5 autoresearch-results.tsv | awk -F'\t' '{print $6}')
4-3. 노이즈 핸들링
목적
벤치마크 시간, API 응답 시간 같은 메트릭은 측정할 때마다 값이 흔들린다. 노이즈에 속아서 잘못된 keep/discard를 하는 걸 방지한다.
주요 포인트
- 전략 1: 3~5회 측정 후 중앙값 사용
- 전략 2: 최소 개선 임계값 설정 (예: 2% 이하 개선은 무시)
- 전략 3: 개선된 것 같으면 한 번 더 측정해서 확인
- 노이즈 없는 메트릭(커버리지 %, 번들 사이즈)은 그냥 쓴다
| Metric Type | Noise Level | Strategy |
|-------------|-------------|----------|
| Test coverage (%) | None | No special handling |
| Bundle size (bytes) | None | No special handling |
| Benchmark time (ms) | Medium | Multi-run median (3 runs) |
| Lighthouse score | Medium | Multi-run median (5 runs) |
| ML training loss | High | Environment pinning + confirmation run |
| API response time | High | Warm-up + multi-run + min-delta |
실제 레퍼런스
파일 위치: references/autonomous-loop-protocol.md (Phase 5.1)
중앙값:
for i in 1 2 3; do
npm run benchmark 2>&1 | grep 'avg' | awk '{print $2}'
done | sort -n | sed -n '2p' # median of 3 runs
최소 임계값:
Min-Delta: 2.0 # only keep if improvement > 2%
확인 실행:
IF metric_improved:
second_metric = run_verify()
IF abs(second_metric - first_metric) / first_metric < 0.01:
STATUS = "keep" # confirmed
ELSE:
STATUS = "discard" # first result was noise
4-4. 크래시 복구
목적
에이전트 자체가 죽을 수 있다 (API 타임아웃, 컨텍스트 초과, 유저가 프로세스 종료 등). 다음 실행시 어디서 죽었는지 판단하고 자동 복구한다.
주요 포인트
- Git 상태와 결과 로그를 크로스 체크해서 어느 단계에서 죽었는지 판단
- 커밋 전에 죽었으면 → 변경 버림
- 커밋 후 검증 전에 죽었으면 → 검증 안 된 커밋이므로 revert
- 검증/로깅 다 끝난 후에 죽었으면 → 복구할 것 없음
실제 레퍼런스
파일 위치: references/autonomous-loop-protocol.md (Session crash)
IF working tree is dirty (changes not yet committed):
# Agent crashed during Phase 3 (modify) — before commit
git checkout -- <in-scope files>
LOG "Recovered from session crash: discarded uncommitted modifications"
IF last commit is "experiment(...)" with no matching results log entry:
# Agent crashed after Phase 4 (commit) but before Phase 6 (decide)
safe_revert()
LOG "Recovered from session crash: reverted unverified experiment"
IF working tree is clean AND last commit has a results log entry:
# Nothing to recover. Resume normally.