코드에 대한 최고의 설명은 채팅 로그에 묻혀 있다

당신은 Claude와 45분을 들여 재시도 회로를 설계했다. 실패 모드를 설명하고, 계단식 압력을 숨기기 때문에 지수 백오프를 거부한 뒤, jitter가 포함된 token-bucket rate limiting으로 결정하고 작동하는 구현을 생성했다. 설명은 명확했고, 추론은 타당했으며, 코드는 실제로 테스트를 통과했다.

그리고 탭을 닫았다.

두 주 후 동료가 왜 재시도 로직이 지수 백오프 대신 jitter를 사용하는지 묻는다. 당신은 새로운 Claude 세션을 열고 기억에서 논증을 재구성한다. 새로운 설명은 비슷하지만 동일하지 않다. 당신은 약간 다른 두 번째 구전 전통을 만들었다. 둘 다 검색으로 찾을 수 없다. 둘 다 pull request에서 리뷰할 수 없다. 둘 다 당신이 회사를 떠나면 사라질 것이다.

이것은 도구의 문제가 아니다. 카테고리 오류다. 우리는 LLM 대화를 개인용 스크래치패드로 다루지만, 사실 그것은 대부분의 엔지니어가 literate programming에 가장 가까이 다가간 것이다.

Knuth가 의미한 것, 그리고 LLM이 왜 우연히 이를 실현했는가

Donald Knuth는 1984년에 literate programming을 문학 작품으로 프로그램을 작성하는 행위로 정의했다. 코드와 문서는 단일 내러티브 속에서 함께 짜인다. 독자는 저자의 추론을 따라가며, 고려된 대안을 보고, 최종 형태가 존재하는 이유를 이해한다.

40년 동안 literate programming은 틈새 관행으로 남았다. WEB과 CWEB 같은 도구는 규율을 요구했다. 대부분의 개발자는 코드를 한 파일에, 문서를 다른 파일에 썼고, 둘은 즉시 벌어졌다.

LLM 대화는 우연에 의한 literate programming이다. 당신은 산문으로 문제를 진술한다. 모델은 명확화 질문을 한다. 당신은 constraints를 다듬는다. 모델은 코드를 제안한다. 당신은 제안을 거부하고 이유를 설명한다. 모델은 수정한다. 최종 산출물은 코드 블록만이 아니다. 버려진 접근법, 트레이드오프, 도메인 가정을 포함한 전체 스레드다.

문제는 매체가 덧없다는 것이다. 채팅 인터페이스는 지식 보존이 아닌 작업 완료를 위해 설계되었다. 세션이 끝나면 내러티브는 호박 속에 얼어붙어, 검색 불가능하고, 버전 관리되지 않으며, 벤더에 의해 소유된다.

복잡한 의사결정에서 채팅 로그가 전통적 문서를 이기는 이유

전통적인 문서는 최종 상태를 기술한다. “무엇”을 답한다. 좋은 채팅 로그는 “왜”를 답한다. 이것이 더 어려운 질문이며 가장 빨리 부패하는 질문이다.

전형적인 architecture decision record를 생각해 보자. “강한 일관성 요구사항 때문에 인벤토리 서비스에 DynamoDB 대신 PostgreSQL을 선택했다”라고 적혀 있을 수 있다. 그것은 결론이다. 어떤 쿼리가 너무 느렸는지, 허용 가능한 복제 지연이 무엇이었는지, 논의 후 기각된 vendor lock-in에 대해서는 아무것도 알려주지 않는다.

Claude 스레드는 그 모든 것을 담고 있다. 실패한 스키마 반복, 놀라웠던 쿼리 플랜, 복합 인덱스가 상태 필터를 커버해야 한다는 것을 깨달은 순간이 그 안에 있다. 이것은 완전한 맥락을 가진 decision record다.

문제는 그 맥락이 대화 형식에 갇혀 있다는 것이다. 100턴짜리 스레드를 스크롤하여 인덱스 설계에 대한 단 하나의 통찰을 찾는 것은 비참하다. 지식은 거기에 있지만 접근 가능하지 않다.

Chat-as-Docs의 세 가지 실패 모드

원시 채팅 로그를 문서로 다루는 것은 예측 가능한 방식으로 실패한다. 각 실패 모드에는 해결책이 있지만, 의도적으로 해야 한다.

Hallucination drift. Claude는 API를 지어내고, 존재하지 않는 논문을 인용하며, 실제 constraints를 무시하는 디자인을 자신 있게 제안한다. 문서로 보존된 채팅 로그는 지혜와 함께 hallucinations도 보존한다. 어떤 부분이 검증되었고 어떤 부분이 추측적인지 표시하지 않으면 다음 독자는 모든 것을 진리처럼 받아들인다.

Narrative sprawl. 좋은 대화는 우왕좌왕한다. 막다른 길을 탐색하고, 엣지 케이스에 주의가 산만해지고, 되돌아간다. 그 우왕좌왕함은 이해에는 valuable하지만 참조에는 끔찍하다. 재시도 정책이 필요한 신입 엔지니어는 TCP congestion control로의 20분 탈선을 읽을 필요가 없다.

Vendor lock-in. 당신의 문서는 Anthropic의 데이터베이스 안에 있고, 그들의 검색 인터페이스 뒤에 있으며, 그들의 보존 정책에 따른다. 계정이 만료되거나 인터페이스가 변경되면 당신의 문서는 사라진다. grep할 수 없는 문서는 문서가 아니다.

덧없는 채팅에서 지속 가능한 문서를 추출하는 방법

해결책은 채팅을 최종 산출물이 아닌 초안으로 다루는 것이다. 추출하고, 검증하고, 게시한다. 워크플로우는 단순하며 중대한 결정 하나당 약 10분이 걸린다.

1단계: 턴에 태그를 단다. 대화 중에 핵심 결정에 표시한다. 나는 간단한 규칙을 사용한다. Claude가 보관할 의도의 코드 블록을 생성하면 KEEP: <한 줄 이유>로 답한다. 거부할 제안이 있으면 REJECT: <이유>로 답한다. 이 태그들은 추출을 사소하게 만든다.

2단계: 마크다운으로 추출한다. 세션 후 스레드를 마크다운 파일에 복사하고 잡음을 제거한다. 인사말, “생각 좀 해볼게” 같은 채움말, 둘 다 혼란스러웠던 턴을 제거한다. 문제 진술, 고려된 대안, 최종 결정, 검증된 코드를 유지한다. 결과는 대화록이 아닌 기술 노트처럼 읽혀야 한다.

Claude API를 사용하거나 대화를 JSON으로 내보내는 경우 추출을 자동화하는 스크립트는 다음과 같다.

#!/usr/bin/env python3
"""
Extract a readable technical note from a Claude conversation export.
Expects Anthropic's conversation JSON format.
"""

import json
import argparse
from pathlib import Path


def extract_note(conversation_path: Path, output_path: Path) -> None:
    with open(conversation_path) as f:
        data = json.load(f)

    turns = data.get("chat_messages", [])
    lines = []

    for turn in turns:
        sender = turn.get("sender", "unknown")
        text = turn.get("text", "").strip()

        if not text:
            continue

        # Skip pleasantries and meta-turns
        if any(phrase in text.lower() for phrase in [
            "hello", "how can i help", "you're welcome", "glad i could help"
        ]):
            continue

        if sender == "human":
            lines.append(f"**Q:** {text}\n")
        else:
            lines.append(f"**A:** {text}\n")

    with open(output_path, "w") as f:
        f.write("# Technical Note: Extracted from Claude Session\n\n")
        f.write("\n".join(lines))


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--input", type=Path, required=True)
    parser.add_argument("--output", type=Path, required=True)
    args = parser.parse_args()
    extract_note(args.input, args.output)

3단계: 모든 코드 블록을 검증한다. 추출한 코드를 실행한다. 컴파일되지 않거나 테스트를 통과하지 못하면 마크다운에서 수정하고 수정 사항을 기록한다. 추출된 문서는 실수를 포함했을 수 있는 대화의 대화록이 아닌, 신뢰할 수 있는 정보 출처여야 한다.

4단계: 리포지토리에 커밋한다. 마크다운을 기술하는 코드 옆의 docs/decisions/ 또는 docs/notes/에 저장한다. 의미 있는 파일명을 지어라: retry-circuit-jitter-over-exponential.md이지, claude-chat-july-19.md가 아니다. 코드 변경과 동일한 pull request에 추가하거나 즉시 후속 PR을 열어라. 문서가 버전 관리에 없다면 존재하지 않는 것이다.

이것이 작동하는 경우와 작동하지 않는 경우

이 접근법은 추론이 결과만큼 중요한 복잡하고 모호한 결정에서 뛰어나다. 회로 설계, 스키마 마이그레이션 전략, API versioning 정책, 성능 트레이드오프는 모두 좋은 후보다.

참조 문서에는 작동하지 않는다. 내부 API로 인증하는 방법에 대한 채팅 로그는 구조화된 OpenAPI spec과 cURL 예제를 대체하기에 끔찍하다. 작업에 맞는 올바른 도구를 사용하라.

또한 큐레이션 없이는 작동하지 않는다. 원시 채팅 로그를 위키에 쏟아붓는 것은 문서가 아니다. 그것은 비축이다. 10분의 추출 및 편집은 절대 타협할 수 없다. 이를 건너뛰면 검색 가능한 쓰레기를 생산한다.

실용적인 출발점

새로운 도구가 필요한 것이 아니다. 습관이 필요하다.

다음에 사소하지 않은 설계 결정에 대해 Claude와 길고 생산적인 세션을 가졌을 때, 탭을 닫기 전에 스레드를 내보내라. 10분을 들여 다음 세 가지 질문에 답하는 마크다운 노트로 편집하라: 어떤 문제를 해결하고 있었는가? 어떤 대안을 고려하고 거부했는가? 무엇으로 결정했고 왜인가?

그 노트를 리포지토리에 커밋하라. 그것이 설명하는 함수 위의 코드 주석에서 링크하라. 그 코드를 건드리는 다음 엔지니어는 당신에게 감사할 것이며, 당신의 추론을 재구성하기 위해 Claude를 열 필요가 없을 것이다.

Claude와의 채팅은 문서가 될 수 있다. 단 코드처럼 다뤄야 할 뿐이다: 추출되고, 검증되고, 버전 관리되고, 유지보수되어야 한다.