프롬프트로 부탁하는 것과 구조로 강제하는 것은 다릅니다. 이 수업에서는 에이전트가 정해진 절차를 건너뛰지 못하게 만드는 강제 기법과, 에이전트 간에 작업을 넘길 때 맥락 손실 없이 핸드오프하는 방법을 다룹니다.
프롬프트 지시의 한계와 구조적 강제
"배포 전에 반드시 테스트를 실행하세요"라고 시스템 프롬프트에 쓰는 것은 요청이지 강제가 아닙니다. 모델은 대체로 지시를 따르지만, 컨텍스트가 길어지거나 상황이 애매하면 단계를 건너뛸 수 있습니다. 규정 준수가 필수인 절차는 프롬프트 바깥의 구조로 강제해야 합니다.
대표적인 강제 수단은 세 층위입니다.
- 도구 설계 층 — 절차를 도구 안에 내장합니다.
deploy도구가 내부적으로 테스트를 먼저 실행하고 실패 시 배포를 거부하게 만들면, 모델이 잊어도 절차는 지켜집니다. - 훅(hook) 층 — 도구 호출 전후에 결정적 코드를 실행합니다. Claude Code의 PreToolUse 훅은 조건에 맞지 않는 도구 호출을 차단할 수 있습니다.
- 상태 기계 층 — 오케스트레이션 코드가 상태를 관리하며, 현재 상태에서 허용되지 않는 행동을 아예 노출하지 않습니다.
# 상태 기계 층의 예: 상태에 따라 노출하는 도구 자체를 바꾼다
TOOLS_BY_STATE = {
"drafting": [edit_tool, run_tests_tool],
"tested": [deploy_tool], # 테스트 통과 후에만 배포 도구 노출
"deployed": [rollback_tool],
}
tools = TOOLS_BY_STATE[current_state]
이 접근의 원칙은 "모델이 올바르게 행동하기를 기대하지 말고, 잘못 행동할 수 없게 만들라"입니다. 시험에서는 "중요 절차를 보장하는 가장 확실한 방법"을 묻는 문제에서 프롬프트 강화(오답)와 구조적 강제(정답)를 대비시킵니다. 프롬프트는 확률을 높이고, 구조는 보장을 만듭니다.
게이트와 검증 체크포인트
**게이트(gate)**는 워크플로의 단계 사이에 놓인 프로그램적 검증 지점입니다. 게이트를 통과하지 못하면 다음 단계로 진행할 수 없습니다. 좋은 게이트의 조건은 결정적(deterministic)이고 자동화 가능하다는 것입니다.
실무에서 자주 쓰는 게이트:
- 테스트 게이트 — 전체 테스트 통과 없이는 커밋·배포 단계로 못 넘어감
- 스키마 게이트 — LLM 출력이 JSON 스키마 검증을 통과해야 다음 호출의 입력이 됨
- 린트·타입 게이트 — 정적 검사 통과를 편집 완료의 정의로 삼음
- 휴먼 게이트 — 위험 행동(프로덕션 배포, 대량 삭제) 앞에서 사람의 승인을 요구
async function gatedPipeline(spec: string) {
const code = await agentEdit(spec);
const test = await runTests();
if (!test.passed) {
// 게이트 실패를 다음 턴의 입력으로 되돌린다
return agentEdit(spec, { feedback: test.failures });
}
const approval = await requestHumanApproval(diffSummary(code));
if (!approval.granted) return abort(approval.reason);
return deploy(code);
}
게이트 실패의 처리가 설계의 절반입니다. 실패를 그냥 예외로 터뜨리면 파이프라인이 죽습니다. 올바른 패턴은 실패 정보를 구조화해 에이전트의 다음 턴 입력으로 되돌리는 것입니다. 이때 재시도 상한을 함께 둬야 같은 실패를 무한 반복하지 않습니다.
LLM을 게이트 판정자로 쓰는 경우(LLM-as-judge)도 있지만, 결정적 검사로 가능한 것을 LLM 판정에 맡기는 것은 안티패턴입니다. JSON 유효성은 파서로, 테스트 통과는 테스트 러너로 검사하고, LLM 판정은 문체·적절성처럼 코드로 검사할 수 없는 축에만 씁니다.
에이전트 간 핸드오프 설계
긴 작업을 여러 에이전트(또는 여러 세션)에 나눠 맡기면 핸드오프(handoff) 지점에서 맥락이 손실됩니다. 앞 에이전트가 알아낸 제약 조건, 시도했다가 버린 접근, 미완료 항목이 넘어가지 않으면 뒤 에이전트는 같은 시행착오를 반복하거나 앞 작업과 모순된 결정을 내립니다.
핸드오프의 표준 해법은 **구조화된 인수인계 산출물(handoff artifact)**입니다. 대화 이력 전체를 넘기는 것이 아니라, 다음 항목을 담은 요약 문서를 만들어 넘깁니다.
- 완료된 것 — 무엇이 끝났고 어디에 있는가 (파일 경로, 커밋 해시)
- 미완료된 것 — 남은 작업 목록과 우선순위
- 결정과 근거 — 내려진 설계 결정과 그 이유 (뒤 에이전트가 번복하지 않도록)
- 막다른 길 — 시도했지만 실패한 접근과 실패 이유 (재시도 낭비 방지)
- 주의사항 — 건드리면 안 되는 것, 알려진 제약
## Handoff: 결제 모듈 리팩터링 (2/3 완료)
### 완료
- PaymentService 인터페이스 분리 — src/payments/service.ts (commit a1b2c3)
### 남은 작업
1. 웹훅 핸들러를 새 인터페이스로 이전 (src/webhooks/)
### 결정 사항
- 재시도 로직은 서비스 층이 아닌 큐 층에 둔다 — 중복 결제 방지 때문
### 막다른 길
- Stripe SDK v12 업그레이드 시도 → 타입 비호환으로 보류. 재시도하지 말 것
Claude Code의 컴팩션(compaction)이나 세션 요약도 본질적으로 자기 자신에게 하는 핸드오프입니다. 시험 함정: "대화 이력 원문 전체를 넘기는 것이 가장 안전하다"는 오답입니다. 원문 전체는 다음 에이전트의 컨텍스트를 즉시 소진시키며, 중요 정보가 잡음에 묻힙니다. 선별·구조화된 요약이 원문보다 실용적으로 우월합니다.
시험 함정
- 프롬프트 지시를 '강제'로 취급하는 선택지 — 프롬프트는 확률을 높일 뿐, 보장은 도구 설계·훅·상태 기계가 만듭니다.
- 게이트 실패 시 파이프라인을 즉시 중단하는 것을 정답으로 제시하는 함정 — 표준 패턴은 실패 정보를 구조화해 재시도 입력으로 되돌리되 상한을 두는 것입니다.
- 결정적으로 검사 가능한 것(JSON 유효성, 테스트 통과)을 LLM 판정에 맡기는 오답.
- 핸드오프에서 대화 원문 전체 전달을 권하는 오답 — 구조화된 요약(완료/미완료/결정/막다른 길)이 표준입니다.
- 휴먼 게이트를 모든 단계에 두는 선택지 — 승인 피로를 낳아 실효성이 떨어지며, 위험도 높은 행동에 선별 적용해야 합니다.
실습 시나리오
실제 시험과 같은 형식의 시나리오 문제입니다.
에이전트가 마이그레이션 스크립트를 프로덕션 DB에 바로 실행하는 사고가 발생했습니다. 재발을 막는 가장 확실한 조치는 무엇입니까?
빌드 연습 · 게이트 있는 배포 파이프라인 만들기
약 50분1.상태 정의
drafting → tested → approved → deployed 네 상태와 전이 조건을 표로 정리합니다.
기대 결과 · 각 전이에 필요한 게이트(테스트/승인)가 명시되어 있습니다.
2.상태별 도구 노출 구현
현재 상태에 따라 에이전트에 제공하는 도구 목록을 바꾸는 코드를 작성합니다.
기대 결과 · tested 상태 이전에는 deploy 도구가 목록에 나타나지 않습니다.
3.테스트 게이트 구현
테스트 실패 시 실패 목록을 구조화해 에이전트의 다음 입력으로 되돌립니다.
기대 결과 · 실패 → 수정 → 재검증 사이클이 최대 3회 상한과 함께 동작합니다.
4.휴먼 게이트 구현
배포 직전 diff 요약을 출력하고 y/n 입력을 기다리는 승인 단계를 넣습니다.
기대 결과 · 승인 거부 시 사유가 기록되고 파이프라인이 안전하게 종료됩니다.
5.핸드오프 문서 자동 생성
파이프라인이 중단될 때 완료/미완료/결정/막다른 길 구조의 인수인계 문서를 자동 생성합니다.
기대 결과 · 중단 후 새 세션이 문서만 읽고 이어서 작업할 수 있습니다.