
4계층 접근 방식: 실무자의 기록 (Part III)
요약
효율적인 소프트웨어 개발을 위해 외부 루프와 내부 루프의 격차를 해소하는 '4계층 실행 아키텍처'를 소개합니다. 프리미티브, 시퀀스, 프로시저, 플로우로 구성된 이 설계는 LLM과 협업하여 코드베이스 작업을 체계적으로 수행하도록 돕습니다.
핵심 포인트
- 프로젝트와 작업 수준의 루프를 통합하는 4계층 아키텍처 제안
- LLM의 실행 범위를 제한하고 구조화하는 시스템 설계 방법론
- 프리미티브, 시퀀스, 프로시저, 플로우를 통한 관심사 분리
- 설계 단계의 결정이 실제 구현 작업의 효율성을 극대화함
§0 — 도입
이 시리즈의 Part I에서는 두 가지 루프를 설명했습니다. 프로젝트 수준에서의 외부 루프— 아이디어 구상, 문제 정의, 아키텍처, 로드맵 —와 작업(work) 수준에서의 내부 루프: io-스키마에 맞춰 유닛을 명세하고, 패키지 의존성 맵을 준수하며, 테스트를 먼저 작성하고, 구현하고, 검증하고, 통합하는 것입니다. Part II에서는 이 내부 루프의 인테이크(intake) 과정을 추가했습니다. 즉, 결과물을 도출하는 리뷰 세션, 발견된 사항들을 버킷으로 분류하고, 그 버킷들이 계획된 작업 항목이 되는 과정입니다. Part I에서는 또한 세 가지 격차를 언급하기도 했습니다: 버전 관리되지 않고 덮어쓰여지는 문서들, 임시방편적인 세션 인계(handoff), 그리고 규율은 갖추었으나 아직 체계적이지 않은 프로세스였습니다.
이 글은 그러한 격차들을 해소하는 설계를 소개합니다: 4계층 실행 아키텍처입니다. 이는 더 나은 구조, 관심사 분리(segregation of concerns), 명시적인 규칙과 제약 조건을 제공합니다. 이 시스템을 사용하자 며칠 만에 LLM과 저는 paragraf의 코드베이스에서 20개 이상의 작업 항목을 완료했습니다. 새로운 UI 패키지, 버그 수정, 테스트 커버리지 확보, API 정리 등이었습니다. 계획하고, 구현하고, 테스트로 검증하며, 아카이빙까지 했습니다. 일반적인 하나의 작업 항목에 제가 기여한 것은 두 가지 결정뿐이었는데, 나머지 백 개는 설계 단계에서 이미 한 번에 이루어졌기 때문입니다. 저는 LLM이 무엇을 할 수 있는지 제한하는 목적의 시스템을 구축하는 데 약 일주일을 보냈습니다: 실행 과정 중
이 글은 독립적인 글입니다. 시리즈(Part I, Part II)의 연장선에 있지만, 이전 내용을 따라갈 필요는 없습니다. 방법론의 이 부분 — 흐름(flows), 절차(procedures), 시퀀스(sequences), 상태(state), 계획(plans), 세션 로그(session logs), 그리고 실행된 그대로의 아카이브(archives) — 은 여기 방법론 저장소(methodology repository)에 게시되어 있습니다. 아래의 모든 주장은 해당 파일들을 통해 확인할 수 있습니다.
§1 — 4계층: 프리미티브(Primitive), 시퀀스(Sequence), 프로시저(Procedure), 플로우(Flow)
전체 아키텍처가 이 두 용어의 차이에 기반하므로, 먼저 두 용어를 정의하겠습니다. **조건(condition)**은 파일이나 상태에 이미 기록된 데이터를 바탕으로 분기하는 것입니다 — 예: IF status == done. **판단(judgment)**은 해석, 모호성, 또는 선호도에 따라 분기하는 것입니다 — 그리고 이 시스템에서 모든 판단은 하나의 메커니즘인 ASK를 통해 인간의 **결정(decision)**으로 해결되며, 이는 인간이 응답할 때까지 실행을 차단합니다. 조건은 상위 두 계층(프로시저 및 플로우)에서 허용되지만, 판단은 정확히 하나의 계층(플로우)에서만 허용됩니다.
4계층:
- **프리미티브 (Primitive)**는 고정된 시그니처를 가진 단일 동사입니다 —
READ,WRITE,VERIFY,ASK를 포함하여 총 9개 — 분기가 없으며, 다른 무엇도 호출할 수 없습니다. - **시퀀스 (Sequence)**는 이름이 지정된, 조용하고 고정된 프리미티브 호출의 연속입니다 (파일 이동, 로그 추가 등). 조건, 판단, 상태 쓰기, 인간과의 접촉이 없습니다: 성공하여 데이터를 반환하거나, 실패하여 호출자가 결정하도록 합니다.
- **프로시저 (Procedure)**는 선형적인 세션 장부 기록입니다 (세션 열기, 닫기, 완료된 항목 아카이브하기). 조건(디스크 상의 데이터)에 따라 분기할 수 있지만, 판단에 따라 분기할 수는 없습니다.
- **플로우 (Flow)**는 분기하고 반복되는 워크플로우(workflow)입니다 — 인간이 호출하는 유일한 계층이며,
ASK를 통해 판단이 행사되는 유일한 계층입니다.
각 계층은 오직 자신보다 아래에 있는 계층만 호출할 수 있습니다. 상위 계층을 호출하는 것은 아무것도 없습니다.
| 계층 | 조건 (data) | 판단 (ASK를 통해) | 호출 가능 대상 |
|---|---|---|---|
| Primitive (원시) | 없음 | 없음 | 없음 |
| ... |
만약 여러분이 최첨단 (Frontier) LLM 플랫폼들이 수렴하는 도구/기술/에이전트(tool/skill/agent) 어휘를 알고 있다면, 그 매핑은 일대일이 아닙니다. 산업 용어는 경계가 모호한 띠(bands) 형태이지만, 이 계층들은 하나의 명확한 경계(hard edge)를 가진 사다리 칸(rungs)입니다.
| Frontier LLM | 제안된 시스템 |
|---|---|
| Tool (도구) | Primitive (원시), Sequence (시퀀스), Procedure (프로시저) |
| Skill (기술) | Sequence (시퀀스), Procedure (프로시저), Flow (플로우) |
이 중첩이 핵심입니다. 산업 용어는 메커니즘(mechanics)이 어디서 끝나고 판단(judgment)이 어디서 시작되는지를 알려주지 않습니다. 플랫폼의 "기술 (skill)" 파일은 명목상 기계적인 지침 중간에 판단("사용자가 X를 원하는지 Y를 원하는지 선택하라")을 조용히 포함할 수 있으며, 형식상 이를 막는 것은 아무것도 없습니다. 이것이 바로 기술 (Skill) 띠가 플로우 (Flow) 열까지 닿아 있는 이유입니다. 여기서 판단의 경계는 계층의 가장자리입니다. 즉, 시퀀스 (Sequence)나 프로시저 (Procedure)는 판단을 위한 어휘를 전혀 가지고 있지 않습니다. "이 계층이 해석을 수행해야 하는가?"라는 질문은 규율(discipline)에 의해 답해지는 것이 아닙니다. 그것은 표현 불가능한 영역입니다.
위의 표에 에이전트 (Agent) 행이 없으므로, 이와 관련된 한 가지 구분을 덧붙이자면: 플로우 (flow)는 에이전트 (agent)가 아닙니다. 두 개의 서로 다른 에이전트가 동일한 플로우를 실행할 수 있습니다. 하위 계층들은 차이가 발생할 여지를 남기지 않기 때문에 기계적인 단계들은 동일해야 합니다. 오직 ASK 지점 — 즉, 표면화된 판단과 반환된 결정 — 에서만 정당하게 차이가 발생할 수 있습니다. 플로우는 악보이며, 에이전트는 연주자입니다.
§2 — 플로우 샘플 (A Flow Sample)
워크스루 (walkthrough)를 진행하기 전에, 지도를 먼저 보겠습니다. 플로우 (Flows, 녹색)는 소수의 스토어 (stores, 회색) 세트로부터 읽고 쓰기를 수행합니다. 발견 사항 (findings)은 이슈 테이블 (issues table)을 통해 들어오고; 그룹화 (grouping)를 통해 작업 풀 (work-pool) 항목으로 변환됩니다; 작업 항목 (work item)을 실행하면 풀 (pool)과 현재 상태 (current state)를 읽어 계획 (plan, 빨간색 — 작업이 종료될 때 아카이브 (archive)로 이동하는 유일한 아티팩트 (artifact))을 생성하고, 결과를 다시 풀 (pool)과 상태 (state)에 기록합니다. 이 지도에서 인간은 두 번 나타나는데, 두 번 모두 동일한 메커니즘을 통해 나타납니다: 플로우가 출력물을 완전히 제시하고, 질문을 던지며, 답변이 올 때까지 차단 (blocks)합니다. 다이어그램의 나머지 모든 것은 읽기 (reads)와 쓰기 (writes)입니다. 이 섹션의 나머지 부분에서는 실제 작업 항목 하나가 이를 어떻게 통과하는지 살펴봅니다.
버그: 최신 컬러 프로파일 (color profiles)에 대해 잘못된 색상을 조용히 생성하는 컬러 프로파일 파서 (color-profile parser)입니다. 이 함수는 다섯 가지 유형의 파라메트릭 커브 (parametric curve) 모두에 대해 하나의 파라미터 (gamma)만 읽었으며, 나머지 네 가지 유형에서 요구하는 추가 파라미터들을 버렸습니다. 모든 입력이 수락되었고, 잘못된 값들이 하류 (downstream)로 연쇄적으로 전달되었지만, 아무런 경고도 발생하지 않았습니다. 바로 출시되어 버리는 (ships) 그런 종류의 버그입니다.
내 이슈 트래커 (issue tracker)에서 이것은 하나의 발견 사항 (finding)이었습니다; 동일한 렌더링 경로 (rendering path)에 있는 실제 테스트 공백 (test gap) 옆에 놓인 오해의 소지가 있는 주석 (misleading comment)이라는 두 번째 사항과 함께 분류 (triaged)되어, issue-bucket 유형의 단일 작업 항목 (work item)이 되었습니다. 거기서부터 각 계층 (layers)은 자신이 존재하는 목적에 맞는 역할을 수행합니다.
Flow — 결정이 이루어지는 곳. 계획-생성 (plan-create) 플로우는 아키텍처 맵과 영향을 받는 패키지들의 컨텍스트 파일 (context files)을 로드하고, 계획을 초안했습니다. 여기에는 8개의 작업 (tasks), 7개의 필수 테스트, 그리고 명시적인 제약 조건(public curve type은 변경되어서는 안 되며, 4개의 복잡한 curve type은 해석적 방식 (analytically)으로 해결하는 대신 룩업 테이블 (lookup table)로 샘플링되어야 함)이 포함되었습니다. 그 후 플로우는 멈췄습니다. 요약이 아닌 **전체 계획 (full plan)**을 제시하며, 승인 (approve), 변경 요청 (request changes), 또는 취소 (cancel)를 요구하는 ASK를 발행했습니다. 아직 디스크에 기록된 것은 아무것도 없었습니다. 그 일시 정지야말로 시스템의 하중을 지탱하는 벽 (load-bearing wall)입니다. 첫 번째 결정: 나는 승인했습니다. 두 번째 결정 또한 ASK에 대한 나의 결정이었습니다: 해석적 해결 대신 룩업 테이블을 선택했습니다.
Flow, 계속 — 조건 하에서의 메커니즘. 계획-실행 (plan-execute) 플로우는 작업을 엄격하게 한 번에 하나씩 처리했습니다. 작업 1은 WRITE를 통해 실패하는 테스트들을 작성했습니다. 구현이 시작되기 전, 레드 페이즈 (red-phase) 체크가 새로운 테스트들에 대해 VERIFY를 실행하여 현재 코드에 대해 테스트가 _실패_함을 확인했습니다. 이는 판단이 아닌 하나의 조건(condition)입니다: 디스크 상의 테스트 출력 결과입니다. 그제서야 수정 작업이 진행되었습니다 — 파서를 READ하고, 수정된 섹션들을 UPDATE하며, 누락된 테스트를 WRITE했습니다. 각 작업의 필수 테스트들은 VERIFY를 통해 실행되었으며, 각 결과는 세션 로그 (session log)에 추가되었습니다. 모든 작업의 경계마다 플로우는 '인간에게 질문 (ask-human)' 트리거를 확인했습니다 — 계층을 가로지르는 작업, 새로운 의존성 (dependencies), 공개 타입 (public type)의 변경, 계획되지 않은 엣지 케이스 (edge cases) 등 — 그리고 아무것도 트리거되지 않았기에, 플로우는 멈출 필요가 없었습니다.
Procedures — 누구도 임의로 수정해서는 안 되는 장부 기록. session-open 프로시저 (procedure)는 상태 필드 (state fields)를 설정하고 로그를 초기화했습니다. session-close 프로시저는 스냅샷 (snapshot, 고정된 필드 세트이며 각 필드는 정확히 한 번씩 기록됨)을 작성하고, 계획 (plan), 워크 풀 (work-pool) 항목, 그리고 두 가지 결과물 (findings)을 모두 완료 (done) 상태로 전환한 뒤 로그를 닫았습니다. archive 프로시저는 완료로 표시된 모든 것 — 계획 파일, 워크 풀 행, 세션 로그, 결과물 — 을 추가 전용 (append-only) 아카이브로 쓸어 넣었으며, 각 결과물에는 이를 해결한 작업 항목 (work item)이 스탬프로 찍혔습니다. 이 모든 과정은 디스크 상태를 조건으로 하며, 그 어떤 것도 해석 (interpretation)에 의존하지 않습니다.
시퀀스 (Sequences) — 드리프트 (drift)가 소멸하는 곳. 세션 로그는 오직 쓰기 로그 시퀀스 (write-log sequence)를 통해서만 기록되었습니다: 로그를 읽고, 항목을 추가하고, 전체를 다시 쓰는 방식입니다. 이는 각각이 조금씩 다르게 수행될 수 있는 세 단계 대신, 하나의 명명된 호출 (named call)로 처리됩니다. 조사 결과의 상태가 변경될 때는 update-issue-status 시퀀스가 고정된 전이 테이블 (transition table)을 적용했습니다. 그리고 아카이빙은 이동 시퀀스 (move sequence)를 통해 실행되었습니다: 소스를 읽고, 목적지에 쓰고, 목적지를 VERIFY한 다음, 그제서야 DELETE합니다. 시스템 내에서 단 하나뿐인 되돌릴 수 없는 프리미티브 (primitive)는 오직 해당 시퀀스를 통해서만 도달할 수 있습니다. 그 외에 다른 문은 존재하지 않습니다.
작업 항목의 라이프사이클 (Lifecycle of one work item)
| 작업 상태 (Work Status) | 작업 (Action) |
|---|---|
| open | 작업 항목 선택 (work item selected) |
| ... |
모든 계층은 가시적이며, 각 계층은 오직 자신의 단계 (rung)가 허용하는 일만 수행합니다. 두 가지 결정은 흐름 (flows) 내부의 ASK 지점에서 제가 내린 것이었습니다. 그 외의 모든 것은 단일 동사를 수행하는 프리미티브 (primitives), 고정된 일련의 과정을 수행하는 시퀀스 (sequences), 그리고 디스크 상태에 따라 분기하는 프로시저 (procedures)였습니다. 이는 판단을 하지 않은 것이 아니라 — 할 수 없었던 것입니다. 해당 단계들이 실행되는 계층에는 판단을 위한 메커니즘이 존재하지 않습니다.
§3 — 보장 (Guarantees): 구조적 vs 관습적
이 시스템의 모든 규칙이 동일한 방식으로 유지되는 것은 아니며, 이와 같은 글에서는 대개 그 차이를 뭉뚱그려 설명하곤 합니다. 기준은 다음과 같습니다: 어떤 규칙을 위반하기 위해 어떤 파일에도 존재하지 않는 호출 경로 (call path)를 구축해야 한다면, 그 규칙은 구조적으로 (structurally) 보장됩니다. 즉, 위반 행위가 의도적이고 가시적입니다. 반면, 규칙을 위반하는 것이 단순히 한 단계를 빠뜨린 정상적인 동작처럼 보인다면, 그 규칙은 관습적으로 (conventionally) 보장됩니다. 모델이 단순히 텍스트를 따르지 않음으로써 모델이 규칙을 깨뜨리게 되며, 그 실패는 소리 없이(silent) 일어납니다.
VERIFY 후 DELETE하는 이동 시퀀스를 통해서만 도달 가능한 DELETE는 구조적입니다. 어휘 (vocabulary) 내의 어떤 경로도 검증을 먼저 거치지 않고는 삭제에 도달할 수 없습니다. "ASK는 오직 흐름 (Flows) 내에만 존재한다"는 관습적입니다. 이는 마크다운 파일에 적힌 문장일 뿐이며, 실행 모델이 텍스트를 준수하기로 선택하는 것 외에는 프로시저가 ASK를 포함하는 것을 기계적으로 막을 수 있는 것은 아무것도 없습니다. "한 번에 하나의 작업만 수행, 배치 (batching) 금지"와 계층 호출 제한 규칙 자체도 마찬가지입니다.
그리고 저는 관습적인 규칙들이 표류(drift)한다는 직접적인 증거를 가지고 있습니다. 제 파일들 자체가 그 흔적(scars)을 담고 있기 때문입니다. 여러 절차 파일에는 "어느 것도 누락하지 말 것"과 같은 호출(callout)이 포함되어 있는데, 이러한 강조는 모델이 단계를 건너뛰는 것을 관찰하고 더 강한 텍스트로 패치(patch)했기 때문에 존재하는 것입니다. 이는 시스템이 관습은 쇠퇴하며, 저의 현재 완화 조치(mitigation)는 더 강력하게 반복되는 단어라는, 현재 가용한 가장 취약한 보증책일 뿐이라는 점을 글로써 인정하고 있는 것입니다.
시스템에는 이를 위한 복구 메커니즘이 있으며, 이는 앞서 언급한 흔적들과 대조되는 개념입니다. 특정 고정된 단계의 시퀀스(sequence)는 여러 절차나 흐름 전반에 걸쳐 동일한 단계가 반복되면서 동시에 표류(drift)가 관찰되었을 때만 생성됩니다. 예를 들어, '확인 후 삭제(verify-then-delete)' 이동 시퀀스가 존재하는 이유는 삭제는 되돌리기 어렵기 때문이며, '로그 기록(write-log)' 시퀀스가 존재하는 이유는 그 세 단계가 즉흥적인 임기응변(improvisation)을 유발했기 때문입니다. 이 규칙은 반복되는 관습적 실패를 구조적 보증(structural guarantee)으로 전환합니다. 강조를 통한 패치는 증상이며, 시퀀스로의 승격(promotion)이 치료제입니다. 시스템의 솔직한 상태는, 관찰된 일부 표류는 승격되었고, 일부는 여전히 패치된 텍스트 상태로 남아 있다는 것입니다.
완전히 솔직히 말하자면, 이 구분 자체에도 한계가 있습니다. 마크다운 기반 로직(logic-in-markdown) 시스템에서 "구조적(structural)"이라는 개념조차 기저(substrate) 단계에서는 관습에 불과합니다. 모든 것은 모델이 따르기로 선택한 텍스트일 뿐입니다. 그럼에도 살아남는 차이점은 표류 저항성(drift-resistance)입니다. 구조적 위반은 의도적인 구축(construction)을 필요로 하지만, 관습적 위반은 조용한 누락(omission)으로 나타납니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기