가이드

AI 에이전트에게 작업 주는 법: 기능 하나씩, 기능 목록, 완료 기준

업데이트: · Skillaria.Top

AI 에이전트에게 작업은 하나씩 줍니다. 작업 중인 기능은 항상 하나뿐이고, 그 기능에는 외부에서 관찰 가능한 동작과 실행 가능한 검증 명령이 있어야 합니다. 진행 중에 발견한 문제는 고치지 않고 백로그에 적습니다. 모든 기능의 상태는 feature_list.json 같은 기계가 읽을 수 있는 목록에 저장되며, '완료'로 바꾸는 것은 에이전트가 아니라 검증 스크립트만 할 수 있습니다.

에이전트가 한꺼번에 모든 것을 시작하는 이유

에이전트에게 회원가입을 부탁했다고 합시다. 몇 시간 뒤 diff에는 열두 개 파일이 바뀌어 있고 새 코드가 800줄 추가되어 있습니다. 사용자 모델, 이메일 인증, 메일 서비스 연동, 비밀번호 해싱, 리팩터링된 미들웨어, 옮겨진 테스트까지 들어 있습니다. 모든 것이 서로 강하게 얽혀 있고, 어떤 시나리오도 끝까지 통과하지 못합니다. 다음 세션은 이 코드를 열어도 어디서 시작해야 할지 모릅니다.

원인이 '나쁜 모델'에 있는 것은 아닙니다. 사람에게는 '오늘은 이 정도면 충분하다'는 감각과 피로가 있어서 작업의 범위를 스스로 제한합니다. 에이전트에게는 둘 다 없습니다. '겸사겸사 에러 처리도 고치겠다'는 말을 덧붙이는 데는 토큰 몇십 개면 충분하지만, 프로젝트가 치르는 비용은 실제로 큽니다. 주의가 흩어지고, 결합도가 높아지고, 끝난 것은 없습니다.

간단한 산술도 있습니다. C를 유효한 컨텍스트 용량, k를 에이전트가 동시에 붙들고 있는 작업 수라고 하면, 작업 하나에 돌아가는 추론 자원은 대략 C/k입니다. 이 값이 작업 하나를 끝내기에 필요한 양보다 작아지면, 어떤 작업도 끝나지 않습니다. 그래서 '에이전트에게 작업을 어떻게 줄까'라는 질문에 대한 답은 프롬프트 문장에서 시작하지 않고, 제약에서 시작합니다. 작업은 한 번에 하나만 진행합니다.

WIP=1 규칙: 기능은 한 번에 하나

WIP는 work in progress, 즉 진행 중인 작업 수입니다. 에이전트에게는 이 값을 1로 둡니다. 어느 순간에도 활성 기능은 정확히 하나이고, 다음 기능은 현재 기능이 검증을 통과한 뒤에만 시작합니다. 이 규칙은 매 세션 에이전트가 보도록 프로젝트 지침 파일(AGENTS.md 또는 CLAUDE.md)에 적어 둡니다.

## 작업 규칙
- 어느 순간이든 `active` 상태인 기능은 정확히 하나입니다.
- 다음 기능은 현재 기능이 `passing`으로 바뀐 뒤에만 시작합니다.
- '겸사겸사'로 리팩터링하거나, 이름을 바꾸거나, 디렉터리를 재구성하거나,
  관련 없는 버그를 고치는 것은 금지합니다. 문제를 발견하면 `BACKLOG.md`에 적고 넘어갑니다.
- 활성 기능의 범위 밖 변경은, 유용하더라도 규칙 위반으로 봅니다.

마지막 항목이 생각보다 중요합니다. 에이전트는 자신의 '겸사겸사'가 유용하다고 진심으로 믿고, 실제로 유용한 경우도 많습니다. 하지만 범위 밖의 유용한 수정도 두 작업을 한 diff에 섞어 버리므로, 나중에 따로 되돌리거나 따로 검증할 수 없게 됩니다.

BACKLOG.md: '겸사겸사'를 둘 곳

'겸사겸사 고치기' 충동은 그냥 금지만 해서는 없어지지 않습니다. 합법적인 출구가 필요합니다. 그 출구가 BACKLOG.md 파일입니다. 에이전트는 발견한 문제를 한두 줄로 적고, 하던 기능으로 돌아갑니다. 정보는 사라지지 않고, 범위도 커지지 않습니다. 백로그가 없으면 에이전트는 결국 규칙을 우회하기 시작합니다. '문제를 조용히 남겨 두는 것'이 '규칙 위반'보다 나쁘다고 판단하기 때문입니다.

# BACKLOG.md
- [ ] middleware/errors.py: /auth와 /orders의 오류 형식이 다름
- [ ] tests/: 사용자 fixture가 세 파일에 중복됨
- [ ] 회원가입에 이메일 인증이 필요한가? — 확인 필요

완료 기준: '완료'는 명령이어야 한다

문제의 두 번째 절반은 에이전트가 작업이 끝났는지를 스스로 판단한다는 점입니다. '코드가 괜찮아 보인다', '함수가 존재한다', '컴파일 오류가 없다'는 모두 느낌이지 사실이 아닙니다. 작업의 완료 기준은 결과가 명확한 실행 가능한 명령, 즉 통과 또는 실패로 판정되는 명령이어야 합니다.

F01: 사용자 회원가입
verification: pytest tests/e2e/test_auth.py::test_register_flow
state: active

약한 기준과 강한 기준의 차이는 예를 보면 분명합니다.

약한 기준강한 기준
코드가 컴파일된다pytest tests/e2e/test_cart.py -q가 통과한다
문법 오류가 없다/cart에 대한 curl 요청이 200을 반환한다
addToCart 함수가 있다상품을 담은 뒤 GET /cart에 그 상품과 맞는 합계가 나온다
올바르게 보인다npx playwright test cart.spec.ts가 통과한다

좋은 검증 명령은 결정적입니다(같은 코드에는 항상 같은 결과가 나옵니다). 사람의 개입 없이 실행되고, 사용자나 API 클라이언트가 보는 그대로 외부에서 동작을 확인합니다. 그런 명령을 쓸 수 없다면, 작업이 모호하게 정의되었다는 신호입니다.

작업 분해: 기능은 어느 크기여야 하나

'한 번에 한 기능' 다음으로 가장 자주 나오는 질문은 기능의 크기입니다. 너무 넓은 작업은 한 세션에 끝나지 않고, 너무 좁은 작업은 실행, 맥락 로드, 검증의 오버헤드에 묻혀 버립니다.

  • 너무 넓음: '장바구니 구현'. 일주일치 작업이고 시나리오가 수십 개입니다.
  • 적당함: '사용자가 상품을 장바구니에 담고 목록에서 확인할 수 있다'. 관찰 가능한 동작 하나, 검증 시나리오 하나입니다.
  • 너무 좁음: 'CartItem 모델에 quantity 필드 추가'. 구현 단계일 뿐 동작이 아니어서, 따로 검증하는 것이 의미가 없습니다.

크기를 가늠하는 간단한 방법이 있습니다. 기능 하나 = 에이전트 세션 하나 = 의미 있는 커밋 하나 = 엔드투엔드(E2E) 시나리오 하나입니다. 한 세션에 들어가지 않으면 더 나눕니다. 그 기능만을 위한 별도 시나리오를 쓸 수 없다면, 기능이 너무 작으니 이웃한 기능과 합칩니다.

예: '장바구니' 작업 분해

넓은 요구 사항을 원자 단위로 나눕니다. 각 단위는 하나의 동작과 자체 검증 명령을 가집니다.

  1. 상품을 장바구니에 담고 목록에서 확인한다.
  2. 상품 수량을 바꾸면 합계가 다시 계산된다.
  3. 장바구니에서 상품을 삭제한다.
  4. 비회원의 장바구니는 로그인 후에도 유지된다.
  5. 재고가 없는 상품은 담을 수 없고, 이해할 수 있는 오류가 표시된다.

최종 점검: 어떤 단위도 다른 단위를 '겸사겸사' 건드리지 않아야 합니다. 4번 항목을 하려고 1번을 다시 써야 한다면, 순서나 경계가 잘못 잡힌 것입니다.

기능 목록: feature_list.json

WIP=1 규칙은 세션 안의 순서를 잡아 줍니다. 그렇다면 세션과 세션 사이에서는 무엇이 끝났는지 누가 기억할까요? 채팅에 '인증과 카탈로그는 끝났고, 장바구니는 거의 됐지만 버그가 있다'고 적어 두어도 새 세션에는 도움이 되지 않습니다. '거의'가 무엇인지, 어떤 테스트가 통과했는지, 결제를 무엇이 막고 있는지 알 수 없습니다. 매 세션은 같은 차를 모는 새 운전자와 같습니다. 메모가 이전 운전자의 머릿속에만 있었다면, 새 운전자는 아무것도 모릅니다.

그래서 상태를 기계가 읽을 수 있는 파일로 밖에 꺼냅니다. 이 강의에서는 .agent/feature_list.json입니다.

[
  {
    "id": "F01",
    "behavior": "사용자가 이메일과 비밀번호로 가입하고 바로 로그인된다",
    "verification": "pytest tests/e2e/test_auth.py::test_register_flow",
    "state": "passing",
    "evidence": "a3f9c12: 1 passed in 2.31s"
  },
  {
    "id": "F02",
    "behavior": "사용자가 상품을 장바구니에 담고 목록에서 확인한다",
    "verification": "pytest tests/e2e/test_cart.py::test_add_item -q",
    "state": "active"
  },
  {
    "id": "F03",
    "behavior": "카드로 주문 결제",
    "verification": "pytest tests/e2e/test_payment.py -q",
    "state": "blocked",
    "blocker": "결제 게이트웨이의 테스트 키가 없음"
  }
]

각 항목에는 세 가지 필수 필드가 있습니다. 외부에서 관찰되는 동작 설명, 사람 없이 실행 가능한 검증 명령, 그리고 상태입니다. 선택 필드인 evidence에는 커밋 해시와 검증 출력을 적습니다. 실제로 passing으로 바뀐 근거가 되는 증거입니다.

왜 Markdown 목록이 아니라 JSON일까요? 이 파일은 에이전트만 읽지 않고 스크립트도 읽습니다. 스케줄러는 not_started에서 다음 기능을 고르고, 검증기는 검증 명령을 가져오며, 세션 보고서는 자동으로 만들어지고, 상태 분포는 프로젝트 건강도 지표가 됩니다. 기능 목록은 메모가 아니라 하네스의 척추입니다.

네 가지 상태와 허용된 전이

전이언제
not_started → active기능을 작업에 착수함. 다른 활성 기능이 없어야 함
active → passing검증 스크립트가 코드 0으로 종료됨
active → blocked외부 장애물. 원인을 blocker에 적음
blocked → active장애물이 해소됨

이 외의 전이는 없습니다. 특히 검증을 건너뛰고 passing으로 가는 경로가 없고, '거의 완료' 상태도 없습니다.

passing 게이트: 에이전트는 요청할 수 있지만 선언하지는 못한다

이 체계의 핵심 규칙은 에이전트가 state 필드를 직접 편집하지 않는다는 것입니다. 에이전트는 검증을 실행할 수 있지만, 결과를 스스로 선언할 수는 없습니다. 선언은 스크립트가 합니다. '에이전트가 보고했다'와 '스크립트가 확인했다'의 차이는 텍스트와 사실의 차이입니다. 이 규칙도 지침 파일에 적어 둡니다.

## 기능 목록 규칙
- 파일: `.agent/feature_list.json`. 범위에 대한 유일한 기준입니다.
- `active` 상태의 기능은 정확히 하나입니다.
- `state` 필드를 직접 수정하지 마세요. 값은 `scripts/verify.sh`만 바꿉니다.
- 기능을 `passing`으로 만들려면 `./scripts/verify.sh F02`를 실행해 코드 0을 얻으세요.
- 기능이 막혔다면 `blocked`로 바꾸고 반드시 `blocker`를 적으세요.
- 새 기능은 `not_started` 상태로만, 검증 명령과 함께 추가합니다.

스크립트 자체는 두 단계로 검사합니다. 먼저 프로젝트 전체 실행(기능이 다른 부분을 깨뜨리지 않았는지)을 하고, 그다음 해당 기능의 검증 명령을 실행합니다. 전체 검사가 깨져 있다면, 지역적으로 성공해도 성공으로 치지 않습니다.

#!/usr/bin/env bash
# passing으로 표시할 권한을 가진 유일한 구성 요소입니다.
set -uo pipefail
FID="$1"
FILE=".agent/feature_list.json"

CMD=$(jq -r --arg id "$FID" '.[] | select(.id==$id) | .verification' "$FILE")
if [ -z "$CMD" ] || [ "$CMD" = "null" ]; then
  echo "FAIL: $FID에 대한 검증 명령이 없습니다"; exit 2
fi

echo "== 전체 검사 =="
make check || { echo "FAIL: 전체 검사가 실패해 $FID를 passing으로 만들 수 없습니다"; exit 1; }

echo "== 기능 검사: $CMD =="
if OUT=$(eval "$CMD" 2>&1); then
  SHA=$(git rev-parse --short HEAD)
  LAST=$(printf '%s\n' "$OUT" | tail -n 1)
  TMP=$(mktemp)
  jq --arg id "$FID" --arg ev "$SHA: $LAST" \
    'map(if .id==$id then .state="passing" | .evidence=$ev else . end)' \
    "$FILE" > "$TMP" && mv "$TMP" "$FILE"
  echo "PASS: $FID"
else
  printf '%s\n' "$OUT"
  echo "FAIL: $FID"; exit 1
fi

'state를 수정하지 마세요'라는 문장은 여전히 요청일 뿐입니다. 이것을 제약으로 만들려면 파일 쓰기를 기계적으로 막아야 합니다. CI에서 evidence를 커밋 기록과 대조하거나, 도구에서 에이전트의 권한을 제한하는 방식입니다. 원칙은 하네스 전반과 같습니다. 중요한 규칙은 메커니즘으로 뒷받침합니다.

에이전트가 작업을 끝까지 하는지 측정하기

솔깃한 지표는 세션당 코드 양입니다. 실제로는 반대로 작동합니다. diff가 크면 에이전트가 여러 방향으로 흩어졌을 가능성이 큽니다. 줄 수가 아니라 passing 상태의 기능 수를 세세요.

강의에서 쓰는 편리한 지표는 VCR(Verified Completion Rate)입니다. 검증 완료된 작업 수를 활성화된 작업 수로 나눈 값입니다. 규칙은 단순합니다. VCR이 1보다 작은 동안에는 새 작업을 활성화하지 않습니다. 이것은 하네스가 에이전트에게 거는 역방향 압력이며, 미완성 작업을 느낌이 아니라 숫자로 보이게 만드는 방법이기도 합니다.

에이전트에게 작업을 줄 때 흔한 실수

  • 검증 명령이 없는 작업. '회원가입 만들어 줘'라고만 하면, 에이전트가 어디서 멈출지 스스로 정하는데, 대개 잘못 정합니다.
  • 한 요청에 여러 기능. '장바구니와 결제를 만들어 줘'는 활성 작업이 두 개인 것과 같고, 어느 것도 끝까지 가지 못합니다.
  • 상태가 채팅에만 있음. 대화에 적힌 진행 상황은 세션이 끝나면 사라집니다. 파일에 있어야 합니다.
  • 검증 대신 자기 보고. '에이전트가 테스트가 통과했다고 했다'는 스크립트의 종료 코드와 같지 않습니다.
  • 백로그 없음. '겸사겸사'를 금지하면서 발견한 문제를 적을 곳을 주지 않으면, 에이전트는 규칙을 우회합니다.

한 번에 하나로는 부족해질 때

'한 기능 — 한 세션 — 한 검증'이라는 방식은 하나의 루프에서 일하는 에이전트 하나에 잘 맞습니다. 작업이 많아지고 독립적인 가지가 생기면, 이 방식은 여러 루프의 시스템으로 확장됩니다. 계획 노드, 구현 노드, 검증 노드가 있고, 노드 사이의 간선은 병렬이거나 조건부이며, 검증에 실패하면 구현으로 되돌아갑니다. 하지만 그곳에서도 각 구현 노드는 같은 규칙을 따릅니다. 작업은 하나이고, 실행 가능한 완료 기준이 있으며, 상태는 에이전트의 머릿속이 아니라 바깥에 있습니다. 그래프가 한 기능 원칙을 대체하지는 않습니다. 그 원칙 위에 세워집니다.

같은 분석에서 나오는 중요한 경고가 있습니다. 루프는 주어진 지표를 정직하게 최적화합니다. 완료 기준을 잘못 고르면, 예를 들어 '문제 해결' 대신 '티켓 닫힘'을 기준으로 두면, 에이전트는 작업이 아니라 기준을 충족하는 법을 배웁니다. 그래서 동작의 정의와 검증 명령을 다듬는 데는 생각보다 더 많은 시간을 들일 가치가 있습니다.

정리

요약하면, AI 에이전트에게 작업은 하나씩 줍니다. 관찰 가능한 동작과 실행 가능한 검증 명령을 붙이고, 도중에 발견한 것은 백로그에 적게 합니다. 그리고 '완료'로 바꾸는 것은 에이전트가 아니라 스크립트가 하도록 기능 목록을 파일에 둡니다. 에이전트의 속도 제한은 에이전트가 스스로 설정하지 않고, 하네스가 설정합니다. WIP=1 규칙, feature_list.json, verify.sh는 강의에서 실제 프로젝트를 예로 들어 자세히 다룹니다.

자주 묻는 질문

작은 작업이라면 에이전트에게 여러 개를 한꺼번에 줘도 되나요?

기능 목록에 여러 개를 적어 둘 수는 있지만, 동시에 작업 중인 것은 하나여야 합니다. 에이전트는 현재 기능이 검증을 통과한 뒤에만 다음 기능을 집어 들어야 각 작업이 끝까지 완료됩니다.

작업에 자동 검증을 만들 수 없다면 어떻게 하나요?

보통 작업이 동작이 아니라 행동으로 서술되어 있다는 뜻입니다. 사용자나 API 클라이언트가 볼 결과로 다시 쓰세요. 화면 부분은 브라우저 E2E 테스트로 검증할 수 있습니다.

feature_list.json은 사람이 쓰나요, 에이전트가 쓰나요?

처음 목록은 함께 만드는 것이 편합니다. 에이전트가 분해안을 제안하고 사람이 고칩니다. 이후 에이전트는 새 기능을 추가할 수 있지만, not_started 상태로만, 검증 명령과 함께 추가해야 합니다.

feature_list.json은 작업 추적 도구의 티켓과 무엇이 다른가요?

티켓 시스템은 사람을 위해 설계되었고, 기능 목록은 기계가 읽도록 설계되었습니다. 각 항목에는 실행 가능한 명령과 스크립트만 바꿀 수 있는 엄격한 상태가 있습니다. 둘을 동기화할 수는 있지만, 에이전트에게 필요한 것은 저장소 안의 파일입니다.

WIP=1 규칙이 작업 속도를 떨어뜨리지 않나요?

세션은 짧아지지만, 끝까지 완료되는 작업의 비율이 올라갑니다. 여러 기능이 뒤섞인 반쯤 끝난 코드를 해석하느라 쓰던 시간이 줄어듭니다.

영상으로 이 주제 배우기

이 주제는 “Harness Engineering: инженерия надёжности AI-агентов” 강의에서 실습과 함께 자세히 다룹니다.

강의로 이동