FL_10 PromptCore — 결정적 공개안전 운영 프로파일
한 줄 요약
PromptCore는 모델에 주입할 “전이 가능한 운영 절차(procedure)“를 frozen dataclass 단일 정본으로 보유하고, 그것을 결정론적으로 core/promptcore.md와 동등한 마크다운으로 렌더하는 FableLayer의 1층 부품이다. 왜 배우나 — 프롬프트로 모델의 능력을 바꿀 수는 없지만 행동 규율(검증·완결·조사·안전)은 전이된다는 경계를 코드로 강제하는 법을 익히기 위해서다.
그림
flowchart TD
subgraph SRC["정본 소스 (promptcore.py)"]
S["_SECTIONS<br/>9개 Section (frozen)"]
P["_PREAMBLE / _FOOTER<br/>머리말·꼬리말"]
N["_OUTPUT_STRUCTURE_NOTE<br/>§6 뒤 주의블록"]
I["_SECTION_INTROS<br/>섹션 도입문장 8개"]
end
DC["default_core(version)<br/>PromptCore 9 sections"]
RM["render_markdown(core)<br/>결정론적 렌더"]
MD["core/promptcore.md<br/>(정본 마크다운)"]
CHK{"--check<br/>드리프트 검사"}
S --> DC
DC --> RM
P --> RM
N --> RM
I --> RM
RM --> MD
RM --> CHK
MD --> CHK
CHK -->|"동등"| OK["exit 0"]
CHK -->|"불일치"| FAIL["exit 1 (fail-closed)"]
flowchart LR
B["base PromptCore"] --> M["merge(base, overlay)"]
O["overlay PromptCore"] --> M
M --> G{"COMPRESSION_RE<br/>매치 검사"}
G -->|"_VETTED_RULES 면제"| PASS["dedupe 후 병합<br/>version = overlay.version"]
G -->|"신규 압축 강제 rule"| ERR["ValueError (FL3 위반)"]
쉽게 풀기
요리책 비유로 본다. 식당 본사가 “조리 표준 매뉴얼” 한 권을 들고 있다고 하자. 이 매뉴얼은 셰프의 타고난 미각(능력)을 옮겨주지 못한다. 하지만 “재료는 직접 맛보고 내라”, “한 접시라도 미완이면 코스 전체를 미완으로 본다”, “되돌릴 수 없는 행위(손님에게 서빙)는 확인 뒤에만” 같은 절차는 어느 주방에 줘도 그대로 작동한다. PromptCore가 담는 것이 바로 이 “절차” 부분이다.
단계로 풀면 이렇다.
- 매뉴얼의 내용(9개 섹션)을 사람이 수정할 수 없는 형태(frozen dataclass)로 코드 안에 굳혀 둔다.
default_core()를 부르면 그 9개 섹션이 담긴 정본 한 부가 나온다.render_markdown()은 이 정본을 항상 똑같은 글자로 마크다운으로 찍어낸다 — 같은 입력이면 언제 돌려도 한 글자도 안 달라진다(결정론적).- 본사 매뉴얼 파일(
core/promptcore.md)과 코드가 찍어낸 결과가 어긋나면--check가 exit 1로 막는다. 코드와 문서가 따로 노는 것(드리프트)을 차단한다. - 누군가 매뉴얼에 “토큰 아끼려고 답을 강제로 줄여라” 같은 품질 깎는 규칙을 끼워 넣으려 하면,
merge가 그 문장을 잡아내 에러로 거부한다(FL3 압축 금지).
핵심 메시지 하나: PromptCore는 약한 모델에 절차를 얹어 강한 모델인 척 시키지 않는다. 깊은 추론이 필요한 자리에는 “이건 더 강한 모델로 올려라(escalate)“는 정직한 신호를 만든다.
핵심 정리
| 구성요소 | 종류 | 역할 |
|---|---|---|
Section | frozen dataclass | id / title / inspired_by / rules 보유 |
PromptCore | frozen dataclass | version + sections(9개 Section) |
default_core(version="v2") | 함수 | 9 섹션 정본 PromptCore 반환 |
render_markdown(core) | 함수 | 결정론적 마크다운 렌더, [inspired-by:] 태그 방출 |
merge(base, overlay) | 함수 | id별 rule dedupe 병합, FL3 압축 게이트 |
main(--check / --render) | CLI | 드리프트 검사 / 정본 출력 |
| 핵심 규칙 | 무엇을 강제하나 |
|---|---|
| 결정론 | 같은 입력은 항상 같은 마크다운 → 드리프트 --check로 fail-closed |
| FL3 압축금지 | COMPRESSION_RE 매치 시 merge에서 ValueError |
| _VETTED_RULES 면제 | 이미 출하된 정본 rule(§6 “상태 스냅샷으로 압축”)은 오탐 방지 위해 재심 안 함 |
| capability/procedure 경계 | 능력은 전이 안 됨, 절차만 전이 → escalate 신호로 경계 보존 |
[!note]- 9개 섹션 전체 목록 (render_markdown이 §1~§9 번호 부여)
- 운영 정체성 (Operating Identity) — proactive·grounded, 완결 책임, 불확실성 정직 표시, 의도 보존
- 검증 grounding 규율 — 실행 후 관찰, 증거 형태 명시, HARD vs SOFT 구분
- 완결성 evidence gate — 부분 단위 분해, 근거 없는 done 거부, 집계는 fail-closed
- 체계적 조사 규율 — 재현 먼저, 가설 경쟁, 인과 사슬
- early-stop 방지 — continuation 우선, 잔여 단위 점검, 정직한 escalate
- 출력구조 규약 — 구조화 우선, 상태 스냅샷, 참조에 의한 첨부 (+ 압축 주의블록)
- 모델 가성비 운영 — 기본은 비용효율 모델, 깊은 추론은 escalate, 라우팅 근거 기록
- drift 방지 & passive 운영 — passive 상시 적용, drift 점검, 제약 보존
- 안전·승인 경계 — 비가역 행위는 승인 뒤, 비밀값 비노출, leaked 전문 비포함
[!note]- 렌더 구조 (정본 마크다운이 조립되는 순서)
_PREAMBLE(제목 + 인용블록 + §0 전제) → 각 섹션마다## {n}. {title}+[inspired-by:...]+ 도입문장(_SECTION_INTROS, §9는 없음) + rules 목록 → §6(output-structure) 뒤에만_OUTPUT_STRUCTURE_NOTE삽입 → 마지막에_FOOTER(적용 방법). 전체는"\n\n".join(parts) + "\n"로 결합된다. §0은 “capability는 전이 안 되고 procedure만 전이된다”는 이 레이어 전체의 전제를 못 박는다.
실제 예시
정본 데이터와 결정론적 렌더의 실제 형태.
# fablelayer/promptcore.py
COMPRESSION_RE = r"(압축|compress|간결하게 줄|truncate output|글자\s*수\s*제한)"
_COMPRESSION_PATTERN = re.compile(COMPRESSION_RE)
@dataclass(frozen=True)
class Section:
id: str
title: str
inspired_by: tuple[str, ...]
rules: tuple[str, ...]
@dataclass(frozen=True)
class PromptCore:
version: str
sections: tuple[Section, ...]
def default_core(version: str = "v2") -> PromptCore:
"""9 섹션을 가진 정본 PromptCore 를 반환한다."""
return PromptCore(version=version, sections=_SECTIONS)
FL3 압축 게이트 — 출하된 정본 rule은 면제하고, 신규 압축 강제 rule만 차단한다.
# fablelayer/promptcore.py
def _assert_no_compression(rules: tuple[str, ...], origin: str) -> None:
"""FL3: 압축 강제 규칙이 보이면 fail-closed 로 ValueError.
출하 정본(_VETTED_RULES)에 속한 rule 은 이미 게이트 통과분이라 면제한다."""
for rule in rules:
if rule in _VETTED_RULES:
continue
if _COMPRESSION_PATTERN.search(rule):
raise ValueError(
f"FL3 위반: {origin} 에 압축/분량깎기 규칙 금지 "
f"(COMPRESSION_RE 매치): {rule!r}"
)
merge의 id별 dedupe 병합 — base 먼저, overlay 추가, version은 overlay를 따른다.
# fablelayer/promptcore.py
combined_rules = _dedupe_rules(base_section.rules + overlay_section.rules)
combined_inspired = _dedupe_rules(
base_section.inspired_by + overlay_section.inspired_by
)
# ...
return PromptCore(version=overlay.version, sections=tuple(merged))
드리프트 검사 CLI — 코드 렌더와 정본 파일이 한 글자라도 다르면 exit 1.
# 정본과 render_markdown(default_core()) 동등성 검사 (드리프트 시 exit 1)
python -m fablelayer.promptcore --check
# 정본 마크다운 출력
python -m fablelayer.promptcore --render
렌더 결과가 정본 파일과 다르면 _check_drift()가 첫 불일치 라인을 짚는다.
# fablelayer/promptcore.py (_check_drift 내부)
if r != c:
sys.stderr.write(
f" line {i + 1}:\n rendered: {r!r}\n canonical: {c!r}\n"
)
break
요약 & 셀프체크
- PromptCore는 9개 frozen Section을 단일 정본으로 들고,
render_markdown이 그것을 결정론적으로core/promptcore.md와 동등하게 찍어내며--check로 드리프트를 fail-closed로 막는다. - 담는 것은 procedure(검증·완결·조사·early-stop 방지·출력구조·안전)뿐이고, capability가 필요한 자리에는 escalate 경계 신호를 남긴다 — 약한 모델을 강한 모델로 분장시키지 않는다.
merge는 id별 rule을 dedupe로 합치되,COMPRESSION_RE에 걸리는 신규 압축 강제 규칙은ValueError로 거부하고(FL3), 이미 출하된_VETTED_RULES는 오탐 방지를 위해 면제한다.
셀프체크
render_markdown이 “결정론적”이어야 하는 이유는 무엇이며, 그 성질이 깨지면--check는 어떻게 반응하는가?merge에서 §6의 “상태 스냅샷으로 압축”이라는 정본 rule이COMPRESSION_RE에 걸리는데도 ValueError가 나지 않는 까닭은?- PromptCore가 “약한 모델에 procedure를 얹어 강한 모델 행세를 시키지 않는다”는 원칙은 어느 섹션(§)과 §0 전제에서 강제되는가?