
코드로 AI 에이전트의 재현성을 높이는 방법: 실패 기록 메모리의 최소 구현과 체크리스트
요약
AI 에이전트가 동일한 실수를 반복하지 않도록 실패 기록을 제약 사항으로 활용하는 최소 구현 방법을 다룹니다. 실패를 구조화된 Markdown 파일로 저장하고, 가벼운 인덱스를 통해 관련 제약 사항만 컨텍스트에 주입하는 설계 패턴을 제안합니다.
핵심 포인트
- 실패를 트리거, 금지 사항, 대안을 포함한 구조적 제약으로 저장
- 컨텍스트 비대화를 방지하기 위해 요약 인덱스와 본문을 분리
- Severity(심각도) 기반 정렬로 위험한 실패를 우선 회상
- 검증 게이트를 코드로 구현하여 완료 정의를 강제
AI 에이전트가 "같은 실수를 반복하는 것"을 코드로 줄이는 방법에 대한 이야기입니다. 모델을 똑똑하게 만드는 것이 아니라, 과거의 실패를 제약(Constraint)으로서 기억에 쌓아두고, 작업 착수 전에 읽어들이는 것만으로도 재현성은 확실히 높아집니다.
이 기사는 직접 구현하는 측면에 초점을 맞추어, 실패 기록 메모리의 **최소 구현(Minimal Implementation)**과 운영 시 빠지기 쉬운 함정의 체크리스트를 코드 중심으로 정리합니다. 왜 성공 절차가 아닌 실패 기록을 축으로 삼는지에 대한 설계 판단은 별도의 기사(Zenn)로 나누었습니다. 여기서는 구현을 다룹니다.
해야 할 일은 세 가지만 있습니다.
- 실패를 "제약"으로서 구조화하여 1파일 1건으로 저장한다
- 가벼운 인덱스(Index)를 만들어, 착수 전에 관련 부분만 읽어온다
- 보고 전에 실물 검증 게이트(Gate)를 통과시킨다
특별한 인프라는 필요하지 않습니다. 우선 파일 기반으로 만들고, 필요해지면 MCP 서버로 외부에 분리합니다.
단순한 반성 메모는 효과가 없습니다. 트리거(Trigger) / 하지 말아야 할 것 / 대신할 것의 3요소를 갖추게 합니다. 1파일 1건, 프론트매터(Frontmatter)가 포함된 Markdown 형식으로 만듭니다.
---
name: verify-ui-before-report
type: feedback
...
severity를 갖게 하는 것이 효과적입니다. 나중에 "위험한 실패일수록 회상을 우선"하도록 구현할 때 사용합니다.
모든 건을 매번 읽으면 컨텍스트(Context)가 비대해져 정작 중요한 제약 사항이 희석됩니다. 인덱스에는 한 줄 요약(Summary)만 두고, 본문은 별도 파일로 분리합니다.
memory/
INDEX.md # 한 줄 요약의 집합 (가벼움)
verify-ui-before-report.md # 본문
...
INDEX.md는 다음과 같은 형태입니다.
- [verify-ui-before-report](verify-ui-before-report.md) — 외관 결과물은 실제 화면 육안 확인까지 완료되지 않음 / severity:high
- [identifiers-from-output](identifiers-from-output.md) — hash/uuid/key는 직접 입력하지 말고 출력에서 전기할 것 / severity:high
- [test-never-reaches-production](test-never-reaches-production.md) — 운영 환경 도달 테스트 금지, 테스트용 계정 격리 / severity:high
주의: 인덱스는 쌓을수록 좋은 것이 아닙니다. 비대해지면 에이전트 기동 시의 컨텍스트 읽기 과정에서 인덱스가 중간에 잘려나가, 기억을 쌓아두어도 효과가 없어집니다(실제로 이 문제를 겪었습니다). 한 줄 요약은 짧게 유지하고, 빈도가 낮은 기록은 뒤로 퇴피시킵니다.
작업에 들어가기 전에, 인덱스에서 "지금 관계있는 것"만 끌어옵니다. 최소 구현은 키워드 일치만으로도 충분합니다.
import re
from pathlib import Path
MEM = Path("memory")
...
포인트는 severity로 정렬되어 있다는 점입니다. 위험한 실패(비밀 누설, 운영 환경 파괴, 권한, 법무)가 반드시 상위에 오게 됩니다. 실제 운영에서는 키워드 일치를 임베딩(Embedding) 검색으로 교체하지만, 우선은 일치 방식으로 동작시켜 효과를 확인하는 것이 안전합니다.
재현성을 해치는 가장 큰 요인은 "확인하지 않고 완료라고 선언하는 것"이었습니다. 결과물의 종류별로 완료의 정의를 코드로 구속합니다.
import subprocess
def gate_published(url: str, must_contain: str) -> bool:
"""공개 계열의 완료 조건: 운영 URL이 200이고 본문이 나와야 함."""
...
UI/이미지라면 육안 확인 필수, 데이터 처리라면 출력된 실제 값 대조 등 종류별로 게이트를 바꿉니다. 게이트를 통과할 때까지 "완료"를 출력하게 하지 않는 것이 핵심입니다.
수수하지만 효과적인 한 수입니다. 해시(Hash), UUID, 사이즈, 환경 변수의 키 이름을 기억이나 외관을 보고 직접 입력하면 높은 확률로 틀립니다. **커맨드 출력에서 기계적으로 전기(Copy)**합니다.
# NG: 기억에 의존해 직접 입력
IMAGE_TAG="sha-abc123" # 기억이 가물가물하여 틀림
# OK: 출력에서 전기
...
실제 운영 중에 겪은 사례입니다.
-
색인 비대화 (Index Bloat): INDEX가 너무 길어서 컨텍스트 (Context) 로딩 시 잘리고 있지는 않은가. 한 줄은 짧게 작성하고, 빈도가 낮은 정보는 따로 격리할 것
-
전체 리콜 (Full Recall): 매번 전부 읽고 있지는 않은가. 관련 내용 + 심각도 (Severity) 상위 항목으로만 압축할 것
-
심각도 (Severity) 미설정: 위험 요소 (Secret/운영 환경/권한/법무)가 상위에 배치되어 있는가
-
검증 게이트 (Verification Gate) 스킵: 200 응답이나 존재 여부 (exists) 확인만으로 완료 처리하고 있지는 않은가. 본문 및 실제 화면까지 확인할 것
-
수동 식별자 (Manual Identifier): hash/uuid/key를 출력값에서 그대로 전사(transcribe)하고 있는가
-
임시방편 (Quick Fix): 동일 유형의 작업을 템플릿에 맞추고 차이점만 바꾸고 있는가 (매번 방식이 다르면 실패가 재현되지 않음 = 기억이 작동하지 않음)
-
성공 절차만 기록: 실패 기록 (하지 말아야 할 것)을 한 줄로 남기는 습관이 있는가
-
실패를 「트리거(Trigger) / 하지 말아야 할 것 / 대신할 방법」으로 구조화하여, 1개 파일당 1건씩 저장
-
가벼운 색인으로부터, 작업 착수 전 관련 내용만 심각도 (Severity) 우선순위로 읽어 들임 (read-before-act)
-
보고 전 유형별 검증 게이트 (Verification Gate)를 통과시키며, 통과할 때까지 완료 출력을 내보내지 않음
-
식별자는 출력값에서 전사. 동일 유형의 작업은 템플릿에 맞춤
설계 판단 (왜 성공이 아닌 실패를 축으로 하는지, 위험도에 따라 회상을 우선시하는 사고방식)은 이쪽입니다.
이 메커니즘을 대화 외부에 기억을 쌓아 올리는 서비스로서 그대로 구현하고 있습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기