마지막 업데이트 2026-09-08
2026-09-08 · 대상: 로컬 Claude Code 2.1.263 · codex-cli 0.153.4 · 도구 검토: langsmith-cli
langsmith-cli가 Claude Code와 Codex를 공식 지원한다 — langsmith trace setup claude|codex 한 줄로 프롬프트·응답·툴 호출 전문이 LangSmith 프로젝트로 들어간다.왼쪽 두 칸(수집·저장)은 도구가 이미 해결한 문제입니다. 리스크는 판정과 산출에 몰려 있고, 그 검증은 LangSmith를 켜지 않고도 할 수 있습니다.
Hermes는 전용 수집 경로가 없어 이 도식에서 제외했습니다(2장). 개인정보 게이트는 업로드를 켤 때만 적용되며, 위 검증 레인은 로컬이라 해당되지 않습니다(4장).
에이전트 작업이 실패하면 눈에 보이는 것은 마지막 신호(테스트 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입니다.
consensus-loop).
판정: 조건부 적합. 수집 계층에서 편의성은 최선이지만 유일한 경로가 아닙니다 — Claude Code는 네이티브 OTel로 span도 내보냅니다(2026-09-08 공식 문서 확인). 판정 계층에서는 보조 도구입니다.
| 경로 | Claude Code | Codex |
|---|---|---|
| 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.interaction → llm_request/hook/tool → tool.execution. metrics·logs는 stable |
span 없음. [otel] 블록은 구조화 log event + metrics만 낸다(codex.user_prompt·codex.tool_decision·codex.tool_result·codex.api_request 등). exporter는 none/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는 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장) |
consensus-loop 문서는 "LangChain/LangSmith는 Claude Code 내부 호출을 추적할 수 없어 도입 가치가 낮다"고 기록했습니다. ① langsmith-cli의 trace setup이 그 전제를 깼고, ② Claude Code 자체가 OTel span을 내보내므로 LangSmith에 의존하지 않고도 추적이 가능합니다. 다만 그 문서의 두 번째 근거(핑퐁이 LLM 쪽은 이미 자체 trace가 있다)는 여전히 유효합니다 — 이 문서의 대상은 런타임 AI가 아니라 개발용 코딩 에이전트입니다. 둘을 같은 프로젝트에 섞지 않습니다.
아래 명령·경로는 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 Code | claude plugin marketplace add + claude plugin install | user-global 설정, 또는 --scope project 시 ./.claude/settings.local.json |
| Codex | codex plugin marketplace add | ~/.codex/config.toml + ~/.codex/langsmith.json |
로컬 사전 조건은 충족되어 있습니다 — claude plugin과 codex plugin 서브커맨드가 두 CLI에 모두 존재합니다(각 --help로 확인).
claude-code / codex입니다. PPI 작업과 개인 작업이 한 프로젝트에 섞이면 4장 게이트를 지킬 수 없으므로 --project를 반드시 명시합니다.
trace setup은 git 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장).
어느 방식이든 보존 기간과 워크스페이스 권한을 먼저 정합니다. 트레이스에는 코드 전문이 포함되므로 사실상 저장소 사본입니다.
first_bad_turn: 7이 성립하려면 "7"이 세션 사이에서 같은 의미여야 합니다. LangSmith가 저장하는 것은 run 트리이므로, 선형 인덱스를 뽑는 규칙을 먼저 고정해야 합니다. 이 규칙이 없으면 9장의 지표는 전부 비교 불가능해집니다.
turn.step 부번호를 붙인다. 병렬 툴 호출은 같은 step, 다른 slot.sub 접미사를 붙인다 — 별도 turn으로 세지 않는다.| Claude Code | Codex | |
|---|---|---|
| 파일 | ~/.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 |
turn_id가 원본에 있어 규칙 1을 그대로 적용할 수 있습니다. Claude Code는 promptId 기준으로 경계를 재구성해야 하고, 훅이 주입한 system 레코드(preventedContinuation·stopReason 포함)를 turn에서 제외하는 처리가 추가로 필요합니다. 로컬 세션 규모는 Claude Code 17개 프로젝트, Codex 453세션입니다.
모든 트레이스를 Judge에 보내는 설계는 비용으로 먼저 죽습니다. PPI에는 이미 확립된 반대 원칙이 있습니다 — 판정은 룰, LLM은 해석만(수업 로그 야간 트리아지). 여기에도 그대로 적용합니다.
핵심은 사람이 방향을 되돌린 지점이 First Bad Turn의 사실상 정답 라벨이라는 점입니다. 사용자가 에이전트의 진행 중간에 끼어들어 교정했다면, 잘못된 방향은 그 직전 turn에서 이미 정해져 있었습니다. 라벨링 비용이 0입니다.
| 시그널 | 원본 근거 | 의미 |
|---|---|---|
| 진행 중 사용자 개입 | 직전 assistant가 tool_use 진행 중인데 isMeta가 아닌 user 레코드 등장 / Codex는 task_complete 없이 새 task_started | 1순위 후보. 사람이 중단·교정했다 |
| 훅 차단 | 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
{
"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는 대상 에이전트와 다른 모델 계열로 고정한다 |
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.
로컬 스크립트로 판정이 안정된 뒤에 옮깁니다. 순서를 뒤집으면 프롬프트를 고칠 때마다 서버 왕복이 생깁니다.
# 룰 기반 후보 추출을 온라인 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 + 로컬 스크립트가 더 빠릅니다(추정, 실행 미검증).
| 지표 | 정의 | 읽는 법 |
|---|---|---|
| First Bad Turn Depth | 세션당 첫 잘못된 turn의 번호 | 낮을수록 초반에 헤맨다. 프롬프트·CLAUDE.md·AGENTS.md 문제를 시사 |
| Wasted Tool Calls | First Bad Turn 이후의 Read/Grep/Bash/Edit 수 | 낭비 토큰의 직접 대리 지표 |
| Premature Edit Rate | 첫 Edit가 첫 검증보다 앞선 세션 비율 | 룰로만 계산 가능 — Judge 없이 즉시 볼 수 있는 첫 지표 |
| Recovery Rate | 새 근거 등장 후 가설을 실제로 바꾼 비율 | 낮으면 확증 편향이 하네스 차원에서 강화되고 있다 |
| Human Intervention Rate | 사람이 진행 중 끼어든 세션 비율 | 가장 정직한 종합 지표. 6장 라벨과 같은 데이터 |
| Judge–Human Agreement | Judge 판정과 사람 개입 turn의 일치율 (±1 turn 허용) | 이 값이 낮으면 위 지표 전부를 신뢰할 수 없다. 도입 여부를 결정하는 관문 |
성공률(Success Rate) 단독은 쓰지 않습니다. 사람이 여러 번 교정해서 결국 성공한 세션과 한 번에 성공한 세션이 같은 값으로 집계됩니다.
| 단계 | 할 일 | 완료 기준 |
|---|---|---|
| Phase 0 | self-hosted vs 범위 격리 결정, 보존 기간·권한 정의 | 아동 데이터 세션이 클라우드로 나가지 않음이 구조적으로 보장됨 |
| Phase 1 | langsmith trace setup 적용. 동시에 로컬 JSONL에서 Premature Edit Rate·Human Intervention Rate만 계산 | LangSmith 없이도 지표 2개가 나온다 → 도구 종속 없이 가치 검증 |
| Phase 2 | 실패 세션 10~20건에 Judge 적용. 성공 세션을 라벨 없이 섞어 null 반환율 확인 | Judge–Human Agreement가 목표선을 넘음. 넘지 못하면 Phase 3으로 가지 않는다 |
| Phase 3 | evaluator 등록, 상위 실패 원인 주간 집계 → 프롬프트·CLAUDE.md·스킬·툴 정책 개선 | 지표 개선이 다음 주 데이터에서 확인됨 |
Phase 1의 설계 의도는 도구 없이도 성립하는 최소 가치를 먼저 확보하는 것입니다. 원본이 로컬 JSONL에 남으므로 LangSmith를 걷어내도 데이터는 남습니다.
| 선택지 | 강점 | 이 목적에서의 약점 |
|---|---|---|
| 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 단독으로 가도 됩니다.
langsmith-cli를 로컬에 설치하지 않았습니다. 3·8장 명령은 README(2026-09-08) 기준이며 실행 결과는 확인하지 않았습니다.trace get --full로 직접 확인해야 합니다. 4장 판단이 바뀔 수 있습니다.CLAUDE_CODE_ENHANCED_TELEMETRY_BETA), span 속성이 경로 A 플러그인만큼 상세한지 비교하지 않았습니다. 두 경로의 데이터 품질 대조가 Phase 0 과제입니다.codex exec는 OTel metrics를 내보내지 않고 codex mcp-server는 telemetry를 전혀 내보내지 않는다는 보고가 있습니다(openai/codex#12913). 비대화형 실행을 수집 대상에 넣으려면 먼저 확인해야 합니다.