지나가던행인이 (61.♡.201.240)
2026년 4월 13일 PM 11:43

tunaFlow 기술 포스트 시리즈 2편입니다. 1편에서 "에이전트에게 프로세스를 줘라"는 이야기를 했는데, 이번 편은 그 프로세스를 실제로 어떻게 구현 했는지에 대한 이야기입니다. 현재 최종 테스트와 컨텍스트팩으로 발생하는 많은양의 토큰을 어떻게 줄여야 할지 다 방면으로 테스트 중입니다 😁 크다면 크고 아무것도 아니라면 그런 문제 일 수도 있지만, 어느 하나 트레이드 오프하고 싶지 않은 욕심에 공개 시기가 늦어지고 있습니다. 별 것 아닌 프로젝트에 관심 가져주셔서 감사합니다. 멋지고 훌륭하진 못해도 허접하지 않은 오픈소스로 보답드리겠습니다. 🙏
---
에이전트한테 "이거 만들어줘"라고 하면
Claude Code에 "인증 미들웨어 리팩토링 해줘"라고 하면 바로 코드를 쓰기 시작합니다. 설계 없이. 리뷰 없이. 잘 될 때도 있지만, 복잡한 작업일수록 문제가 생깁니다.
- 영향 범위를 파악하지 않고 구현부터 시작
- 수정한 뒤에 "아, 이 방향이 아니었네" 하고 되돌리기
- 자기가 짠 코드를 자기가 리뷰하면서 "잘 짠 것 같습니다 ✅"
이건 사람이 해도 생기는 문제입니다. 팀에서는 코드 리뷰가 이걸 잡아주는데, 에이전트 혼자 쓰면 이 안전장치가 없습니다.
tunaFlow의 워크플로우 파이프라인은 이 문제를 풀기 위한 것입니다. 사람이 하는 개발 프로세스(설계 → 승인 → 구현 → 리뷰)를 에이전트에게 적용하는 구조.
> 이 워크플로우의 영감은 Stavros Korokithakis의 블로그에서 왔습니다. 그가 Claude Code로 프로젝트를 진행하면서 자연스럽게 만들어간 흐름 — Plan을 먼저 세우고, 승인 후 구현하고, 결과를 리뷰하는 — 을 보면서 "이걸 도구가 지원해야 하는 것 아닌가?"라는 생각이 출발점이었습니다.
---
7단계 워크플로우
tunaFlow의 워크플로우는 7단계입니다.
1. Chat — 사용자가 Architect에게 요구사항 설명
2. Drafting — Architect가 Plan 제안
3. Approval — 사용자가 Plan 승인/거부/수정요청
4. Dev — Developer가 코드 구현
5. Review — Reviewer(들)가 리뷰
6. Rework — 리뷰 실패 시 수정 → 재리뷰
7. Done — 완료각 단계 사이에 자동 전환이 있고, 사람이 개입하는 지점이 명확합니다. 전체를 한 번에 구현한 것이 아니라 단계별로 만들었고, 그 과정에서 만난 문제와 결정을 공유합니다.
---
마커 기반 감지: 에이전트 출력에서 구조를 뽑아내기
첫 번째 고민은 "에이전트가 Plan을 제안했다는 것을 어떻게 감지하는가"였습니다.
SDK를 쓴다면 function calling으로 submit_plan_proposal({ title, subtasks }) 같은 구조화 출력을 받을 수 있습니다. 하지만 tunaFlow는 CLI subprocess 방식이라 에이전트 출력이 자유 형식 텍스트입니다.
그래서 HTML 주석 마커를 사용합니다.
<!-- tunaflow:plan-proposal -->인증 미들웨어 OAuth2 전환
subtasks
- JWT 검증 로직을 OAuth2 PKCE로 교체
- Route guard 어댑터 수정
- 테스트 업데이트
description
현재 JWT 기반 인증을 OAuth2 PKCE로...
<!-- /tunaflow:plan-proposal -->
에이전트의 Persona(역할 프롬프트)에 "Plan을 제안할 때는 이 마커 안에 넣으세요"라는 규칙이 있고, 프론트엔드에서 이 마커를 파싱합니다.
// planProposalParser.ts
const MARKER_OPEN = "<!-- tunaflow:plan-proposal -->";
const MARKER_CLOSE = "<!-- /tunaflow:plan-proposal -->";
export function hasPlanProposal(content: string): boolean {
return content.includes(MARKER_OPEN);
} 마커가 감지되면 일반 텍스트 대신 PlanProposalCard UI 컴포넌트로 렌더링됩니다. 사용자는 카드에서 바로 승인/거부/수정요청을 할 수 있습니다.
왜 HTML 주석인가
<!-- tunaflow:plan-proposal --> 는 마크다운에서 렌더링되지 않습니다에이전트가 마커를 넣어도 일반 마크다운 렌더러에서는 보이지 않고, tunaFlow만 감지합니다. 실수로 마커가 출력되어도 사용자에게 노출되지 않습니다.
마커의 한계
마커 기반이라 에이전트가 형식을 안 지키면 감지가 안 됩니다. 실사용에서 이런 경우가 있었습니다.
- 에이전트가 마커를 닫지 않음 <!-- /tunaflow:plan-proposal --> 누락)
- 마커 안에 subtasks 섹션이 없음
- 마커를 코드블록 안에 넣어서 파싱 불가
대응은 두 가지입니다. 첫째, Persona 프롬프트에 형식 규칙을 상세하게 적습니다. 둘째, 파서를 관대하게 만듭니다 — 닫기 마커가 없으면 메시지 끝까지를 본문으로 간주, subtasks가 없으면 description만으로 카드 표시.
---
Approval Gate: 사람이 결정하는 지점
Architect가 Plan을 제안하면, 사용자에게 3가지 선택지가 표시됩니다.
┌───────────────────────────────────┐
│ Plan: 인증 미들웨어 OAuth2 전환 │
│ │
│ Subtasks: │
│ 1. JWT → OAuth2 PKCE 교체 │
│ 2. Route guard 수정 │
│ 3. 테스트 업데이트 │
│ │
│ [승인] [수정 요청] [보류] │
└───────────────────────────────────┘- 승인: Implementation Branch가 자동 생성되고, Developer 에이전트에게 구현 지시가 전송됩니다
- 수정 요청: 사용자가 피드백을 입력하면 Architect에게 재제안 요청이 갑니다
- 보류: 나중에 결정
처음에는 승인/거부 2가지만 있었는데, 실사용에서 "방향은 맞는데 subtask를 좀 바꿔줘"라는 경우가 많아서 수정 요청을 추가했습니다.
왜 3-way인가
2-way(승인/거부)면 거부 후 Architect에게 "왜 거부했는지"를 다시 설명해야 합니다. 3-way면 수정 요청에 바로 피드백을 적을 수 있어서 한 번에 끝납니다.
// ApprovalGate 내부
case "revise":
// 피드백 입력 → Architect에게 재제안 요청
await requestPlanRevision(plan, feedback, architectEngine);
break;---
Implementation Branch: 구현은 격리 공간에서
Plan이 승인되면 자동으로 Branch가 생성됩니다. Branch는 tunaFlow에서 "대화 분기"를 의미합니다 (3편에서 상세히 다룹니다).
승인 클릭
→ branches 테이블에 새 레코드 생성 (mode: "chat")
→ shadow conversation 생성 (branch:{branchId})
→ Plan 문서 생성 (docs/plans/{slug}.md)
→ Developer에게 구현 프롬프트 전송Developer는 이 Branch 안에서 코드를 구현합니다. 메인 대화와 격리되어 있어서, 구현이 실패해도 메인 대화에 영향이 없습니다.
Developer에게 보내는 프롬프트는 이런 구조입니다.
### 🔧 구현 시작
Plan: "인증 미들웨어 OAuth2 전환"
작업 지시서:
- docs/plans/auth-oauth-migration.md
- docs/plans/auth-oauth-migration-task-01.md
- docs/plans/auth-oauth-migration-task-02.md
규칙:
- 각 task 파일을 읽고 순서대로 구현
- 각 완료 시 <!-- tunaflow:subtask-done:N -->
전체 완료 시 <!-- tunaflow:impl-complete -->
여기서도 마커가 쓰입니다. Developer가 subtask를 완료하면 <!-- tunaflow:subtask-done:1 --> 마커를 출력하고, tunaFlow가 이를 감지해서 Plan 상태를 업데이트합니다.
---
Review RT: 리뷰는 다른 에이전트가
구현이 완료되면 자동으로 Review Roundtable(RT)이 시작됩니다. RT는 여러 에이전트가 토론하는 구조입니다 (4편에서 상세히 다룹니다).
Developer 완료 (impl-complete 감지)
→ Review Branch 자동 생성
→ 테스트 실행 (run_project_tests)
→ 2명의 Reviewer에게 리뷰 프롬프트 전송
→ 각 Reviewer가 독립적으로 리뷰 → verdict 출력Reviewer 프롬프트에는 Plan 문서, 구현 결과 요약, 테스트 결과가 포함됩니다. Reviewer는 <!-- tunaflow:review-verdict --> 마커로 판정을 출력합니다.
<!-- tunaflow:review-verdict -->
verdict: pass
findings:
- 구현이 Plan과 일치합니다
- 테스트 커버리지 양호
recommendations: ws_handler.rs에서 인증 호출 방식 확인 필요
<!-- /tunaflow:review-verdict -->``
Reviewer는 코드를 수정하지 않습니다
이건 중요한 설계 결정이었습니다. Reviewer의 Persona에 "코드를 수정하지 마세요. 읽고 판정만 하세요"라는 규칙이 있습니다.
Do NOT modify any code, create commits, push changes.
MUST read files for review.
Submit review verdict only."이유: Reviewer가 코드를 고치면 Developer와 역할이 섞입니다. Reviewer는 판단만 하고, 수정은 Developer가 합니다. 사람 팀에서 코드 리뷰어가 직접 PR에 커밋하지 않는 것과 같습니다.
---
Rework: 실패하면 다시
Review verdict가 "fail"이면 Rework 단계로 전환됩니다.
Reviewer verdict: fail
→ Plan phase: "rework"
→ Developer에게 피드백 전달
→ Developer가 수정
→ 다시 Review RT 실행
→ verdict 확인
→ pass면 Done, fail이면 다시 Rework여기서 문제가 생겼습니다. 무한 루프.
Developer가 수정 → Reviewer가 fail → Developer가 수정 → Reviewer가 fail → ... 이 반복이 3번 이상 되면 같은 문제를 같은 방식으로 반복하는 경우가 많았습니다.
Doom Loop 감지
그래서 Doom Loop 감지를 넣었습니다. plan_events 테이블에서 review_failed 이벤트 횟수를 세고, 3회 이상이면 에스컬레이션합니다.
// reviewWorkflow.ts
const freshEvents = await planApi.listPlanEvents(plan.id);
let lastEscalationIdx = freshEvents.length;
for (let i = freshEvents.length - 1; i >= 0; i--) {
if (freshEvents[i].eventType === "doom_loop_escalated") {
lastEscalationIdx = i;
break;
}
}
const failCount = eventsSinceReset
.filter((e) => e.eventType === "review_failed").length;
3회 도달 시:
"Review 실패 3회 — 설계 재검토를 권장합니다.
Architect 재설계 또는 Developer 계속 rework 중 선택하세요."사용자에게 "이건 구현 문제가 아니라 설계 문제일 수 있습니다"라고 알려주는 것입니다. 계속 Rework할지, Plan 자체를 수정할지는 사람이 판단합니다.
Rework 카운터 리셋
중요한 디테일이 있습니다. Plan이 수정(revision)되면 실패 카운터가 리셋됩니다. 이전 Plan에서 3번 실패해도, 수정된 Plan으로 다시 시작하면 카운터가 0부터 시작합니다.
이건 Optio라는 GitHub CI 자동화 도구에서 영감을 받았습니다. Optio는 "마지막 수동 개입 이후의 자동 재시도 횟수"를 세는데, tunaFlow도 같은 패턴입니다.
---
사람이 개입하는 지점
전체 흐름에서 사람이 반드시 개입하는 곳은 3곳입니다.
1. Plan 승인 — "이 방향으로 갈까?"
2. Review 후 판단 — "리뷰 결과를 확인하고 다음 액션 결정"
3. Doom Loop 대응 — "3번 실패. 설계를 바꿀까, 계속 시도할까?"나머지(Branch 생성, Developer 프롬프트 전송, Review RT 시작, Rework 전환)는 자동입니다.
처음에는 "전부 자동이면 더 좋지 않을까"라고 생각했습니다. Review까지 자동으로 하고, pass면 자동 머지까지. 하지만 실사용에서 바로 문제가 드러났습니다.
- Reviewer가 "pass"를 줬는데 실제로는 엣지케이스를 놓친 경우
- Developer가 Plan의 의도와 다르게 구현했는데 Reviewer도 못 잡은 경우
- 전부 자동으로 돌았는데 결과물이 원하는 것과 완전히 다른 경우
사람이 확인하는 비용(클릭 한 번, 30초 읽기)보다 잘못 진행된 작업을 되돌리는 비용이 훨씬 큽니다. 그래서 "자동화는 하되, 판단 지점은 사람이"라는 원칙을 유지하게 됐습니다.
---
현재 한계
1. 워크플로우 오버헤드
간단한 수정(오타 수정, 한 줄 변경)인데도 Plan → Approve → Dev → Review를 거치면 과도합니다. 지금은 사용자가 판단해서 워크플로우를 건너뛰고 직접 채팅으로 해결할 수 있지만, "이건 워크플로우가 필요한 수준인가?"를 판단하는 것 자체가 비용입니다.
2. 마커 형식 이탈
에이전트가 마커 형식을 안 지키는 경우가 아직 있습니다. 특히 소형 모델(Haiku, Gemini Flash)에서 마커 형식 준수율이 떨어집니다. 관대한 파서로 대응하고 있지만, 근본적으로는 SDK function calling이 더 안정적입니다. 다만 CLI subprocess 방식을 유지하는 한 마커가 현실적인 선택입니다.
3. Review 품질
Reviewer가 "pass"를 줬다고 실제로 괜찮은 것은 아닙니다. Reviewer도 에이전트이기 때문에 놓치는 것이 있습니다. 특히 "이 변경이 다른 파일에 영향을 주는가"를 구조적으로 판단하기 어렵습니다. code-review-graph를 통합한 이유이기도 합니다 (7편에서 다룹니다).
4. Rework의 수렴 문제
Rework 루프가 돌아도 같은 문제를 반복하는 경우가 있습니다. Developer가 Reviewer 피드백을 받아서 수정하지만, 근본 원인을 해결하지 못하고 표면적인 수정만 하는 패턴. 이건 "이전에 비슷한 실패가 있었다"는 정보를 Developer에게 주는 Failure Learning 시스템으로 부분 대응하고 있습니다 (9편에서 다룹니다).
---
실사용 숫자
tunaInsight 프로젝트에서 워크플로우 풀사이클을 4회 테스트한 결과입니다.
- Plan → Done 평균 시간: 에이전트 실행 시간 기준 3-5분 (사용자 승인 대기 제외)
- Rework 발생 확률: 약 40% (4회 중 2회에서 1회 이상 Rework)
- Doom Loop 발생: 0회 (3회 연속 실패 없었음)
- 풀사이클 중 발견한 버그: 15개 (결과 문서 마커 잔존, Reviewer 템플릿 모순 등)
15개 버그는 주로 "자동 생성 문서의 품질"과 "마커 파서 엣지케이스"에서 나왔습니다. 워크플로우 로직 자체보다 주변 인프라(문서 생성, 마커 파싱)에서 문제가 많았습니다.
---
핵심 결정 정리
결정 | 이유 |
|---|---|
마커 기반 감지 | CLI subprocess라 function calling 불가. HTML 주석은 렌더링 안 됨 |
3-way 승인 (승인/수정요청/보류) | 2-way면 거부 후 재설명 비용. 수정 요청으로 1회에 해결 |
Dev Branch 격리 | 실패해도 메인 대화에 영향 없음. git branch와 유사 |
Reviewer는 코드 수정 금지 | 역할 혼동 방지. 판단과 실행을 분리 |
Doom Loop 3회 기준 | 너무 빠르면(1회) 정상 Rework도 차단, 너무 느리면(5회+) 토큰 낭비 |
Review 후 사람 확인 | 에이전트 pass ≠ 실제 pass. 사람이 최종 판단 |
다음 편 예고
3편: "대화를 분기한다" — Branch 설계와 활용
Implementation Branch, Review Branch가 어떻게 만들어지는지, 드로어 UX를 왜 선택했는지, adopt(결과 병합)는 어떻게 동작하는지를 다룹니다. 워크플로우와 Branch의 관계가 핵심입니다.
---
시리즈 목록
1. [에이전트에게 프로세스를 줘라](./01-에이전트에게-프로세스를-줘라.md) — AOC를 만들면서 배운 것
2. Plan → Dev → Review — 워크플로우 파이프라인 구현기 (이 글)
3. 대화를 분기한다 — Branch 설계와 활용
4. 에이전트끼리 토론시키기 — Roundtable 설계와 한계
5. 대화가 길어지면 — 에이전트 장기 메모리 구현기
6. Claude $20으로 워크플로우 돌리기 — 엔진 아키텍처
7. 코드 구조를 에이전트에게 알려주기 — rawq + code-review-graph
8. 246개 스킬 중 필요한 것만 — 스킬 자동 적용 구현기
9. 에이전트가 같은 실수를 반복하면 — 품질 보증 설계
10. tunaFlow로 풀사이클 돌려보기 — 워크플로우 실전 테스트 회고
- [기 발행] ContextPack — 멀티 에이전트 오케스트레이터의 컨텍스트 설계
---
레퍼런스
- Stavros Korokithakis — Claude Code 워크플로우 패턴. Plan → 승인 → 구현 → 리뷰 흐름의 영감. https://www.stavros.io/posts/how-i-write-software-with-llms/
- Optio (github.com/jonwiggins/optio) — CI 기반 자동 PR 관리. Doom Loop의 "마지막 수동 개입 이후 카운팅" 패턴 참고.
- Addy Osmani — "Orchestrating Coding Agents": Plan Approval Gate, Dedicated Reviewer 패턴. https://addyosmani.com/blog/code-agent-orchestra/
- tunaFlow 내부 문서
- 마커 파서: src/lib/planProposalParser.ts
- 워크플로우: src/lib/workflow/ (7파일 모듈)
- Plan 상태: src-tauri/src/commands/plans.rs
- Review: src/lib/workflow/reviewWorkflow.ts
- Doom Loop: src/lib/workflow/reviewWorkflow.ts:160
최근 글
댓글 (5)
-
알알바솔져
04.14 · 211.♡.83.242
-
지지나가던행인이
→ 알바솔져 작성자
04.14 · 118.♡.84.136
넵 열심히(!) 포스팅해보겠습니다! 즐겁게 봐주셔서 감사합니다 😁
- 알
알베르토
04.14 · 61.♡.144.104
어렴풋이 가능하지 않을까 생각만하고있던걸 구현하고 계시다니 결과물이 정말 기대됩니다
-
지지나가던행인이
→ 알베르토 작성자
04.14 · 118.♡.84.136
제가 하는건 아니고 오퍼스가 다하고 있어요 (소곤소곤)
-
칼칼쓰뎅
04.14 · 210.♡.41.89
전 codex 유저이고 플러스 요금제라... 그냥 싱글agent로 실시간 붙어서 대화하면서 진행하는데요.
prd 작성할때 구현의 자유로움을 주지않도록 리뷰 하면 (애매한 표현이거나 여러가지 방법으로 구현가능하거나?) 좀 많이 문제가 줄어드는듯해요.
댓글을 작성하려면 이 필요합니다.
와.. 좋습니다. 다음 내용도 계속 기대할게요.