프롬프트 설계와 SW 설계의 구조적 대응

ERD 유사성, 결정론 vs 확률, 그리고 의도적 반정규화의 판단 기준

Skill-based 프롬프트 설계가 전통적 SW 개발 프로세스(요구사항→SRS→ERD→모듈 설계→구현)와 본질적으로 같은 과정임을 단계별로 대응시켜 설명한다. 특히 ERD 설계와의 강한 유사성(엔티티=스킬, 정규화=공통 추출, 반정규화=의도적 복제)을 분석하고, SW 설계와의 결정적 차이(결정론 vs 확률)가 프롬프트 설계 전략에 미치는 영향을 다룬다. “언제 정규화하고 언제 반정규화하는가”의 판단 기준을 실제 블로그 프로젝트의 예시로 제시한다.

Agent
Architecture
Prompt Engineering
저자

Kwangmin Kim

공개

2026년 03월 25일

1 SW 개발 프로세스와의 구조적 유사성

Skill-based prompt 설계는 요구사항 → SRS → ERD → 모듈 설계 → 구현으로 이어지는 전통적 SW 개발 프로세스와 본질적으로 같은 과정이다.

1.1 단계별 대응

전통 SW 개발                     Skill-Based Prompt 설계
─────────────────────────────    ─────────────────────────────
요구사항 수집                     "agent가 뭘 해야 하는가?" 정리
    ↓                                ↓
SRS/업무기술서 작성               단일 파일 시스템 프롬프트 (v2.0)
    ↓                                ↓
기능 분해 (유스케이스 도출)        독립 태스크 식별 (6단계 중 1단계)
    ↓                                ↓
ERD/도메인 모델 설계              코어 vs 스킬 분류 + 도메인 GUIDE 분리
    ↓                                ↓
모듈/서비스 설계                  스킬 모듈 설계 (tasks/, tools/)
    ↓                                ↓
API/인터페이스 정의               라우팅 테이블 + 스킬 간 호출 규약
    ↓                                ↓
구현 + 테스트                     프롬프트 작성 + agent 실행 검증

1.2 ERD 설계와의 유사성

특히 ERD 설계와의 대응이 강하다.

ERD 설계 Prompt 설계
엔티티 식별 — “이 시스템에 어떤 개체가 존재하는가?” 스킬 식별 — “이 agent에 어떤 태스크가 존재하는가?”
관계 정의 — 1:N, M:N, FK 스킬 간 관계 — prerequisite, 호출, 공유
정규화 — 중복 제거, 단일 진실 원천 공통 요소 추출 — info-search.md, 코어 규칙
반정규화 — 성능을 위해 의도적 중복 허용 컨텍스트 밀도를 위해 일부 규칙을 스킬 안에 복제

1.3 ERD 정규화 수준과의 대응

DB 정규화에 1NF → 2NF → 3NF 수준이 있듯이, 프롬프트 구조화에도 유사한 단계가 존재한다.

정규화 수준 DB에서의 의미 Prompt에서의 의미 예시
비정규화 모든 데이터가 하나의 테이블 모든 규칙이 하나의 파일 (v2.0) 800줄 단일 AGENT_GUIDE.md
1NF 유사 반복 그룹 제거 태스크별로 섹션 분리 (같은 파일 안에서) 파일은 하나, ## 질문응답 / ## 포스트작성으로 구분
2NF 유사 부분 종속 제거 태스크를 별도 파일로 분리 guides/write-post.md, guides/convert-tbd.md
3NF 유사 이행 종속 제거 공통 요소를 독립 모듈로 추출 info-search.md (검색 절차를 별도 공통 모듈로)

3NF를 넘어서면 오히려 JOIN 비용(=라우팅 비용)이 커져서 성능이 떨어지듯, 프롬프트도 과도하게 쪼개면 라우팅 오분류와 스킬 간 호출 체인이 문제가 된다.

1.4 스킬 간 관계 유형

ERD에서 엔티티 간 관계(1:1, 1:N, M:N)를 정의하듯, 스킬 간에도 관계 유형이 존재한다.

[1:1 — 선행/후행 관계]
info-search.md → write-post.md
  info-search가 완료되어야 write-post가 시작된다
  DB의 FK 제약과 유사 — 순서를 강제한다

[1:N — 공통 모듈 참조]
info-search.md ← write-post.md
                ← convert-tbd.md
                ← answer-question.md
  하나의 공통 모듈을 여러 스킬이 참조한다
  DB의 lookup 테이블과 유사

[M:N — 크로스 레퍼런스]
write-post.md ↔ Statistics/GUIDE.md
write-post.md ↔ Agent/GUIDE.md
convert-tbd.md ↔ Statistics/GUIDE.md
  여러 스킬이 여러 도메인 가이드를 참조한다
  DB의 M:N 관계 + 중간 테이블(라우팅 테이블)로 관리

1.5 공통되는 가장 어려운 문제: “경계를 어디에 긋느냐”

  • ERD: 엔티티를 너무 잘게 쪼개면 JOIN 지옥, 너무 크게 잡으면 NULL 투성이
  • Prompt: 스킬을 너무 잘게 쪼개면 라우팅 지옥, 너무 크게 잡으면 컨텍스트 낭비
  • 적정 단위를 찾는 감각이 둘 다 핵심이다

경계 판단의 구체적 기준:

신호 의미 조치
두 스킬이 항상 함께 호출된다 과분리 — 하나로 합쳐야 한다 병합 (ERD의 1:1 테이블 병합과 동일)
하나의 스킬이 300줄을 넘는다 과통합 — 내부에 독립 태스크가 숨어 있다 분리 검토 (ERD의 복합 엔티티 분해와 동일)
스킬 A를 수정할 때마다 스킬 B도 수정해야 한다 결합도 높음 — 경계가 잘못 그어졌다 공통 부분을 추출하거나 경계를 재설정
스킬 내부의 일부 Step만 재사용하고 싶다 응집도 낮음 — 관련 없는 것이 섞여 있다 재사용 대상 Step을 공통 모듈로 추출

2 결정적 차이: 결정론 vs 확률

SW 개발과 prompt 설계가 동일한 원칙을 공유하지만, 하나의 본질적인 차이가 있다.

  • SW: 한번 설계하면 코드가 설계대로 결정론적으로 실행된다. 함수 A를 호출하면 반드시 A가 실행된다
  • Prompt: agent가 확률적으로 해석한다. 라우팅 테이블에 “질문이면 answer-question.md를 로드하라”고 써도, agent가 잘못 판단하면 write-post.md를 로드할 수 있다

이 차이가 설계 전략에 미치는 영향:

SW 설계 Prompt 설계
정규화가 미덕 — 중복 제거, 단일 진실 원천 의도적 반정규화 필요 — 중요한 규칙은 코어에도 쓰고 스킬에도 쓴다
인터페이스만 맞으면 내부 구현은 자유 스킬 내부 구조도 명시적이어야 한다 — agent가 “알아서” 해석하면 결과가 달라진다
에러 시 스택 트레이스로 디버깅 라우팅 실패 시 agent 출력을 사후 분석해야 한다 — 디버깅이 더 어렵다
타입 시스템이 잘못된 호출을 컴파일 타임에 차단 잘못된 스킬 로드를 사전에 차단할 수 없다 — 라우팅 조건을 더 명확하게 써야 한다

즉, prompt 설계는 SW 설계보다 더 명시적이고, 더 중복을 허용하고, 더 방어적으로 작성해야 한다. 코드는 “한 번 정의하면 정확히 그대로 실행된다”는 보장이 있지만, 프롬프트에는 그 보장이 없기 때문이다.

2.1 확률적 실행에 대한 방어 전략

결정론적 시스템에서는 버그가 재현 가능하다. 같은 입력이면 같은 버그가 나온다. 확률적 시스템에서는 같은 입력에도 다른 결과가 나올 수 있다. 이 차이는 설계 전략을 근본적으로 바꾼다.

실패 모드와 방어:

실패 모드 SW에서의 대응 Prompt에서의 대응
잘못된 모듈 호출 컴파일 에러/타입 체크로 사전 차단 라우팅 조건을 겹치지 않게 + 폴백 규칙 정의
규칙 무시 코드에 규칙이 구현되어 있으므로 발생 안 함 중요 규칙을 여러 곳에 반복(반정규화) + 후처리 검증
순서 위반 함수 호출 순서가 코드로 강제됨 Step 1 → Step 2 순서를 명시적으로 번호 매겨 서술
출력 형식 이탈 타입 시스템이 강제 예상 출력 형식을 예시로 보여준다(few-shot)
부분 실행 (중간에 멈춤) try-catch로 예외 처리 체크리스트 패턴 — 각 Step 완료 조건을 명시

핵심 원칙 — “암묵적 계약”을 “명시적 지시”로 바꾼다:

SW에서는 interface, type, assert가 계약을 강제한다. Prompt에서는 이런 강제 수단이 없으므로, 계약의 내용을 텍스트로 풀어서 써야 한다.

[SW — 암묵적 계약이 통한다]
def write_post(title: str, body: str) -> Post:
    # title이 str이 아니면 컴파일/런타임 에러
    # 반환 타입이 Post가 아니면 에러
    → 계약이 코드에 내장되어 있다

[Prompt — 명시적 계약이 필요하다]
## 포스트 작성 스킬
입력: title (문자열), body (문자열)
출력: .qmd 파일 (YAML 헤더 + 본문)
제약:
  - title은 반드시 큰따옴표로 감싼다
  - body는 한다 체로 작성한다
  - 출력 파일명은 기존 패턴을 따른다
→ 계약이 자연어 텍스트로만 존재한다 — 위반해도 에러가 나지 않는다

이 차이 때문에 prompt 설계에서는 SW보다 더 많은 예시(example)반례(counter-example)가 필요하다. SW에서 타입 시스템이 하는 역할을, prompt에서는 WRONG/CORRECT 예시 쌍이 대신한다.

3 SW 디자인 패턴과의 대응

SW 디자인 패턴의 상당수가 prompt 구조에도 대응물을 갖는다. 동기가 같기 때문이다 — 복잡한 시스템을 관리 가능한 단위로 분해하고, 변경의 영향을 국소화하는 것.

SW 패턴 Prompt 대응물 공통 동기
Strategy — 알고리즘을 교체 가능하게 캡슐화 조건부 스킬 로딩 — 같은 태스크를 도메인별로 다르게 처리 동일 인터페이스, 다른 구현
Template Method — 뼈대는 고정, 일부 단계만 오버라이드 코어(고정) + 스킬(가변) 구조 공통 흐름 보존, 세부 절차만 교체
Chain of Responsibility — 요청을 처리할 핸들러를 순서대로 탐색 계층적 라우팅 — 1차 분류 → 2차 분류 처리 책임의 단계적 위임
Facade — 복잡한 서브시스템에 단순한 인터페이스 제공 코어의 라우팅 테이블 — 스킬 내부 복잡도를 숨김 사용자(agent)에게 보이는 복잡도 축소
Observer — 상태 변화 시 관련 객체에 통지 전처리/후처리 스킬 — 태스크 실행 전후 자동 실행 횡단 관심사의 분리

Strategy 패턴의 구체적 대응 예시:

[SW — Strategy Pattern]
class ReportGenerator:
    def __init__(self, formatter: Formatter):
        self.formatter = formatter
    def generate(self, data):
        return self.formatter.format(data)

# 사용 시
pdf_report = ReportGenerator(PDFFormatter())
html_report = ReportGenerator(HTMLFormatter())

[Prompt — 조건부 스킬 로딩]
## 포스트 작성 태스크
Step 3: 카테고리별 규칙 적용
  - Statistics 카테고리 → Statistics/GUIDE.md 로드 (Theorem→Proof 구조)
  - Agent 카테고리 → Agent/GUIDE.md 로드 (코드 예시 필수)
  - Engineering 카테고리 → Engineering/GUIDE.md 로드 (실행 환경 명시)

→ 태스크(뼈대)는 같고, 도메인 규칙(전략)만 교체된다

3.1 SOLID 원칙과의 대응

SOLID의 5가지 원칙 중 4가지가 prompt 설계에도 직접 적용된다.

SOLID 원칙 Prompt에서의 적용 위반 시 증상
SRP — 하나의 클래스는 하나의 책임만 하나의 스킬은 하나의 태스크만 스킬 수정 시 무관한 태스크에 영향
OCP — 확장에 열려 있고 수정에 닫혀 있다 새 태스크 추가 시 기존 스킬을 수정하지 않고 새 스킬 파일 추가 태스크 추가마다 코어를 수정해야 함
ISP — 클라이언트에 필요 없는 인터페이스에 의존하지 않는다 agent가 현재 태스크에 불필요한 규칙을 로드하지 않는다 (lazy loading) 컨텍스트 밀도 저하, attention dilution
DIP — 상위 모듈이 하위 모듈에 의존하지 않고 추상에 의존 코어가 스킬의 내부 구현을 모르고, 라우팅 테이블(추상)만 안다 코어가 스킬 내부 변경에 취약

LSP(리스코프 치환 원칙)는 직접 대응하기 어렵다. SW에서 “서브타입은 슈퍼타입을 대체할 수 있어야 한다”는 타입 시스템에 기반한 원칙인데, prompt에는 타입 시스템이 없기 때문이다. 다만 “모든 태스크 스킬은 동일한 입출력 구조(사용자 요청 → 파일 생성/수정)를 따라야 한다”는 약한 형태로 존재한다.

4 의도적 반정규화: 실제 예시

“반정규화가 필요하다”는 원칙을 구체적으로 보여주는 예시이다. 이 블로그 프로젝트의 실제 파일에서 가져왔다.

4.1 사례: “한다 체” 규칙

“한다 체로 쓴다”는 규칙이 현재 어디에 적혀 있는가:

파일 기술 방식 정규화/반정규화
AGENT_GUIDE.md (코어) “한다 체 사용”, “경어체 금지”, WRONG/CORRECT 예시까지 상세 원본 (정규화 기준점)
convert-tbd.md (스킬) “한다 체로 통일” — 한 줄 정규화 (코어에 위임)
Governance/GUIDE.md (도메인) “공통 규칙(한다 체 등)은 AGENT_GUIDE.md를 따른다” 정규화 (코어에 위임)
retrofit-post.md (스킬) 경어체→한다체 변환 테이블 7행 상세 반정규화 (의도적 복제)

convert-tbd.md는 “한다 체로 통일”이라고 한 줄만 써도 된다. 코어가 항상 로드되어 있으므로 agent가 코어의 상세 규칙을 참조할 수 있기 때문이다. 이것은 정규화가 유지되는 경우이다.

그런데 retrofit-post.md에는 구체적 변환 테이블이 직접 들어 있다.

경어체 한다 체
~합니다 ~한다
~입니다 ~이다
~됩니다 ~된다
~있습니다 ~있다
~하세요 ~한다
~겠습니다 ~겠다 / 제거
~드립니다 제거 또는 재구성

이 테이블은 코어에 있는 “한다 체 사용” 원칙과 중복이다. 그런데 왜 중복을 허용하는가?

retrofit-post.md의 Step 1-C는 기계적 치환 작업이다. agent가 이 단계를 수행할 때:

  • 정규화 상태: “AGENT_GUIDE.md의 콘텐츠 스타일 섹션을 참조하라” → agent가 코어 파일의 해당 섹션을 찾아간다 → 코어에는 “한다 체 사용”이라는 원칙만 있고 구체적 치환 매핑(겠습니다→겠다)은 없을 수 있다 → agent가 원칙을 보고 스스로 치환 규칙을 생성한다 → 확률적 해석 → 불안정
  • 반정규화 상태: 스킬 파일에 변환 테이블이 직접 있다 → 즉시 실행 가능 → 안정적

DB 비유:

-- 정규화: 원칙은 한 곳에만
SELECT rule FROM agents_core WHERE name = '한다체';
-- → "한다 체를 사용한다" (원칙만 반환)
-- → agent가 이걸 보고 변환 규칙을 스스로 만들어야 한다 (불안정)

-- 반정규화: 실행 규칙을 스킬 테이블에 직접 포함
SELECT 경어체, 한다체 FROM retrofit_post_변환맵;
-- → {합니다→한다, 입니다→이다, ...} (JOIN 없이 바로 사용)

4.2 사례: “기존 내용 삭제 금지” 규칙

이 규칙은 위반 시 결과가 치명적(사용자의 기존 콘텐츠 소실)이므로, 코어와 스킬 양쪽에 반복된다.

  • AGENT_GUIDE.md (boundaries): “기존 포스트의 내용을 삭제하거나 축소”하면 안 된다
  • write-post.md (보강 주의사항): “기존 내용을 삭제하거나 축소하지 않는다”
  • retrofit-post.md (보강 원칙): “기존 내용을 삭제하지 않는다 — 추가만 한다”
  • retrofit-post.md (수정하지 않는 것): “명백한 오류가 아닌 한 기존 내용을 삭제하지 않는다”

3개 파일에서 동일한 규칙이 각각의 맥락에 맞게 반복된다. 정규화 관점에서는 중복이지만, agent가 어떤 스킬을 실행하든 “삭제 금지”를 놓치지 않게 하는 방어적 설계이다.

5 판단 기준: 언제 정규화하고 언제 반정규화하는가

            정규화 (코어에만)                  반정규화 (코어 + 스킬 양쪽)
──────────────────────────────────────────────────────────────────
규칙 성격   원칙/방향성                        실행 시 즉시 참조하는 구체적 규칙
            "한다 체로 쓴다"                   "~합니다→~한다, ~입니다→~이다"

위반 빈도   가끔 (agent가 대체로 잘 따름)      자주 (놓치면 바로 품질 저하)

위반 결과   경미 (다음 검수에서 잡힘)          치명적 (내용 소실, 전체 톤 붕괴)

참조 방식   배경 지식으로 인지                 체크리스트로 항목별 실행

정리하면: 원칙은 정규화, 실행 규칙은 반정규화. 위반 시 경미하면 정규화, 치명적이면 반정규화. DB에서 “읽기 성능을 위해 반정규화한다”와 같은 논리인데, prompt에서는 “agent의 실행 정확도를 위해 반정규화한다”가 된다.

5.1 과도한 반정규화의 위험

반정규화가 필요하다고 해서 모든 규칙을 모든 스킬에 복제하면 새로운 문제가 생긴다.

일관성 붕괴: 같은 규칙이 5곳에 있을 때, 규칙을 수정하면 5곳을 모두 찾아 바꿔야 한다. 하나라도 빠뜨리면 agent가 참조하는 위치에 따라 다른 버전의 규칙을 따르게 된다.

[일관성 붕괴 시나리오]
1. "한다 체" 변환 테이블에 "~거든요→~거든" 항목을 추가하기로 한다
2. retrofit-post.md의 테이블을 업데이트한다
3. convert-tbd.md에도 같은 테이블이 있었는데 빠뜨린다
4. retrofit-post에서는 "~거든요"를 변환하지만 convert-tbd에서는 안 한다
→ 사용자가 TBD 전환한 포스트에만 "~거든요"가 남는다

관리 원칙 — 원본과 사본을 구분한다:

구분 역할 수정 가능 여부
원본 (코어) 규칙의 정의, 단일 진실 원천 여기에서만 규칙을 추가/삭제
사본 (스킬) 실행 시 즉시 참조할 수 있도록 복제 원본이 변경되면 반드시 동기화

DB에서 반정규화 컬럼에 트리거를 걸어 원본 변경 시 자동 동기화하듯, 프롬프트에서도 반정규화된 규칙에는 주석으로 원본 위치를 표시해두면 유지보수가 쉬워진다.

## 경어체→한다체 변환 테이블
<!-- 원본: AGENT_GUIDE.md > 콘텐츠 스타일 > 한다 체 사용 -->
| 경어체 | 한다 체 |
|--------|--------|
| ~합니다 | ~한다 |
...

5.2 반정규화 의사결정 플로우차트

규칙 하나를 스킬에 복제할지 판단할 때 다음 순서로 묻는다.

이 규칙이 이 스킬에서 실행 시 즉시 필요한가?
  ├─ No → 정규화 유지 (코어에만)
  └─ Yes
       위반 시 결과가 치명적인가? (내용 소실, 보안 위반 등)
         ├─ Yes → 반정규화 (반드시 복제)
         └─ No
              agent가 원칙만 보고 구체적 실행 규칙을 정확히 생성할 수 있는가?
                ├─ Yes → 정규화 유지 (원칙 한 줄로 충분)
                └─ No → 반정규화 (구체적 규칙/테이블을 복제)

6 테스트와 검증의 대응

SW에서 “코드가 설계대로 동작하는가”를 검증하는 것처럼, prompt에서도 “agent가 규칙대로 실행하는가”를 검증해야 한다. 그러나 검증 방법론이 근본적으로 다르다.

측면 SW 테스트 Prompt 검증
재현성 같은 입력 → 같은 출력 (결정론적) 같은 입력 → 다른 출력 가능 (확률적)
자동화 단위 테스트, CI/CD 출력 패턴 매칭, LLM-as-Judge
커버리지 코드 라인/분기 커버리지 태스크 유형별 시나리오 커버리지
회귀 테스트 기존 테스트 재실행 이전에 성공한 프롬프트를 수정 후 재실행
실패 진단 스택 트레이스, 디버거 agent 출력을 사후 분석, 프롬프트 어느 부분을 따랐는지 추론

Prompt 검증의 실무적 접근:

[SW 단위 테스트와의 대응]
def test_write_post_creates_yaml_header():
    result = write_post("테스트 제목", "본문")
    assert result.yaml_header.title == "테스트 제목"
    assert result.yaml_header.date_format == "MM/DD/YYYY"

↕ 대응

[Prompt 검증 시나리오]
입력: "RAG에 대한 포스트를 써줘"
검증 항목:
  ☐ YAML 헤더에 title, description, categories, author, date가 있는가
  ☐ date 형식이 MM/DD/YYYY인가
  ☐ 본문이 한다 체인가
  ☐ 섹션 헤더에 수동 번호가 없는가
  ☐ index.qmd에 링크가 추가되었는가

SW에서 테스트가 실패하면 코드를 수정한다. Prompt에서 검증이 실패하면 프롬프트를 수정한다 — 규칙을 더 명시적으로 쓰거나, 예시를 추가하거나, 규칙의 위치를 바꾼다(코어 → 스킬로 반정규화). 이것이 prompt 리팩토링이다.

7 버전 관리와 변경 영향 분석

SW에서 모듈을 수정하면 의존 모듈에 대한 영향을 분석한다. Prompt에서도 동일한 분석이 필요하다.

7.1 변경 유형별 영향 범위

변경 대상 영향 범위 검증 대상
코어 규칙 수정 모든 태스크 전체 태스크 시나리오 재검증
스킬 내부 Step 수정 해당 태스크만 해당 태스크 시나리오만
도메인 GUIDE 수정 해당 카테고리의 태스크만 해당 카테고리 포스트 작성 시나리오
라우팅 테이블 수정 모든 태스크의 진입점 다양한 요청 패턴으로 라우팅 정확도 확인
반정규화된 규칙 수정 원본 + 모든 사본 원본과 사본의 일관성 확인

SW에서 import/require 그래프를 추적하여 영향 범위를 파악하듯, prompt에서는 “이 규칙을 참조하는 스킬이 어디인가”를 추적해야 한다. 규모가 커지면 이 추적이 어려워지므로, 반정규화된 규칙에는 원본 위치를 주석으로 남기는 것이 중요하다.

7.2 리팩토링의 대응

SW 리팩토링의 핵심 기법이 prompt에도 대응된다.

SW 리팩토링 Prompt 리팩토링 트리거
Extract Method — 중복 코드를 함수로 추출 공통 절차를 별도 스킬로 추출 같은 절차가 2개 이상 스킬에서 반복
Inline Method — 한 번만 쓰이는 함수를 인라인 1개 스킬에서만 쓰이는 공통 모듈을 해당 스킬에 흡수 공통 모듈이 실제로는 하나의 스킬에서만 호출
Move Method — 메서드를 더 적절한 클래스로 이동 규칙을 더 적절한 레이어로 이동 (코어 ↔︎ 스킬 ↔︎ 도메인) 규칙이 현재 위치에서 자주 무시되거나 불필요하게 로드
Rename — 이름을 더 명확하게 스킬 파일명/라우팅 조건을 더 명확하게 agent가 스킬을 잘못 라우팅하는 패턴 관찰

8 관련 주제

선행 지식

후속 주제

관련 포스트

Subscribe

Enjoy this blog? Get notified of new posts by email: