계획부터 수정까지 상태 전이
65분 안팎
학습 목표
계획·구현·테스트·검토·수정 루프에서 실패하면 완료로 이동하지 못하게 구현합니다.
개념
완료라는 문장을 상태로 믿지 않습니다
할 일 완료 API의 수정 요청을 받고 AI가 구현과 테스트를 끝냈다고 답했다고 가정합니다. 담당자는 계획이 무엇이었는지, 실제로 어떤 파일을 바꾸었는지, 어떤 검사에서 통과했는지, 누가 diff를 읽었는지를 따로 확인합니다. 이 연결이 없으면 테스트 실패 뒤에도 다음 작업을 시작하거나 검토 없는 변경을 완료로 표시하기 쉽습니다. 이번 레슨은 자연어 보고 대신 허용된 상태 전이만 받아들이는 작은 함수를 작성합니다. 모델은 다음 행동을 제안하고 상태 변경은 하네스가 결정합니다.
실습을 실행하는 공통 방법
따라하기의 Python 코드는 각 단계마다 별도 파일에 저장하고 python3 파일명.py로 실행합니다. 브라우저 과제는 JSON 하나를 표준 입력으로 읽고 결과만 출력합니다. JSON 문법 오류는 입력 범위에서 제외하며 각 과제의 필드와 자료형 계약을 먼저 읽습니다. 로컬 ZIP은 압축을 푼 루트에서 bash check.sh로 검사합니다. 미션은 앞 모듈 앱과 Maven Wrapper를 포함합니다. JDK 17에서 ./mvnw test를 실행하며 캐시가 준비된 작성 환경은 ./mvnw -o -q test입니다. 실제 모델 계정 없이 고정 응답으로 모든 경로를 재현합니다.
계획은 검증할 변경 하나를 고릅니다
계획 상태 plan에서는 이번 변경을 할 일 완료 응답의 상태 확인처럼 한 가지 계약으로 좁힙니다. 수정 파일과 유지할 테스트와 예상 실패를 적고 planned 사건으로 implement에 이동합니다. planned는 모델이 계획을 작성했다는 관찰을 간단히 표현한 이름이며 좋은 계획이라는 자동 증명은 아닙니다. 실제 업무에서는 사람이 계획의 범위를 먼저 읽습니다. 이미 정해진 실행 순서까지 매번 모델에게 선택하게 할 필요는 없습니다. 이번 상태표는 검증 순서를 고정하고 구현 후보만 제안받는 흐름에 맞춥니다.
구현과 검사 사이에 경계를 둡니다
implement에서 implemented를 받으면 test로 이동합니다. 이 사건은 코드 후보가 준비되었다는 뜻이며 정상 작동 선언이 아닙니다. test_pass가 관찰되어야 review로 이동하고 test_fail이면 repair로 돌아갑니다. 검사 호출 전부터 통과 사건을 만들어 넣으면 이 상태표도 거짓 자료를 믿게 됩니다. 따라서 브라우저 실습은 주어진 사건을 판정하는 함수이고, 미션은 실제 검사 종료 코드와 현재 파일 해시에서 사건의 근거를 얻습니다. 상태 구조와 근거 수집이라는 두 책임을 구분해 설명합니다.
검토 거절도 수정 경로입니다
review에서 approve를 받으면 completed이고 reject를 받으면 repair입니다. 테스트는 명세의 일부 사례를 확인하지만 관련 없는 파일 수정이나 불필요한 의존성 추가는 diff 검토가 발견할 수 있습니다. 검토 거절을 테스트 실패와 같은 원인으로 기록하지 않습니다. 둘 다 수정이 필요하더라도 전달할 자료는 다릅니다. 실패 사례에는 입력과 기대값을, 검토 거절에는 변경 줄과 이유를 남깁니다. 아직 사람이 읽지 않은 경우에는 아무 사건도 추가하지 않아 review에 머뭅니다.
수정 뒤에는 검사를 다시 실행합니다
repair에서 fixed를 받으면 test입니다. 이전 검사 통과나 이전 승인으로 review 또는 completed에 바로 가지 않습니다. 고친 파일은 검사했던 파일과 달라졌기 때문입니다. 상태표에서 repair와 approve 조합을 제외하면 검토 승인 문장으로 검사 실패를 우회할 수 없습니다. 재검사 통과 뒤 다시 검토하는 과정을 하나의 반복으로 읽습니다. 수정을 반복할 수 있다는 것과 계속 반복해도 된다는 것은 다른 문제이며 자원 한도는 뒤 레슨에서 별도 검사합니다.
상태와 사건의 조합을 표로 구현합니다
파이썬 딕셔너리의 키를 상태와 사건의 튜플로 만듭니다. 값은 다음 상태입니다. 같은 approve라도 review에서만 의미가 있으므로 사건 이름만으로 분기하지 않습니다. get으로 조합을 찾고 등록되지 않은 것은 invalid를 반환합니다. 구현에 else로 completed를 두면 오타 사건과 잘못된 순서까지 성공으로 바뀝니다. 허용 전이 여덟 개를 명시하면 코드 검토자가 목록을 명세와 한 줄씩 대조할 수 있습니다. 새로운 전이를 추가할 때는 그 전이를 허용할 근거와 반례를 함께 추가합니다.
끝난 실행에는 사건을 덧붙이지 않습니다
completed는 이번 사건 묶음의 종결 상태입니다. 뒤에 planned나 approve를 또 붙이면 별도 작업을 같은 실행에 섞은 것으로 보고 invalid입니다. invalid도 뒤 사건으로 회복하지 않습니다. 잘못된 순서를 지우고 조용히 이어가면 어느 사건이 문제였는지 기록에서 사라집니다. 재개가 필요하면 새 실행 ID와 계획으로 시작합니다. 빈 사건 배열의 결과는 plan이며 미실행이라는 사실을 보존합니다. 사건을 하나도 받지 않은 상태가 완료가 되는 기본값은 피합니다.
실패 로그에서 순서를 읽습니다
기대 repair인데 completed가 나오면 test_fail을 성공과 같은 분기로 처리했는지 확인합니다. 기대 invalid인데 review가 나오면 잘못된 사건을 무시하고 계속 진행했을 수 있습니다. 파이썬 KeyError가 나온다면 직접 인덱싱한 조합이 상태표에 있는지 읽습니다. 잘못된 사건은 프로그램 예외가 아니라 계약상 invalid이므로 get의 기본값을 둡니다. 브라우저 출력에는 최종 상태 한 줄만 두고 디버깅용 상태 목록은 제거합니다. 채점기가 출력하는 기대값과 실제값을 먼저 비교한 다음 구현을 고칩니다.
정상 경로와 우회 시도를 함께 검사합니다
정상 경로는 planned, implemented, test_pass, approve입니다. 실패 뒤 회복은 planned, implemented, test_fail, fixed, test_pass, approve입니다. 반례로 계획 없이 implemented, 검사 실패 직후 approve, 완료 이후 추가 approve를 넣습니다. 정상 입력만 검사하면 모든 사건을 성공 처리하는 구현도 드러나지 않습니다. starter는 사건이 있으면 완료라고 답해 정상 완료는 맞추지만 중간 상태와 우회 사례를 놓칩니다. 함수의 목표는 사례 이름을 외우는 것이 아니라 허용 조합의 구조로 판정하는 것입니다.
미션에서 이어받을 부분을 확인합니다
앞 모듈의 ALLOW는 실행 후보이지 검사 통과가 아니었습니다. 이번에는 허용된 검사만 호출하고 결과의 최신성까지 확인하여 다음 상태로 이동합니다. 문서 속 명령 제안은 여전히 DENY이며 검증 루프라는 이름으로 범위를 넓히지 않습니다. 미션에는 자동 코드 수정기가 없으므로 고정 fixture의 시도들이 수정 후보가 준비된 상황을 흉내 냅니다. 제출 설명에서는 상태 판정의 확인 범위와 실제 모델 미확인 범위를 분리합니다. 에이전트의 일반적인 행동·관찰 구조는 연결 원고에서 더 읽습니다.
따라하기
정상 상태 연결
허용 조합을 표로 두고 정상 완료와 검사 대기를 비교합니다.
def transition(state, event):
edges = {
('plan', 'planned'): 'implement',
('implement', 'implemented'): 'test',
('test', 'test_pass'): 'review',
('test', 'test_fail'): 'repair',
('review', 'approve'): 'completed',
('review', 'reject'): 'repair',
('repair', 'fixed'): 'test',
}
return edges.get((state, event), 'invalid')
def replay(events):
state = 'plan'
for event in events:
if state in ('completed', 'invalid'):
return 'invalid'
state = transition(state, event)
return state
print(replay(['planned','implemented','test_pass','approve']))
print(replay(['planned','implemented','test_pass']))실행 결과
completed review
실패 뒤 다시 검사
수정 전 승인 우회와 수정 뒤 재검사 성공을 비교합니다.
def transition(state, event):
edges = {
('plan', 'planned'): 'implement',
('implement', 'implemented'): 'test',
('test', 'test_pass'): 'review',
('test', 'test_fail'): 'repair',
('review', 'approve'): 'completed',
('review', 'reject'): 'repair',
('repair', 'fixed'): 'test',
}
return edges.get((state, event), 'invalid')
def replay(events):
state = 'plan'
for event in events:
if state in ('completed', 'invalid'):
return 'invalid'
state = transition(state, event)
return state
print(replay(['planned','implemented','test_fail','approve']))
print(replay(['planned','implemented','test_fail','fixed','test_pass','approve']))실행 결과
invalid completed
종결과 미실행
빈 기록과 끝난 기록에 사건을 덧붙인 경우를 확인합니다.
def transition(state, event):
edges = {
('plan', 'planned'): 'implement',
('implement', 'implemented'): 'test',
('test', 'test_pass'): 'review',
('test', 'test_fail'): 'repair',
('review', 'approve'): 'completed',
('review', 'reject'): 'repair',
('repair', 'fixed'): 'test',
}
return edges.get((state, event), 'invalid')
def replay(events):
state = 'plan'
for event in events:
if state in ('completed', 'invalid'):
return 'invalid'
state = transition(state, event)
return state
print(replay([]))
print(replay(['planned','implemented','test_pass','approve','approve']))실행 결과
plan invalid
확인 문제
실습
JSON 사건 문자열 배열을 읽고 최종 상태 한 줄을 출력합니다. 시작은 plan입니다. planned는 plan→implement, implemented는 implement→test, test_pass는 test→review, test_fail은 test→repair, approve는 review→completed, reject는 review→repair, fixed는 repair→test입니다. 등록되지 않은 조합과 종결 이후 사건은 invalid입니다. invalid 이후에도 invalid입니다. 빈 배열은 plan입니다. 배열과 각 문자열의 자료형은 올바르게 제공됩니다.
모범 답안
def transition(state, event):
edges = {
('plan', 'planned'): 'implement',
('implement', 'implemented'): 'test',
('test', 'test_pass'): 'review',
('test', 'test_fail'): 'repair',
('review', 'approve'): 'completed',
('review', 'reject'): 'repair',
('repair', 'fixed'): 'test',
}
return edges.get((state, event), 'invalid')
def replay(events):
state = 'plan'
for event in events:
if state in ('completed', 'invalid'):
return 'invalid'
state = transition(state, event)
return state
import json,sys
print(replay(json.load(sys.stdin)))
더 읽기
면접 질문
- AI가 만든 코드가 실행될 때 추가로 확인할 내용을 설명해 주시면 됩니다.