스펙 시스템을 완성했다. 넉 달 동안 나도 안 썼다

손맛(sonmat) 만들기 · 6편 중 6편


2026-04-2627에 sonmat v0.11v0.13을 만들었다. 2026-08-29에 그 결과를 다시 셌다.

이게 좀 민망한 이야기인데, 나는 지난 4월에 스펙 시스템을 꽤 진지하게 만들었다.

문서 양식만 하나 던져놓은 수준은 아니었다. 스펙 조항마다 MUST, SHOULD, MAY를 구분하고 거절한 대안도 함께 적게 했다. 기존 스펙을 고칠 때는 본문을 덮어쓰지 않고 후속 문서로 잇도록 했으며, 폐기할 때는 종료일과 대체 관계를 밝히게 했다. 프로젝트에는 docs/specs/와 50줄 이하의 색인을 두고, 에이전트는 작업을 시작할 때 관련 스펙을 찾아 읽는다. 실제 작업이 스펙 밖으로 나가면 기록 담당 에이전트가 개정안까지 제안한다.

작성, 참조, 개정, 폐기. 한 바퀴가 다 있었다.

그리고 넉 달 뒤 확인했다.

사용한 프로젝트는 0개였다. 가장 먼저 써야 했던 나도 안 썼다.

꽤 제대로 만든 실패

처음부터 허술했던 시스템이라면 이야기가 간단하다. 양식이 불편했거나 기능이 빠졌다고 하면 된다. 그런데 이 시스템은 오히려 지나치게 성실했다.

v0.11에서는 행동 전에 암묵적인 가정을 찾고, 작업 뒤에는 기존 규칙이 다루지 못한 경우를 기록하게 했다. v0.12에서는 RFC 2119의 강제력 어휘와 PEP 404의 명시적인 폐기 방식을 가져왔다. v0.13에서는 프로젝트용 양식과 색인, 에이전트의 자동 참조, 후속 스펙 제안까지 붙였다.

건축과 항공, 린 건설, RFC와 소프트웨어 설계 문헌도 조사했다. 물리적 건축의 의례를 소프트웨어에 그대로 옮기면 안 된다는 반론까지 검토했고, 그래서 기본값은 비활성화했다. 사용자가 명시적으로 켠 프로젝트에서만 작동하도록 안전장치도 걸었다.

당시 초고의 마지막 문장은 이랬다.

진짜 검증은 첫 사용자 프로젝트가 spec_awareness: enabled를 박을 때 시작된다.

문장은 맞았다. 문제는 그 첫 프로젝트가 영원히 오지 않았다는 점이다.

0에도 분모가 있다

처음에는 발견 경로를 의심했다. 기능이 너무 깊이 숨어 있고 켜려면 디렉터리, 색인, 설정값, 스펙 파일을 차례로 만들어야 했다. 진입 단계가 네 개나 되니 아무도 안 쓰는 게 당연하다고 봤다.

그래서 생성 명령을 만들면 해결될 것 같았다. 한 번 실행하면 구조를 전부 만들어주는 도구 말이다.

그 전에 분모를 세어봤다. 여기서 이야기가 이상해졌다.

같은 작업 규약에서 함께 권한 decisions/plans/는 여러 프로젝트에 널리 퍼져 있었다. 설계 결정은 수십 건, 계획 문서는 그보다 더 많았다. docs/specs/만 0이었다. 발견 경로가 같고 사용자가 같으며 지난 시간도 같았다. 그동안 스펙 성격의 문서가 필요하지 않았던 것도 아니다. 기능 정의서, API 계약, MVP 요구사항, 프롬프트 명세가 다른 이름으로 여러 프로젝트에 존재했다.

수요가 없었던 게 아니다. 내가 만든 형식만 거부당했다.

진입 단계가 많다는 진단은 틀리지 않았다. 다만 절반짜리였다. 생성기가 풀어주는 것은 손작업 비용뿐이다. 앞으로 지킬 계약을 미리 선언하고 계속 관리해야 하는 부담은 그대로 남는다. 결정문은 이미 벌어진 일을 기록하지만 스펙은 미래의 나에게 의무를 건다. 파일 하나를 만드는 비용보다 그 약속을 떠안는 비용이 더 컸다.

도구가 없어서 못 쓴 게 아니었다. 쓰겠다고 약속하기 싫어서 안 쓴 것이었다.

문서가 없던 게 아니었다

여기서 두 번째 오판도 드러났다. 나는 문제를 “스펙 문서가 부족하다”로 잡고 있었다. 실제 프로젝트를 보니 문서는 이미 있었다. 정의서도 있었고 API 계약도 있었으며 운영 메모도 있었다.

없던 것은 문서와 코드 사이의 연결이었다.

기획이 바뀌었는데 기능 정의서는 그대로 남는다. 데이터 스키마가 바뀌었는데 API 설명은 예전 형태를 말한다. 설정 저장소는 고쳤지만 실제 설치본은 이전 값을 쓴다. 코드와 인프라는 각자 정상인데 둘 사이의 재시작 순서 때문에 서비스가 끊긴다.

문서 한 장을 더 만든다고 이 문제가 사라지지는 않는다. 실패는 산출물 안보다 산출물 사이에서 생겼다.

제품을 다음처럼 놓으니 그제야 자리가 보였다.

사용자·목적 → 서비스 접점 → 기능·도메인 규칙 → 애플리케이션·데이터
            → 인프라·실행 환경 → 운영 관측 → 사용자·정의 변경

화면은 서비스 접점의 중요한 형태지만 전부는 아니다. API, 알림, 파일, 배치 작업처럼 화면이 없는 접점도 있다. 문서와 테스트, 설계 결정 기록은 이 고리의 별도 층이 아니다. 각 부분과 연결을 표현하거나 검증하는 산출물이다. 보안과 성능 같은 비기능 요구도 별도 상자가 아니라 고리 전체에 걸리는 제약이다.

이렇게 보면 해야 할 일은 “스펙 문서를 잘 쓰기”보다 “변경이 고리를 따라 끝까지 전달됐는지 확인하기”에 가깝다.

더 큰 시스템을 만들 뻔했다

문제의 모양을 알았으니 다음 해법은 자연스러워 보였다.

화면과 기능에 식별자를 붙이고, 문서와 코드와 인프라의 관계를 목록으로 만든다. 변경된 파일을 스캔해 연결된 산출물을 찾아내고, 빠진 갱신이 있으면 훅에서 막는다. 새 프로젝트에는 공통 관계표를 심고 기존 프로젝트에는 점진적으로 그래프를 채운다.

꽤 그럴듯했다. 무엇보다 만들 수 있었다.

그래서 내가 만든 반대 역할 검토 도구인 devil로 한 번 두들겼다. 하중을 받치는 가정은 하나였다. “지금 관계를 선언하는 비용보다 미래에 얻을 검증 가치가 크다.”

바로 전에 실패한 스펙 시스템과 같은 가정이었다.

얇은 관계 목록도 결국 미래의 효용을 위해 현재의 선언을 요구한다. 식별자를 만들고 관계를 등록하며 실제 구조가 바뀔 때마다 그래프를 관리해야 한다. 이름만 스펙에서 관계표로 바뀌었을 뿐 부담은 그대로였다.

더 나쁜 문제도 있었다. 부분 그래프의 초록불이다.

등록된 관계 열 개를 모두 검사해서 문제가 없다고 해보자. 도구는 성공을 보여준다. 하지만 열한 번째 관계가 등록되지 않았는지는 알 수 없다. 실패를 찾는 도구가 아니라 미등록 관계를 가리는 안심 장치가 될 수 있었다. 자동화 범위가 부분적이라는 사실을 사용자가 잊는 순간, 초록불은 검증 결과가 아니라 착시가 된다.

스펙 시스템을 반성하다가 더 큰 스펙 시스템을 만들 뻔했다.

남긴 것은 다섯 질문

그래프와 식별자, 공통 설정 파일은 만들지 않기로 했다. 대신 실제 변경마다 연결을 놓치지 않게 하는 다섯 질문만 남겼다.

변경 전에는 세 가지를 묻는다.

  1. 목적: 실제로 무엇이 달라져야 하는가?
  2. 반경: 어느 사용자 접점, 기능, 소비자, 설치본, 파생 산출물까지 영향을 받는가?
  3. 닫는 방법: 각 연결을 단일 원천, 단방향 생성, 독립 검사, 사람 판단 중 무엇으로 맞출 것인가?

변경 뒤에는 두 가지를 확인한다.

  1. 현실 대조: 저장소의 코드가 아니라 실제 소비자, 설치 상태, 실행 환경에서 확인했는가?
  2. 잔여: 아직 끝내지 못한 것은 무엇이며 누가 어떤 조건에서 마무리하는가?

이 다섯 항목을 적는 새 양식은 만들지 않았다. 매번 문서를 요구하면 질문 자체가 또 하나의 의례가 된다. 작업 대화 안에서 유지하고, 계약이나 아키텍처가 바뀌었거나 다음 세션까지 열린 의무가 남을 때만 적절한 기록으로 옮긴다.

자동화도 금지한 건 아니다. 같은 관계가 실제 작업에서 반복되고 기계가 판정할 외부 근거가 생기면 그때 올린다. OpenAPI에서 클라이언트 타입을 생성하거나 스키마와 마이그레이션을 대조하는 일은 좋은 후보다. 반면 화면의 의도와 거절한 대안처럼 의미 판단이 필요한 관계는 사람에게 남는다.

순서가 바뀌었다. 구조를 만들고 사용 사례를 기다리는 대신, 사용 사례를 관찰하고 반복되는 구조만 만든다.

사람과 에이전트의 경계

작업 방식을 고치다 보니 오래된 역할 구분도 같이 흔들렸다. 사람은 전략을 세우고 에이전트는 전술을 수행한다는 구분이다.

요즘 코딩 에이전트는 전략 대안을 만들고 인터페이스를 설계하며 아키텍처의 반례도 찾는다. 그렇다고 모든 결정을 맡길 수 있다는 뜻은 아니다. 문제는 능력이 아니라 권한이었다.

그래서 자율성의 기준을 세 가지로 바꿨다.

  • 기존 계약 안에서 이루어지는가
  • 되돌릴 수 있는가
  • 외부 증거로 검증할 수 있는가

세 조건을 만족하면 에이전트가 설계부터 구현과 검증까지 넓게 움직여도 된다. 가치 판단, 계약 변경, 큰 영향 범위, 되돌리기 어려운 위험은 사람이 최종 결정한다. 훅은 이미 합의된 계약을 실행할 뿐 새 계약을 정하지 않는다.

이 구분은 스펙 문제와도 이어진다. 자동화가 판단권까지 가져가면 등록되지 않은 관계를 스스로 안전하다고 선언하게 된다. 도구가 강해질수록 어디까지가 집행이고 어디부터가 결정인지 더 분명해야 한다.

이번에는 훅을 만들지 않았다

4월의 나는 조사하고 설계한 뒤 바로 템플릿과 자동 참조, 개정 의례까지 만들었다. 8월의 나는 전역 작업 지침에 다섯 질문을 넣고 멈췄다.

공통 지침 파일도 얇게 유지했다. 매 세션 반드시 읽을 문서에는 판정 기준과 상세 지침으로 가는 길만 남겼다. 철학은 원칙 문서에, 실제 작업법은 별도 지침에 뒀다. 그리고 네 작업 기기의 저장소와 Claude, Codex 연결이 모두 같은 정본을 읽는지 실제 해시까지 대조했다.

이 마지막 확인은 사소해 보이지만 이번 글의 주장과 정확히 같다. 저장소에 커밋했다고 네 기기에 적용된 것은 아니다. 실제 설치본과 소비자가 새 규칙을 읽는지 확인해야 변경이 끝난다. 그 과정에서 한 기기의 Codex는 4월에 만든 일반 파일을 계속 읽고 있었고, Claude는 공통 지침을 불러오지 않았다. 저장소는 맞았지만 현실은 달랐다.

첫 적용 사례가 거창한 제품 기능이 아니라 지침 자체의 전파였던 셈이다.

아직 검증되지 않은 것

이번 결론도 아직 완성품은 아니다.

다섯 질문이 실제 프로젝트에서 반복해서 쓸 만큼 가벼운지 데이터가 없다. 어떤 관계가 자동화 후보로 자주 나타나는지도 모른다. 사람이 판단해야 할 연결과 기계가 검사할 연결을 나누는 기준도 사례가 쌓이면 달라질 수 있다.

그래서 지금의 성공 기준은 그래프 완성도나 스펙 개수가 아니다. 실제 변경에서 놓친 연결이 무엇이었는지, 같은 연결이 다시 나타났는지, 현실 대조로 저장소와 설치본의 차이를 잡았는지를 센다.

sonmat의 기존 스펙 기능도 당장 지우지 않았다. 이미 채택한 프로젝트가 있다면 쓸 수 있다. 다만 새 프로젝트의 기본 구조로 권하지 않고, 사용 증거 없이 더 확장하지 않는다.

나는 한 번 “사용자 의지가 기반”이라고 적어두고 시스템을 완성했다. 넉 달 뒤에 보니 그 문장은 안전장치인 동시에 변명이었다. 사용자가 의지를 내야만 작동하는 도구라면, 왜 의지를 내지 않았는지까지 설계가 설명해야 한다.

도구가 안 쓰일 때 가장 먼저 할 일은 진입 단계를 줄이는 게 아닐 수 있다. 그 도구가 맡은 일이 실제 병목이었는지 다시 묻는 편이 먼저다.

내 경우에는 스펙이 부족한 게 아니었다. 변경이 끝까지 닫히지 않고 있었다.

그래서 이번에는 시스템 대신 질문을 남겼다.


릴리스 노트: v0.11.0, v0.12.0, v0.13.0 리포: https://github.com/jun0-ds/sonmat


시리즈 손맛 (sonmat) 만들기 여섯 번째 글. 이전 글: sonmat v0.9 — 증인을 세우다

GitHub · LinkedIn