개발 에이전트 관측성 + Judge LLM 적용 설계

마지막 업데이트 2026-09-08

2026-09-08 · 대상: 로컬 Claude Code 2.1.263 · codex-cli 0.153.4 · 도구 검토: langsmith-cli

요약. PPI 개발에 쓰는 코딩 에이전트가 실패한 시점이 아니라 처음 잘못된 방향으로 좁힌 지점(First Bad Turn)을 데이터로 남기고 자동 판정하기 위한 설계입니다. 결론 세 줄:
  1. 수집기는 자작하지 않는다. langsmith-cli가 Claude Code와 Codex를 공식 지원한다 — langsmith trace setup claude|codex 한 줄로 프롬프트·응답·툴 호출 전문이 LangSmith 프로젝트로 들어간다.
  2. 차단 조건은 개인정보다. PPI 세션 트리아지 대화에는 아동 실명이 그대로 들어 있고, 플러그인 경로는 redaction 없이 전문을 클라우드로 보낸다. Claude Code는 네이티브 OTel + Collector로 전송 전에 걸러낼 수 있지만(2·4장), Codex는 그 경로가 없다. 게이트를 정하기 전에는 켜지 않는다.
  3. Judge를 먼저 붙이면 비용만 태운다. 사람이 개입해 방향을 되돌린 턴이 곧 First Bad Turn의 무료 라벨이다. 룰로 후보를 뽑고, LLM은 그 후보의 해석만 맡는다.

전체 구조와 검증 대상

왼쪽 두 칸(수집·저장)은 도구가 이미 해결한 문제입니다. 리스크는 판정과 산출에 몰려 있고, 그 검증은 LangSmith를 켜지 않고도 할 수 있습니다.

1 수집 해결됨 Claude Code · Codex 경로 A langsmith trace setup claude|codex — 프롬프트·응답·툴 출력 전문 경로 B 네이티브 OTel — OTEL_TRACES_EXPORTER + ENHANCED_TELEMETRY_BETA (Claude Code 전용) 어느 경로든 원본 JSONL은 로컬에 그대로 남는다 2 저장 · 조회 해결됨 LangSmith Trace 트리 — Prompt · Agent output · Read/Grep · Tool call · Tool result · Edit/Test trace list --error --show-hierarchy / trace export --full 로 실패 세션만 추출 3 판정 미검증 Judge LLM · GPT · Claude · Gemini 편향 통제가 설계의 핵심 — trajectory 프리픽스만 순차 제공하고 결말을 감춘다 성공 세션을 라벨 없이 섞어 null 반환율 측정 · 대상과 다른 모델 계열로 고정 툴 출력은 앞/뒤 N줄 + 크기 메타로 자른다 (의미 요약으로 대체 금지) 4 산출 미검증 First Bad Turn 리포트 first_bad_turn: 7 · category: 가설 조기 확정 · severity: 3 / 3 missing_evidence: [readyState, media.error, codec metadata] better_action: 런타임 미디어 상태를 먼저 확인 2번 칸 건너뜀 사전 검증 경로 — LangSmith 없이, 지금 바로 로컬 세션 JSONL (Claude Code 17개 프로젝트 · Codex 453세션) → Judge LLM → 내가 실제로 끼어든 턴과 대조 합격선 — Judge가 짚은 First Bad Turn이 내 개입 턴 ±1 이내인 비율 사람이 방향을 되돌린 턴 = 비용 0의 정답 라벨. 판정은 룰로 후보를 뽑고 LLM은 해석만 맡는다.

Hermes는 전용 수집 경로가 없어 이 도식에서 제외했습니다(2장). 개인정보 게이트는 업로드를 켤 때만 적용되며, 위 검증 레인은 로컬이라 해당되지 않습니다(4장).

1. 문제 정의 — 왜 First Bad Turn인가

에이전트 작업이 실패하면 눈에 보이는 것은 마지막 신호(테스트 FAIL, 사용자의 "그거 아니야")뿐입니다. 그런데 실제 손실은 그보다 훨씬 앞, 탐색 공간을 근거 없이 좁힌 순간에 발생합니다. 그 지점 이후의 Read·Grep·Edit는 전부 낭비입니다.

Step 1  사용자 프롬프트: "iPad에서 영상 디코드 안 됨 원인 분석"
Step 2  Read player.ts                                  → 정상
Step 3  "codec 문제로 보인다" 라고 확정                  → ★ First Bad Turn
Step 4  codec 관련 코드만 Grep                          → 탐색 편향
Step 5  codec config Edit                               → 검증 전 수정
Step 6  Test FAIL                                       → 여기서야 실패가 보인다

Step 3에서 readyState·media.error·실제 codec metadata·네트워크 상태를 아무것도 확인하지 않았습니다. 판정 대상은 Step 6이 아니라 Step 3입니다.

숨겨진 사고과정(chain of thought)을 수집하려 하지 않습니다. 판정은 외부로 드러난 것 — 메시지, 툴 호출, 툴 결과, 수정, 테스트 결과 — 만으로 합니다. PPI에는 이미 같은 원칙의 선례가 있습니다: 자기 보고는 사후 합리화가 섞이므로 실제 화면 로그와 대조한다(consensus-loop).

2. 도구 판정 — langsmith-cli가 적절한가

판정: 조건부 적합. 수집 계층에서 편의성은 최선이지만 유일한 경로가 아닙니다 — Claude Code는 네이티브 OTel로 span도 내보냅니다(2026-09-08 공식 문서 확인). 판정 계층에서는 보조 도구입니다.

2-1. 수집 경로는 둘이다

경로Claude CodeCodex
A. langsmith-cli 플러그인 trace setup claude — 프롬프트·응답·툴 출력 전문을 LangSmith 프로젝트로 trace setup codex — 동일
B. 네이티브 OTel traces/spans 지원(beta). CLAUDE_CODE_ENABLE_TELEMETRY=1 + CLAUDE_CODE_ENHANCED_TELEMETRY_BETA=1 + OTEL_TRACES_EXPORTER=otlp. span 계층은 claude_code.interactionllm_request/hook/tooltool.execution. metrics·logs는 stable span 없음. [otel] 블록은 구조화 log event + metrics만 낸다(codex.user_prompt·codex.tool_decision·codex.tool_result·codex.api_request 등). exporternone/otlp-http/otlp-grpc, log_user_prompt 기본 false

경로 B의 결정적 차이는 중간에 OTel Collector를 끼울 수 있다는 점입니다. 4장 개인정보 게이트를 self-hosted 없이 통과할 수 있는 유일한 방법입니다.

경로 A  Claude Code / Codex ── 플러그인 ──────────────────→ LangSmith (전문, 중간 개입 지점 없음)

경로 B  Claude Code ── OTLP ──→ [OTel Collector: 속성 필터·마스킹] ──→ LangSmith / 임의 OTLP 백엔드
                                 ↑ 여기서 아동 실명을 걸러낼 수 있다

경로 B  Codex ── OTLP ──→ (log event·metrics만; span 없음) → trajectory 판정에는 부족
비대칭이 생깁니다. Claude Code는 경로 A·B 모두 가능하지만 Codex는 span이 없어 trajectory 판정용으로는 경로 A(플러그인) 또는 로컬 JSONL만 쓸 수 있습니다. 5장의 turn 좌표계는 어느 경로든 원본 JSONL 기준으로 정의하므로 이 비대칭에 영향받지 않습니다.

2-2. 계층별 판정

항목평가
수집(Claude Code)적합. 경로 A는 설정 한 줄로 끝나고, 경로 B는 redaction 지점을 확보한다. 4장 결론에 따라 고른다
수집(Codex)적합, 단 경로 A뿐. 네이티브 OTel에 span이 없어 대안이 없다. trace setup codex~/.codex/config.toml + ~/.codex/langsmith.json을 쓴다
조회·추출적합. trace list --error --show-hierarchy, trace export --full로 실패 트레이스만 JSONL로 뽑을 수 있다. Judge 입력 준비에 그대로 쓴다
Judge 실행부분 적합. CLI는 evaluator를 등록만 한다(evaluator create-llm / upload). 실행은 서버 쪽이고, First Bad Turn처럼 trajectory 전체를 보는 판정은 variable-mapping만으로 표현하기 어렵다. 초기에는 로컬 스크립트가 빠르다
개인정보경로에 따라 갈린다. 경로 A는 redaction 훅이 없어 전문이 그대로 나간다. 경로 B는 Collector에서 막을 수 있다. 4장 참조
비용·종속SaaS 종속이 생기지만 원본은 로컬 JSONL에 남으므로 이탈 비용은 낮다. 경로 B는 OTLP 표준이라 백엔드 교체가 더 쉽다(11장)
이전 판단이 두 번 뒤집혔습니다. 2026-09-03 consensus-loop 문서는 "LangChain/LangSmith는 Claude Code 내부 호출을 추적할 수 없어 도입 가치가 낮다"고 기록했습니다. ① langsmith-clitrace setup이 그 전제를 깼고, ② Claude Code 자체가 OTel span을 내보내므로 LangSmith에 의존하지 않고도 추적이 가능합니다. 다만 그 문서의 두 번째 근거(핑퐁이 LLM 쪽은 이미 자체 trace가 있다)는 여전히 유효합니다 — 이 문서의 대상은 런타임 AI가 아니라 개발용 코딩 에이전트입니다. 둘을 같은 프로젝트에 섞지 않습니다.

3. 검증된 적용 경로

아래 명령·경로는 langsmith-cli README(2026-09-08 기준)에서 확인한 것입니다. 단, 이 문서 작성 시점에 로컬에는 아직 설치하지 않았고 실제 실행은 미검증입니다.

# 1) 설치
curl -fsSL https://cli.langsmith.com/install.sh | sh

# 2) 인증 (trace setup은 API 키만 지원 — OAuth 프로필 불가)
export LANGSMITH_API_KEY="lsv2_pt_..."

# 3) 에이전트별 tracing 설정 — 변경 내용을 미리 보여주고 확인을 받는다
langsmith trace setup claude --project ppi-agent-claude
langsmith trace setup codex  --project ppi-agent-codex

# 프로젝트 로컬 범위로만 켜기 (./.claude/settings.local.json)
langsmith trace setup claude --scope project

# 4) 동작 확인
tail -f ~/.claude/state/hook.log
에이전트설치 방식기록 위치
Claude Codeclaude plugin marketplace add + claude plugin installuser-global 설정, 또는 --scope project./.claude/settings.local.json
Codexcodex plugin marketplace add~/.codex/config.toml + ~/.codex/langsmith.json

로컬 사전 조건은 충족되어 있습니다 — claude plugincodex plugin 서브커맨드가 두 CLI에 모두 존재합니다(각 --help로 확인).

기본 프로젝트명은 claude-code / codex입니다. PPI 작업과 개인 작업이 한 프로젝트에 섞이면 4장 게이트를 지킬 수 없으므로 --project를 반드시 명시합니다.

4. P0 게이트 — 개인정보

이 게이트를 통과하기 전에는 tracing을 켜지 않습니다. 경로 A(플러그인)는 프롬프트·응답·툴 출력 전문을 보냅니다. PPI 세션 트리아지 대화에는 아동 실명이 그대로 등장하고, 툴 출력에는 LogRocket 세션 데이터, Grafana 로그, AWS 리소스명, S3 키가 섞입니다. 덧붙여 trace setupgit config user.name/user.email을 자동 감지해 모든 트레이스에 user_name/user_email 메타데이터로 붙입니다.

선택 가능한 네 가지 태도

방식내용판단
OTel Collector 경유
(경로 B · 권장)
Claude Code의 OTEL_TRACES_EXPORTER를 로컬 Collector로 보내고, Collector의 attribute processor에서 아동 실명·자격증명·S3 키를 지운 뒤 LangSmith(또는 임의 OTLP 백엔드)로 전달한다. 전송 전 차단이므로 유일하게 구조적으로 안전하다. 다만 Claude Code만 가능하고 traces가 beta다. Codex는 이 경로로 trajectory를 못 만든다
self-hosted LangSmith LANGSMITH_ENDPOINT를 자체 호스트로. CLI는 /info로 배포 버전을 감지해 쿼리 API를 고른다(>= 0.16은 v2). 두 에이전트를 모두 덮는 유일한 방법. 대신 운영 부담이 생기고 < 0.16에서는 trace messages·thread messages가 동작하지 않는다
범위 격리 --scope project아동 데이터를 다루지 않는 저장소에서만 켠다. PPI 본체·트리아지 작업 세션에서는 끈다. 즉시 시작 가능하고 Codex에도 적용된다. 사람이 규율을 지켜야 하므로 새는 경로가 남는다
사후 삭제 의존 일단 켜고 문제되는 트레이스를 지운다. 부적합. 전송이 곧 유출이다. 채택하지 않는다

에이전트별 권고. Claude Code는 Collector 경유, Codex는 self-hosted 또는 범위 격리 — 또는 Phase 1처럼 Codex는 아예 업로드하지 않고 로컬 JSONL로만 지표를 계산합니다(10장).

어느 방식이든 보존 기간워크스페이스 권한을 먼저 정합니다. 트레이스에는 코드 전문이 포함되므로 사실상 저장소 사본입니다.

5. turn 정의 — 판정의 좌표계

first_bad_turn: 7이 성립하려면 "7"이 세션 사이에서 같은 의미여야 합니다. LangSmith가 저장하는 것은 run 트리이므로, 선형 인덱스를 뽑는 규칙을 먼저 고정해야 합니다. 이 규칙이 없으면 9장의 지표는 전부 비교 불가능해집니다.

규칙 (제안)

  1. turn = 사용자 프롬프트 1건 + 그에 이어지는 에이전트 행동 전체. 사용자 프롬프트 경계에서만 번호가 올라간다.
  2. 한 turn 안의 툴 호출은 turn.step 부번호를 붙인다. 병렬 툴 호출은 같은 step, 다른 slot.
  3. subagent는 부모 turn 번호를 상속하고 sub 접미사를 붙인다 — 별도 turn으로 세지 않는다.

두 에이전트의 원본 필드 (로컬 트랜스크립트에서 직접 확인)

Claude CodeCodex
파일~/.claude/projects/<slug>/<sessionId>.jsonl~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl
turn 경계type:"user" + promptId. uuid/parentUuid로 체인을 복원한다. isMeta·attachment·isSidechain은 turn으로 세지 않는다turn_context.payload.turn_id가 이미 존재한다. event_msg.task_started/task_complete가 경계를 명시
툴 호출message.content[]tool_use{id,name,input}tool_result{tool_use_id,is_error}response_item.custom_tool_call{call_id,name}custom_tool_call_output
토큰message.usage (cache_read/creation 분리)event_msg.token_count
Codex가 유리합니다. turn_id가 원본에 있어 규칙 1을 그대로 적용할 수 있습니다. Claude Code는 promptId 기준으로 경계를 재구성해야 하고, 훅이 주입한 system 레코드(preventedContinuation·stopReason 포함)를 turn에서 제외하는 처리가 추가로 필요합니다. 로컬 세션 규모는 Claude Code 17개 프로젝트, Codex 453세션입니다.

6. 룰 우선 — 사람의 개입이 곧 라벨이다

모든 트레이스를 Judge에 보내는 설계는 비용으로 먼저 죽습니다. PPI에는 이미 확립된 반대 원칙이 있습니다 — 판정은 룰, LLM은 해석만(수업 로그 야간 트리아지). 여기에도 그대로 적용합니다.

핵심은 사람이 방향을 되돌린 지점이 First Bad Turn의 사실상 정답 라벨이라는 점입니다. 사용자가 에이전트의 진행 중간에 끼어들어 교정했다면, 잘못된 방향은 그 직전 turn에서 이미 정해져 있었습니다. 라벨링 비용이 0입니다.

후보 추출 시그널 (설계 제안 — 미구현)

시그널원본 근거의미
진행 중 사용자 개입직전 assistant가 tool_use 진행 중인데 isMeta가 아닌 user 레코드 등장 / Codex는 task_complete 없이 새 task_started1순위 후보. 사람이 중단·교정했다
훅 차단type:"system" + preventedContinuation / stopReason가드가 막았다 = 하네스가 판단한 잘못된 행동
툴 오류 연쇄tool_result.is_error: true 반복같은 벽에 반복 충돌
같은 파일 반복 Edit동일 경로 Edit 3회 이상가설 미확정 상태의 시행착오
검증 전 수정세션 내 첫 Edit가 첫 테스트·런타임 확인보다 앞섬Premature Edit

LangSmith 쪽에서는 --filter DSL과 --error로 1차 축소가 가능하고, 위 시그널 중 원본 필드에 의존하는 것들은 로컬 JSONL에서 계산하는 쪽이 정확합니다.

langsmith trace list --project ppi-agent-claude --error --limit 50 --include-metadata
langsmith trace export ./traces --project ppi-agent-claude --limit 20 --full

7. Judge — 스키마와 편향 통제

출력 스키마

{
  "session_id": "3b84054d-...",
  "agent": "claude-code",
  "first_bad_turn": 7,
  "first_bad_step": "7.2",
  "category": "premature_hypothesis",
  "severity": 3,
  "reason": "런타임 근거 없이 codec으로 탐색을 좁혔다",
  "missing_evidence": ["readyState", "media.error", "codec metadata"],
  "better_action": "런타임 미디어 상태를 먼저 확인",
  "confidence": 0.72
}

평가 축은 5개입니다: Evidence Sufficiency · Hypothesis Validity · Tool Appropriateness · Premature Commitment · Recovery Quality (각 0~3).

편향 통제 — 이게 없으면 판정이 무의미하다

편향증상대책
Hindsight bias실패한 전체 trajectory를 통째로 주면 Judge가 결말을 알고 후반 스텝을 과잉 비난한다turn 1..N 프리픽스만 순차 제공하고 "여기까지의 근거로 정당한가"를 묻는다. 결말을 보여주지 않는다
모든 세션에서 결함을 찾아냄Judge는 문제를 찾도록 요청받으면 없어도 만든다성공 세션을 라벨 없이 섞는다. 성공 세션에 first_bad_turn: null을 반환하는 비율이 Judge의 신뢰도 지표다
요약이 근거를 지운다긴 툴 출력을 요약해 넣으면 "Evidence Sufficiency"를 판정할 근거 자체가 사라진다툴 출력은 앞/뒤 N줄 + 전체 크기·행수 메타를 유지하는 방식으로 자른다. 의미 요약으로 대체하지 않는다
자기 채점같은 모델 계열이 자기 trajectory를 관대하게 본다Judge는 대상 에이전트와 다른 모델 계열로 고정한다

Judge 프롬프트 골자

You are evaluating a coding-agent trajectory PREFIX (turns 1..N).
You do NOT know whether the task eventually succeeded. Do not guess.

Find the earliest turn where the observable decision or action became
unjustified, or narrowed the search space without supporting evidence.

Judge only from observable data: messages, tool calls, tool results,
edits, tests, metadata. Do not infer hidden chain-of-thought.

If every turn is justified given the evidence available AT THAT TURN,
return first_bad_turn: null. Returning null is a correct answer.

Return the JSON schema above.

8. evaluator 등록 (LangSmith 온라인 평가로 넘길 때)

로컬 스크립트로 판정이 안정된 뒤에 옮깁니다. 순서를 뒤집으면 프롬프트를 고칠 때마다 서버 왕복이 생깁니다.

# 룰 기반 후보 추출을 온라인 evaluator로 (샘플링 가능)
langsmith evaluator upload evals.py \
  --name premature-edit --function check_premature_edit \
  --project ppi-agent-claude --sampling-rate 0.5

# LLM-as-judge 등록 (--model-config 필수)
langsmith evaluator create-llm \
  --name first-bad-turn --project ppi-agent-claude \
  --prompt prompt.json --schema schema.json --model-config model.json \
  --variable-mapping '{"input":"input.prompt","output":"output.trajectory"}'
--variable-mapping은 run의 단일 input/output 필드를 가리킵니다. First Bad Turn 판정은 run 트리 전체를 봐야 하므로 이 매핑만으로는 표현되지 않습니다. 트리를 하나의 정규화된 문자열로 미리 만들어 넣는 전처리가 필요합니다 — 그래서 초기에는 trace export + 로컬 스크립트가 더 빠릅니다(추정, 실행 미검증).

9. 운영 지표

지표정의읽는 법
First Bad Turn Depth세션당 첫 잘못된 turn의 번호낮을수록 초반에 헤맨다. 프롬프트·CLAUDE.md·AGENTS.md 문제를 시사
Wasted Tool CallsFirst Bad Turn 이후의 Read/Grep/Bash/Edit 수낭비 토큰의 직접 대리 지표
Premature Edit Rate첫 Edit가 첫 검증보다 앞선 세션 비율룰로만 계산 가능 — Judge 없이 즉시 볼 수 있는 첫 지표
Recovery Rate새 근거 등장 후 가설을 실제로 바꾼 비율낮으면 확증 편향이 하네스 차원에서 강화되고 있다
Human Intervention Rate사람이 진행 중 끼어든 세션 비율가장 정직한 종합 지표. 6장 라벨과 같은 데이터
Judge–Human AgreementJudge 판정과 사람 개입 turn의 일치율 (±1 turn 허용)이 값이 낮으면 위 지표 전부를 신뢰할 수 없다. 도입 여부를 결정하는 관문

성공률(Success Rate) 단독은 쓰지 않습니다. 사람이 여러 번 교정해서 결국 성공한 세션과 한 번에 성공한 세션이 같은 값으로 집계됩니다.

10. 도입 단계

Phase 0개인정보 게이트 결정
(4장) — 선행 조건
Phase 1룰 지표만
로컬 JSONL · Judge 없음
Phase 2Judge 1개
실패 10~20건 · 사람 대조
Phase 3온라인 평가
evaluator 등록 · 주간 집계
단계할 일완료 기준
Phase 0self-hosted vs 범위 격리 결정, 보존 기간·권한 정의아동 데이터 세션이 클라우드로 나가지 않음이 구조적으로 보장됨
Phase 1langsmith trace setup 적용. 동시에 로컬 JSONL에서 Premature Edit Rate·Human Intervention Rate만 계산LangSmith 없이도 지표 2개가 나온다 → 도구 종속 없이 가치 검증
Phase 2실패 세션 10~20건에 Judge 적용. 성공 세션을 라벨 없이 섞어 null 반환율 확인Judge–Human Agreement가 목표선을 넘음. 넘지 못하면 Phase 3으로 가지 않는다
Phase 3evaluator 등록, 상위 실패 원인 주간 집계 → 프롬프트·CLAUDE.md·스킬·툴 정책 개선지표 개선이 다음 주 데이터에서 확인됨

Phase 1의 설계 의도는 도구 없이도 성립하는 최소 가치를 먼저 확보하는 것입니다. 원본이 로컬 JSONL에 남으므로 LangSmith를 걷어내도 데이터는 남습니다.

11. 대안 비교

선택지강점이 목적에서의 약점
langsmith-cli SaaS
(경로 A)
두 에이전트 공식 지원. 설정 한 줄. 전문이 다 들어와 근거 판정에 유리. 조회·추출 CLI 완비redaction 지점이 없다. SaaS 종속
네이티브 OTel + Collector
(경로 B · Claude Code)
전송 전 마스킹 가능. OTLP 표준이라 백엔드를 갈아탈 수 있다. 플러그인 설치가 필요 없다traces가 beta. span 속성이 플러그인만큼 상세한지 미확인. Codex에는 span이 없어 적용 불가
LangSmith self-hosted경로 A의 장점 + 개인정보 문제 제거. 두 에이전트를 모두 덮는다운영 부담. < 0.16은 일부 기능 제한
Langfuse · Phoenix 등 OSS자체 호스팅 전제. OTLP를 받으므로 Claude Code는 경로 B로 그대로 붙는다Codex는 span이 없어 log event만 들어온다. langsmith-cli 수준의 에이전트 전용 조회·evaluator 도구가 없어 판정 파이프라인을 더 많이 자작해야 한다
Grafana Loki (기존 PPI 인프라)이미 운영 중. Codex의 log event는 형태가 맞아 수용 가능trace 트리를 표현하지 못해 Claude Code의 span·First Bad Turn 판정에는 부적합
로컬 JSONL + 스크립트추가 인프라 0. 개인정보 유출 0. 두 에이전트 모두 원본이 이미 디스크에 있다세션 간 비교·시각화가 없어 규모가 커지면 한계

결론. Phase 1은 로컬 JSONL + 스크립트로 시작합니다. 업로드를 켤 때는 Claude Code = 경로 B(Collector 경유), Codex = 범위 격리 또는 업로드 보류가 현재 제약에 가장 맞습니다. 개인정보 게이트를 self-hosted로 풀면 두 에이전트 모두 경로 A 단독으로 가도 됩니다.

12. 주의사항 · 미검증 항목

관련 문서