Skip to content

fix(nunchi): isolate claude synthesis from the node harness and reserve '기록 없음' for whole-answer refusals (#2531) - #2532

Merged
jinon86 merged 2 commits into
mainfrom
fix/nunchi-synth-isolation-2531
Oct 26, 2026
Merged

jinon86 merged 2 commits into
mainfrom
fix/nunchi-synth-isolation-2531

Conversation

@jinon86

@jinon86 jinon86 commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

Closes #2531

무엇 / 왜

2026-10-26 정기 bench에서 플릿 전 노드가 unanswered 5893%로 게이트 FAIL했다(지난주 1320%). 원인 두 가지가 겹쳤다(상세는 #2531).

  1. 합성 claude -p가 노드 하네스를 상속했다. CLAUDE.md, SessionStart 메모리 주입, ccc-report output style, hooks, 도구, MCP가 그대로 들어가 답변이 보고서 형식으로 부풀었다. 세션 제약을 인용하고 라이브 git 조회까지 했다.
    → judge-batch #2245와 같은 격리 플래그를 붙인다: --safe-mode --tools "" --disallowedTools mcp__* --strict-mcp-config --permission-mode dontAsk --no-session-persistence. --safe-mode는 OAuth 로그인을 유지한다. prompt는 계속 argv[2]에 둔다(variadic 옵션 앞이어야 하고, 테스트 stub이 $2로 읽는다).

  2. feat(nunchi): recall --as-of, [#id] citation footer and why provenance (#2464 제안 3, #2466) #2473 프롬프트가 부분 '기록 없음'을 유도했다. "인용할 근거가 없으면 '기록 없음'"이라는 지시 때문에, [#id]가 없는 Wiki/MemPalace 근거로 정답을 내면서도 "기록 없음" 절이 붙었다. bench.sh NO_RECORD_RE는 이런 답을 미답변으로 셌다.
    → 공통 NO_RECORD_RULE을 만든다. '기록 없음'은 근거가 전혀 없을 때만 쓰고, 부분 공백은 근거에 포함되지 않음(NO_RECORD_RE에 걸리지 않음을 테스트로 고정)으로 적는다. id가 없는 발췌는 인용 없이 써도 된다. dialectic과 synthesize(Wiki 계층) 양쪽에 적용한다.

  3. bench 거부 판정을 결론 줄 기준으로 바꾼다(2차 커밋, nunchi/bench: 10-26 플릿 전 노드 unanswered 58~93% FAIL — dialectic 부분 '기록 없음' 지시(#2473) + claude -p 하네스 상속 #2531 제안 3). 1차 실측에서, 결론에 부분 공백 문구를 쓴 올바른 거부 2건이 FABRICATED로 판정됐다. is_refusal()은 lead block(앞쪽 마크다운 제목 + 첫 내용 줄)만 NO_RECORD_RE 또는 PARTIAL_GAP_RE(근거에 포함되지 않)와 대조한다. nunchi 계층, wiki 계층, 음성 대조군 판정에 모두 쓴다. 프롬프트에는 "첫 줄은 결론, 핵심이 없으면 '기록 없음'으로 시작" 규칙을 넣는다. 게이트 임계값(≤40%, fabrication hard fail)은 바꾸지 않았다.

영향 범위

  • dialectic은 세션 recall 경로이기도 하다. 합성 호출에서 도구, MCP, 하네스 주입이 빠지는 것은 의도한 변화다(근거 밖 정보 혼입과 도구 실행 차단).
  • 표본 노드 gongyung·daegyo·soonwook·gwakga 모두 claude CLI 2.1.305이며 --safe-mode를 지원한다. 미지원 CLI라면 claude 후보가 exit≠0으로 실패하고 codex/piri로 넘어간다. 이 경우 backend-health에 degraded로 드러난다(조용히 실패하지 않음).

검증

  • nunchi.test.sh 224/224, bench.test.sh 78/78(결론 줄 판정 +4), stale-bench 8/8.
  • nunchi.test.sh 신규(1차): 신규 항목: 격리 플래그, --tools "", mcp__*, argv[2] prompt, 부분 공백 문구가 NO_RECORD_RE를 피하는지, 구 지시문 제거.
  • asof-why 31/31, state-aware 10/10, conflict-sweep 33/33.
  • validate-harness.sh static phase PASS. shellcheck는 기존과 같은 SC2016 info만 있다.
  • 실측(곽가, 15문항 부분 bench): 10-26 기준 미답변 10/10 → 2/10, fabrication 0, 게이트 PASS. 상세는 PR 코멘트에 있다.

🤖 Generated with Claude Code

…ve '기록 없음' for whole-answer refusals (#2531)

The 2026-10-26 weekly bench failed on every node (unanswered 58-93%, was
13-20%). Two causes compounded:

- `claude -p` synthesis ran with no isolation, so it inherited CLAUDE.md,
  SessionStart memory injection, the ccc-report output style, hooks, tools
  and MCP. Answers became full reports that quoted session constraints and
  ran live lookups. Pass the judge-batch #2245 flag set (--safe-mode,
  --tools "", --disallowedTools mcp__*, --strict-mcp-config,
  --permission-mode dontAsk, --no-session-persistence); the prompt stays
  argv[2].
- #2473 told dialectic to write '기록 없음' whenever it had nothing to cite.
  Wiki/MemPalace evidence has no [#id], so correct answers grew partial
  "기록 없음" clauses and bench.sh graded them UNANSWERED. A shared
  NO_RECORD_RULE now reserves '기록 없음' for "no evidence at all" and gives
  partial gaps the wording '근거에 포함되지 않음', asserted to stay clear of
  NO_RECORD_RE. id-less excerpts may be used without a citation.

Tests: nunchi.test.sh pins the isolation flags, argv[2] prompt and the
partial-gap wording (223/223); asof-why 31/31, state-aware 10/10,
bench 74/74, conflict-sweep 33/33; validate-harness static PASS.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jinon86
jinon86 requested a review from seoseo-ai as a code owner October 26, 2026 03:28
…artial-gap wording there (#2531)

A 15-question live bench of the first commit (gwakga, real DB + haiku)
brought unanswered from 10/10 to 0/10, but graded two negative controls
FABRICATED: q45/q48 refused correctly ("…는 근거에 포함되지 않음.") using
the partial-gap phrase as their conclusion, which bench.sh did not read
as a refusal.

- bench.sh: is_refusal() checks only the lead block (leading markdown
  headings plus the first content line) against NO_RECORD_RE or the new
  PARTIAL_GAP_RE (default "근거에 포함되지 않"). Used for the nunchi
  layer, the Wiki layer and negative-control grading. A gap phrase deeper
  in the body is not a refusal, so a correct answer with a partial gap
  stays ANSWERED and a fabricated conclusion stays FABRICATED.
- nunchi.py NO_RECORD_RULE: the first line must be a one-sentence
  conclusion; '기록 없음' leads it when the core of the question has no
  evidence.
- tests: bench.test.sh +4 (partial gap ANSWERED, heading+refusal,
  gap-worded negative CORRECT-REJECT, body-only gap FABRICATED);
  nunchi.test.sh pins PARTIAL_GAP_RE against PARTIAL_GAP_PHRASE.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jinon86

jinon86 commented Oct 26, 2026

Copy link
Copy Markdown
Contributor Author

실측: 곽가 15문항 부분 bench (실제 facts.db·haiku, 출력은 /tmp로 분리해 공식 리포트와 라이브 backend-health는 건드리지 않음)

문항: 10-26 공식 bench에서 미답변이던 양성 10문항(q1·q3·q4·q5·q6·q11·q12·q13·q20·q43)과 음성 대조군 5문항 전부(q44~q48).

10-26 공식 (main) 1차 커밋 4a897fc4 2차 커밋 9f7b455b
양성 미답변 10/10 0/10 2/10 (20%)
nunchi / wiki 답변 0 / 0 7 / 3 4 / 4
음성 올바른 거부 5/5 3/5 5/5
fabrication 0 2 (q45·q48) 0
게이트 FAIL FAIL (fabrication) PASS
"확정 사실" 보고서 형식 흔적 다수 0 0
backend claude claude 20/20 claude
  • 1차 커밋의 fabrication 2건은 실제로는 올바른 거부였다. 지어낸 IP·엔드포인트는 없었다. q45 "주유(juyu)의 Tailscale IP는 근거에 포함되지 않음.", q48 "Supermemory의 활성 엔드포인트(주소/URL) 정보는 근거에 포함되지 않음." 모델이 부분 공백용 문구를 결론에 썼는데 bench가 이를 거부로 읽지 못했다. 2차 커밋은 이를 고친다: 결론 줄 판정(is_refusal) + 결론 첫 줄 규칙. 2차 실측에서 q45·q48은 첫 줄을 기록 없음.으로 시작했다.
  • 2차에서 남은 미답변 2건은 정직한 거부다. q5(현재 HEAD)는 이전엔 하네스 도구로 라이브 git을 조회해 답했지만, 격리 후엔 "근거에 해시 없음"이라고 답한다. 의도한 변화다. q13은 MemPalace 발췌에 본문이 없는 실제 회수 공백이다.
  • 15문항은 표본이다. 48문항 전체 회복은 머지·self-update 후 다음 정기 bench(2026-11-02 11:07 KST)에서 확인한다.

CI

  • 1차 head 4a897fc4: validate-harness-shard (4)가 실패했다. 원인은 scripts/phone-browser-mcp.test.sh의 (e) SIGKILLed wrapper: reaper removed its forward + state 1건이다. 이 PR과 무관한 파일이다. 같은 base 3a0ed3de의 main CI는 성공했고, 로컬 재실행 2/2가 통과(73/73)해 타이밍 flake로 분류한다.
  • 2차 head 9f7b455b에서 CI가 다시 돌고 있다.

🤖 Generated with Claude Code

@seoseo-ai seoseo-ai left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved after exact-head validation and fresh operator authorization using the relay-held seoseo-ai credential.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

nunchi/bench: 10-26 플릿 전 노드 unanswered 58~93% FAIL — dialectic 부분 '기록 없음' 지시(#2473) + claude -p 하네스 상속

2 participants