Orca × KaizenLab × Loop Engineering: 자율형 AI가 실제 제품을 개선하는 '경계와 판단 요청' 메커니즘
요약
본 글은 AI 에이전트가 실제 웹 서비스에 가설을 세우고, 코드를 구현하며, 자동 테스트를 거쳐 실시간으로 제품 개선 루프를 돌리는 '제품 자율 개선 루프' 아키텍처를 설명합니다. 이 시스템은 Orca Automation과 Loop Engineering 플러그인을 결합하여 안전성과 실효성을 확보하는 것이 핵심입니다.
핵심 포인트
- AI 에이전트가 실제 서비스에 자동 배포 및 테스트하는 제품 자율 개선 루프 구축.
- Orca Automation, Loop Engineering 등 여러 기술을 조합해 시스템의 안정성(프로세스 보호)과 제어력을 높임.
- 인간 개입 지점(Decision Request)을 명확히 설계하여 AI의 위험한 판단이나 오염된 데이터 수집을 방지함.
이것은 무엇인가
AI 에이전트(Claude Code)가 실제 제품의 과제를 발견하고, 가설을 세우고, 코드를 구현하여 테스트를 통과시킵니다. 그 변경 사항을 인간의 승인 없이 main에 병합(merge)하여 실시간 환경에 자동 배포하고, 수일간의 측정 메트릭스에서 가설의 성공 여부를 판별합니다. 이를 3시간마다 무인으로 계속 순환시키는 '제품 자율 개선 루프(Product Loop)'를 구축했습니다.
대상은 가설 검증 플랫폼 KaizenLab 자체의 실제 서비스(Cloudflare Workers와 Supabase)입니다.
가설 수립 (HYPOTHESIZE)
→ 구현 & PR (BUILD)
→ 자동 테스트 & 병합 (SHIP)
...
퀀트 연구 같은 폐쇄된 시뮬레이션 환경과 달리, 실제 웹 서비스를 상대로 무인으로 루프를 돌릴 때는 운영 특유의 위험이나 기술적 과제가 발생합니다.
첫째, 주기적 실행 하네스나 대화 세션이 종료될 때, 백그라운드 작업이 프로세스 트리 전체와 함께 종료되는 문제가 있습니다.
둘째, AI가 승인 코드나 병합 스크립트, 채점 정의를 수정하여 제약을 스스로 무효화해 버릴 위험입니다.
셋째, 루프 자체가 서비스의 엔드포인트를 호출하여 분모를 부풀림으로써 오류율이 낮아졌다고 착각하게 만드는 지표 오염이 꼽힙니다.
넷째, 새로운 파일 추가나 사양 선택에 직면했을 때, AI가 위험한 판단을 내리거나 작업이 중단되어 버리는 과제입니다.
이러한 문제들을 해결하기 위해 설계한 것이 Orca Automation, Loop Engineering(toshipon-mode 플러그인), 그리고 KaizenLab의 판단 요청(Decision Request)과 Slack 연동을 결합한 자율 아키텍처입니다.

자율형 가설 검증 루프의 전체 아키텍처 (draw.io로 작성)
본고에서는 이 자율 루프의 구체적인 구조와, 안전성과 실효성을 지탱하는 방벽에 대해 설명합니다.
전체 아키텍처와 4가지 기둥
이 메커니즘은 크게 4개의 레이어로 구성되어 있습니다.
| 레이어 | 주요 역할 | 구현 수단 |
|---|---|---|
| 1. Orca Automation | 주기적 실행 트리거 및 프로세스 트리 보호 | precheck.sh , start-tick.sh , Orca PTY Terminal |
| 2. Loop Engineering | 결정표를 통한 자율 행동 선택과 물리적인 경계 제어 | toshipon-mode 플러그인, loop.yaml , records.sh |
| 3. KaizenLab & Slack | 인간에게 판단 요청(에스컬레이션) 및 원탭 답변 | MCP 툴, Slack Block Kit, 승인 체크 |
| 4. CI/CD & 실제 운영 | 자동 병합, Workers 자동 배포, 측정 메트릭스 집계 | loop-merge.sh , Cloudflare Workers, Supabase view |
1. Orca Automation: 주기적 실행과 프로세스 트리 보호
루프의 주기적 실행 기반으로는 Orca Automation을 채택했습니다. 실행 간격은 3시간마다(15 */3 * * *)입니다.
사전 판정(precheck.sh)으로 불필요한 LLM 구동 제로화
스케줄러가 시작될 때, 바로 Claude Code를 호출하지 않습니다. 먼저 경량의 셸 스크립트 precheck.sh을 실행합니다.
precheck.sh
여기서는 loop.yaml에 인간의 서명이 있는지, 긴급 정지용 paused.flag가 놓여있지 않은지, 다른 실행이 잠금(lock)을 보유하고 있지 않은지, 결정표가 '대기(WAIT)' 이외의 행동을 반환하는지를 판정합니다.
종료 코드가 0일 때만 LLM을 구동하도록 설계했습니다. 서명이 없거나 휴지 상태이거나, 측정 창 경과 대기로 할 일이 없을 때는 LLM을 전혀 사용하지 않고 수 밀리초 만에 종료되므로 불필요한 API 비용이 발생하지 않습니다.
Seat와 Terminal 분리 패턴
자율 루프 운영에서 가장 심각했던 것이 프로세스 트리의 함께 종료되는 문제였습니다.
Claude Code를 대화적으로 구동하는 감독 역할의 프로세스(Orca의 Seat)는, 대화 한 왕복이 끝나면 종료됩니다. 하지만 많은 에이전트 실행 기반 시스템은 세션 종료 시 관련 백그라운드 프로세스를 부모와 함께 모두 종료시킵니다. 장시간 테스트나 빌드가 도중에 갑자기 사라지는 트러블은 이 동작 때문에 발생했습니다.
이 문제를 해결한 것이, Orca의 PTY 바로 아래에 독립적인 Terminal 탭을 여는 접근 방식입니다.
# scripts/loop/start-tick.sh에서 발췌
cmd=$(printf '/bin/bash %q %q %q 2>&1 </dev/null | tee -a %q %q; exit' \
"$skill/scripts/loop-run.sh" "$repo" "$label" "$log_dir/orca.log" "$run_log")
...
Seat은 Terminal을 생성한 직후 wait-tick.sh로 대기 상태에 들어갑니다. Terminal은 Orca의 PTY host의 자식 프로세스로 동작하기 때문에, Seat의 컨텍스트가 종료되어도 실행 중인 tick 자체는 영향을 받지 않고 끝까지 돌아갑니다.

Orca Terminal에서 독립적으로 자율 주행하는 product-tick의 실행 로그
2. Loop Engineering: 결정표와 경계 제어
에이전트에게 '자유롭게 제품을 개선해 주세요'라고 맡기면, 높은 확률로 방황(迷走)이 시작됩니다. 무엇을 만들어야 할지 계속 고민하거나, 기존 코드를 망가뜨리거나, 자신의 권한을 멋대로 확장하려고 하기 때문입니다.
루프의 사고 로직은 오픈소스 Claude Code 플러그인 toshipon-mode의 loop-engineering skill이 담당하고 있습니다.
결정표(Decision Table)를 통한 유일한 상태 전이
루프의 행동은 프롬프트의 변덕에 맡기지 않고, 코드로 정의된 결정표에 의해 위에서부터 순차적으로 판정됩니다. 한 번의 tick으로 선택되는 액션은 항상 단 하나입니다.
| 순위 | 액션 | 실행 조건 |
|---|---|---|
| 1 | MEASURE | 측정 기간(7일 등)이 끝난 가설이 있다면, 점수를 매겨 합격/불합격을 판정한다 |
| ... | ||
| 이 결정표를 통해 에이전트는 '다음엔 무엇을 할까' 하고 고민할 필요 없이, 보드 상태에서 도출된 단 하나의 수순만을 착실히 진행할 수 있습니다. |
origin/main 준거의 경계 제어 (loop.yaml)
에이전트가 자율적으로 코드를 변경하여 PR을 만들 때, 가장 막아야 할 것은 에이전트 자신이 자신의 행동 범위를 넓히는 것입니다.
리포지토리 직하단의 product/loop.yaml에서, 변경을 허용하는 파일 경로(allowed_paths)를 명시적으로 좁혀서 지정하고 있습니다.
# product/loop.yaml
approved_by: toshipon
approved_at: 2026-10-07
...
allowed_paths: 왜 디렉터리가 아닌 '파일 단위의 명시 지정'인가
이 화이트리스트에서 가장 중요한 설계 방침은, 디렉터리 단위(app/src/mcp/ 등)로 묶어서 허용하지 않고, 파일을 하나씩 명시적으로 지정하는 점입니다.
예를 들어 app/src/mcp/ 디렉터리 아래에는, 에러 메시지 형태를 처리하는 errors.ts와 같은 안전한 파일이 있는 반면, 사용자의 권한 체크를 하는 소유권 확인 코드(supabase-tools.ts)나, 관리자 권한으로 데이터베이스를 조작하는 클라이언트 코드(supabase-client.ts)가 공존하고 있습니다.
만약 디렉터리 단위로 변경 권한을 부여해 버리면, 무인 에이전트가 승인 체크를 느슨하게 하거나 우회하는 코드를 작성하여, 그것을 프로덕션 환경에 자동 병합/배포할 수 있게 됩니다. 이는 중대한 보안 사고로 이어질 수 있습니다.
따라서 현재의 allowed_paths는, 승인 처리를 포함하지 않은 파일을 사람이 하나하나 검토해서 추가하고 있습니다.
허용된 파일
| 파일 | 역할 및 허가 이유 |
|---|---|
app/src/mcp/errors.ts | MCP 툴의 에러 메시지 및 결과 포맷팅만 담당하는 파일 |
app/src/mcp/__tests__/errors.test.ts | 포맷팅 로직을 검증하는 단위 테스트. 구현과 테스트를 동일 PR에서 업데이트할 수 있도록 함 |
app/src/lib/supabase/mappers/verification-canvas-mapper.ts | DB 레코드와 내부 데이터 구조 간의 상호 변환 (권한 처리는 포함하지 않음) |
app/src/mcp/tool-definitions.ts | MCP 툴의 설명문(description)만 업데이트 가능 (툴 이름이나 스키마는 별도 테스트에서 고정) |
app/public/.well-known/mcp/server-card.json | 설명문을 툴 정의와 일치시키기 위한 메타데이터 |
경계 정의 자체를 '경계 바깥'에 두기
나아가, 경계 정의 그 자체인 loop.yaml
그리고 채점표의 metrics.yaml은 당연히 allowed_paths
에 포함시키지 않았습니다.
이것들을 포함하게 되면 에이전트가 자신의 권한을 확장하는 코드를 작성하거나, 자신의 채점 기준을 느슨하게 수정할 수 있게 되기 때문입니다. 루프 자체에는 '자신의 경계를 확장할 권한'도 '자신의 채점표를 수정할 권한'도 일절 부여되지 않았습니다.
origin/main에 의한 물리적 병합 거부
만약 에이전트가 새로운 가설을 검증하기 위해 allowed_paths 바깥의 파일을 변경하고 싶다면, 에이전트 스스로 임의로 PR을 통과시킬 수 없습니다.
병합 게이트(merge gate)(loop-merge.sh)는 PR 브랜치 설정이 아니라, 반드시 origin/main에 있는 loop.yaml 을 읽어 검사합니다. 에이전트가 PR 내에서 loop.yaml을 수정하더라도, 병합 게이트가 보는 것은 origin/main의 원래 화이트리스트입니다. 리스트 외 파일에 단 하나라도 손을 댄 PR은 기계적으로 병합 거부(exit 3)됩니다. 이렇게 거부된 PR은 후술할 '판단 요청(Decision Request)'으로 등록되며, 인간 오너에게 Slack을 통해
예를 들어, 신규 기능을 위해 allowed_paths에 새로운 파일을 추가하고 싶거나, 외부 API의 사양 변경에 따라 하위 호환성을 어떻게 처리할지 선택하는 경우, 여러 유력한 설계 방침 중 오너(owner)의 생각에 맞는 선택을 내려야 하는 경우가 해당됩니다.
이러한 국면에서 에이전트가 위험한 판단을 내리거나 포기하고 멈춰버리면 루프가 지속되지 않습니다. 그래서 KaizenLab에 도입한 것이 '판단 요청(Decision Request)' 메커니즘입니다.
루프에서 인간으로의 안전한 에스컬레이션
루프가 경계 벽에 부딪혔을 때, 에이전트는 MCP 툴 create_decision_request를 호출합니다.
{
"title": "allowed_paths に errors.test.ts を追加したい",
"description": "PH-0002 の実装に伴い、errors.ts の単体テストを同じ PR で追加したいです。",
...
}
이 판단 요청이 생성되면, 프로젝트에 연결된 Slack 채널로 인터랙티브한 알림 메시지가 도착합니다.
Slack Block Kit을 이용한 원탭 조작
바쁜 오너가 매일 브라우저의 관리 화면을 열어 확인하는 것은 부담스러울 수 있습니다. 그래서 Slack 상에서 버튼을 누르는 것만으로 즉시 답변할 수 있는 시스템으로 구축했습니다.

Slack에 도착하는 판단 요청 알림(상)과, 오너가 답변한 후 자동 대체 표시(하)
Slack 연동에서는 몇 가지 중요한 노력을 기울였습니다.
첫째, 모달을 열지 않는 원탭 답변입니다. 권장 옵션 버튼을 누르면, 모달을 열 필요 없이 그 자리에서 답변이 KaizenLab에 저장됩니다.
둘째, 메시지의 즉시 대체입니다. 답변이 완료되면, Slack 상의 원래 메시지가 chat.update를 통해 답변 완료된 표시로 대체되기 때문에, 오래된 버튼을 실수로 다시 누를 염려가 없습니다.
셋째, 오너 승인 확인입니다. 누구든지 버튼을 누를 수 있는 것이 아니라, Slack 사용자 정보와 KaizenLab 사용자 정보를 대조하여 프로젝트의 오너(projects.owner_id)가 눌렀을 때만 답변을 받습니다.
넷째, 루프 자체의 답변 금지입니다. 루프 자신에게 판단 요청 답변 툴을 넘겨주지 않아, AI가 스스로 판단 요청을 만들고 스스로 승인하는 매치펌프를 막는 구조로 했습니다.
인간의 판단 수령 후 자율 재개
오너가 Slack에서 선택지를 누르면, 답변 내용이 KaizenLab에 기록됩니다. 다음 틱(tick, 3시간 후)에 에이전트는 결정표에 따라 acknowledge_decision_request를 호출하여 인간의 결정을 수령합니다. 인간이 내린 판단을 확인한 에이전트는 보류했던 빌드 작업을 재개하고, main으로 병합되는 흐름입니다.
4. '극장(Theater)' 방지 및 건전성 방어선
자율 개선 루프를 운영하는 데 있어 가장 경계해야 할 상태는 '극장(Theater)'입니다.
극장이란, 에이전트가 코드를 작성하고 PR을 만들고 배포를 반복하고 있지만, 핵심 지표에는 유의미한 변화가 나타나지 않아 그저 API 비용과 계산 자원만 낭비하는 상태를 말합니다.
1. 샘플 수가 쌓이는 엔드포인트를 선택
초기 설계에서는 웹 화면 UI 개선(클릭률 등)을 대상으로 하는 것도 고려했습니다. 하지만 초기 제품의 웹 화면 PV는 2주 동안 수십 건 정도밖에 되지 않아, 통계적 판단 창을 채우려면 몇 달이 걸립니다.
그래서 이번 루프에서는 외부 에이전트나 CLI에서 일상적으로 수십~수백 번 호출되는 MCP 엔드포인트(/api/mcp)를 개선 대상으로 설정했습니다.
| 지표명 | 측정 내용 | 판단 기준 |
|---|---|---|
count:mcp_tool_calls | MCP 툴의 총 호출 수 | 증가하는 것을 좋게 봄 |
rate:mcp_tool_failures | 호출 전체에서의 실패율 | 낮아지는 것을 좋게 봄 |
avg_ms:mcp_tool_call | 정상 종료된 툴의 평균 응답 시간 | 짧아지는 것을 좋게 봄 |
분모가 매일 확실하게 축적되는 곳을 대상으로 선택하는 것이 자율 루프를 성립시키는 전제 조건입니다.
2. 기능 불능을 인정하는 철수 기준
아무리 정교한 시스템을 구축하더라도, 가설 검증 자체가 제자리걸음을 하는 경우가 발생할 수 있습니다. 따라서 이 루프에는 객관적인 철수 기준이 설정되어 있습니다.
이 루프가 작동하지 않는다고 판단하는 조건
완료된 가설 기록 중 '판단 불가(inconclusive)'가 50%를 초과할 경우, 이 루프는 가설 검증을 수행하고 있지 못하다고 간주합니다.
결과가 좋아졌다는 것(validated)이든 나빠졌다는 것(invalidated)이든, 둘 다 프로덕트에게 귀중한 학습입니다. 하지만 차이가 나지 않거나, 샘플 부족으로 알 수 없는(inconclusive) 경우가 절반을 넘는다면, 이는 에이전트의 문제가 아니라 지표 설계나 측정 기간 설정에 결함이 있다는 증거입니다.
이 기준을 초과할 경우, 에이전트가 코드를 작성하는 것을 즉시 중단하고, 사람이 메트릭스 자체를 재검토하거나 루프를 중단합니다.
요약: AI와 인간의 협업 형태
Orca Automation, Loop Engineering, 그리고 KaizenLab의 판단 요청을 결합하여 실제 프로덕트의 자율 루프를 운영해 본 경험을 통해 한 가지 실감을 얻었습니다.
그것은, AI에게 모든 것을 맡기는 것의 대척점에 있는 것이 '인간이 전부 수작업으로 하는 것'이 아니라는 점입니다.
| 주체 | 역할 |
|---|---|
| 인간 | loop.yaml에서 물리적 경계(allowed_paths)를 설정하고, metrics.yaml에서 추적할 지표를 정의합니다. Slack으로 흘러들어오는 판단 요청에 대해 오너로서의 의지를 원터치로 결정합니다. |
| AI | 정해진 경계 안에서 24시간 365일 쉬지 않고 관찰, 가설, 구현, 테스트, 배포, 측정을 반복합니다. 경계의 벽에 직면했을 때 필요한 선택지를 정리하여 인간에게 판단을 요청합니다. |
인간이 코드를 한 줄씩 작성하는 개발 방식에서, 인간이 경계와 채점표를 설계하고 에이전트가 그 안에서 자율적으로 프로덕트를 개선해 나가는 방식으로 변화합니다.
이 시스템은 개인 개발자나 소규모 개발팀이 프로덕트의 품질과 검증 속도를 크게 확장(scale)시킬 수 있는 실용적인 접근 방식이라고 생각합니다.
관련 링크
토론 (Discussion)

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기