판단층 설계, 2026-09-26 서비스 반영

Jev 판단층: 설계, 명령, 적용, 실습

저는 10,000+ 노트 볼트의 두 명령, 질문에 답하는 /query와 새 자료를 분류해 들이는 /connect 뒤에 Jev를 판단층으로 붙였습니다.

Jev는 TypeSafe System One의 판단 모델입니다. 글을 쓰지 않고, 주어진 상태(state)와 질문(questions)에 대해 Choice(여러 선택지의 확률), Noul(예/아니오 확률), Score(척도 값) 세 형태의 판단만 돌려줍니다. 가격은 입력 100만 tokens당 $0.042입니다.

이 페이지는 그 판단층의 구조, 로컬 명령 세 개(retrieve, classify, project), 볼트 명령에 실제로 반영한 것, 그리고 누구나 따라 해 볼 수 있는 실습 키트를 정리합니다. 실행 코드 저장소는 비공개이고, 여기서는 명령의 입출력 규약과 설계를 설명합니다.

수치 읽는 법. 모든 수치는 2026-09-26에 사전 등록한 세 라운드(r1 1,557회, r2 1,726회, r3 277회, 합계 3,560회 호출, 입력 8,264,570 tokens, $0.3471, 전부 HTTP 200, 재시도 0)에서 나왔습니다. 정답은 사람이 새로 붙인 라벨이 아니라 볼트에 이미 적혀 있던 분류값과 위치입니다. 그래서 모든 비율은 "이미 있는 판단을 얼마나 재현하는가"로만 읽습니다. 사람 골드는 아직 0건입니다. 비율 뒤 대괄호는 Wilson 95% 구간입니다. 모델은 jev-1.13.0 하나이고 조건마다 한 번씩 보냈습니다. 용어는 용어 사전에 있습니다.

1. Architecture아키텍처: 명령 뒤의 판단층

1.1 네 층의 분업

코드와 규칙

검색 후보 만들기, 경로와 개인정보 규칙, 길이 상한, 해시, 예산과 재시도, 모델 핀 검사, 결정 규칙
모호한 문장의 의미를 정규식으로 확정

Jev

한 번의 호출에 한 가지 판단: 후보 하나 고르기, 후보마다 필요 여부, 분류 확률, 프로젝트 확률
글 생성, 산술, 날짜 비교, 권한 결정, 실행

생성형 LLM

답변 합성, 요약, 후보 문장 작성(기존 명령과 에이전트가 담당)
자기 산출물 검증

사람

읽을 출처 채택, 분류값 확정, 파일 이동, 발행과 삭제, 임계값 승인
코드가 끝낸 계산을 눈대중으로 반복

Jev에게 묻는 것은 코드로 결정론적으로 계산할 수 없고, 답이 문장이 아니라 선택이며, 한 질문에 한 판단으로 끝나는 것뿐입니다. 되돌릴 수 없는 행동은 사람이 하고, 보내면 안 되는 자료는 코드의 경로와 패턴 규칙이 막습니다. Jev에게 "이것이 비밀인가"를 묻지 않습니다.

1.2 PROPOSE 원칙

Jev는 제안하고, 코드와 사람이 실행합니다. 판단층이 남기는 것은 제안 JSON과 로그 한 줄뿐입니다.

1.3 판단이 흐르는 길

/query에 한국어 질문이 들어오면 판단은 다섯 단계를 거칩니다. 점선 안이 판단층입니다.

1사람
질문
/query에 한국어 자연어 질문이 들어옵니다.
넘기는 것: 질문 문장
2코드
후보
로컬 검색 도구 qmd로 음절 구문 BM25와 벡터 검색을 각각 돌려 부적격 노트를 걸러 내고, 두 목록을 RRF(k=60)로 합칩니다.
넘기는 것: 후보 평균 17~18개
3Jev
판단
후보 목록과 "해당 없음" 중 하나를 Choice로 고르고, 후보마다 "이 질의가 찾는 바로 그 노트인가"를 Noul로 답합니다. 글을 쓰지도, 실행하지도 않습니다.
넘기는 것: 확률값
4코드
결정과 영수증
선택지 순서를 뒤집어 한 번 더 묻고, 두 답이 다르면 보류합니다. Noul이 0.3을 넘는 후보만 유지 집합에 남기고, 호출마다 영수증 한 행을 씁니다.
넘기는 것: 제안 또는 보류, 로그 한 줄
5사람
최종 선택
답변을 쓰는 모델이 Jev가 고른 노트와 유지 집합을 먼저 읽되, 원래 검색 결과를 버리지 않습니다. 출처 채택은 사람이 하고, 고른 값은 human_final에 적습니다.
남는 것: 사람 골드

fail-open. 키가 없거나 Jev가 오류를 내면 제안 없이 원래 절차가 그대로 진행되고, 보류나 실패 때도 로컬 후보 목록은 그대로 돌아옵니다.

그림. /query에서 판단이 흐르는 길. 판단층(2~4단계)에서 나가는 것은 제안 JSON과 로그 한 줄뿐이고, 볼트에는 쓰지 않습니다.
되먹임. human_final은 사람이 실제로 고른 값을 적는 칸입니다. 채워지는 순간 그 줄이 사람 골드가 되고, 앞으로의 정확도는 이 칸으로 잽니다. 2026-09-26 기준 사람 골드는 0건입니다.

/connect와 /query의 답변 저장 단계에서는 저장 전 초안을 classify에 넘겨 분류 후보 세 개를 옆에 보여 줍니다. 최종 분류값은 사람이 고릅니다.

같은 거버넌스를 2026-09-24에 만든 shadow 모듈 네 개도 씁니다: /connect 경로 제안, 요청 난이도 티어 제안, 문서와 주장의 관계 판정, 해시 기반 변경 감지 게이트입니다.

1.4 영수증

호출 하나가 영수증 한 행입니다. 각 행에는 다음이 남습니다.

payload_sha256는 키를 정렬해 계산하므로 정순과 역순에서 같은 값이 나옵니다. 제시 순서의 차이는 candidate_set_revision이 담습니다. 이 값이 바뀌면 그 후보 집합에서 고른 이전 컷은 무효입니다.

판단 영수증 뷰어는 이 기록을 한 항목씩 펼칩니다. 두 순서의 확률 막대와 컷 위치, 제안과 보류를 만든 규칙, 보낸 필드별 글자 수와 한도, 브라우저 안에서 다시 계산한 해시 일치 여부, 호출 기록을 보여 주고, 사람 검토(수락, 수정, 보류 유지)를 JSON으로 내보냅니다. 연 파일은 브라우저 밖으로 나가지 않습니다.

1.5 shadow 로그와 human_final

명령을 한 번 실행하면 로그에 한 줄이 쌓입니다.

{"schema": "jev-cmds-shadow/1",
 "cmd": "retrieve",
 "run_id": "…", "ts": "…",
 "dry_run": false,
 "writes_vault": false,
 "input": {…}, "result": {…},
 "calls": [영수증 행, …],
 "human_final": null,
 "human_note": null,
 "human_reviewed_at": null}

1.6 거버넌스

모델 핀
요청과 응답 모두 jev-1.13.0. 응답 모델이 다르면 결과를 버리고 기록만 남깁니다.
필드 상한
state의 모든 문자열 1,200자. 노트 판단은 제목 200자, 설명 600자, 태그 8개 × 60자, 본문 앞 1,200자. 검색 후보 설명과 프로젝트 README 설명은 각 200자. 넘으면 보내지 않습니다.
보내지 않는 필드
파일 경로, 노트 type, 기존 분류값. 정답이나 위치를 누설하므로 사람 비교용으로 로그에만 남깁니다.
경로 거부 목록
인물, 회의, 영성 기록, 고객 자료, 녹취 전사, AI 대화 로그, 에이전트 세션 기록 폴더. 읽기 전에 거릅니다. 원래 문자열과 심볼릭 링크를 푼 경로를 모두 보고, 대소문자를 구분하지 않습니다.
노트 적격성
경로 규칙, 제외 type(인물, 회의, 설교, 전사, 거래 문서 등), 인물 태그, 민감 패턴(주민등록, 계좌, 비밀번호류), 전사본 파일명, 개인정보 규칙 순서로 검사합니다.
개인정보 규칙 8종
비공개와 가족, 건강, 대화와 회의 파생, 개인 생활, 제3자 연구와 대화 기록, 자격증명, 거주지, 학생과 고용 기록. 노트의 제목, 설명, 태그, 본문 앞 1,500자를 대소문자 구분 없이 봅니다.
조립된 payload 검사
보내기 직전, 조립이 끝난 요청 본문 전체에 다시 겁니다. 노트에서 온 문자열(질의, 후보 이름과 설명, Noul 지시문)에는 개인정보 규칙을, 모든 문자열에는 이메일, 전화번호, 홈 경로 패턴과 세션 기록 서명을 겁니다. 고정 지시문과 고정 분류 정의는 대상이 아닙니다. 걸리면 전송도 예산 차감도 없이 보류하고, 로그에는 차단 사유만 남깁니다. dry-run도 같은 검사를 거칩니다.
가림 처리
보낼 문자열의 홈 경로, 이메일, 전화번호, 분류 링크를 자리표시자로 바꿉니다.
재시도
401이면 그 실행을 멈추고 뒤 호출을 보내지 않습니다. 422는 재시도하지 않습니다. 429와 529만 지수 백오프로 최대 3회 시도합니다. 그 밖의 오류는 기록만 합니다.
예산
월 20,000,000 입력 tokens($0.84) 상한. 보내기 전에 본문 글자 수 ÷ 1.2로 추정해 넘으면 보내지 않고, 재시도 전에도 다시 검사합니다. 누적은 실측 tokens로 하고, 실패한 재시도 입력도 추정치로 산입합니다.
경계 동률
1위와 2위 확률 차가 1e-9 이하면 보류입니다. 부동소수 잡음이 동률을 제안으로 뒤집지 못합니다.
검색 색인
qmd의 전문 검색 색인은 읽기 전용으로만 열고, 벡터는 읽기 질의만 씁니다.

1.7 설계에서 얻은 교훈

2. CLICLI 명령 3개: retrieve, classify, project

세 명령은 노트를 읽기만 하고, 제안 JSON과 shadow 로그 한 줄만 남깁니다. 키는 환경 변수로만 받고 로그에 남기지 않습니다.

export TYPESAFE_API_KEY="YOUR_KEY"
python3 -m jev_cmds retrieve \
  --query "배운 걸 점점 긴 간격을 두고 다시 떠올리는 공부법을 정리한 노트가 어디 있지?" --json
python3 -m jev_cmds classify --file "<노트.md>" --top 3
python3 -m jev_cmds project  --file "<노트.md>"

명령 형태입니다. 실행 코드 저장소는 비공개입니다.

2026-09-26 13:07~13:12 KST 통합 스모크에서 세 명령을 실제로 14회 호출했습니다. 입력 47,658 tokens, $0.0020, 전부 HTTP 200, 요청과 응답 모델 일치, 재시도 0, 호출당 지연 228~339ms였고, 그동안 볼트에서 바뀐 파일은 0건이었습니다. 단위 테스트는 가짜 전송층과 가짜 검색 색인으로 90개를 돌리며, 뷰어의 판정 코드도 node에서 함께 검사합니다.

2.1 공통 옵션과 종료 코드

옵션뜻
--dry-run후보와 요청 본문을 만들고 경로, 개인정보, 1,200자, 금지 표지 검사를 모두 돌리되 API를 부르지 않습니다. 로그에 조립된 본문과 추정 tokens가 남습니다.
--json기계용 JSON으로 출력합니다. 빼면 짧은 텍스트입니다.
--log PATHshadow 로그 위치. 볼트나 검색 색인 컬렉션 안이면 거부합니다.
종료 코드경우
0정상 제안, 그리고 모든 fail-open 경우: 키 없음, 401, 422, 예산 초과, 모델 핀 불일치, 전송 오류, 개인정보 차단. 제안만 비어 있습니다.
2인자 오류: 허용되지 않은 컬렉션, 없는 파일, 볼트 안 로그 경로

2.2 retrieve: 검색 후보 재선택과 유지 집합

옵션기본값뜻
--query(필수)찾는 질문. 한국어 또는 영어
--collections측정한 3개검색할 색인 컬렉션. 측정한 기본값은 영구 노트, 문헌 노트, LLM 위키입니다. 미측정 컬렉션 4개는 허용하되 collections_measured: false를 붙이고, 인박스, 인물과 회의, 데일리, 설정, 원본 자료 컬렉션은 거부합니다.
--k10걸러 낸 뒤 검색 팔마다 남길 후보 수(1~20). 두 팔을 합치면 후보는 평균 17~18개입니다.
--keep-cut0.3유지 집합 문턱. Noul이 이 값보다 클 때만 남깁니다(같으면 제외).
--no-reverse끔역순 재질문을 생략합니다. 순서 불일치 보류도 함께 빠집니다.

음절 구문 BM25. qmd의 전문 검색 색인은 한글을 음절마다 띄어 저장합니다. "기록"으로 찾으면 0건, "기 록"으로 찾으면 2,726건입니다(2026-09-26 07:30 재확인). 그래서 한국어 단어를 인접 음절 구문으로 바꿔 보냅니다. 색인을 고치지 않는 우회입니다. r2 최종 표본에서 후보에 표적이 든 비율이 46/55에서 50/55로, 목록 1위 적중이 21/55에서 30/55로 올랐습니다.

측정 결과. 한국어 질문, 기존 노트 하나를 정답으로 둔 재현입니다.

지표r1 설계r1 임계값r2 최종r3 처음 보는 위키 표적
검색 목록 1위(코드)8/2411/2830/5516/4932.7% [21.2, 46.6]
로컬 qmd query 1위미실행미실행26/5547.3%미실행
Jev 재선택 끝단20/2423/2845/5581.8% [69.7, 89.8]44/4989.8% [78.2, 95.6]
표적이 후보에 없을 때 "해당 없음" 1위2/31/30/50/4
말하지 못하는 것"찾는 노트가 없다"는 아직 말하지 못합니다(표적 부재 때 r2 0/5, r3 0/4). 그래서 보류나 실패 때는 후보 목록 자체를 답으로 돌려줍니다.r3 표적은 모두 위키 페이지라 개인 볼트 검색 성능은 재지 않았습니다. 질문은 실제 사용자가 아니라 다른 생성 모델이 노트 설명을 보고 만든 것입니다. 0.3은 이 과제의 컷이며 다른 과제로 옮기지 않습니다.

2.3 classify: CMDS 87 서브카테고리 top-3 제안

옵션기본값뜻
--file(필수)분류할 노트 파일
--top3보여 줄 제안 수(1~10)
--title자동state.title로 보낼 제목. 볼트 안 노트는 파일명, 볼트 밖 저장 전 초안은 frontmatter title, 첫 H1, 파일명 순으로 고릅니다. 초안을 분류할 때는 저장할 파일명을 넘깁니다.

2.4 project: 활성 프로젝트 라우팅

옵션기본값뜻
--file(필수)라우팅할 노트 파일
--title자동classify와 같은 제목 규칙

2.5 출력 필드(--json)

명령주요 필드
retrievecandidates(제목, 컬렉션, BM25 순위, 벡터 순위, 융합 순위), choice(정순 1위, 두 순서 확률, decision, rule), keep(유지 집합과 Noul 값), pool(BM25 팔 비었는지, 음절 구문 질의, 걸러 낸 수), model, input_tokens, latency_ms
classifysuggestions(분류와 확률, 초안 정의 표시), reverse_top, decision, rule, definitions_version, existing_cmds(로그에만, 보내지 않음), title_source
projectoptions(이름, 측정 여부, README 사용 여부와 사유), suggestion, probs, reverse_top, decision, rule

2.6 영수증 예시(합성 데이터)

아래는 retrieve 한 번의 shadow 로그 한 줄을 줄인 것입니다. 질문, 노트 제목, 설명, 확률, tokens, 지연은 모두 설명을 위해 지어낸 값이고, 요청 본문과 해시는 실제 명령과 같은 코드로 만들었습니다.

{
 "schema": "jev-cmds-shadow/1",
 "cmd": "retrieve",
 "run_id": "sample-retrieve-1",
 "dry_run": false,
 "writes_vault": false,
 "input": {"query": "배운 걸 점점 긴 간격을 두고 다시 떠올리는 공부법을 정리한 노트가 어디 있지?",
           "collections": ["wiki"], "k": 10, "keep_cut": 0.3, "reverse": true},
 "result": {
  "choice": {"top_label": "간격 반복 (Spaced Repetition)", "decision": "제안",
             "rule": "both orders agree on a non-abstain argmax (no tie)"},
  "keep": [{"title": "간격 반복 (Spaced Repetition)", "p": 0.88},
           {"title": "인출 연습 (Retrieval Practice)", "p": 0.41}],
  "keep_rule": "keep iff noul > 0.3 (strict; equal = not kept)",
  "pool": {"n_candidates": 8,
           "fts_query": "\"배 운\" OR \"점 점\" OR \"간 격\" OR \"두 고\" OR \"다 시\" OR \"떠 올 리\" OR \"공 부 법\" OR \"정 리 한\" OR \"노 트\" OR \"어 디\" OR \"있 지\""},
  "model": "jev-1.13.0", "input_tokens": 1258, "latency_ms": 1551
 },
 "calls": [
  {"cond": "C1", "order": "normal", "model_requested": "jev-1.13.0", "model_actual": "jev-1.13.0", "http": 200,
   "input_tokens": 342, "latency_ms": 512, "retries": 0,
   "payload_sha256": "ed7e3c30236c7629fadc0d1bf476aafdf1771ae79baf34f92cacdbc0f2512110",
   "candidate_set_revision": "ba29e052325fb1cc3aaa0de10db982bfe34beb01aac1971f4167a21af406ad9c",
   "response": {"probabilities (상위 3)": {"간격 반복 (Spaced Repetition)": 0.78, "분산 학습": 0.09, "인출 연습 (Retrieval Practice)": 0.06}},
   "decision": "간격 반복 (Spaced Repetition)"},
  {"cond": "C1R", "order": "reversed", "model_requested": "jev-1.13.0", "model_actual": "jev-1.13.0", "http": 200,
   "input_tokens": 342, "latency_ms": 498, "retries": 0,
   "payload_sha256": "ed7e3c30236c7629fadc0d1bf476aafdf1771ae79baf34f92cacdbc0f2512110",
   "candidate_set_revision": "afb08e84ef647db52d2d4c630d30724a4d1a599fe855edbf50e05e69c9fd1acf",
   "response": {"probabilities (상위 3)": {"간격 반복 (Spaced Repetition)": 0.71, "분산 학습": 0.12, "인출 연습 (Retrieval Practice)": 0.09}},
   "decision": "간격 반복 (Spaced Repetition)"},
  {"cond": "C2", "order": "normal", "model_requested": "jev-1.13.0", "model_actual": "jev-1.13.0", "http": 200,
   "input_tokens": 574, "latency_ms": 541, "retries": 0,
   "payload_sha256": "739e237edd80b8760cc32d442ad090e1f3b8aa275fb1ea7897135494af20018b",
   "noul_cut": 0.3,
   "response": {"nouls (상위 3)": {"간격 반복 (Spaced Repetition)": 0.88, "인출 연습 (Retrieval Practice)": 0.41, "분산 학습": 0.27}},
   "decision": "noul_set"}
 ],
 "human_final": null, "human_note": null, "human_reviewed_at": null
}

읽는 법:

전체 샘플은 뷰어에서 "샘플 영수증 열기"를 누르면 바로 열리고, 원본은 /viewer/sample-receipt.json입니다. 세 건이 들어 있습니다.

  1. 두 순서가 합의한 제안. 위 예시입니다.
  2. 순서 불일치 보류. 노트 제목 짓기 질문: 정순 1위는 "노트 이름 짓기 규칙" 0.47, 역순 1위는 "제목은 주장으로 짓는다" 0.52로 갈려 보류됩니다. 유지 집합은 두 노트를 남겨, 보류여도 읽을 목록은 좁혀 줍니다.
  3. 전송 전 차단. 실습 키트 S4의 회의 정리 질문: 회의 관련 후보 두 개는 후보를 만드는 단계에서 이미 빠지고, 질문 자체가 "대화와 회의 파생" 규칙에 걸려 첫 호출부터 보내지 않습니다. 결정은 보류, 사유는 전송 전 차단입니다.

3. Service서비스 적용: 2026-09-26에 바뀐 것

세 라운드 결과를 같은 날 볼트 명령에 옮겼습니다. 바뀐 곳은 모두 shadow 단계라 제안만 보이고, 키가 없거나 Jev가 실패하면 원래 절차로 돌아갑니다.

같은 변경을 세 실행 환경(Claude Code, Codex 단일 에이전트, Codex 멀티 에이전트)의 명령 파일에 함께 넣었습니다. 멀티 에이전트 환경에서는 작업자가 Jev 제안을 돌려주기만 하고, 사용자에게 보이고 결정하는 일은 조정자가 합니다.

3.1 /query

3.2 /connect

3.3 지렛대는 카테고리 정의였다

87분류 재현을 20.0%에서 75.8%로 올린 것은 모델 설정이 아니라 선택지 설명이었습니다(r2 최종, n = 120). 이득은 대부분 설명이 비어 있던 클래스에서 났습니다.

87분류 전체r2 최종, n = 120
이름만24/120 (20.0%)
이름 + 정의91/120 (75.8%)
발행용 글802 Articles
이름만0/27
이름 + 정의26/27
용어 노트104 Terminologies
이름만9/66
이름 + 정의54/66

"이름 + 정의"는 87개 분류마다 정의 한 줄과 형식 우선 지시를 함께 준 조건입니다. 정답은 볼트에 이미 있던 분류값입니다.

이름만 주면 지식관리를 다룬 뉴스레터 원고가 "지식관리" 주제 분류로 끌려가지만, "주제가 무엇이든 발행 형식의 원고면 여기"라고 적어 주면 형식 분류로 돌아옵니다.

그래서 볼트의 카테고리 노트 중 설명(description)이 비어 있던 14개를 채웠고, 이제 87개 모두 설명이 있습니다. 802와 104에는 "주제가 아니라 형식으로 분류한다"는 문장을 넣었습니다. 14개 문구는 제가 최종 확인하기 전인 초안입니다.

주의classify는 볼트의 현재 문구가 아니라 등록한 정의 버전(측정 조건)을 보냅니다.볼트 문구를 고쳐도 명령이 보내는 내용은 바뀌지 않고, 새 문구를 쓰려면 새 정의 버전을 등록하고 다시 재야 합니다.

3.4 사람에게 남긴 것

단계누가무엇
후보 만들기, 걸러내기코드음절 BM25와 벡터 검색, 경로와 개인정보 규칙, 1,200자 상한
판단Jev후보 하나 고르기, 후보별 필요 확률, 87분류 확률, 프로젝트 확률
결정 규칙코드최댓값, 동률과 역순 불일치는 보류, 절단 0.3 엄격 부등호, 로그 한 줄
실행사람읽을 출처 채택, 분류값 확정, 파일 이동, 답변 저장, 로그의 human_final

하지 않는 것: 분류값 자동 기입, 이동, 삭제, 발행, 확신도로 자동 승인, Jev 결과로 출처 제외.

3.5 만들지 않은 것

시스템 파일(CLAUDE.md 등)은 바꾸지 않았습니다. Jev 단계는 두 명령 안의 선택 단계라서, 사람 골드가 쌓인 뒤 다시 판단합니다.

4. Practice실습 키트: 입력 3종 + 검색 3종

2026-09-24 AI & Beyond 발표의 후속 자료로 만든 요청 파일 6개입니다. 노트를 어디에 넣을지(입력 라우팅)와 검색 후보 중 무엇을 고르고 남길지(검색)를 Jev에 직접 묻습니다. 모든 문장, 노트 제목, 프로젝트 이름은 이 키트를 위해 새로 쓴 가상 데이터라 실제 노트 원문이나 사람 이름이 없습니다.

시나리오별 설명, 웹 Playground와 curl로 해 보는 법, 내 노트로 바꿔 쓸 때의 주의는 실습 페이지에 있습니다. 공개 전에 요청 파일 6개를 실행해 보지는 않았으므로 응답값은 직접 확인해 주세요.

실습 키트 보기 판단 영수증 뷰어 열기

5. Limits주장하지 않는 것

측정 방법과 결과 전체는 실측 결과와 원본 학습 페이지에 있습니다.

Upcoming다가오는 행사

요즘 제가 진행하는 세미나와 컨퍼런스입니다. 궁금하시면 오세요.