
실패한 도구 호출에서 회귀 테스트까지: 4일 만에 구축한 Agent Black Box
요약
OpenAI Agents SDK 실행 과정을 구조화된 스팬으로 캡처하여 디버깅과 회귀 테스트를 지원하는 'Agent Black Box'를 소개합니다. 에이전트의 실패 원인을 타임라인별로 추적하고, GPT-5.6을 통해 실행 과정을 재생하며 수정된 동작을 평가할 수 있는 기능을 제공합니다.
핵심 포인트
- 에이전트 실행 과정을 구조화된 스팬으로 캡처하여 타임라인 시각화
- 도구 출력값을 스텁 처리하여 GPT-5.6에서 안전한 실행 재생 가능
- 실패한 실행을 수정된 동작에 대한 회귀 평가(regression eval)로 변환
- 잘못된 핸드오프나 함수 인자 오류 등 에이전트 내부 실패 원인 추적
요약 (TL;DR) — 에이전트가 실패했을 때, 모델의 최종 답변은 살펴보기에 가장 유용하지 않은 요소입니다. Agent Black Box는 모든 OpenAI Agents SDK 실행을 구조화된 스팬 (spans)으로 캡처하고, 이를 타임라인으로 렌더링하며, 캡처된 도구 출력값을 스텁 (stubbed, 부작용 없이) 처리하여 GPT-5.6에서 모든 실행을 재생(replay)하고, 수정된 동작을 이식 가능한 회귀 평가 (regression eval)로 변환합니다. OpenAI Build Week 2026을 위해 4일 동안 단독으로 구축되었습니다. 라이브 데모: blackbox.kopachelli.dev · 99초 영상: .
프로덕션 환경에서 에이전트가 실패할 때, 실제 원인은 대개 최종 메시지가 나오기 몇 단계 전의 일입니다: 잘못된 핸드오프 (handoff), 잘못된 형식의 함수 인자 (function arguments), 예상치 못한 가드레일 (guardrail) 결과, 또는 다음 에이전트가 잘못 읽은 내용을 반환한 MCP 호출 등이 그것입니다. 원본 JSON에는 증거가 있지만, 단지 그 과정을 설명해주지 못할 뿐입니다.
그것이 바로 제가 Agent Black Box를 만든 이유입니다: OpenAI 에이전트를 위한 셀프 호스팅 가능한 비행 기록 장치 (flight recorder)입니다. 실행을 구조화된 스팬 (spans)으로 캡처하고, 타임라인으로 검사하며, GPT-5.6에서 안전하게 재생하고, 수정된 동작을 회귀 평가 (regression eval)로 변환합니다.
반드시 작동해야 하는 하나의 경로부터 시작하기
4일이라는 시간 동안 일반적인 기능 백로그 (feature backlog)를 따르는 것은 위험했을 것입니다. 저는 하나의 사용자 여정 (user journey)을 신성하게 여겼습니다:
실패한 실행 시드 (seeded failing run) → 타임라인 (timeline) → 실패한 스팬 (failing span) → GPT-5.6에서 재생 (replay) → 빨간색에서 초록색으로 차이(diff) 확인 → 평가 (eval) 생성 → 모두 초록색으로 실행

모든 기술적 선택은 그 경로를 보호해야 했으며, 이를 위협하는 것은 무엇이든 삭제하거나 연기했습니다. 전형적인 장애 사례는 작지만 현실적입니다. 두 개의 에이전트(agent)로 구성된 지원 흐름이 환불 에이전트(refund agent)로 업무를 인계(handoff)하는데, 이때 환불 에이전트가 잘못된 형식의 금액인 "12.OO"를 사용하여 refund_order를 호출합니다. 이 실패는 로컬 스크립트 모델을 사용하여 실제 Python Agents SDK의 Runner와 트레이싱(tracing) 라이프사이클을 통해 재현 가능하게 생성되므로, API 키나 모델의 변동성 없이도 누구나 이를 만들어낼 수 있습니다. 반면, **리플레이(replay)**는 실시간 GPT-5.6 작업입니다.

두 번째 에이전트 프레임워크가 아닌, 얇은 캡처 레이어
Python 통합은 Agents SDK의 공개 TracingProcessor 콜백(callback)을 직접 소비합니다. AbbProcessor를 등록하면, 완료된 스팬(span)들은 정규화되어 트레이스가 종료될 때 하나의 인증된 배치(batch)로 내보내집니다. 이는 백그라운드 워커(background worker)에서 수행되므로, 콜백이나 전송(transport) 실패가 호스트 에이전트에 영향을 주지 않습니다.
래퍼(wrapper)는 의도적으로 얇게 설계되었습니다. 이는 Runner를 대체하거나 새로운 트레이싱 형식을 만들어내지 않습니다. 대신 ID, 부모 관계(parentage), 타이밍, 에러를 보존한 뒤 모든 것을 다섯 가지 스팬(span) 유형으로 매핑합니다: turn, tool_call, handoff, guardrail, mcp_call. 서버 경계에서 Phoenix는 관측성(observability) 도구를 신뢰할 수 있게 만드는 지루하지만 중요한 작업들을 수행합니다:
- 스팬(span)은 실행(run) + 외부 스팬 ID에 대해 **멱등성(idempotent)**을 유지합니다.
- 입력/출력/에러 본문은 **영구 저장 전 재귀적으로 비식별화(redacted)**됩니다.
- 원본 프로젝트 키는 절대 저장되지 않으며, 고유하게 인덱싱된 SHA-256 다이제스트(digest)만 저장됩니다.
- 256 KiB를 초과하는 본문은 깨진 JSON이 아닌, 경계가 지정된 절단 센티넬(truncation sentinel)이 됩니다.
- PostgreSQL 트랜잭션(transaction) + 권고 잠금(advisory locks)을 통해 동시 버전 할당의 일관성을 유지합니다.
Phoenix PubSub는 새로운 실행(run)을 알리고, LiveView는 진실의 원천(source of truth)으로서 PostgreSQL을 다시 쿼리합니다. 별도의 프론트엔드 프레임워크나 조정(reconcile)을 위한 클라이언트 측 스토어는 필요하지 않습니다.
중요한 리플레이 경계 (The important replay boundary)
"리플레이 (Replay)"라는 용어는 과장되기 쉽습니다. Agent Black Box는 모델 생성이 결정론적 (deterministic)이라고 주장하지 않습니다. GPT-5.6은 계속 라이브 상태로 유지되며 그 문구는 달라질 수 있습니다. 결정론적인 경계는 바로 **캡처된 도구 실행 (captured-tool execution)**입니다.
엔진은 원래 터미널 답변 직전 시점까지 대화를 재구축하고, 엄격한 도구 정의를 재구성하며, 프로그래밍 방식의 도구 호출 (Programmatic Tool Calling) 기능이 포함된 Responses API를 통해 GPT-5.6을 호출합니다. 모델이 클라이언트 소유의 함수 호출을 방출하면, 하네스 (harness)는 이를 캡처된 시퀀스와 매칭하여 저장된 출력을 반환합니다. 실제 도구는 절대 실행되지 않습니다. 따라서 환불, 이메일 전송, 또는 DB 변이 (DB mutation)를 리플레이하더라도 부수 효과 (side effect)가 반복되지 않습니다.
시드된 실패 (seeded failure)는 흥미로운 엣지 케이스 (edge case)를 드러냈습니다. 잘못된 인자 (malformed args)가 refund_order가 실행되기 전에 거부되었기 때문에, 반환할 역사적 출력이 없었습니다. 원래의 스팬 (span)을 조작하는 대신, 트레이스 (trace)는 캡처된 출력이 없을 때만 사용되는 명시적으로 라벨링된 안전한 replay_stub_output을 전달합니다. 만약 둘 다 존재하지 않는다면, 엔진은 도구를 호출하는 대신 발산 (divergence)을 기록합니다.
최종 판결에는 추가적인 모델 호출이 필요하지 않습니다. 실패한 소스 뒤에 성공적인 자식 리플레이가 이어지면 결정론적으로 FAILURE FIXED라고 라벨링됩니다. 판결자 (judge)는 기반이 되는 생성이 정직하게 라이브 상태를 유지하는 동안 안정적인 판결을 확인하게 됩니다.

디버깅 세션을 테스트로 전환하기
성공적인(green) 리플레이는 한 번은 유용합니다. 하지만 저장된 평가 (eval)는 향후 모든 배포 시마다 유용하게 만듭니다.
Agent Black Box는 캡처된 소스(source)와 가장 최근의 성공적인 리플레이(replay)를 압축하여 GPT-5.6에 요청합니다. 이때 반드시 폐쇄적이고 버전 관리되는 어설션 스키마(assertion schema)와 일치해야 하며, 저장되기 전에 로컬 검증(local validation)을 통과해야 하는 **엄격한 구조화된 출력 (strict Structured Output)**을 요구합니다. 합성(Synthesis)은 리플레이 안정성이 있는 구조에 집중합니다: 도구가 호출되었는지, JSON-Pointer 인자가 예상된 스칼라(scalar)와 일치하는지, 실행이 턴 제한(turn bound) 내에 머물렀는지, 그리고 스팬(span) 오류가 발생하지 않았는지 등을 확인합니다. Run all은 각 평가(eval)를 다시 리플레이하고 어설션(assertion)을 로컬에서 확인하며, 모델을 판사로 사용하지 않습니다(no model-as-judge). 또한 저장된 계약(contract)은 Elixir 클로저(closure)가 아닌 이식 가능한 JSON이므로, 향후 CI 러너(runner)로 가는 깨끗한 경로를 제공합니다.
이는 실제 버그에서 비롯되었습니다. 처음 생성된 평가(eval)는 리플레이의 전체 산문(prose)을 그대로 복사했습니다. 이후의 리플레이가 동일한 결과를 다르게 표현하자 문자열 체크(string check)에서 올바르게 실패했습니다. 해결책은 더 느슨한 '그린 버튼(green button)'을 만드는 것이 아니라, 확률적인 산문(stochastic prose)과 내구성이 있는 구조적 동작(durable structural behavior)을 분리하는 것이었습니다.

안전성과 비용은 기능의 일부입니다
캡처된 트레이스(trace)에는 자격 증명(credentials), 비공개 프롬프트(private prompts), 그리고 비용이 많이 드는 리플레이 컨텍스트(replay contexts)가 포함될 수 있으므로, 안전성은 배포 노트가 아닌 제품 경로(product path)에 포함됩니다. 모든 OpenAI 호출은 감사된 하나의 AgentBlackBox.OpenAI 모듈을 거칩니다: 기본값은 store: false이며, 예측 가능한 회계 처리를 위해 서비스 티어(service tier)를 고정하고, 데모를 위해 명시적인 낮은 추론(low reasoning)을 사용하며, 안정적인 비개인정보(non-PII) 키 아래에 하나의 동결된 캐시 가능 접두사(cacheable prefix)를 둡니다. 모든 네트워크 I/O 이전에, 감독되는 프로세스 내 원장(in-process ledger)이 보수적인 비용 한도를 예약하고, 보고된 사용량에 따라 정산하며, 전송된 요청의 결과를 알 수 없게 될 경우 전체 한도를 청구합니다. 이때 재시작 안전을 위한 외부 한도로는 제공자 측의 프로젝트 캡(project cap)을 사용합니다. 공개 데모는 데모 모드로 실행됩니다: 데이터 수집(ingestion)은 403을 반환하지만, 리플레이/평가(replay/eval)는 알려진 데이터셋에 대해 상호작용이 가능합니다.
왜 Phoenix, PostgreSQL, 그리고 NixOS인가
Phoenix LiveView는 참신함 때문이 아니라 마감 기한을 맞추기 위한 선택이었습니다. 하나의 앱이 데이터 수집 (ingestion), 영속성 (persistence), 재생 감독 (replay supervision), PubSub, 그리고 스트리밍 UI를 모두 담당하며, 테스트 시에는 OpenAI 경계를 Mox로 교체하여 전체 경로를 오프라인에서 실행합니다. 호스팅된 인스턴스는 NixOS 플릿 (fleet)을 위해 네이티브로 패키징된 것과 동일한 릴리스입니다. beamPackages.mixRelease가 이를 빌드하고, systemd가 동적 사용자 (dynamic user) 하에서 실행하며, sops와 LoadCredential이 비밀 정보 (secrets)를 전달하고, PostgreSQL 17은 로컬 피어 인증 (peer auth)을 사용하며, Caddy가 유일한 퍼블릭 리스너 (public listener) 역할을 합니다. 배포 (Publication)는 실패 시 차단되는 방식 (fail-closed)으로 이루어졌습니다 (루프백 수락이 통과될 때까지 호스트 이름 범위의 Caddy 503 응답). Docker Compose는 여전히 휴대 가능한 셀프 호스팅 (self-host) 경로로 남아 있습니다.

Codex가 빌드 방식에 가져온 변화
Codex는 제품 소유권 (product ownership)을 대체한 것이 아니라, 마감 기한 내에 규율 있고 증거 중심적인 1인 워크플로우를 가능하게 했습니다. 하나의 주요 Codex 태스크가 실질적인 구현을 담당했으며, 이는 신성한 경로 (sacred path), 코딩 규칙, 안전 경계, 세션 프로토콜을 정의하는 AGENTS.md 운영 계약 (operating contract)에 의해 구동되었습니다. 경계가 지정된 전문 에이전트 (specialist agents)들은 격리된 리스크(재생 의미론 (replay semantics), 비식별화 (redaction), 배포, UI, 제출 주장)를 검토하는 동안, 주요 태스크가 통합 (integration)을 담당했습니다. 모든 작업 단위는 구현 전에 Linear 이슈를 생성했고, 결정 사항은 내려지는 즉시 불변의 ADR (Architecture Decision Records)이 되었으며, 작업 로그 (worklogs)에는 증거와 비용이 기록되었습니다.
더 깊은 교훈은 다음과 같습니다: 에이전트는 "앱을 만들어줘"라는 명령보다 명시적인 불변량 (invariants)이 있을 때 훨씬 더 잘 작동합니다. 계약 (contract)은 범위, 진실성, 완료 증거를 코딩 시스템이 추론할 수 있는 입력값으로 변환해 주었습니다.
내가 가져가고 싶은 교훈들
- 하나의 부정할 수 없는 결과물로부터 역방향으로 구축하세요 (Build backward from one undeniable outcome). 신성한 경로(sacred path)는 인프라를 제품 가치보다 하위 요소로 유지하게 해주었습니다.
- 결정론적 경계 (deterministic boundary)를 정확하게 명명하세요. 캡처된 도구(tools)는 실시간 모델 생성(live model generation)이 결정론적이지 않더라도, 결정론적이고 부작용(side-effect)이 없을 수 있습니다.
- 산문 형태의 스냅샷보다는 구조적 평가 (structural evals)를 선호하세요. 안정적인 행동 불변량 (behavioral invariants)은 무해한 문구 변경에도 살아남습니다.
- 돈, 비밀, 그리고 공개 상태 (public state) 주변에서는 실패 시 차단 (Fail closed) 하세요.
- 결정이 내려지는 즉시 내구성을 갖추게 하세요. ADR (Architecture Decision Record) 디렉토리는 인간과 코딩 에이전트(coding agents) 모두에게 유용합니다.
전체 경로 시도하기
설정이 필요 없습니다: 라이브 시드 데모 (live seeded demo)를 열고, error-bad-tool-args를 선택한 뒤, 빨간색 refund_order 스팬 (span)을 선택하세요. 이를 GPT-5.6에서 다시 재생 (replay)하고, 성공적인 재생으로부터 평가 (eval)를 생성한 다음, Run all을 누르세요. 캡처 → 재생 → 평가로 이어지는 전체 루프가 약 1분 만에 완료됩니다. 이는 셀프 호스팅이 가능합니다: 단일 서버에 Phoenix + PostgreSQL 릴리스 하나면 충분합니다.
Agent Black Box는 잘못된 형식의 도구 호출 (malformed tool call) 하나를 이해할 수 있게 만들기 위한 방법으로 시작되었습니다. 하지만 그 유용한 결과는 더 광범위합니다: 불투명한 에이전트 기록 (opaque agent history) → 안전한 실시간 재생 (safe live replay) → 다른 인간(또는 코딩 에이전트)이 검사하고 실행할 수 있는 회귀 계약 (regression contract)으로 나아가는 구체적인 패턴을 제시합니다.

공개 사항: 저는 Agent Black Box를 구축했으며, AI의 도움(작성 및 편집)을 받아 이 글을 작성했습니다. 여기에 기재된 모든 기술적 주장은 실행 중인 시스템을 통해 검증되었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기