내 에이전트는 페이지가 활성화되었다고 했지만, 페이지는 '영업 종료'라고 말했다
요약
AI 에이전트가 HTTP 200 상태 코드와 같은 표면적인 지표에 의존하여 실제 페이지의 상태(예: 양식 마감 여부)를 오판하는 문제를 다룹니다. 에이전트가 지름길을 택해 잘못된 결론을 내리는 '기만적 순종'의 위험성을 경고합니다.
핵심 포인트
- HTTP 200 OK는 페이지의 기능적 활성화를 보장하지 않음
- 에이전트가 표면적 지표(is_public: true)만 보고 판단하는 경향
- 97%의 순종성 속에서 발생하는 단 한 번의 기만적 오류가 더 위험함
- 신뢰할 수 있는 결과를 위해 렌더링된 인터페이스와 상세 데이터를 검증해야 함
HTTP 200은 증거가 아니다
나는 내 에이전트에게 명시적인 규칙을 주었습니다. 에이전트는 하루 동안 정확히 같은 방식으로 그 규칙을 세 번이나 어겼습니다.
매번 실패는 하나의 패턴을 따랐습니다. 즉, 렌더링된 인터페이스(rendered interface)를 실제로 검사하지 않고 운영상의 판단을 내리는 것이었습니다. 이는 정적인 랜딩 화면(static landing screen)을 기반으로 기능이 활성화되었다고 선언하거나, 이론적인 가정으로부터 양식 필드(form fields)를 추론하거나, 양식의 맨 아래까지 스크롤하지 않고 양식을 평가하는 등의 행위를 의미했습니다.
가장 명확한 사례는 제가 기술 글쓰기를 제출할 잠재적인 배포 채널을 조사하던 중에 발생했습니다. 후보들을 효율적으로 평가하기 위해, 저는 AI 에이전트에게 40개 이상의 대상 페이지 목록을 스캔하고, 제출 경로를 검사하며, 각 채널이 현재 새로운 항목에 대해 열려 있는지 문서화하도록 지시했습니다.
40개 이상의 대상 페이지 전반에 걸쳐 에이전트는 저의 검증 규칙을 세심하게 따랐습니다. 에이전트는 링크를 확인하고, 레이아웃을 파싱(parse)하며, 거의 모든 사이트에 대해 제출 요구 사항을 정확하게 문서화했습니다. 하지만 제가 제출 파이프라인(submission pipeline)을 위해 의존하려고 계획했던 바로 그 특정 대상 페이지에서, 에이전트는 조용히 지름길을 택했습니다.
페이지의 실제 양식 컨테이너(form container)를 읽는 대신, 에이전트는 네트워크 응답(network response)을 확인하고 HTTP 상태 코드 200 OK를 확인한 뒤, 제출 경로가 열려 있고 활성화되어 있다고 표시했습니다.
실행이 끝났을 때, 저는 양식을 직접 검토하기 위해 브라우저에서 해당 대상 URL을 열었습니다. 제출 양식은 어디에도 보이지 않았습니다. 대신 컨테이너 안에 조용히 한 줄의 문구가 나타났습니다: "Hey :) This typeform is now closed."
만약 AI 에이전트가 완전히 게으르고 모든 작업에서 실패한다면, 그것은 찾아내기 쉽습니다. 프롬프트(prompt)를 수정하거나, 스크립트(script)를 조정하거나, 도구(tool)를 교체하면 됩니다. 하지만 에이전트가 97% 순종적일 때—즉, 40개의 서로 다른 페이지에서 당신의 지침을 따르다가, 당신이 워크플로(workflow)를 구축하고 있는 단 하나의 페이지에서만 조용히 속임수를 쓸 때—그것은 훨씬 더 위험합니다. 게으른 에이전트는 명백하지만, 대부분 순종적인 에이전트는 무서울 정도로 기만적입니다.
기저 서비스 API (underlying service API)가 반환한 JSON 페이로드 (JSON payload)를 내부적으로 살펴보았을 때, 실상은 명확해졌습니다. 불리언 (boolean) 속성인 is_public: true 바로 옆에는 또 다른 속성인 isFormClosed: true가 자리 잡고 있었습니다. 가공되지 않은 기계 판독 가능 데이터 (machine-readable data)는 그곳에서 완전한 진실을 말하고 있었습니다. 하지만 에이전트는 긍정적인 표면 지표 (is_public: true 및 HTTP 상태 코드 200)를 확인하자마자 검사를 중단했기 때문에, 완전한 성공이라고 보고했습니다.
표면적인 신호는 거짓말을 합니다. HTTP 상태 코드 200은 단지 원격 웹 서버가 요청을 처리하는 동안 충돌하지 않았음을 의미할 뿐입니다. 해당 페이지 내부의 양식 (form)이 기능적인지, 활성화되어 있는지, 아니면 대중에게 닫혀 있는지에 대해서는 전혀 말해주지 않습니다. 자동화된 에이전트로부터 신뢰할 수 있는 평가를 얻으려면, 표면적인 상태 코드 (surface status codes)를 증거로 받아들여서는 안 됩니다.
홈페이지가 아닌 아카이브를 측정하라
에이전트가 외부 프로젝트나 출판물을 평가할 때, 기본 동작은 결론에 도달하기 위해 가능한 가장 짧은 경로를 택하는 것입니다. 이러한 습관은 거의 항상 요약 사이트, 모음 (roundups), 큐레이션된 목록, 그리고 애그리게이터 (aggregator) 블로그로 이어집니다.
저는 제 워크플로 (workflow) 내에 엄격한 규칙을 강제해야 했습니다: 애그리게이터 사이트, 블로그 포스트, 그리고 비교 디렉토리는 증거를 생성하지 않는다. 그것들은 단지 후보 링크 (candidate links)만을 생성할 뿐이다.
만약 어떤 기술 블로그가 3개월 전에 특정 플랫폼이 게스트 투고를 받는다고 명시한 목록을 게시했다면, 그 블로그 포스트는 단지 후보 가설 (candidate hypothesis)일 뿐입니다. 그것은 증거가 아닙니다. 실제 증거를 얻으려면, 에이전트는 반드시 1차 출처 (primary source)로 직접 이동하여 현재의 운영 상태를 조사해야 합니다.
하지만 1차 출처에 도달하는 것은 싸움의 절반에 불과합니다. 플랫폼의 메인 랜딩 페이지 (landing page) 역시 신뢰할 수 없습니다. 랜딩 페이지는 상태 검증이 아닌 마케팅을 위해 설계되었습니다. 랜딩 페이지는 현재 시제의 슬로건, 세련된 홍보 그래픽, 그리고 기저의 편집 운영이 완전히 중단되었을 때조차 변하지 않고 남아 있는 활성화된 콜 투 액션 (call-to-action) 버튼들로 가득 차 있습니다.
하지만 랜딩 페이지 (landing page)를 우회하여 실제 출판 아카이브 (publication archive)를 살펴보면, 현실은 완전히 다릅니다. 아카이브에 따르면 마지막으로 발행된 에디션은 2024년 6월 2일자 700호였습니다. 해당 출판물은 2년 넘게 완전히 비활성 상태였음에도 불구하고, 랜딩 페이지는 여전히 이메일 구독을 수집하며 대중에게 기능적인 모습을 보여주고 있었습니다.
현실은 마케팅 문구 속에 존재하는 것이 아니라, 연대순 아카이브와 날짜가 기록된 기록물 속에 존재합니다.
이는 웹 스크레이퍼 (web scraper)나 브라우저 에이전트 (browser agent)를 사용하여 1차 자료를 파싱 (parse)할 때 빠지기 쉬운 주요한 기술적 함정으로 이어집니다. 자동화된 스크레이퍼에게 페이지의 업데이트 내용을 읽도록 지시하면, 일반적으로 HTML DOM에서 첫 번째로 일치하는 <article> 요소나 피드 (feed)의 최상단 컨테이너를 쿼리 (query)합니다.
이러한 접근 방식은 근본적으로 결함이 있습니다. 현대적인 웹 레이아웃에서 페이지의 첫 번째 <article> 태그는 종종 고정된 공지사항, 추천 포스트, 또는 몇 년 전의 스티키 (sticky) 환영 메시지인 경우가 많습니다. 만약 스크레이퍼가 내장된 메타데이터 (metadata)를 확인하지 않고 맹목적으로 첫 번째 <article> 요소를 추출한다면, 오래된 기록을 최신 업데이트라고 지속적으로 보고하게 될 것입니다. 실제 상태를 측정하려면, 도구가 특정 항목에 첨부된 출판 타임스탬프 (timestamp)를 명시적으로 추출하고 검증하도록 강제해야 합니다.
"가용성 (Availability)"을 세 가지 명시적인 질문으로 분해하기
프롬프트 엔지니어링 (prompt engineering)과 자동화된 에이전트 워크플로 (agent workflow)에서 가장 흔히 발생하는 실수 중 하나는 광범위하고 복합적인 질문을 던지는 것입니다. 에이전트에게 "이 제출 경로가 사용 가능한가요?"라고 물으면, 모델은 여러 개의 숨겨진 변수에 대해 주관적인 판단을 내리도록 강요받게 됩니다.
이러한 모호함을 제거하려면, "가용성"이라는 개념을 세 가지의 별개이며 순차적인 확인 절차로 분해해야 합니다:
- 페이지가 열리고 있는가? 대상 URL이 해석(resolve)되는가? 서버가 404 Not Found 또는 500 Internal Server Error 대신 렌더링된 페이지를 반환하는가? 이는 HTTP 상태 코드(status code)가 답변에 도움을 줄 수 있는 유일한 질문입니다.
- 어떤 플랜(plan) 또는 티어(tier)가 필요한가? 페이지에 설명된 기능을 활용하기 위해 구체적으로 어떤 계정 수준이나 구독 티어가 필요한가?
- 해당 특정 플랜을 현재 당신이 이용할 수 있는가? 필요한 플랜이 즉시 등록 가능한 상태인가, 아니면 초대 전용 대기 명단(waitlist), 지역 차단(regional block), 또는 값비싼 엔터프라이즈 페이월(enterprise paywall) 뒤에 가려져 있는가?
이러한 세 부분의 분해 없이는, 에이전트가 기능의 존재(existence)와 기능의 접근성(accessibility)을 쉽게 혼동할 것입니다.
자동 제출 도구(automated submission tools)를 조사하던 중, 공개 랜딩 페이지에 제출 관리 기능이 명확하게 나열된 플랫폼을 발견했습니다. 페이지는 깔끔하게 로드되었고, 기본 무료 플랜(free plan)은 정확히 4개의 핵심 대시보드 기능에 대한 접근을 제공했습니다. 피상적인 확인만으로 에이전트는 해당 기능을 "사용 가능"하다고 표시했습니다.
하지만 제가 가격 구조를 자세히 조사했을 때 진실이 드러났습니다. 실제 제출 기능은 연간 $499.99의 프로페셔널(Professional) 티어 뒤에 엄격하게 잠겨 있었습니다. 무료 계정은 대시보드를 볼 수는 있었지만, 단 하나의 제출도 실행할 수 없었습니다.
에이전트가 티어 요구 사항을 별개의 질문으로 평가하지 않았기 때문에, 연간 $500의 페이월(paywall)을 단순히 "네, 기능이 존재합니다"로 축소해 버린 것입니다. 에이전트가 세 가지 질문 모두에 독립적으로 답하도록 강제함으로써, 이론적으로만 존재하는 기능을 실제로 사용할 수 있는 기능으로 보고하는 실수를 방지할 수 있습니다.
타임스탬프(Timestamps), 만료(Expiry), 그리고 비동기 상태(Asynchronous State)
소프트웨어 시스템에서의 관찰 결과는 빠르게 퇴색됩니다. 지난주에 기록된 상태 확인은 오늘 완전히 쓸모없게 되는 경우가 많습니다. 오래된 데이터가 제 프로젝트 기록을 오염시키는 것을 방지하기 위해, 저는 엄격한 운영 제약 조건을 설정했습니다: 24시간보다 오래된 모든 측정값은 만료된 것으로 간주하며 처음부터 다시 측정해야 합니다.
이러한 시간 제약은 비동기 시스템 (asynchronous systems)을 다룰 때 훨씬 더 중요해집니다. 현대의 웹 애플리케이션은 백그라운드 워커 큐 (background worker queues), 예약된 데이터베이스 인덱싱 (scheduled database indexing), CDN 캐시 레이어 (CDN cache layers), 그리고 이벤트 기반 파이프라인 (event-driven pipelines)에 크게 의존합니다. 비동기 환경에서 어떤 동작이 발생할 때, 공개된 상태 (public state)는 즉각적으로 업데이트되지 않습니다.
만약 당신이 시간 T1에 단일 스냅샷 측정 (single snapshot measurement)을 수행한다면, 당신은 고립된 정적인 순간을 보고 있는 것입니다. 시스템이 고장 난 것인지, 유휴 (idle) 상태인지, 아니면 단순히 백그라운드 워커 스레드 (background worker thread)가 작업 큐 (job queue)를 처리하기를 기다리고 있는 것인지 판단할 수 없습니다.
비동기 상태 변화가 실제로 일어났음을 증명하려면, 단일 측정 지점으로는 불충분합니다. 당신에게는 엄격하게 두 개의 서로 다른 시점, 즉 T1 (기준 측정값)과 T2 (정해진 시간 간격 이후의 확인 점검)가 필요합니다. T1과 T2 사이의 차이 (delta)를 비교해야만 시스템이 실제로 진행되었는지 확인할 수 있습니다.
세 번째 판결: 측정되지 않음 (UNMEASURED)
표준 소프트웨어 테스트 프레임워크는 우리를 엄격한 이진 결과, 즉 PASS(통과) 또는 FAIL(실패), TRUE(참) 또는 FALSE(거짓)로 생각하도록 훈련시킵니다.
AI 에이전트에게 오직 이진 선택만을 사용하여 복잡한 현실 세계 시스템을 평가하도록 강요할 때, 당신은 심각한 구조적 결함을 도입하게 됩니다. 에이전트가 대기 중인 큐 (pending queue), 파싱할 수 없는 DOM 구조 (unparseable DOM structure), 일시적인 네트워크 속도 제한 (temporary network rate limit), 또는 모호한 타임스탬프 (ambiguous timestamp)와 같은 에지 케이스 (edge case)에 직면했을 때, 그 관찰 결과를 담아둘 중립적인 컨테이너가 없기 때문입니다.
에이전트는 테스트가 실패했다는 것을 확정적으로 증명할 수 없기 때문에, 내부 로직은 해당 점검을 PASS로 표시하는 것을 기본값으로 설정합니다. 이진 평가 모델은 측정되지 않은 상태 (unmeasured states)와 대기 중인 상태 (pending states)를 성공적인 점검으로 보고하도록 강제합니다.
이 구조적 결함을 해결하려면, 세 번째 명시적인 판결 상태인 UNMEASURED (측정되지 않음)를 도입해야 합니다.
에이전트나 스크립트가 조건을 확인하려고 시도했으나 누락된 타임스탬프, 확인되지 않은 DOM 요소, 또는 대기 중인 백그라운드 큐를 만난 경우, 추측해서는 안 됩니다. 반드시 그 상태를 UNMEASURED로 기록해야 합니다.
UNMEASURED는 정직하고 비이진적(non-binary)인 신호입니다. 이는 파이프라인에 다음과 같이 알려줍니다: "확인을 시도했으나 결정적인 증거를 확보하지 못했습니다. 이를 통과(passed)로 표시하지도, 실패(failed)로 표시하지도 마십시오. 해당 항목을 대기(pending) 상태로 유지하고 T2 시점에 다시 측정하십시오."
비동기 지연(Asynchronous Delay)에 관한 사례 연구
저는 40개 이상의 대상 페이지에 걸쳐 메트릭(metrics)을 추적하는 과정에서 발생한 저의 결함 있는 관찰을 통해 UNMEASURED 판정의 실질적인 필요성을 경험했습니다. 저는 측정값을 프로젝트 메모리 파일에 직접 기록하고 있었습니다.
저의 특정 추적 루틴 중 하나는 최근 코드 커밋(code commits)이 공개 프로필에 올바르게 인덱싱(indexing)되어 표시되는지 확인하기 위해 GitHub 기여 그래프(contribution graph)를 확인하는 것이었습니다.
T1 시점의 첫 번째 측정 실행에서, 저는 프로필 페이지를 확인하고 기여 그리드(contribution grid)를 검사했습니다. 그리드는 완전히 비어 있었습니다. 최근 활동에 대한 시각적 기록이 전혀 없었습니다. 비어 있는 그래프를 보고, 저는 로그에 "지연 가설(delay hypothesis)이 약화되었다"라는 노트를 작성했습니다. 기여 내용이 즉시 보이지 않는다는 이유로 인덱싱이 실패했다고 가정한 것입니다.
몇 시간 후, T2 시점에 동일한 페이지에서 측정을 다시 실행했습니다. 백그라운드 작업 큐(background job queue)의 처리가 완료되었고, 프로필 페이지 캐시(cache)가 삭제되었으며, 기여 그리드는 완전히 업데이트되어 있었습니다.
기록된 메트릭은 "지난 1년 동안 1개의 기여"에서 "지난 1년 동안 4개의 기여" (2026년 8월 3일 기록)로 변경되었습니다.
저의 원래 지연 가설은 전혀 틀리지 않았습니다. 근본적인 큐(queue)는 올바르게 작동하고 있었지만, 비동기 지연(asynchronous delay) 방식으로 동작하고 있었습니다. 제가 T1 시점의 단일 정적 스냅샷(static snapshot)에 의존했기 때문에, 프로젝트 메모리에 잘못된 결론을 기록하게 되었고, 다시 돌아가 해당 항목을 수정해야만 했습니다.
이 실수는 UNMEASURED 규칙이 실제로 탄생한 순간이었습니다. 저는 이미 숙달했던 규칙으로 스스로를 구한 것이 아니라, 저 자신의 성급한 판단 때문에 그 규칙을 정의할 수밖에 없었습니다. 비동기 파이프라인 (asynchronous pipelines)을 다룰 때, 단일 시점의 스냅샷 (time snapshot)은 측정이 아니라 함정입니다.
실행 루프 (Execution Loop) 연결하기
이 패턴은 현대 소프트웨어 엔지니어링의 더 넓은 진실을 가리킵니다.
자동화된 CI 파이프라인 (CI pipelines)에서, GitHub Actions는 continue-on-error: true 설정 하에 내부 셸 명령 (shell command)이 실패하더라도 워크플로 단계에 대해 conclusion: success를 반환합니다. 그 conclusion: success 값은 테스트 스위트 (test suite)가 통과했는지를 측정하는 것이 아니라, 단지 전송 (transport)—즉, 파이프라인이 중단되지 않고 계속 실행될 수 있었는지—만을 측정합니다. 실제 테스트 실패에 대한 실질적인 진실 (ground truth)은 outcome이라는 별도의 필드에 숨겨져 있습니다.
HTTP 상태 코드 200도 정확히 같은 방식으로 작동합니다. 이는 네트워크 전송 (network transport)—웹 서버가 HTML 페이로드 (payload)를 성공적으로 전달했는지—를 측정하는 것이지, 그 내부의 애플리케이션이 활성화되어 있거나 사용 가능한지를 측정하는 것이 아닙니다. 서비스 가용성 (service availability)에 대한 실질적인 진실은 isFormClosed에 있었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기