이 레포가 무엇을 하는 물건인가.
보통 AI 코딩은 "내가 켜고 → 시키고 → 받는" 수동 작업이다. loop-harness는 이 과정을 예약·자동화한다. "CI 깨지면 10분마다 점검해서 고쳐", "새 PR 올라오면 3분 안에 리뷰해", "매일 아침 의존성 업데이트 PR 올려" 같은 상시 임무를 등록해 두면, 사람이 보지 않는 동안에도 루프들이 돌아간다.
핵심은 안전장치다. 일하는 AI는 절대 직접 push·댓글을 못 하고, 결과를 '대기 줄(staging)'에만 쌓는다. 그걸 두 번째 AI가 검사해 VERDICT: PASS를 찍어야만 비로소 세상에 나간다.
loop-harness는 코딩 에이전트(특히 Claude Code)를 위한 "루프 엔지니어링(loop engineering)" 하니스다. '하니스(harness)'란 무언가를 붙잡아 안전하게 부리는 마구·받침대를 뜻한다. 즉 이 프로젝트 자체는 AI가 아니라, AI를 정해진 규칙·주기·검증 아래 가둬 자율로 굴리는 '운영 틀'이다.
구성은 단순하다. orchestrator.sh라는 데몬이 30초마다 깨어나 "지금 돌려야 할 루프가 있나?"를 확인하고, 있으면 격리된 작업 공간에서 claude -p(헤드리스 모드)를 띄워 일을 시킨다. 일이 끝나면 또 다른 claude -p가 그 결과를 검증한다. 통과하면 gh CLI로 PR을 열거나 댓글을 달고, Slack으로 알린다. 실패하면 로그만 남기고 다음 주기에 재시도한다.
트렌딩 이유 · 경쟁 대비 장점.
2026년 들어 "자율 에이전트를 어떻게 안전하게 24시간 굴릴까"가 큰 화두가 됐다. loop-harness가 주목받는 이유는 세 가지다. 첫째, 무거운 SaaS·프레임워크 없이 셸 스크립트 1,000줄로 '에이전트 운영체제'의 핵심을 다 보여준다 — 읽으면 전체가 머리에 들어온다. 둘째, 2-에이전트 검증 게이트(만드는 AI ≠ 검사하는 AI)라는 신뢰성 패턴을 정석대로 구현했다. 셋째, GitHub Actions 같은 클라우드 CI가 아니라 내 노트북/서버에서 로컬로 돌면서도 PR·리뷰·Slack까지 연결된다.
| 비교 항목 | 그냥 cron + claude -p | loop-harness |
|---|---|---|
| 안전 격리 | 현재 체크아웃에서 바로 작업 → 위험 | git worktree로 매번 격리된 사본 |
| 발행 통제 | AI가 곧장 push/댓글 → 사고 | 스테이징 후 검증 통과 시에만 발행 |
| 중복 작업 | 같은 이슈를 매번 다시 처리 | 상태 파일로 처리한 항목 dedup |
| 관측성 | 로그가 흩어짐 | dashboard.sh 실시간 상태표 |
| 장애 내성 | 한 작업이 죽으면 전체 중단 | 루프 하나 실패해도 데몬은 안 죽음 |
| 의존성 | — | bash·git·jq·curl·gh·claude 만 |
자율 에이전트를 그냥 cron에 걸어 두면, 잘못된 수정이 곧장 메인 브랜치에 push되거나, 엉뚱한 댓글이 고객 이슈에 달리거나, 한 작업이 무한 루프에 빠져 전체가 멈출 수 있다. "AI에게 셸 권한을 주고 사람 없이 돌린다"는 건 본질적으로 위험한 일이다.
① 쓰기 루프는 항상 일회용 worktree에서만 일한다(원본 불가침). ② 일하는 AI는 push·댓글 권한이 아예 없고 결과를 파일로만 '쌓는다'. ③ 두 번째 AI가 diff와 스테이징 결과를 검사해 VERDICT: PASS를 내야 한다. ④ 각 루프는 필요한 도구만(allowed_tools) 받는다. 게다가 모든 작업은 멱등(idempotent)이라 중복 실행이 안전하다.
이 레포는 2026년 6월 기준 커밋 4개, 스타 2개의 초기 프로젝트다. 인기는 '완성도'가 아니라 설계 아이디어와 코드 가독성 덕이다. README도 "당신 루프가 내보내는 것에 대한 책임은 당신에게 있다"고 못 박는다. 즉 제품으로 갖다 쓰기보다, 자율 에이전트 운영 패턴을 '교재'로 읽기에 최적이다.
무거운 프레임워크 0개. 유닉스 기본기로만 짠 자율 에이전트 런타임.
이 프로젝트의 가장 큰 특징은 언어가 셸 스크립트(bash) 단 하나라는 점이다. Python도, Node도, Docker도 없다. 대신 모든 개발자의 컴퓨터에 이미 있는 유닉스 도구들(git·jq·curl)과 CLI(gh·claude)를 '레고 블록'처럼 조립했다. 덕분에 git clone 후 chmod +x 한 번이면 바로 돈다.
| 레이어 | 기술 | 역할 |
|---|---|---|
| 실행 엔진 | bash 3.2+ (macOS 기본 호환) | 스케줄러 데몬·러너·상태관리 전부. set -uo pipefail |
| AI 두뇌 | claude (Claude Code CLI) | claude -p 헤드리스 모드로 1차 작업 + 2차 검증 |
| 상태 저장 | jq + JSON 파일 | 루프별 처리 이력·메트릭. DB 없이 파일 하나로 |
| 코드 격리 | git worktree | 루프마다 loop/<name>/<ts> 브랜치의 일회용 사본 |
| GitHub 연동 | gh (GitHub CLI) | PR 생성·리뷰 댓글·이슈 라벨·CI 로그 조회 |
| 알림 | curl → Slack Incoming Webhook | 웹훅 미설정 시 조용히 건너뜀(graceful) |
| 설정 | YAML (자체 파서) | awk/sed로 짠 미니 파서 — 외부 의존성 0 |
| 스케줄 | 자체 cron 매처 | every 10m / 5필드 cron식 둘 다 순수 bash로 해석 |
| 동시성 | 백그라운드 잡 + pidfile | &로 병렬 실행, concurrency 캡으로 제한 |
| 잠금 | mkdir 원자적 락 | 같은 루프 중복 실행 방지(디렉토리 생성의 원자성 이용) |
--append-system-prompt로 스킬을, --allowedTools로 권한을 주입한다.state/<loop>@<repo>.json 파일에 상태를 두고, jq로 "원자적 읽기-수정-쓰기"(임시파일에 쓰고 mv로 교체)를 한다.한 번의 루프가 어떻게 깨어나고, 일하고, 검증받고, 발행되는가.
전체 흐름은 README의 한 줄 그림으로 요약된다: 스케줄러 틱 → 마감된 루프 → worktree → 1차 에이전트 → 스테이징 → 검증 에이전트 → (PASS) 발행 → 상태 갱신. 이걸 파일 단위까지 펼치면 다음과 같다.
이 프로젝트의 심장. 일을 만드는 AI와 그것을 검사하는 AI를 분리한다. 1차 에이전트는 절대 외부로 무언가를 내보낼 수 없고(권한 자체가 없음), 오직 ① worktree 안의 커밋과 ② outbox 폴더 안의 '액션 파일'만 남긴다. 그러면 run_verifier가 별도의 claude -p 세션을 띄워 — 이번엔 읽기 전용 도구(Bash·Read·Glob·Grep)만 쥐어 주고 — "회의적으로 검사하라"는 시스템 프롬프트로 diff와 스테이징 결과를 점검한다.
# lib/runner.sh — 검증관 프롬프트의 핵심(요약)
local verifier_system="You are a strict verification agent...
# 1. git diff 가 최소·정확·시크릿/디버그 잔재 없는가?
# 2. PR_BODY/outbox 내용이 실제 파일·라인을 가리키는가?
# 3. 빠르게 가능하면 테스트/린트를 직접 돌려 본다
# 4. 에이전트의 보고가 디스크의 실제와 일치하는가?
# 마지막 줄은 반드시 'VERDICT: PASS' 또는 'VERDICT: FAIL — 이유'
if grep -q "VERDICT: PASS" "$vout"; then return 0; fi
return 1 # PASS 가 없으면 발행 차단
요리사와 검식관. 주방의 요리사(1차 AI)는 음식을 만들어 '패스 창구'에 올려만 둔다. 손님에게 직접 내가지 못한다. 별도의 검식관(2차 AI)이 간을 보고 "통과"를 외쳐야만 음식이 홀로 나간다. 만든 사람이 "맛있어요"라고 우기는 걸 막는 구조다.
1차 에이전트가 "이 이슈에 이런 댓글을 달자"고 결정하면, gh를 직접 호출하는 게 아니라 $LOOP_OUTBOX 폴더에 약속된 이름의 파일을 떨군다. 발행은 검증 후 flush_outbox가 파일명을 보고 대신 처리한다. 파일명 앞 NN- 숫자가 발행 순서를 정한다.
| 아웃박스 파일명 | 발행되는 동작 |
|---|---|
NN-issue-comment-<n>.md | 이슈 #n 에 댓글 |
NN-issue-label-<n>.txt | 이슈 #n 에 라벨(파일 내용: bug,P2) |
NN-pr-comment-<n>.md | PR #n 요약 댓글 |
NN-pr-inline-<n>.jsonl | 인라인 리뷰(줄마다 {path,line,body}) |
NN-pr-approve-<n>.md | PR 승인(내용=승인 노트) |
NN-slack.md | Slack 메시지 |
각 루프@저장소 조합마다 JSON 파일 하나가 '처리 이력'을 들고 있다. processed에 들어간 항목 ID는 다음 실행 때 1차 에이전트에게 "이건 건너뛰어"라고 알려 주는 목록이 된다. 상태 변경은 항상 임시파일에 jq로 쓰고 mv로 교체해, 중간에 죽어도 파일이 깨지지 않는다(원자적 쓰기).
// state/<loop>@<repo>.json — 한 루프의 기억
{
"last_run": 1760000000, "last_status": "success",
"processed": {"12345": "2026-06-10T09:00:00+0000"},
"in_progress": {},
"failures": [{"item":"98","error":"verification failed","retries":1}],
"metrics": {"runs":42,"successes":40,"items_processed":61}
}
오케스트레이터는 일부러 set -e를 안 쓴다(set -uo pipefail만). "루프 하나가 실패해도 데몬은 절대 죽으면 안 된다"는 원칙 때문이다. SIGTERM을 받으면 진행 중인 루프가 끝나길 최대 5분 기다린 뒤 우아하게 종료하고, 1초 단위로 쪼개 자며 신호에 빠르게 반응한다. 같은 루프의 중복 실행은 mkdir 락(디렉토리 생성이 실패하면 이미 도는 중)으로 막는다.
every 10m·every 1h·every 30s 같은 간격형과, 0 6 * * *(매일 06시) 같은 5필드 cron식. 둘 다 외부 라이브러리 없이 lib/common.sh의 순수 bash 함수로 해석한다.약 1,000줄, 18개 파일(셸 8 + YAML 5 + 스킬 MD 5)이 어떻게 나뉘어 있나.
| 경로 | 역할 |
|---|---|
lib/runner.sh | 레포의 두뇌. 격리→실행→검증→발행→정리 한 사이클이 전부 여기. 가장 먼저 읽어야 할 파일. |
lib/common.sh | 외부 의존성을 없애려고 직접 짠 YAML 파서·cron 매처가 들어 있는 '기반' 모듈. |
definitions/*.yaml | 루프의 "무엇을·언제·어떻게" — 주기·worktree 여부·output 타입·허용 도구·타임아웃. |
skills/*.md | 루프의 "어떻게 일하나" — 그대로 Claude의 시스템 프롬프트가 된다. 역할·단계·RESULT 형식 포함. |
connectors/ | git·gh·slack을 감싼 얇은 셸 래퍼. 읽기 명령은 에이전트도 직접 호출 가능, 발행 명령은 러너만. |
config.yaml | (사용자가 config.example.yaml을 복사해 작성) 어떤 저장소에 어떤 루프를 걸지 매핑. |
채용 공고 vs 직무 매뉴얼. definitions/*.yaml은 "이 자리는 3분마다 출근, 도구는 읽기 전용, 결과는 댓글로"라는 근무 조건(공고)이고, skills/*.md는 "PR을 이런 순서로 검토하고, 이렇게 보고하라"는 실제 일하는 법(매뉴얼)이다. 둘을 분리해 두면 같은 매뉴얼을 다른 주기·권한으로 재활용할 수 있다.
이 레포에서 배울 만한 것 + 어디를 보면 되는지.
자율 에이전트를 실전에 쓰려면 "만든 AI가 스스로 OK라고 우기는" 문제를 풀어야 한다. 해법은 만드는 세션과 검사하는 세션의 분리 + 검사 세션엔 읽기 권한만. lib/runner.sh의 run_verifier와 거기 박힌 '회의적 검증관' 시스템 프롬프트를 정독하면, LLM-as-judge를 운영에 녹이는 실전 감각이 잡힌다.
"AI에게 쓰기 권한을 어떻게 안전하게 줄까"의 모범답안. 쓰기는 일회용 worktree에서만, 발행은 검증 후 러너만. connectors/git-worktree.sh(생성/정리/푸시/커밋유무 판정)와 runner.sh의 flush_outbox를 같이 읽으면 "권한 최소화"가 코드로 어떻게 구현되는지 보인다.
외부 라이브러리 0으로 스케줄러·동시성·신호 처리·원자적 락을 구현한 교과서. set -uo pipefail을 쓰고 set -e는 피한 이유, mkdir 락의 원자성, trap으로 SIGTERM을 받아 1초씩 쪼개 자는 기법까지. orchestrator.sh가 통째로 좋은 예제다.
"라이브러리 없이 직접 짜기"의 실전. lib/common.sh의 yaml_get·config_repo_pairs(awk로 repo×loop 펼치기)와 cron_matches_now(*/5·범위·리스트 처리)를 읽으면, 작은 DSL을 셸로 파싱하는 방법을 배운다. "작게 만들려고 일부러 기능을 제한한" 설계 판단도 함께.
lib/state.sh의 _state_update는 "임시파일에 jq로 쓰고 mv로 교체"하는 원자적 패턴의 정석. 처리 이력·실패 재시도 횟수·메트릭을 파일 하나로 관리하는 법, 그리고 그게 어떻게 "중복 실행 안전"으로 이어지는지 본다.
돌리려면 무엇이 필요한가 (모두 흔한 CLI들).
| 항목 | 요구사항 |
|---|---|
| 셸 | bash 3.2+ (macOS 기본 bash로도 동작하도록 작성) |
| 필수 도구 | git · jq · curl — 대부분 이미 설치돼 있음 |
| GitHub CLI | gh 설치 + 인증(gh auth login 또는 GITHUB_TOKEN) |
| AI 두뇌 | claude (Claude Code CLI) 설치 + 로그인 |
| OS | macOS·Linux. GPU 불필요(모델을 로컬에서 돌리는 게 아니라 CLI를 호출) |
| 선택 환경변수 | LOOP_SLACK_WEBHOOK(알림) · LOOP_WORKTREE_ROOT(worktree 위치) · GITHUB_TOKEN |
| 비용 | Claude API/구독 사용량이 든다 — 검증까지 하므로 한 작업에 최소 2회 모델 호출 |
쓰기 루프는 코드를 커밋하고 PR을 열며, output: commit이면 브랜치에 직접 push까지 한다. README는 명령한다 — 읽기 전용 루프(pr-reviewer·issue-groomer)부터 시작하고, run-once로 포그라운드 스모크 테스트를 먼저 하고, 루프에 필요 없는 도구는 절대 주지 말 것. "당신의 루프가 내보내는 것에 대한 책임은 당신에게 있다." 토큰은 절대 트리에 하드코딩하지 말고 환경변수 이름만 설정한다.
난이도별로 손에 익히는 단계. (위험하므로 테스트용 저장소에서)
가장 안전한 첫걸음. 내 테스트 저장소를 config.yaml에 매핑하고, 리뷰(읽기 전용) 루프를 run-once로 한 번만 돌려 로그를 읽는다. worktree도 안 만들고 발행도 댓글뿐이라 위험이 적다.
# 설정 복사 후 내 레포로 수정
cp config.example.yaml config.yaml && $EDITOR config.yaml
chmod +x orchestrator.sh dashboard.sh connectors/*.sh
# 단일 루프를 포그라운드로 (발행 전 로그부터 확인)
./orchestrator.sh run-once pr-reviewer ~/projects/test-repo
./orchestrator.sh start로 데몬을 띄우고 ./dashboard.sh로 루프별 성공률·평균 실행시간·실패 수를 본다. state/*.json을 직접 열어 processed 원장이 어떻게 쌓이는지도 확인하자.
skills/my-loop.md(역할·단계·RESULT 형식)와 definitions/my-loop.yaml(주기·output·allowed_tools)을 만들고 config.yaml에 연결한 뒤 run-once로 시험한다. 예: "매일 README의 깨진 링크를 찾아 이슈로 보고하는" 읽기 전용 루프.
# definitions/my-loop.yaml 예시
name: my-loop
cadence: every 30m # 또는 cron: "0 9 * * 1-5"
skill: my-loop.md
worktree: false
output: issue-comment
allowed_tools: "Bash,Read,Glob,Grep"
verify: true
스킬에 일부러 "엉성한 수정"을 유도하는 지시를 넣어, run_verifier가 VERDICT: FAIL로 발행을 막는지 확인한다. .verify.log를 열어 검증관이 어떤 근거로 거절했는지 읽으면, 검증 프롬프트 설계 감각이 생긴다.
lib/common.sh의 cron_matches_now에 단위 테스트를 붙여 */5·범위·요일 처리를 검증하고, mkdir 락을 일부러 두 번 잡아 중복 차단을 재현한다. 나아가 상태 파일을 지웠을 때 루프가 전부 재처리하는지(멱등 원장 동작)도 실험해 본다.
한 주씩 따라가는 계획.
| 주차 | 주제 | 학습 자료 |
|---|---|---|
| 1주차 | Claude Code 헤드리스(claude -p)·시스템 프롬프트·allowedTools | Claude Code 공식 문서 · skills/*.md |
| 2주차 | 프로덕션 bash: set 옵션·trap·백그라운드 잡·pidfile | Bash 매뉴얼 · orchestrator.sh 정독 |
| 3주차 | git worktree 워크플로 + gh CLI 자동화 | git-worktree 문서 · connectors/*.sh |
| 4주차 | jq로 JSON 상태/원자적 쓰기 · 멱등 설계 | jq 매뉴얼 · lib/state.sh |
| 5주차 | cron 표현식·스케줄링 이론 + 자체 파서 읽기 | crontab(5) · lib/common.sh |
| 6주차 | 에이전트 평가: LLM-as-judge·검증 게이트·best-of-N | 관련 논문/블로그 · run_verifier 분석 |
본문에 나온 용어 빠른 참조.
| 용어 | 의미 |
|---|---|
| 하니스 (harness) | AI 에이전트를 감싸 실행·제어·검증하는 바깥 운영 틀. |
| 루프 (loop) | 주기적으로 깨어나 한 가지 임무를 수행하는 자율 작업 단위. |
| cadence | 루프 실행 주기. every 10m 간격형 또는 cron식. |
| 오케스트레이터 | 마감된 루프를 골라 동시성 한도 안에서 실행하는 스케줄러 데몬. |
| git worktree | 한 저장소에서 여러 작업 폴더를 동시에 띄우는 격리 기능. |
| 검증 게이트 | 만든 결과를 두 번째 AI가 검사해 PASS해야만 발행되는 관문. |
| 아웃박스(outbox) | 에이전트가 발행할 동작을 파일로 쌓아 두는 스테이징 폴더. |
| 멱등성 | 같은 작업을 여러 번 해도 결과가 같은 성질. 중복 실행 안전. |
| 상태 원장(ledger) | 처리한 항목 ID를 기록해 재처리를 막는 JSON 파일. |
| claude -p | 대화창 없이 한 번에 실행하고 결과를 출력하는 헤드리스 모드. |
| LLM-as-judge | 한 AI의 결과를 다른 AI가 객관적으로 평가하는 패턴. |
| graceful degradation | 일부가 실패해도 전체는 멈추지 않고 우아하게 견디는 설계. |