한 줄 요약
FableLayer는 모델 출력의 “조기 종료” 패턴을 종료 코드로만 차단하는 bash hook(early-stop.sh)과, 그 hook을 포함한 절차/구조 레이어를 Claude Code에 배포하는 플러그인 매니페스트(plugin.json, marketplace.json)로 구성된다. 왜 배우나: 산문 경고가 아니라 exit code로 약속-미실행과 범위 축소를 결정론적으로 막는 방법, 그리고 그 규율 레이어를 어떻게 설치 가능한 단위로 묶는지를 직접 만들 수 있게 하기 위해서다.
그림
flowchart TD A["모델 출력 텍스트"] --> B["early-stop.sh<br/>입력 수집: 인자 / --file / stdin"] B --> C{"입력 비어 있나?"} C -->|"예"| E1["exit 1 (사용법 오류)"] C -->|"아니오"| D["1) 범위 축소 패턴 검사<br/>SCOPE_REDUCTION_PATTERNS"] D -->|"매치"| BLOCK["exit 2 (차단, fail-closed)"] D -->|"미매치"| F["2) 약속 패턴 검사<br/>PROMISE_PATTERNS"] F -->|"미매치"| PASS["exit 0 (통과)"] F -->|"매치"| G{"완료-증거 패턴<br/>EVIDENCE_PATTERNS 있나?"} G -->|"있음"| PASS G -->|"없음"| BLOCK subgraph PLUGIN["플러그인 패키징"] P1["plugin.json<br/>layers: procedure_harness=agents/ + hooks/"] P2["marketplace.json<br/>plugins[].source = ./"] P2 --> P1 end BLOCK -.-> P1 PASS -.-> P1
쉽게 풀기
조기 종료(early-stop)란, AI가 “이제 보고서를 작성하겠습니다”라고 말만 하고 실제로는 작성하지 않은 채 끝내거나, “시간 관계상 나머지는 생략하겠습니다”처럼 시킨 일의 범위를 제멋대로 줄이는 행동이다. 사람으로 치면 “다음 주에 할게요”라고 약속하고 자리를 뜨는 것과 같다.
early-stop.sh는 이 두 가지 나쁜 습관을 잡아내는 자동 검사관이다. 검사 절차는 단순하다.
- 검사할 텍스트를 받는다. 방법은 세 가지다. 따옴표로 직접 넘기거나,
--file 경로로 파일을 읽게 하거나, 파이프(|)로 흘려보낸다. - 먼저 “범위를 줄이는 말”이 있는지 본다. “이하 생략”, “여기까지만”, “omitted for brevity” 같은 표현이 하나라도 보이면 변명의 여지 없이 즉시 차단한다.
- 다음으로 “하겠습니다 / I will / let me” 같은 미래 약속 표현이 있는지 본다. 약속만 있고 실제로 한 흔적(“완료했습니다”, “테스트 통과”, “exit 0” 같은 증거 표현)이 전혀 없으면 차단한다. 약속과 증거가 함께 있으면 통과시킨다.
핵심은 이 검사관이 말로 “위험합니다”라고 경고하지 않는다는 점이다. 대신 종료 코드라는 숫자 신호만 남긴다. 통과는 0, 차단은 2, 사용법 오류는 1이다. 이 숫자는 거짓말을 할 수 없으므로, 상위 시스템은 산문을 읽고 해석할 필요 없이 숫자만 보고 다음 단계로 갈지 멈출지 결정한다(fail-closed: 의심스러우면 막는다).
플러그인 구조는 이 검사관을 비롯한 여러 규율 도구를 한 상자에 담아 Claude Code에 설치할 수 있게 만든 포장지다. plugin.json이 상자 안에 무엇이 들었는지(스킬, 에이전트, hook 폴더)를 적은 내용물 목록이고, marketplace.json은 그 상자를 가게 진열대에 올리는 라벨이다.
핵심 정리
| 구성요소 | 역할 | 판정/식별 방식 |
|---|---|---|
hooks/early-stop.sh | 조기 종료(약속-미실행, 범위 축소) 감지 | 종료 코드(0/1/2)로만 |
| 범위 축소 패턴 | 명시 범위 임의 축소 차단 | 매치 즉시 exit 2 |
| 약속 패턴 + 증거 패턴 | 약속만 있고 증거 없으면 차단 | 약속 매치 & 증거 미매치 → exit 2 |
plugin.json | 플러그인 내용물 매니페스트 | name/version/skills/agents/layers |
marketplace.json | 마켓 등록 라벨 | plugins[].source = ”./“ |
| 종료 코드 | 의미 | 발생 조건 |
|---|---|---|
| 0 | 통과 | 조기 종료 패턴 없음 |
| 2 | 차단 | 범위 축소 매치, 또는 약속만 있고 증거 없음 |
| 1 | 사용법 오류 | 입력 없음 / 파일 없음 / 빈 입력 |
입력 수집 분기 (early-stop.sh 31~57행)
- 인자 1개 이상이고 첫 인자가
--file이면: 두 번째 인자를 경로로 보고cat. 인자 부족 시usage(exit 1), 파일 없으면error: file not found(exit 1).- 첫 인자가
--file이 아니면: 첫 인자 문자열 자체를 입력으로 사용.- 인자가 없으면: stdin을 읽음. 단 터미널(
[ -t 0 ])이면 입력 없음으로 보고usage(exit 1).- 수집 후
$INPUT이 비면error: empty input(exit 1).
패턴 목록 (early-stop.sh 62~116행)
- PROMISE_PATTERNS:
하겠습니다,하겠음,할 것입니다,할 예정입니다,할 예정이다,다음으로 .*하겠,이제 .*하겠,진행하겠습니다,작성하겠습니다,구현하겠습니다,확인하겠습니다,시작하겠습니다,will now,I will,next, I'll,going to,let me- SCOPE_REDUCTION_PATTERNS:
시간 관계상,지면 관계상,나머지는 생략,이하 생략,이하 동일,여기까지만,일부만,간단히만,생략하겠습니다,생략합니다,omitted for brevity,truncated for brevity,left as an exercise,rest is similar,and so on,\.\.\. *$- EVIDENCE_PATTERNS:
exit code,exit 0,종료 코드,테스트 통과,tests passed,PASS,완료했습니다,작성했습니다,구현했습니다,실행 결과,WROTE
bash 3.2 안전·결정론 규칙 (early-stop.sh 20
24, 119125행)
set -u로 미정의 변수 사용 차단.LC_ALL=C.UTF-8우선, 실패 시en_US.UTF-8, 그것도 실패 시 통과(한글 크래시 방지).- 매치 여부는
grep -Eq만 사용.grep -c(카운트)를 쓰지 않아|| echo 0함정을 피한다.- 빈 배열은
[ "${#arr[@]}" -ne 0 ]로 가드한 뒤에만 순회(macOS bash 3.2 안전).- newline 보존을 위해
printf '%s'로 grep에 전달.
실제 예시
조기 종료 감지 hook의 핵심 판정부다. 범위 축소는 증거와 무관하게 즉시 차단하고, 약속은 증거가 없을 때만 차단한다.
# 파일경로: /home/seunghyeong/harness-fablelayer/hooks/early-stop.sh (127~162행)
# 1) 범위 축소 패턴: 발견 즉시 차단(증거와 무관하게 명시적 범위 축소는 위반).
if [ "${#SCOPE_REDUCTION_PATTERNS[@]}" -ne 0 ]; then
for pat in "${SCOPE_REDUCTION_PATTERNS[@]}"; do
if has_match "$INPUT" "$pat"; then
printf 'BLOCK: scope-reduction pattern detected: %s\n' "$pat" >&2
exit 2
fi
done
fi
# 2) 약속-미실행 패턴: 약속 표현이 있고 완료-증거 표현이 전혀 없으면 차단.
PROMISE_HIT=""
if [ "${#PROMISE_PATTERNS[@]}" -ne 0 ]; then
for pat in "${PROMISE_PATTERNS[@]}"; do
if has_match "$INPUT" "$pat"; then
PROMISE_HIT="$pat"
break
fi
done
fi
if [ -n "$PROMISE_HIT" ]; then
EVIDENCE_HIT=""
if [ "${#EVIDENCE_PATTERNS[@]}" -ne 0 ]; then
for pat in "${EVIDENCE_PATTERNS[@]}"; do
if has_match "$INPUT" "$pat"; then
EVIDENCE_HIT="$pat"
break
fi
done
fi
if [ -z "$EVIDENCE_HIT" ]; then
printf 'BLOCK: promise-without-execution detected: %s (no completion evidence)\n' "$PROMISE_HIT" >&2
exit 2
fi
fi
exit 0세 가지 입력 방식과 기대 종료 코드:
# 직접 인자: 범위 축소 → exit 2 차단
early-stop.sh "시간 관계상 나머지는 생략하겠습니다"
# 약속만 있고 증거 없음 → exit 2 차단
echo "이제 보고서를 작성하겠습니다" | early-stop.sh
# 약속 + 완료 증거 동반 → exit 0 통과
early-stop.sh "보고서를 작성했습니다. 테스트 통과(exit 0)"
# 파일 검사
early-stop.sh --file ./model_output.txt플러그인 매니페스트의 레이어 매핑부다. hook은 procedure_harness 레이어에 묶인다.
// 파일경로: /home/seunghyeong/harness-fablelayer/.claude-plugin/plugin.json (29~50행)
"claude_code": {
"min_version": "1.0.0",
"skills_dir": "./.claude/skills",
"agents_dir": "./agents"
},
"layers": {
"promptcore": "./core",
"procedure_harness": "./agents",
"value_optimizer": "./styles",
"skillpack_router": "./skills"
},
"layers_note": "Layer directories are populated by the FableLayer build (modes: new, layer 1-4). promptcore=core/, procedure_harness=agents/ + hooks/, value_optimizer=styles/, skillpack_router=skills/. The plugin packages whichever layers exist at install time and does not synthesize missing layer content."마켓 등록 라벨이다. 플러그인 소스를 현재 디렉터리(./)로 가리킨다.
// 파일경로: /home/seunghyeong/harness-fablelayer/.claude-plugin/marketplace.json (11~16행)
"plugins": [
{
"name": "fablelayer",
"source": "./",
"description": "Discipline layer (not a capability transplant): verification grounding, completion evidence gate, systematic investigation, early-stop prevention, and value output style packaged for Claude Code, CLI, and local LLM adapters. Performance claims are deferred to bench/RESULTS.md.",
"version": "0.1.1"
}
]요약 & 셀프체크
3줄 요약:
early-stop.sh는 범위 축소 패턴은 즉시, 약속-미실행 패턴은 완료 증거가 없을 때만 차단하며, 판정은 오직 종료 코드(0 통과 / 2 차단 / 1 사용법 오류)로 한다.- 설계 원칙은 fail-closed와 결정론이다. 산문 경고를 신뢰 근거로 쓰지 않고,
grep -Eq로 매치 여부만 보며, bash 3.2 안전 가드와 LC 고정으로 한글 크래시를 막는다. plugin.json은 hook을 procedure_harness 레이어(agents/ + hooks/)로 묶어 패키징하고,marketplace.json은 그 플러그인을source: ./로 마켓에 등록한다. 단, 빌드 시 존재하는 레이어만 패키징하며 없는 레이어 내용을 합성하지는 않는다.
셀프체크:
- “이제 구현하겠습니다”만 있는 출력과, “이제 구현하겠습니다. 구현했습니다.”가 있는 출력의 종료 코드는 각각 무엇이며 왜 다른가?
- 범위 축소 패턴이 약속 패턴보다 먼저, 그리고 증거와 무관하게 차단되도록 설계한 이유는 무엇인가?
plugin.json의 layers_note는 “없는 레이어 내용을 synthesize하지 않는다”고 명시한다. 이 문장이 플러그인 설치 동작에 대해 보장하는 것은 무엇인가?