reins — 퀘스트 CLI에서 도메인만 남기고, 래칫은 프레임워크로 Image: AI generated

how-make-quest는 퀘스트 CLI를 맨손으로 짓는 법이었다. 래칫이 무엇인지, 게이트를 어떻게 거는지, 치즈를 어떻게 막는지. 글 하나를 에이전트에게 주면 cobra 기반 Go CLI가 나온다.

그런데 두 번째 퀘스트 CLI를 지으면 무슨 일이 벌어지는가. 같은 단방향 상태기계를 또 짠다. 같은 scan/next/submit/status/export를 또 짠다. 같은 PASS 잠금, 같은 remaining 단조감소, 같은 JSONL export를 또 짠다. 달라지는 건 게이트 하나뿐인데, 매번 나머지 전부를 다시 짠다. 이게 퀘스트를 하나 더 지을 때마다 무는 보일러플레이트 세금이다.

패턴은 재사용 가능했다. 코드는 아니었다. reins는 그 간극을 닫는다.

무엇이 불변이고 무엇이 도메인인가

퀘스트 CLI를 두 개 겹쳐 놓고 차분(差分)을 보면 경계가 선명하다.

불변 (모든 퀘스트가 공유)        도메인 (퀘스트마다 다름)
─────────────────────────       ─────────────────────
래칫: TODO→PASS 불가역            무엇이 한 퀘스트인가
명령 골격: scan/next/submit…      무엇이 "사실"인가
레벨 집계: Fail/Review→verdict    어떤 치즈를 막아야 하나
진행 영속·resumable
export: 1회 방출

왼쪽은 how-make-quest가 증명한 그대로다 — 도메인이 회사명이든 엔드포인트든 함수든, 래칫의 톱니는 똑같이 걸린다. 오른쪽만 사람이 안다. reins는 왼쪽을 프레임워크로 공급하고, 당신에게 오른쪽만 남긴다.

이것은 새로운 주장이 아니라 reins가 코드로 강제하는 오래된 원칙이다 — 결정과 구현의 분리. 게이트는 결정(이 도메인에서 무엇이 참인가)이고, 래칫·CLI·집계는 구현이다. 구현을 매번 다시 쓰는 건 결정을 구현에 묶어두는 실패다.

게이트 하나만 구현한다

reins로 퀘스트를 만든다는 건 인터페이스 하나의 네 메서드를 채우는 일이다.

type Definition interface {
    Seed(args []string) ([]*quest.Item, error)            // 입력 → 초기 TODO 시드
    Render(it *quest.Item) (string, error)                // next가 보일 작성 프롬프트 + 검증 컨텍스트
    Prepare(it *quest.Item, raw []byte) (gate.Context, *quest.Verdict, error) // 제출 디코드
    Rules() []gate.Rule                                   // 게이트 위반-규칙 카탈로그
}

func main() { cli.NewQuestCmd("myquest", myDef{}, cli.Options{}).Execute() }

main 한 줄이 래칫·여섯 개 명령·집계·export·resumable 세션을 전부 공급한다. 당신이 쓴 건 도메인 네 조각뿐이다. 에이전트는 여전히 명령 두 개만 알면 된다 — next로 받고 submit으로 낸다. 나머지는 기계가 결정한다.

게이트는 치즈 방어 규칙의 카탈로그다

how-make-quest의 핵심은 “치즈 불가능한 게이트를 설계하라"였다. reins는 그 설계를 데이터 구조로 만든다 — 게이트 = 규칙 카탈로그. 규칙 하나가 치즈 탐지기 하나다. 위반을 발견하면 발동(true)하고 사실(Fact)을 싣는다.

// 뉴스 이벤트 추출 퀘스트의 치즈 방어 규칙 하나.
// "who 앵커가 원문에 실재하는가" — 에이전트가 인물을 지어내면 들통난다.
var whoAnchorPresent = gate.Rule{
    Meta: gate.RuleMeta{ID: "who-anchor-present", Level: gate.LevelFail, Desc: "필수 who 앵커가 원문에 실재"},
    Check: func(ctx gate.Context) (bool, quest.Fact) {
        sub := ctx.Submission.(*Event)
        if miss := textmatch.MissingTokens(ctx.Source, sub.Who.Anchors); len(miss) > 0 {
            return true, quest.Fact{Where: "who.anchors", Expected: "원문 substring", Actual: miss[0]}
        }
        return false, quest.Fact{}
    },
}

이 구조의 미덕은 자라난다는 것이다. 새 치즈를 발견할 때마다 규칙 하나를 추가하면 게이트가 그만큼 단단해진다. 그리고 카탈로그는 자기를 문서화한다 — rules 명령이 규칙 목록을 출력하면, 그게 곧 “내가 막고 있는 치즈의 감사 목록"이다. 무엇을 막는지 모르는 게이트는 없다.

심각도는 가중치가 아니라 레벨이다. Fail 하나면 곧 FAIL. 결정적 위반은 협상되지 않는다 — 99점짜리 위반 아홉 개로 한 개의 Fail을 덮을 수 없다. Evaluate는 발동한 규칙을 레벨로 집계한다: 하나라도 Fail이면 FAIL, 아니고 Review가 있으면 REVIEW, 다 통과면 PASS.

권한 비대칭을 타입으로 강제한다

how-make-quest에서 가장 중요한 한 줄은 “PASS 잠금은 기계만"이었다. reins는 이걸 규약이 아니라 타입으로 박는다.

L1 기계(결정론)   PASS를 잠그는 유일한 권한
L2 AI(회의자)     REVIEW만 — 의심을 제기하되 완료를 수여하지 못함
L3 사람           둘 다 놓친 잔여

기계 게이트는 PASS를 낸다. AI 검증기를 게이트에 넣더라도 그것이 할 수 있는 최대는 REVIEW로 빼는 것이다. 틀린 일을 애초에 불가능하게 만든다 — 프레임워크가 AI에게 PASS 권한을 주는 API를 제공하지 않으면, 실수로라도 술 취한 친구에게 판정을 맡길 수 없다.

두 번째 백엔드 — defeat 그래프

독립적인 규칙들의 레벨 집계로 충분한 게이트가 많다. 하지만 규칙들이 서로 경합하기 시작하면 — “이 위반은 저 위반이 있을 때만 의미가 있다”, “이 실패의 근본 원인은 사실 저것이다” — 손으로 짠 if-else 가드가 게이트를 잠식한다. 약한 게이트가 깨지는 곳이 아니라, 복잡한 게이트가 썩는 곳이다.

reins의 두 번째 게이트 백엔드는 이 경합을 선언적 그래프로 옮긴다 — toulmin h-Categoriser. 툴민 논증 모델이 그대로 자료구조가 된다:

  • Warrant — tautology PASS. “반박이 없으면 통과"라는 근거.
  • Counter — 위반이 warrant를 공격한다.
  • Supersedes — 규칙 간 우선순위. 어떤 반박이 어떤 반박을 이기는가.

손으로 짠 가드 절들이 Attacks·Supersedes 엣지로 증발한다. 그리고 엣지가 0이면 이 그래프는 레벨 집계와 정확히 동치다 — 복잡성은 필요할 때만 켜지는(opt-in) 비용이다(Definitiongate.Evaluator를 구현하면 켜진다).

그래프가 주는 진짜 선물은 판정이 아니라 피드백이다. 그래프 평가는 에이전트에게 직통 공략집을 돌려준다 — Verdict.Feedback: “왜 졌나, 그리고 뭘 바꾸면 이기나.” 단순한 “FAIL"이 아니라 논증의 구조에서 계산된 근본 원인이다.

여기서 how-make-quest의 역설이 다시 작동한다. 모델은 아첨한다 — 지시를 순순히 따른다. 의견에는 아첨이 독이지만, 사실에는 아첨이 자산이다. 공략집은 의견(“좀 이상한데”)이 아니라 사실(“who.anchors가 원문에 없다, 이걸 바꿔라”)이다. 아첨하는 모델일수록 그 사실을 순순히 수용해 수렴한다. 결정론적 그래프 + 아첨하는 LLM = 수렴이 보장되는 루프.

부작용은 격리한다 — ground와 staged 평가

게이트가 결정론적이려면 네트워크가 게이트 안에 있으면 안 된다. net/http를 직접 부르는 규칙은 단위테스트가 불가능하고, 판정이 회선 사정에 따라 흔들린다.

reins는 부작용을 pkg/ground로 몰아넣는다 — HTTPBody·MXResolves 같은 원시연산이 주입형 Resolver와 요청당 스냅샷으로 외부 조회를 소유한다. 규칙은 순수하게 남고, 외부 세계는 ground가 책임진다.

그리고 staged 평가: 싼 검사가 먼저 돌고, 그게 실패하면 네트워크 fetch는 아예 일어나지 않는다. 형식이 틀린 제출에 DNS를 조회할 이유가 없다. 비싸고 흔들리는 것을 싸고 확실한 것 뒤에 세운다.

N=1 추상화 금지

reins의 규약 중 하나가 이 프레임워크의 성격을 가장 정확히 드러낸다 — 하나의 소비자에서 추상을 뽑지 마라. 새 추상은 두 번째 소비자로 검증한 뒤에야 동결한다.

이건 까다로움이 아니라 제1원리다. 한 사례에서 뽑은 추상은 그 사례의 우연을 본질로 착각한다. 두 번째 도메인이 같은 추상을 요구할 때 비로소 그것이 불변임이 증명된다. 프레임워크가 자기 진화에까지 “주장이 아니라 검증"을 적용하는 것이다. 게이트가 에이전트의 주장을 믿지 않듯, 추상은 한 사례의 주장을 믿지 않는다.

같은 문장, 라이브러리가 되다

reins는 pkg/의 일곱 패키지로 서 있다 — textmatch(환각 차단 원시연산), temporal(시간 정규화), quest(래칫 코어), gate(게이트 계약), graph(defeat 그래프), ground(네트워크 격리), cli(cobra 스캐폴드). go build·go test 통과, 전 함수 커버. 그리고 toulmin은 그래프 백엔드에만 단방향으로 결합되어, 그래프를 안 쓰는 소비자는 toulmin을 링크조차 하지 않는다.

코드: github.com/park-jun-woo/reins

how-make-quest가 한 문장이었다면 — 생성은 확률적이어도 된다, 검증은 결정론적이어야 한다 — reins는 그 문장을 컴파일 가능한 형태로 굳힌 것이다. 게이트는 도메인의 사실을 재검증하고, 래칫은 통과한 것을 잠그고, 그래프는 진 이유를 사실로 돌려주고, 아첨하는 모델은 그 사실에 순응한다.

다음에 퀘스트 CLI가 필요하거든, 래칫을 다시 짜지 마라. 도메인의 게이트만 쓰고, 고삐는 빌려라.


같이 읽을거리

reins가 코드로 굳힌 원칙 — 생성은 확률적, 검증은 결정론적 — 은 reins만의 발견이 아니다. 서로 모르는 사람들이 같은 벽에 부딪혀 같은 결론에 도달했다. how-make-quest가 모은 독립 수렴 프로젝트들이 그 증거다.

  • episteme — 되돌릴 수 없는 작업 전 Reasoning Surface를 강제. reins의 래칫과 같은 직관 — PASS는 잠그기 전에 검증한다.
  • MagLab — “LLM은 추론만, 숫자는 결정론적 도구가.” reins가 부작용을 pkg/ground로 격리하는 것과 같은 분리.
  • Manifesto — “Agent proposes, World verifies.” reins의 권한 비대칭(L1만 PASS를 잠근다)을 한 문장으로 요약한다.
  • oh-my-kamisama — “diffs beat claims.” 게이트가 에이전트의 주장이 아니라 사실을 재검증하는 것과 같은 원칙.

그리고 defeat 그래프 백엔드의 뿌리는 논증 이론이다 — 아래 출처의 Toulmin·Dung·Amgoud 계열. reins의 pkg/graph는 그 60년 넘은 형식논리를 Go 자료구조로 옮긴 것이다.


출처

  • Toulmin, S. (1958). The Uses of Argument. Cambridge University Press. — defeat 그래프의 Warrant·Ground·Backing이 그대로 따온 논증 모델.
  • Dung, P.M. (1995). “On the Acceptability of Arguments and its Fundamental Role in Nonmonotonic Reasoning, Logic Programming and n-Person Games.” Artificial Intelligence, 77(2), 321–357. — 추상 논증 프레임워크와 attack(defeat) 그래프의 원전.
  • Amgoud, L. & Ben-Naim, J. (2013). “Ranking-based semantics for argumentation frameworks.” SUM 2013, LNCS 8078, 134–147. — pkg/graph가 채택한 weighted h-Categoriser. 공격받은 노드가 다시 방어되면 수용도가 회복되는 Compensation 성질, 수렴 보장.
  • Nute, D. (1994). “Defeasible Logic.” In Handbook of Logic in Artificial Intelligence and Logic Programming, Vol. 3. Oxford University Press. — strict/defeasible/defeater 분류. reins의 규칙 레벨(Fail/Review)과 Supersedes 우선순위의 형식적 뿌리.
  • Modgil, S. & Prakken, H. (2014). “The ASPIC+ Framework for Structured Argumentation: A Tutorial.” Argument & Computation, 5(1), 31–62. — Nute의 분류를 Dung 프레임워크 안에서 구조화한 논증 체계. defeat 그래프의 계보.
  • Gabriel, V.O. et al. (2020). “Reasoning in BDI agents using Toulmin’s argumentation model.” Theoretical Computer Science, 805, 76–91. — 툴민 모델을 소프트웨어로 구현한 선행 사례(BDI 에이전트). reins의 pkg/graph는 이를 게이트 판정으로 옮긴다.
  • Von Neumann, J. (1956). “Probabilistic Logics and the Synthesis of Reliable Organisms from Unreliable Components.” Automata Studies, Princeton University Press. — 불안정한 부품 위에 신뢰 가능한 프로토콜을 올리는 원리(reins의 전제).
  • Stechly, K., Valmeekam, K., & Kambhampati, S. (2024). “On the Self-Verification Limitations of Large Language Models.” arXiv:2402.08115 — 자기검증은 성능을 거의 못 올린다 → PASS 권한을 L1 기계에 둬야 하는 이유.
  • McKee-Reid, L. et al. (2024). “Honesty to Subterfuge: In-Context RL Can Make Honest Models Reward Hack.” arXiv:2410.06491 — 정직한 모델도 자기 보상을 판정하면 조작한다 → 권한 비대칭의 근거.
  • Bondarenko, A. et al. (2025). “Demonstrating Specification Gaming in Reasoning Models.” arXiv:2502.13295 — 능력이 높을수록 게이트의 빈틈을 더 잘 찾는다 → 게이트=규칙 카탈로그가 자라나야 하는 이유.
  • Thaman, K. (2026). “Reward Hacking Benchmark: Measuring Exploits in LLM Agents with Tool Use.” arXiv:2605.02964 — 게이트를 의도적으로 단단히 만들면 익스플로잇이 87.7% 줄었다.
  • Fanous, A. et al. (2025). “SycEval: Evaluating LLM Sycophancy.” AAAI/ACM AIES 2025. arXiv:2502.08177 — 아첨 굴복률 측정. “사실에는 아첨이 자산"의 양면.
  • Shapira, I. et al. (2026). “How RLHF Amplifies Sycophancy.” arXiv:2602.01002 — RLHF가 아첨을 증폭한다는 정리. 사실 피드백 + 아첨 = 수렴 루프의 전제.
  • Deque Systems (2021). “Automated Testing Study Identifies 57 Percent of Digital Accessibility Issues.” — 기계 판정 가능 영역(57%)과 사람 잔여(20%)의 경계.

관련 글

변경이력

  • 2026-06-05: 초판