비싼 실패: 에이전트가 팀에서 이미 폐기한 것을 다시 구현하는 경우
요약
AI 에이전트가 과거 팀의 결정이나 폐기된 구현 사항을 인지하지 못해 동일한 실수를 반복하는 '비싼 실패' 문제를 다룹니다. 이를 해결하기 위해 과거의 맥락과 결정 사항을 구조화된 방식으로 캡처하여 에이전트에게 제공하는 기술적 필요성을 강조합니다.
핵심 포인트
- 에이전트의 비결정론적 레이블 생성은 검색 및 맥락 파악 실패를 야기함
- 과거의 결정 사항(revert, migration 등)이 세션 종료 후 유실되는 문제 발생
- 단순 코드 오류보다 팀의 지식과 맥락을 재현하는 것이 더 큰 비용을 초래함
- 구조화된 캡처를 통해 에이전트가 과거의 실패 사례를 참조하도록 해야 함
"grep이 당신의 커밋을 찾지 못할 것이라고 99% 확신합니다. 당신은 'oauth-library'를 거부했는데 'auth'로 grep을 하고 있기 때문이죠. LLM이 카테고리 이름을 임의로 만들어낼 것이라는 점을 고려하면, 결정론적 강제(deterministic enforcement)가 없다면 상황은 더 악화될 뿐입니다."
이 인용구가 어디에서 왔는지 솔직하게 말씀드리겠습니다. 이는 찬성(endorsement)의 의미가 아니기 때문입니다. 이는 Contextual Commits 스레드에 달린 한 댓글 작성자의 적대적인 반대 의견입니다. 이 스레드는 전반적으로 selvedge가 수행하는 것과 같은 구조화된 캡처(structured capture)에 대해 회의적입니다. 저는 홍보 문구가 아니라, 하나의 반대 의견을 빌려오고 있습니다. 하지만 이를 빌려올 가치가 있는 이유는, 구조화된 캡처의 비판가들조차 실패 모드(failure mode)가 무엇인지에 대해서는 동의하기 때문입니다: 바로 비결정론적 레이블(nondeterministic labels)입니다. 만약 모델이 캡처 시점에 카테고리 이름을 지어낸다면, 읽기 시점에 검색은 실패할 것이며, grep은 당신을 구원해주지 못할 것입니다. 이 생각을 잠시 간직해 두세요. 이 전체 릴리스는 이 문제의 하류(downstream)에 있습니다.
실제로 비용이 발생하는 실패
여기에 제가 사람들에게 계속 보여주는 기록이 있습니다.
(전체 버전은 리포지토리에 체크인되어 있습니다).
몇 달 전, 한 에이전트 세션이 users.auth_token 컬럼을 추가했습니다. 이는 모바일 앱이 재시작 후에도 로그인 상태를 유지할 수 있도록 하는 사용자별 장기 토큰(long-lived per-user token)이었습니다. 이틀 후 팀은 이를 되돌렸습니다(revert). 당시 기록된 되돌림 사유는 다음과 같습니다: DB에 토큰이 있으면 취소(revocation) 시 쓰기(write) 작업이 필요하므로, 상태를 저장하지 않고 검증하는(verified statelessly) 수명이 짧은 JWT로 전환하기로 했습니다. 결정은 내려졌고, 대가는 치러졌으며, 세션은 종료되었습니다.
몇 달 후, 공유된 컨텍스트가 전혀 없는 새로운 세션이 시작되었습니다. 사용자가 질문합니다: "모바일 사용자들이 앱을 다시 열 때마다 로그아웃됩니다. 로그인 상태를 유지할 수 있을까요?" 에이전트의 첫 번째 본능은 명백한 것입니다. users에 지속적인 인증 토큰(persistent auth token) 컬럼을 추가하는 것입니다.
그것은 그럴듯한 계획입니다. 자신 있게 전달됩니다. 그리고 팀이 이미 비용을 지불하며 배웠던 결정—사건, 롤백(revert), 마이그레이션(migration), 그 모든 것—속으로 곧장 다시 걸어 들어갑니다. 이 모든 과정을 두 번이나 다시 구매하게 될 상황입니다.
이것이 바로 이 제품이 겨냥하고 있는 실패입니다. AI 보조 코딩 (AI-assisted coding)에서의 비싼 실패는 잘못된 코드 한 줄이 아닙니다. 그런 것은 린터 (linters)와 테스트 (tests)가 잡아냅니다. 진짜 실패는 에이전트 (agent)가 당신의 팀이 이미 폐기한 것을 자신 있게 다시 구현하는 것입니다. 지식은 존재했습니다. 누군가는 심지어 그것을 기록하기도 했습니다. 단지 종료된 채팅 세션 속에 머물러 있었을 뿐입니다.
데모에서는 이야기가 반대로 진행됩니다. 에이전트는 마이그레이션 (migration)을 작성하기 전에 users.auth_token에 대해 prior_attempts를 호출합니다. 그리고 거절된 이유와 함께 롤백된 판결을 받아낸 뒤, 방향을 전환합니다. OS 키체인 (keychain)의 리프레시 토큰 (refresh token), 상태가 없는 (stateless) /auth/refresh 엔드포인트 (endpoint)를 사용하며, 거절된 컬럼을 다시 도입하지 않습니다. 그런 다음 자신의 결정을 로그 (log)로 남겨 다음 세션도 이를 상속받을 수 있게 합니다.
그러한 답변을 가능하게 만드는 것은 저장소 (store)의 형태, 즉 추가 전용 (append-only) 증언입니다. 추론 (reasoning)은 에이전트가 변경 시점에 변경을 일으킨 것과 동일한 컨텍스트 (context) 내에서 직접 작성한 것입니다. 그리고 거절은 영구적이고 쿼리 가능한 (queryable) 기록이 됩니다. 이 두 측면 모두가 중요합니다. 사후 설명 도구 (post-hoc explainer tools)는 나중에 두 번째 LLM에게 당신의 디프 (diff)를 살펴보라고 요청합니다. 원래의 프롬프트 (prompt)를 본 적이 없는 모델은 의역을 생성하며, 이를 다시 실행하면 다른 카테고리 이름이 생성될 수 있습니다. 이것이 바로 0x457이 반대하는 지점입니다. 또한, Selvedge의 '제로 LLM 결정론 (zero-LLM determinism)'을 공유하는 OpenLore는 동기화 후 쿼리 가능한 저장소에서 거절된 결정을 삭제합니다. 주석은 동기화된 스펙 마크다운 (spec markdown)에 살아남지만, 쿼리 가능한 기록은 남지 않습니다. 어떤 라인 속성 도구 (line-attribution tools)도 에이전트가 실제로 가졌던 질문에 답할 수 없습니다. '이것이 이전에 시도된 적이 있는가, 그리고 결과는 어떠했는가?' 거절된 경로가 쿼리 가능한 상태로 유지되는 것이 핵심적인 차이입니다.
내가 틀렸던 부분: 스토리지는 전달이 아니다
v0.3.7 버전은 질문 프리미티브 (asking primitive)인 prior_attempts를 출시했습니다. 이것은 — 호출될 때 — 제대로 작동합니다. 하지만 그 "호출될 때"라는 조건이 엄청난 양의 작업을 수행해야 한다는 사실이 밝혀졌고, 올해 두 편의 독립적인 논문이 그 수치를 제시했습니다. "Delivery, Not Storage" (arXiv 2607.20972)와 PROJECTMEM (arXiv 2606.12329) 논문 모두 풀 모델 (pull-model) 메모리 도구가 사용되지 않는 현상을 측정했습니다. 미리 씨딩된 (pre-seeded) 저장소가 있음에도 114회의 턴 (turn) 동안 자발적인 메모리 작업이 단 한 건도 발생하지 않은 반면, 결정론적 주입 (deterministic injection)은 매번 성공했습니다.
0건입니다. 114회의 턴 동안 말이죠. 관련 메모리가 바로 그곳에 있었음에도 불구하고 말입니다.
에이전트가 직접 찾아봐야 하는 메모리는 대부분 찾아보지 않게 되는 메모리입니다. selvedge는 이미 정답의 절반을 가지고 있었습니다 (에이전트가 감시 대상 엔티티 (watched entity)에 접근할 때 실행되는 PreToolUse 훅 (hook)). 부족했던 점은 거부할 사항이 없을 때의 전달 (delivery)이었습니다. 따라서 v0.3.10의 핵심 테마는 이제 메모리가 에이전트에게 직접 찾아온다는 것입니다.
SessionStart digest (세션 시작 요약). 세션이 시작될 때 훅 (hook)이 관련성 기반으로 필터링된 압축된 요약본을 주입합니다: 재검토가 필요한 결정 사항, 최종 판결이 번복된 (reverted) 엔티티, 최근의 변경 세트 (changesets) 등이 포함됩니다. 3월의 번복 사항이 6월의 에이전트가 무엇인가를 계획하기 전에 먼저 제시됩니다. 도구 호출 (tool call)이 필요하지 않으며, 물어봐야 한다는 사실을 기억할 필요도 없습니다. 할 말이 없을 때는 조용히 유지되며, digest_max_bytes를 통해 크기가 제한되고, 읽기 전용이며, 실패 시에도 개방형 (fail-open)으로 작동하며, 코어의 다른 모든 요소와 마찬가지로 템플릿화되어 있습니다.
PreCompact (압축 전). 컨텍스트 압축 (context compaction)은 세션의 추론이 소멸하는 지점입니다. 따라서 selvedge는 이제 압축 직전에 실행되어, 이번 세션 동안 log_change가 기록되지 않은 채 편집된 감시 대상 엔티티들을 식별하고, 이미 저장소에 있는 내용은 제외합니다. 이 기능은 의도적으로 권고 (advisory) 목적으로만 제공됩니다. 훅 API (hook API)는 차단 (blocking)을 허용하지만, selvedge는 이를 사용하지 않습니다. 도구 호출을 차단하는 것은 에이전트에게 불편을 주지만, 압축을 차단하는 것은 세션 전체를 막아버리기 때문입니다. 우리는 이를 절대 수행하지 않는다는 것을 보장하는 테스트가 존재합니다.
selvedge export --format markdown. 이는 스토어(store)의 결정론적이고(deterministic) 안정적으로 고정된(stably-anchored) 요약본(digest)으로, 엔티티(entity)별로 그룹화되며 되돌려진 결정(reverted decisions)이 가장 먼저 나타납니다. 이는 .selvedge/ 옆에 커밋되도록 설계되어, 캡처된 의도(intent)를 일반적인 diff를 통해 검토할 수 있습니다. 새로운 이벤트 없이 재생성하면 diff 결과가 0줄이 됩니다. LLM 호출이 없으며, — 특히 — 새로운 MCP 도구(tool)도 필요하지 않습니다.
마지막 지점은 일반화될 수 있습니다: 전체 전달(delivery) 과정이 도구 인터페이스(tool surface)를 전혀 건드리지 않고 배포되었습니다. 여전히 8개의 MCP 도구가 있으며, 스키마 비용(schema tax)은 3705/3800 토큰으로 변함이 없습니다. 전달 과정에서 에이전트(agent)에게 도구 스키마(tool schema)를 위한 추가 토큰이 단 하나도 소모되지 않았습니다.
설정(config) 부분
두 번째 테마: 스토어에 조절 장치(dials)가 생깁니다. .selvedge/config.toml은 이제 일급 객체(first-class)가 되었으며, 전체 키 세트 — retention_days_events (기본값: never), retention_days_tool_calls, backup_keep_last, diff_bytes, reasoning_bytes, db_size_warn_mb, stale_days, digest_max_bytes, redaction_patterns — 와 하나의 정형화된 우선순위 체인(precedence chain)을 가집니다: CLI 플래그(flag) > 환경 변수(env var) > 프로젝트 설정(project config) > 글로벌 ~/.selvedge/config.toml > 기본값 (단, SELVEDGE_DB는 예외이며 데이터베이스 결정 시 항상 우선합니다). selvedge doctor는 설정별로 유효한 값과 해당 값을 생성한 우선순위 단계를 출력하므로, "왜 이게 30이지?"라는 질문에 명령어 하나로 답할 수 있습니다.
두 가지 항목은 각각 한 단락의 설명이 필요합니다:
selvedge prune --include-events는 캡처된 추론(reasoning)을 삭제할 수 있는 첫 번째 코드 경로이므로, 이중 게이트(double-gated) 구조를 가집니다: 대화형 확인(interactive confirmation)과 환경 변수의 SELVEDGE_DESTRUCTIVE=1이 모두 필요하며, .selvedge/prune.log에 감사 로그(audit line)가 남습니다. 어느 한 쪽의 게이트만으로는 충분하지 않습니다. cron 항목의 --yes는 프롬프트(prompt)를 무력화하고, 셸 프로필(shell profile)의 한 줄은 환경 변수를 무력화합니다. 의도적으로 두 가지 모두가 필요합니다.
log_change에서의 비밀 형태 (secret-shape) 경고: 보수적인 내장 세트 —
공급업체 접두사가 붙은 키 (vendor-prefixed keys), PEM 헤더, 베어러 토큰 (bearer tokens), SECRET= 할당,
인증된 연결 문자열 (credentialed connection strings) — redaction_patterns를 통해 확장 가능합니다. 이 기능은 경고를 보낼 뿐, 절대 거부하지 않습니다. 과도하게 열성적인 필터로 인해 추론 (reasoning) 과정을 잃어버리는 것이야말로 이 도구가 존재하는 바로 그 손실을 방지하기 위함입니다. 그리고 쓰기 시점 (write-time) 체크는 과거를 볼 수 없기 때문에, doctor는 이미 저장된 내용을 스캔하는 기능을 갖추게 되었습니다. (이벤트 크기 (event-size) 제한도 동일한 철학을 따릅니다: 너무 큰 디프 (diffs)와 추론 (reasoning)은 마커, 경고, 그리고 selvedge stats의 카운트와 함께 매우 명시적으로 (loudly) 잘립니다.)
그리고 고정된 컬럼(fixed column)으로부터 빠르게 살펴보면: PreToolUse 훅 (hook)의 허용 경로 (allow path)가 약 40% 더 빠릅니다 — 14ms의 인터프리터 (interpreter) 하한선 대비, n=60의 인터리브 (interleaved) 측정 결과, 게이트 호출 (gated call)당 p50이 33.6ms에서 20.1ms로 단축되었습니다. 훅 자체의 로직은 항상 0.58ms였으며 나머지는 임포트 (import) 비용이었습니다. SELVEDGE_HOOK_DISABLE=1은 이제 실제로 단락 회로 (short-circuits)를 구성합니다 (이전에는 임포트가 이미 실행된 후에 확인되고 있었습니다). log_change는 이제 이름 변경 (rename) 및 대체 (supersede) 분기에서 revisit_after, constraint, 또는 stale_when을 조용히 폐기하지 않습니다. 도커 이미지 (docker image)는 더 이상 저의 개인 데이터베이스를 포함하지 않습니다 (네, 정말입니다). CLI와 MCP 서버는 이제 하나의 공유된 프레젠터 레이어 (presenter layer)를 통해 동일한 구조를 반환합니다. 그리고 변이 테스트 (mutation pass)를 통해 실행은 되었으나 한 번도 어설션 (asserted)되지 않은 약 12개의 의도적인 가드 (guards)를 찾아냈습니다 — 샘플링된 세트의 변이 점수 (mutation score)는 64%에서 100%로 상승했습니다. 테스트 스위트 (suite)는 826개에서 984개로, 커버리지 (coverage)는 88.3%에서 89.0%로 증가했습니다.
만약 7월 말에 pip install selvedge가 실패했다면
두 가지 별개의 사건이 발생했으며, 둘 다 종료되었습니다:
mcp2.0.0 (2026-07-28 출시) 버전에서mcp.server.fastmcp가 제거되었고, selvedge는 상한선 없이mcp>=1.0.0을 선언했습니다. 따라서 2026-07-28부터 2026-08-01 사이의 모든 신규 설치는 2.0.0 버전을 해결(resolve)했고, 임포트(import) 단계에서 실패했습니다. v0.3.9.3 버전은mcp<2.0.0으로 고정(pinned)되어 있었습니다.- 공식 MCP Registry의 최신 버전 해결(latest-resolution) 방식은 유의적 버전(semver) 형태를 따르며, 4개 세그먼트로 구성된 PEP 440 릴리스를
0.3.9보다 높게 순위 매기지 못했습니다. 그래서?version=latest는 계속해서 0.3.9를 제공했고, 이는 바로 mcp 2.0.0 환경에서 임포트 시 실패하는 릴리스였습니다. 3개 세그먼트로 구성된0.3.10이 지속 가능한 수정 버전입니다. 현재 기준으로 레지스트리는 0.3.10을 최신(latest)으로 제공하고 있음을 확인했습니다.
따라서 만약 당신이 7월 말에 selvedge를 시도했을 때 임포트 오류가 발생했다면, 그것은 실제 발생했던 기간(window)이었으며, 현재는 종료되었습니다. pip install -U selvedge를 실행하면 0.3.10 버전을 얻을 수 있습니다.
직접 시도해보세요
selvedge는 AI가 작성한 코드베이스를 위한 의사결정 출처(decision provenance)입니다. 즉, '왜' 그렇게 했는지, 그리고 '무엇이' 이미 시도되었다가 거부되었는지를 보여줍니다.
pip install selvedge
cd your-project
selvedge setup # claude code / cursor / copilot 감지
그 다음 세션을 진행하고, 종료한 뒤, 다른 세션을 시작하세요. 두 번째 세션은 첫 번째 세션에서 결정된 사항들에 대한 요약(digest)과 함께 시작됩니다. 여기에는 새로운 에이전트가 다시 제안하기 전에, 이미 되돌려진(reverted) 모든 사항이 포함됩니다.
- 사이트: selvedge.sh
- 리포지토리: github.com/masondelan/selvedge
- PyPI: pypi.org/project/selvedge
- 변경 이력: v0.3.10 release notes
- 데모 트랜스크립트: docs/demos/prior-attempts.md
이 프로젝트는 오픈 소스이며, MIT 라이선스이고, 로컬 우선(local-first) 방식이며, 코어(core) 내에서 LLM 호출을 전혀 사용하지 않습니다. 제가 현장 보고(field reports)를 가장 받고 싶은 부분은 SessionStart 요약(digest)의 관련성 게이팅(relevance gating)입니다. 만약 세션 상단에 쓸모없는 내용이 나타난다면, 그것은 제가 꼭 듣고 싶은 문제입니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기