재사용 절차와 팀 규칙 - 지침·스킬·명령 파일
이 장에서 배우는 것
작업을 여러 번 맡기다 보면 같은 설명을 반복하게 된다. “변경한 코드는 실행해서 확인한다”, “검토할 때는 차이부터 읽는다”, “운영 반영에는 별도 승인이 필요하다” 같은 문장이다. 이를 모두 작업 요청에 붙이면 요청이 길어지고, 일부를 생략하면 팀이 합의한 기준이 빠진다. 반복되는 설명을 파일로 옮기는 것만으로는 충분하지 않다. 언제 읽어야 하는 파일인지까지 구분해야 한다.
앞 장에서 도구를 일정한 구조로 연결했다면, 이번에는 그 도구를 어떤 순서와 기준으로 사용할지 정리한다. 늘 적용되는 지침과 작업에 따라 선택하는 절차를 분리하고, 작업 설명에 맞는 절차 본문만 불러오는 작은 로더(loader)를 만든다. 선택은 규칙 기반 가짜 모델이 담당하므로 같은 입력에는 같은 결과가 나온다.
- 항상 적용할 지침과 필요할 때 선택할 스킬·명령 파일을 구분한다.
- 재사용 절차를 설명·단계·확인의 세 부분으로 작성한다.
- 절차 목록에서 후보를 고르고 선택한 본문만 작업 맥락에 넣는다.
- 코드 리뷰 기준, 생성 코드 표시, 운영 승인 조건을 팀 규칙과 연결한다.
- 임시 폴더에서 절차 묶음을 만들고 선택 결과와 파일 차이를 검증한다.
문제 상황
작은 팀이 문서 정리 하네스를 함께 관리한다고 가정한다. 어떤 날은 보고서를 고치고, 다른 날은 하네스의 Python 코드를 검토한다. 운영 환경에 결과를 반영하는 작업도 있다. 세 작업은 같은 저장소에서 시작하지만 필요한 절차는 다르다. 문서 수정에는 문장과 파일 변경 확인이 필요하고, 코드 검토에는 실행 결과와 변경 차이를 읽는 기준이 필요하다. 운영 반영에는 승인 대상을 특정하는 과정이 더해진다.
처음에는 모든 규칙과 절차를 하나의 긴 지침 파일에 적는다. 파일은 점점 커지고, 문서를 고치는 요청에도 운영 반영 절차가 붙는다. 코드 검토자가 승인 문구를 읽고 작업을 멈추기도 하고, 운영 작업자가 문서 수정 절차만 따라가기도 한다. 내용을 더 자세히 쓰면 해결될 것 같지만, 실제 문제는 적용 범위가 섞여 있다는 데 있다.
반대로 작업별 파일만 만들면 공통 규칙이 빠질 수 있다. 문서 절차에는 실행 확인이 있고 코드 절차에는 없거나, 생성 코드 표시 방식이 파일마다 달라진다. 팀원이 절차를 복사해 새 파일을 만들 때 오래된 기준까지 복사하는 일도 생긴다. 공통 기준은 한곳에 두고, 각 절차는 해당 작업에서 필요한 순서와 확인 항목만 맡아야 한다.
팀의 공통 지침 예시다. 변경은 허용된 작업 폴더 안에서 수행한다. AI가 제안한 코드도 실행 결과와 테스트, 변경 차이를 확인한다. 생성한 코드의 범위와 검증 상태를 기록한다. 운영 반영은 대상과 변경 내용이 명시된 승인을 받은 뒤 수행한다.
이번 예제의 작업 요청은 “문서 수정과 코드 검토”다. 로더는 문서 수정 절차와 코드 검토 절차를 선택하고, 운영 반영 절차는 불러오지 않는다. 운영 절차를 읽지 않았다는 이유로 공통 승인 규칙이 사라지는 것은 아니다. 지침은 항상 유지되고, 상세한 운영 순서만 현재 작업 맥락에서 제외된다.
항상 적용하는 지침과 선택하는 절차
지침 파일은 작업 전반에 적용되는 기준을 담는다. 수정 가능한 위치, 완료 보고에 포함할 증거, 팀이 쓰는 생성 코드 표시 방식처럼 작업 종류가 달라져도 지켜야 할 항목이다. 지침은 짧고 판단 가능해야 한다. “좋은 코드를 작성한다”보다 “변경한 코드의 실행 결과와 테스트 결과를 보고한다”가 확인하기 쉽다.
스킬(skill)은 특정 작업을 수행하는 재사용 절차다. 무엇을 할 수 있는지 설명하고, 수행 단계를 정리하고, 끝났는지 확인할 기준을 제공한다. 명령 파일은 사용자가 이름으로 절차를 호출하는 진입점으로 생각할 수 있다. 예를 들어 “코드 검토”라는 명령이 검토 절차를 선택하게 만들 수 있다. 명령 파일에 절차 전체를 복제하기보다 사용할 절차 식별자를 연결하면 수정할 곳이 줄어든다.
2026년 10월 기준 예시에서 지침·스킬·명령이라는 이름과 파일을 찾는 방식은 제품마다 다를 수 있다. 이 장에서는 특정 제품의 자동 탐색 규칙을 가정하지 않는다. 우리 프로그램이 직접 파일을 만들고 읽는다. 파일명이 특별한 능력을 주는 것이 아니라, 하네스가 정한 읽기 규칙이 파일의 역할을 결정한다.
| 구분 | 적용 시점 | 담는 내용 | 예제의 표현 |
|---|---|---|---|
| 팀 지침 | 모든 작업 | 공통 경계와 보고 기준 | team.json |
| 절차 목록 | 절차 선택 전 | 식별자·설명·선택어 | catalog.json |
| 스킬 본문 | 선택된 작업 | 단계와 확인 항목 | 식별자별 JSON 파일 |
| 명령 파일 | 이름으로 호출할 때 | 절차 식별자 연결 | 이번 코드에서는 구현하지 않는다 |
팀 지침과 절차 사이에 충돌이 생겼을 때의 처리도 정해야 한다. 예를 들어 절차에 “바로 반영한다”라고 적혀 있어도 공통 지침이 운영 승인을 요구한다면 승인 조건을 건너뛰어서는 안 된다. 이번 하네스에서는 공통 지침을 경계로 두고 절차를 그 안에서 사용하는 것으로 정한다. 로더 자체가 모든 충돌을 판별하는 것은 아니므로 절차 작성과 리뷰에서도 이 관계를 확인해야 한다.
그림의 두 경로를 구분하는 것이 핵심이다. 지침은 모든 작업에 들어가고, 절차는 목록을 거쳐 선택된다. 목록에는 선택을 위한 짧은 설명만 둔다. 본문을 읽어야 적용 여부를 알 수 있다면 모든 파일을 먼저 읽게 되므로 선택 단계의 의미가 줄어든다.
절차를 설명·단계·확인으로 작성하기
절차의 설명은 적용 조건을 알려 준다. “검토에 사용한다”처럼 넓게 쓰면 문서 검토와 코드 검토가 구분되지 않는다. “Python 코드 변경의 실행 증거와 변경 차이를 검토한다”처럼 대상과 목적을 함께 적는다. 사람이 읽는 설명은 의미를 전달하고, 이번 예제의 선택어는 결정적인 선택을 위한 간단한 장치로 사용한다.
단계는 행동의 순서를 나타낸다. “확인한다”만 반복하지 말고 무엇을 읽고 무엇을 비교하는지 적는다. 코드 검토라면 변경 차이를 읽고, 실행 증거를 확인하고, 생성 코드 표시를 확인하는 순서가 된다. 각 단계는 실제 하네스의 기능과 연결할 수 있어야 한다. 도구가 없는 행동을 목록에 적는 것만으로 실행 능력이 생기지는 않는다.
확인은 완료 판단에 필요한 관찰값이다. 단계가 “문서를 수정한다”라면 확인은 “의도한 파일만 바뀌었고 변경 차이를 검토했다”가 될 수 있다. 단계 목록이 비어 있지 않다는 사실과 작업이 성공했다는 사실은 다르다. 이번 로더는 절차 파일의 구조를 확인하지만 실제 문서 품질이나 승인 여부까지 판단하지는 않는다.
| 절차 | 설명 | 단계 | 확인 |
|---|---|---|---|
| edit_docs | 문서 변경과 차이 확인 | 대상 읽기 → 수정 → 차이 검토 | 변경 범위 확인 |
| review_code | 코드 변경의 검증 증거 검토 | 차이 읽기 → 실행 증거 확인 → 생성 표시 확인 | 검증 근거 확인 |
| publish_ops | 승인을 거친 운영 반영 | 대상 특정 → 승인 확인 → 반영 | 승인 대상 일치 |
문서 수정 절차의 설명이다. 요청한 문서의 변경 범위를 확인하며 수정한다. 먼저 대상을 읽고, 요청에 맞게 고친 뒤 변경 차이를 검토한다. 의도한 범위 밖의 변경이 없는지 확인하고 결과를 보고한다.
코드 검토 절차의 설명이다. 코드 변경을 읽고 실행 증거를 확인한다. 변경 차이, 실행 결과, 생성 코드 표시를 차례로 검토한다. 테스트가 통과했더라도 요청과 무관한 변경이 있는지 별도로 확인한다.
완성 코드에서는 긴 자연어 지침 대신 짧은 식별자를 파일에 저장한다. 예를 들어 check_diff는 변경 차이 검토, mark_generated는 생성 범위와 검증 상태 기록을 뜻한다. 이는 파일 선택과 구조 검증에 집중하기 위한 예제 표현이다. 실제 절차 파일에는 위 인용문처럼 행동과 판단 근거를 설명하는 문장을 함께 둘 수 있다.
생성 코드 표시는 작성 경로를 남기는 수단이다. 파일 전체에 같은 주석을 반복하기보다 변경 기록에 생성 범위, 사람이 수정한 범위, 수행한 검증을 적는 방식도 가능하다. 팀이 한 방식을 정하고 검토자가 확인할 위치를 합의해야 한다. 생성 표시가 있다고 검토 책임이 줄어들지는 않는다.
운영 승인도 구체적으로 정의해야 한다. 승인자는 어떤 변경을 어떤 대상에 반영하는지 알아야 한다. “진행해도 된다”라는 문장이 있어도 승인 이후 내용이 바뀌었다면 그 문장이 현재 변경을 포함하는지 다시 판단해야 한다. 이번 코드는 운영 반영을 실행하지 않고, 운영 절차가 선택 대상에서 제외되는지만 확인한다.
목록을 먼저 읽고 선택한 본문만 불러오기
로더는 두 단계로 움직인다. 먼저 목록에서 식별자, 설명, 선택어를 읽는다. 그다음 작업 설명과 선택어가 겹치는 항목의 본문만 읽는다. 선택 결과는 파일 저장 순서와 무관하게 식별자 순으로 정렬한다. 출력 순서가 고정되면 실행 결과를 비교하기 쉽다.
여기서 “필요한 절차만 불러온다”는 말의 범위를 분명히 해야 한다. 목록은 모든 항목의 정보를 포함하므로 전체 목록을 읽는다. 대신 선택하지 않은 절차의 단계와 확인 항목은 읽지 않는다. 목록까지 전혀 보지 않고 필요한 파일을 알아내는 방식은 아니다.
가짜 모델(fake model)은 작업 설명에서 선택어를 찾아 식별자 목록을 반환한다. 설명의 의미를 해석하지 않는다. “운영 반영 금지”에도 “운영”이 들어 있으므로 운영 절차를 선택할 수 있다. 이 제한을 감추면 선택 결과를 과신하게 된다. 예제에서는 요청 문구를 통제하고, 모르는 작업은 빈 목록으로 반환하며, 선택 결과를 테스트한다.
가짜 모델에 주는 작업 설명은 “문서 수정과 코드 검토”다. 반환하는 답은 edit_docs와 review_code라는 두 식별자다. 이 답은 절차를 선택했다는 뜻이며, 해당 작업을 완료했다는 뜻이 아니다.
모델이 반환한 식별자도 확인해야 한다. 목록에 없는 이름으로 파일을 읽으면 선택 단계가 파일 접근 경로를 직접 결정하게 된다. 이번 구현은 목록의 식별자를 먼저 검사하고, 선택된 이름이 그 목록에 있는지 다시 검사한다. 절차 파일의 내부 식별자까지 비교하여 잘못 연결된 파일도 발견한다.
검증은 현재 예제의 구조와 경계를 확인하는 수준이다. 선택한 절차의 단계가 팀 지침을 실제로 준수하는지, 승인자가 권한을 갖는지까지 보장하지는 않는다. 이런 판단을 파일 로더 하나에 숨기기보다 실행 단계의 별도 검사로 연결하는 편이 역할을 이해하기 쉽다.
완성 코드
다음 프로그램을 main.py로 저장하고 실행한다. 표준 라이브러리만 사용하며, 프로그램이 읽고 쓰는 데이터 파일은 모두 임시 폴더 안에 만든다. 임시 폴더의 무작위 이름은 출력하지 않는다. 코드 안의 파일 내용은 절차 식별자와 구조화된 예제 데이터로 구성한다.
import difflib
import json
import re
import tempfile
from pathlib import Path
CATALOG = [
{
"id": "edit_docs",
"description": "document_changes",
"keywords": ["문서", "문서 수정"],
},
{
"id": "review_code",
"description": "code_review",
"keywords": ["코드 검토", "코드 리뷰"],
},
{
"id": "publish_ops",
"description": "approved_publication",
"keywords": ["운영", "배포"],
},
]
PROCEDURES = {
"edit_docs": {
"id": "edit_docs",
"steps": ["read_target", "edit_target", "check_diff"],
"checks": ["change_scope_checked"],
},
"review_code": {
"id": "review_code",
"steps": ["read_diff", "check_execution", "check_marker"],
"checks": ["evidence_checked"],
},
"publish_ops": {
"id": "publish_ops",
"steps": ["identify_target", "check_approval", "publish"],
"checks": ["approval_matches_target"],
},
}
TEAM = {
"rules": [
"workspace_only",
"run_test_diff",
"mark_generated",
"approval_before_publish",
]
}
def write_json(path, value):
text = json.dumps(value, ensure_ascii=False, indent=2)
path.write_text(text + "\n", encoding="utf-8")
def read_json(path):
return json.loads(path.read_text(encoding="utf-8"))
def require_strings(value, label):
if not isinstance(value, list) or not value:
raise ValueError(f"{label}: nonempty list required")
if any(not isinstance(item, str) or not item.strip()
for item in value):
raise ValueError(f"{label}: nonempty strings required")
def validate_catalog(catalog):
if not isinstance(catalog, list):
raise ValueError("catalog: list required")
seen = set()
for entry in catalog:
if not isinstance(entry, dict):
raise ValueError("catalog entry: object required")
name = entry.get("id")
if not isinstance(name, str):
raise ValueError("id: string required")
if re.fullmatch(r"[a-z][a-z0-9_]*", name) is None:
raise ValueError("id: invalid format")
if name in seen:
raise ValueError("id: duplicate")
seen.add(name)
description = entry.get("description")
if not isinstance(description, str) or not description.strip():
raise ValueError("description: string required")
require_strings(entry.get("keywords"), "keywords")
def fake_model(task, catalog):
matches = {
entry["id"]
for entry in catalog
if any(word in task for word in entry["keywords"])
}
return sorted(matches)
def load_context(root, task, selector=fake_model):
team = read_json(root / "team.json")
if not isinstance(team, dict):
raise ValueError("team: object required")
require_strings(team.get("rules"), "rules")
catalog = read_json(root / "catalog.json")
validate_catalog(catalog)
selected = selector(task, catalog)
if not isinstance(selected, list):
raise ValueError("selection: list required")
known = {entry["id"] for entry in catalog}
for name in selected:
if not isinstance(name, str) or name not in known:
raise ValueError("selection: unknown id")
selected = sorted(set(selected))
procedures = []
read_names = []
for name in selected:
path = root / "procedures" / f"{name}.json"
body = read_json(path)
if not isinstance(body, dict) or body.get("id") != name:
raise ValueError("procedure: id mismatch")
require_strings(body.get("steps"), "steps")
require_strings(body.get("checks"), "checks")
procedures.append(body)
read_names.append(path.name)
context = {"team": team, "procedures": procedures}
return context, read_names
def main():
with tempfile.TemporaryDirectory() as folder:
root = Path(folder)
procedure_dir = root / "procedures"
procedure_dir.mkdir()
write_json(root / "team.json", TEAM)
write_json(root / "catalog.json", CATALOG)
for name, body in PROCEDURES.items():
write_json(procedure_dir / f"{name}.json", body)
task = "문서 수정과 코드 검토"
context, read_names = load_context(root, task)
ids = [body["id"] for body in context["procedures"]]
expected = ["edit_docs", "review_code"]
if ids != expected:
raise RuntimeError("unexpected selection")
if read_names != ["edit_docs.json", "review_code.json"]:
raise RuntimeError("unexpected file reads")
if context["team"] != TEAM:
raise RuntimeError("team rules changed")
empty, empty_reads = load_context(root, "일정 계산")
if empty["procedures"] or empty_reads:
raise RuntimeError("unmatched task loaded procedures")
def unknown_selector(task, catalog):
return ["missing"]
try:
load_context(root, task, unknown_selector)
except ValueError as error:
if str(error) != "selection: unknown id":
raise
else:
raise RuntimeError("unknown id accepted")
target = root / "report.txt"
target.write_text("상태: 초안\n", encoding="utf-8")
before = target.read_text(encoding="utf-8")
target.write_text("상태: 검토 대기\n", encoding="utf-8")
after = target.read_text(encoding="utf-8")
diff = list(difflib.unified_diff(
before.splitlines(),
after.splitlines(),
fromfile="before/report.txt",
tofile="after/report.txt",
lineterm="",
))
expected_diff = [
"--- before/report.txt",
"+++ after/report.txt",
"@@ -1 +1 @@",
"-상태: 초안",
"+상태: 검토 대기",
]
if diff != expected_diff:
raise RuntimeError("unexpected document diff")
print(f"작업: {task}")
print("팀 규칙: " + ", ".join(context["team"]["rules"]))
print("선택 절차: " + ", ".join(ids))
print("읽은 절차 파일: " + ", ".join(read_names))
for body in context["procedures"]:
print(body["id"] + " 단계: " + " → ".join(body["steps"]))
print(body["id"] + " 확인: " + ", ".join(body["checks"]))
print("검증: 선택·미선택·없는 식별자·변경 차이 통과")
print("생성 기록: report.txt / 예제 규칙 / 차이 확인")
print("변경 차이:")
for line in diff:
print(line)
print("운영 반영: 수행하지 않음")
if __name__ == "__main__":
main()
이 프로그램에서 문서 변경은 로더와 별도로 작성한 고정 실습 동작이다. 선택된 단계 이름을 실행 함수로 연결하지 않는다. 따라서 출력에 문서 수정 단계가 나타나더라도 절차 엔진이 그 단계를 실행했다고 해석해서는 안 된다. 이 장의 완성 범위는 절차 선택, 본문 로딩, 구조 검증, 검증 증거 출력이다.
줄별 해설
첫 다섯 줄은 변경 차이, JSON, 식별자 검사, 임시 폴더, 경로 처리를 위한 모듈을 불러온다. 외부 패키지나 네트워크 기능이 없다. Path는 파일 경로를 이어 붙일 때 사용하며, 데이터 파일의 기준 경로는 main에서 만든 임시 폴더다.
CATALOG는 선택에 필요한 목록이다. id는 절차를 찾는 키이고 description은 절차의 목적을 나타내는 짧은 예제 식별자다. keywords는 이번 가짜 모델이 사용하는 선택어다. 설명은 존재 여부를 검증하지만 선택 계산에는 사용하지 않는다. 이것이 의미 이해 모델과 이번 규칙 선택기의 차이다.
PROCEDURES는 본문을 담는다. steps와 checks를 따로 두어 행동 순서와 완료 근거를 구분한다. TEAM의 rules는 선택 결과와 관계없이 맥락에 들어간다. approval_before_publish는 운영 절차를 불러오지 않는 작업에도 남아 있는 공통 규칙이다.
write_json의 ensure_ascii=False는 한글을 그대로 저장한다. indent=2는 사람이 파일 차이를 읽기 쉽게 만든다. 마지막 줄바꿈도 고정한다. read_json은 UTF-8로 파일을 읽고 Python 값으로 변환한다. 읽기에 실패하거나 JSON이 깨져 있으면 예외가 전달되어 프로그램이 멈춘다. 실패한 파일을 빈 절차로 바꾸지 않는다.
require_strings는 값이 비어 있지 않은 목록인지 확인한 뒤 각 원소가 공백만 있는 문자열이 아닌지 검사한다. checks에 빈 문자열 하나만 넣어 형식 검사를 통과하는 일을 막는다. 이 함수는 규칙, 선택어, 단계, 확인 항목에 반복해서 사용한다.
validate_catalog의 seen은 중복 식별자를 찾는다. 정규식은 영문 소문자로 시작하고 소문자·숫자·밑줄만 포함하는 이름을 허용한다. 이 형식에서는 경로 구분자가 들어가지 않는다. 이 검사는 예제 식별자 형식을 위한 것이며, 파일시스템의 모든 접근 위험을 처리하는 기능은 아니다.
fake_model의 집합은 여러 선택어가 같은 절차에 맞아도 한 번만 선택되게 한다. sorted는 결과 순서를 고정한다. 부분 문자열 비교이므로 문장의 의도나 부정을 이해하지 못한다. 규칙을 확장한다면 이 한계를 먼저 테스트로 표현해야 한다.
load_context는 지침과 목록을 먼저 읽는다. selector 매개변수는 기본적으로 fake_model을 사용하지만, 테스트에서는 다른 함수를 전달할 수 있다. 선택 결과가 목록인지 검사하고, 모든 이름이 known에 포함되는지 확인한 뒤 본문 파일을 읽는다. 본문 내부 id까지 같아야 같은 절차로 받아들인다.
read_names는 실제 본문 읽기를 마친 위치에서 추가한다. 선택 결과를 그대로 복사한 기록이 아니다. 검사에 실패하면 반환되지 않으므로 정상적으로 구성한 맥락에 포함된 파일만 나타낸다. team과 procedures를 별도 키로 반환하는 것은 공통 기준과 선택 절차의 역할을 유지하기 위해서다.
main은 임시 폴더를 만들고 예제 묶음을 저장한다. 정상 선택, 무관한 요청, 없는 식별자 거부를 차례로 검사한다. 검증을 assert로 작성하지 않고 조건과 예외로 작성했으므로 Python의 최적화 옵션으로 이 검사들이 제거되지 않는다.
문서 실습은 수정 전 내용을 메모리에 보관하고 수정 후 내용과 비교한다. unified_diff의 표시용 파일명은 고정 문자열이라 임시 경로가 결과에 나타나지 않는다. 예상 차이와 실제 차이를 비교한 뒤 같은 차이를 출력하므로 자동 확인과 사람의 검토가 같은 변경을 대상으로 한다.
마지막 진입 조건은 main.py를 직접 실행할 때 main을 호출한다. 임시 폴더 문맥을 벗어나면 예제 파일은 정리된다. 결과물을 보존하는 프로그램을 만들 때는 저장 정책이 필요하지만, 이번 실습은 실행 후 폴더가 남지 않는 방식으로 끝난다.
실행 결과
저장한 파일의 문법 검사와 실행은 다음 명령으로 수행한다. 첫 명령은 바이트코드 파일을 만들지 않고 소스를 컴파일한다. 성공하면 출력이 없다. 다음 출력은 코드에서 고정한 데이터에 따른 예상 결과다.
python3 -W error -c 'from pathlib import Path; compile(Path("main.py").read_text(encoding="utf-8"), "main.py", "exec")'
python3 main.py
작업: 문서 수정과 코드 검토
팀 규칙: workspace_only, run_test_diff, mark_generated, approval_before_publish
선택 절차: edit_docs, review_code
읽은 절차 파일: edit_docs.json, review_code.json
edit_docs 단계: read_target → edit_target → check_diff
edit_docs 확인: change_scope_checked
review_code 단계: read_diff → check_execution → check_marker
review_code 확인: evidence_checked
검증: 선택·미선택·없는 식별자·변경 차이 통과
생성 기록: report.txt / 예제 규칙 / 차이 확인
변경 차이:
--- before/report.txt
+++ after/report.txt
@@ -1 +1 @@
-상태: 초안
+상태: 검토 대기
운영 반영: 수행하지 않음
선택 절차와 읽은 절차 파일이 대응하는지 먼저 본다. publish_ops가 두 목록에 없고 공통 승인 규칙은 남아 있어야 한다. 이어서 변경 차이가 요청한 한 줄에 한정되는지 검토한다. “검증 통과”라는 문장만 읽으면 실제로 무엇을 바꿨는지 놓칠 수 있다.
이 검증은 예제의 선택 결과와 고정 문서 변경을 확인한다. 일반적인 문서 품질이나 임의의 Python 코드 실행 결과를 평가한 것은 아니다. AI가 이 프로그램을 수정해 제안했다면 문법 검사, 실행, 출력 비교에 더해 main.py 자체의 변경 차이도 읽어야 한다.
실무에서 자주 틀리는 것
모든 절차 본문을 먼저 읽는다
다음 독립 실행 예제는 작업과 무관한 본문도 맥락에 넣는다. 파일 수가 늘수록 관계없는 단계가 함께 전달된다. 본문을 모으기 전에 선택해야 한다.
def build_context(task, bodies):
return list(bodies.values())
print(build_context("문서", {
"문서": ["read_target"],
"운영": ["check_approval"],
}))
고친 예제는 현재 작업과 대응하는 본문만 추가한다. 완성 코드에서는 이 선택을 목록 정보로 수행하고 이후에 파일을 읽는다.
def build_context(task, bodies):
return [
body for keyword, body in bodies.items()
if keyword in task
]
print(build_context("문서", {
"문서": ["read_target"],
"운영": ["check_approval"],
}))
선택된 절차가 없으면 팀 지침도 버린다
절차 선택이 실패해도 공통 기준은 필요하다. 무관한 요청이나 아직 절차를 만들지 않은 작업이 들어올 수 있기 때문이다. 다음 코드는 빈 선택에서 공통 규칙을 잃는다.
def build_context(rules, selected):
if not selected:
return {}
return {"team": rules, "procedures": selected}
print(build_context(["workspace_only"], []))
고친 코드는 절차 목록의 길이와 관계없이 팀 지침을 유지한다. 빈 목록은 현재 요청에 맞는 절차가 없다는 뜻으로 남긴다.
def build_context(rules, selected):
return {"team": rules, "procedures": selected}
print(build_context(["workspace_only"], []))
절차를 읽었다는 이유로 완료 처리한다
다음 코드는 단계가 존재한다는 사실을 성공으로 바꾼다. 파일을 읽는 일과 행동을 수행하는 일, 그 결과를 확인하는 일은 각각 구분해야 한다.
def is_done(procedure):
return bool(procedure["steps"])
print(is_done({"steps": ["check_diff"]}))
고친 코드는 필요한 확인 항목이 관찰 결과에서 모두 참인지 검사한다. 관찰 결과 자체가 신뢰할 수 있는 실행에서 나왔는지는 실행 담당 코드가 책임져야 한다.
def is_done(procedure, observations):
required = procedure["checks"]
return bool(required) and all(
observations.get(name) is True for name in required
)
print(is_done(
{"checks": ["change_scope_checked"]},
{"change_scope_checked": True},
))
승인 여부만 저장하고 승인 대상을 잃는다
다음 코드는 승인한 변경과 현재 변경이 같은지 확인할 수 없다. 수정된 결과물에도 이전 승인 값을 그대로 사용할 수 있다.
def can_publish(approved):
return approved
print(can_publish(True))
고친 예제는 승인 기록에 대상과 변경 식별자를 포함한다. 아래 값은 실제 승인 행위가 아니라 비교 방식을 보여 주는 테스트 데이터다. 실제 하네스에서는 권한이 있는 승인 경로에서 받은 기록을 사용해야 한다.
def can_publish(record, target, revision):
return (
record.get("approved") is True
and record.get("target") == target
and record.get("revision") == revision
)
approval = {
"approved": True,
"target": "docs",
"revision": "change_a",
}
print(can_publish(approval, "docs", "change_b"))
한눈에 보기
| 대상 | 책임 | 확인 근거 |
|---|---|---|
| 팀 지침 | 모든 작업의 공통 경계를 유지한다 | 선택이 비어도 맥락에 남는다 |
| 절차 목록 | 본문을 읽기 전에 후보를 설명한다 | 식별자·설명·선택어 검증 |
| 가짜 모델 | 작업 설명으로 식별자를 고른다 | 고정 요청의 예상 선택 결과 |
| 본문 로더 | 선택된 파일만 읽고 구조를 검사한다 | 읽은 파일 목록과 내부 식별자 |
| 실행 담당 | 단계를 실제 행동으로 연결한다 | 실행 결과·테스트·변경 차이 |
| 운영 승인 | 승인한 대상과 변경을 일치시킨다 | 승인 기록과 현재 변경의 비교 |
절차를 재사용하려면 내용을 잘 쓰는 일과 읽는 시점을 정하는 일이 함께 필요하다. 이번 코드에서는 팀 규칙, 선택 목록, 본문이 각각 다른 책임을 가진다. 다음 장에서는 작업을 여러 에이전트에 나눌 때 이 공통 기준과 선택된 절차를 어떻게 전달할지 살펴본다.
연습 문제
- 작업 설명을 “문서 수정”으로 바꾼다. 선택 절차, 읽은 파일, 유지되는 팀 규칙을 예상하고 코드의 검증값도 함께 수정한다.
- catalog.json에 같은 id를 가진 항목을 하나 더 넣는다. 어느 함수가 이를 거부하는지 설명하고, 본문을 읽기 전에 중복을 검사하는 이유를 적는다.
- review_code의 checks를 빈 목록으로 바꾼다. 발생해야 할 오류와 그 검사가 실제 코드 검토 성공을 뜻하지 않는 이유를 설명한다.
- “운영 반영 금지”를 가짜 모델에 입력하면 어떤 절차가 선택되는지 예상한다. 명시적인 절차 지정 방식을 추가한다면 무엇을 검증해야 하는지 적는다.
정답과 해설
- edit_docs만 선택되고 edit_docs.json만 본문으로 읽힌다. 팀 규칙 네 항목은 모두 유지된다. main의 expected를 단일 식별자 목록으로, 예상 파일 목록도 단일 파일 목록으로 바꿔야 한다. 무관한 작업과 없는 식별자를 검사하는 부분은 그대로 사용할 수 있다.
- validate_catalog가 id: duplicate라는 오류로 거부한다. 같은 식별자에 다른 설명이나 선택어가 붙으면 목록의 의미가 모호해진다. 본문을 읽기 전에 목록 자체를 검사하면 어떤 항목을 기준으로 선택했는지 불분명한 상태로 진행하지 않는다.
- require_strings가 checks: nonempty list required라는 오류를 발생시킨다. 확인 항목이 없는 절차를 받아들이지 않는 구조 검사다. 확인 항목에 적힌 행동이 실제로 수행되었다는 증거는 별도로 수집해야 한다.
- “운영”이라는 선택어가 포함되어 publish_ops가 선택된다. 부정 표현을 이해하지 못하는 부분 문자열 규칙의 한계다. 명시적으로 지정한 식별자를 받는 방식에서는 값의 자료형, 목록에 존재하는 이름인지, 중복 처리, 파일 내부 식별자 일치를 확인해야 한다. 명시적 선택도 운영 반영 승인을 대신하지 않는다.