에이전트 프레임워크 스터디 Day 13: Trace는 로그가.
에이전트 시스템에서 trace를 콘솔 로그나 비용 기록으로만 보면 왜 부족한지, 사용자 요청부터 도구 호출, 산출물, 검증, 다음 실행 개선까지 이어지는 증거 사슬로 어떻게 설계해야 하는지 reverse-engineering합니다.
예상 읽기 시간: 20~30분
Day 12에서는 Agent Card를 에이전트 소개문이 아니라 실행 가능한 경계선으로 봤습니다. 어떤 일을 맡길 수 있는지, 입력과 산출물은 무엇인지, 어떤 권한과 실패 모드를 갖는지 적어야 다른 런타임이 책임을 넘길 수 있다는 이야기였습니다.
오늘은 그 다음 질문입니다.
책임을 넘겼다.
작업이 끝났다고 한다.
그런데 우리는 무엇을 보고 믿을 수 있을까?
여기서 필요한 것이 trace입니다. 다만 trace를 단순 로그로 보면 부족합니다. 로그는 “무슨 일이 있었는지”를 남깁니다. 에이전트 프레임워크에서 trace는 한 단계 더 나아가야 합니다.
Trace는 실행 기록이 아니라, 다음 실행을 바꾸기 위해 보존되는 증거 사슬이다.
오늘 글은 LangSmith, OpenTelemetry, OpenAI tracing 같은 제품을 비교하는 글이 아닙니다. 직접 에이전트 프레임워크를 만든다고 생각하고, trace가 어떤 정보를 어떤 모양으로 남겨야 실제로 신뢰와 개선에 쓰이는지 역으로 뜯어봅니다.
일반적인 서버 로그는 대략 이렇게 생깁니다.
04:00 job started
04:01 fetched repo
04:04 generated file
04:06 build passed
04:07 pushed commit
이 정도만 있어도 사람이 대충 흐름을 읽을 수 있습니다. 하지만 에이전트 작업에서는 중요한 질문이 더 많습니다.
사용자가 실제로 무엇을 요청했나?
그 요청에서 어떤 성공 기준을 뽑았나?
어떤 파일을 읽고 어떤 파일은 건드리지 않았나?
도구 출력 중 어떤 부분을 근거로 삼았나?
실패한 명령은 있었나?
실패를 어떻게 해석했나?
검증은 실제로 실행됐나, 아니면 말로만 통과했다고 했나?
최종 보고는 검증 결과와 일치하나?
이번 실행에서 다음에 재사용할 만한 절차가 생겼나?
콘솔 로그만 있으면 이 질문에 답하기 어렵습니다. 특히 에이전트는 자연어 판단과 도구 실행이 섞입니다. 사람이 보기에는 “작업 완료”처럼 보이지만, 실제로는 이런 빈틈이 생길 수 있습니다.
- 파일은 썼지만 build를 돌리지 않음
- build는 돌렸지만 다른 디렉터리에서 돌림
- 테스트 실패를 unrelated로 판단했지만 근거가 없음
- 외부 리뷰가 High issue를 냈는데 최종 보고에서 빠짐
- 세션 안에서는 맥락이 있었지만 다음 세션이 이어받을 증거가 없음
그래서 에이전트용 trace는 단순 stdout 모음이 아니라, 판단 가능한 실행 사슬이어야 합니다.
Post Q&A
에이전트 프레임워크 스터디 Day 13: Trace는 로그가 아니라 다음 실행을 바꾸는 증거 사슬이다 전체를 기준으로 질문과 피드백을 받아요.답을 본 뒤에는 이 내용을 댓글로 달아서 서징에게도 물어볼 수 있어요. 작성자가 직접 볼 수 있어요!
가장 단순한 trace 모델은 이벤트 나열입니다.
Event 1: user_message
Event 2: tool_call
Event 3: tool_result
Event 4: assistant_message
이것도 필요합니다. 하지만 이것만 있으면 “무엇이 바뀌었는지”가 흐릿합니다. 프레임워크 설계에서는 각 이벤트가 어떤 상태 전환을 만들었는지 함께 봐야 합니다.
Request
-> Interpreted Goal
-> Planned Scope
-> Evidence Read
-> Action Taken
-> Verification Run
-> Result Reported
-> Reusable Learning Candidate
이 흐름에서 trace가 잡아야 하는 것은 단순히 “도구를 호출했다”가 아닙니다.
도구 호출 전 상태:
- 어떤 가정이 있었나?
- 어떤 파일/리소스를 확인해야 했나?
- 어떤 위험이 있었나?
도구 호출 후 상태:
- 무엇이 사실로 확인됐나?
- 무엇이 틀렸나?
- 다음 행동이 어떻게 바뀌었나?
예를 들어 pnpm build를 실행한 이벤트는 이렇게 기록될 수 있습니다.
event: verification_command
command: pnpm build
scope: SEOJing web app
reason: new MDX route and content tree generation must compile
exit_code: 0
result: pass
state_change:
before: post written but route/build unverified
after: local production build verified
이렇게 적어야 나중에 “build를 왜 돌렸고, 무엇을 증명했나”를 재사용할 수 있습니다.
에이전트 실행에서 섞이면 안 되는 증거가 있습니다.
1. 사용자 의도 증거
2. 외부/로컬 source 증거
3. 실행 증거
4. 검증 증거
각각의 성격이 다릅니다.
이것은 “왜 이 일을 했는가”입니다.
사용자 요청:
- 04:00 run에서 agent-framework study post를 실제로 작성하고 push한다.
- backend series는 Day 12로 제한한다.
- LMS는 방학 중이므로 크롤링하지 않는다.
- 새 기술은 중복 상태를 갱신하고 반복 노출하지 않는다.
이 증거가 없으면 나중에 “왜 push까지 했지?”가 불명확해집니다. 반대로 이 증거가 있으면 push가 임의 행동이 아니라 standing approval 범위였는지 판별할 수 있습니다.
source 증거는 작업자가 읽은 외부/로컬 근거입니다.
- repo status
- 기존 Day 12 글
- Agent Framework Evolution Standards
- briefing_seen.json
- package scripts
- ticket 상태
중요한 점은 source와 결론을 분리하는 것입니다. 예를 들어 “Day 13을 써야 한다”는 결론은 source가 아닙니다. source는 “origin/main에 day12.mdx가 있다”입니다.
실행 증거는 실제 변화입니다.
- apps/web/content/study/agent-framework/day13.mdx 생성
- cover metadata 삽입
- inline visual asset 추가
- git commit 생성
- origin main push
여기서도 범위가 중요합니다. 에이전트가 “새 글을 작성했다”고 보고하려면 어떤 파일이 실제로 바뀌었는지 trace에 남아야 합니다. 특히 로컬 작업 트리가 더러울 때는 staged scope와 pushed scope가 다를 수 있습니다.
검증 증거는 “결과를 믿어도 되는 이유”입니다.
- prettier check
- lint
- build
- content tree generation
- git diff --check
- origin/main HEAD 일치 확인
- public route readback
검증은 실행과 다릅니다. 파일을 썼다는 사실은 검증이 아닙니다. commit이 생겼다는 사실도 검증이 아닙니다. 검증은 “그 산출물이 의도한 조건을 만족하는지 확인한 행위”입니다.
outcome만으로는 부족하다많은 시스템은 실행 결과를 이렇게 저장합니다.
{
"task": "publish study post",
"status": "success"
}
성공/실패는 필요하지만 충분하지 않습니다. 다음 실행을 바꾸려면 최소한 이런 정보가 더 있어야 합니다.
{
"workflow_key": "seojing-agent-framework-study-publish",
"outcome": "success",
"scope": {
"repo": "SEOJing",
"branch": "main",
"files": ["apps/web/content/study/agent-framework/day13.mdx"]
},
"verification": {
"format": "pass",
"lint": "pass",
"build": "pass",
"public_route": "pass"
},
"lessons": [
"dirty canonical checkout requires isolated origin/main worktree",
이렇게 남겨야 나중에 세 가지 일이 가능해집니다.
1. 같은 workflow가 반복 성공했는지 센다.
2. 실패가 반복되는 지점을 찾는다.
3. skill/checklist/eval로 승격할 만한 절차를 고른다.
여기서 중요한 점은 “workflow compilation”을 너무 빨리 모델 학습으로 해석하지 않는 것입니다. 반복 trace가 많아졌다는 말은 곧바로 fine-tuning을 하라는 뜻이 아닙니다.
반복 trace
-> checklist 개선
-> skill/reference 강화
-> 작은 eval set 생성
-> 필요하면 local policy/router 데이터셋
-> 훨씬 나중에 adapter/fine-tune 검토
도구 실행, 권한, 검증은 계속 deterministic runtime에 남아야 합니다. 모델이 배워도 되는 것은 안정적인 판단 패턴이지, push 같은 side effect 자체가 아닙니다.
에이전트 작업은 텍스트 답변으로 끝나지 않습니다. 중간과 끝에 여러 artifact가 생깁니다.
- draft file
- patch
- screenshot
- generated image
- ticket report
- build log
- public URL
- review finding
- workflow trace
이 artifact들이 trace 밖에 있으면 다음 세션이 이어받기 어렵습니다. “어제 뭘 했더라?”를 사람이 기억해야 합니다.
에이전트 프레임워크에서는 artifact를 이런 식으로 다루는 편이 낫습니다.
그림: trace를 로그 저장소가 아니라 request, evidence, action, artifact, verification, improvement를 잇는 순환 구조로 본다.
중요한 것은 artifact가 단순 첨부물이 아니라는 점입니다. artifact는 다음 판단의 입력입니다.
draft file
-> reviewer가 읽을 입력
build log
-> 실패 원인 분류의 입력
public URL
-> 배포 검증의 입력
ticket report
-> 다음 세션 handoff의 입력
workflow trace
-> skill/eval 후보 판단의 입력
그래서 artifact에는 최소한 다음 정보가 붙어야 합니다.
artifact_id
kind: draft | patch | image | report | log | url | trace
path_or_url
created_by
source_task
verification_status
freshness
safe_to_share
특히 safe_to_share는 중요합니다. 로컬 로그나 세션 원문에는 민감한 경로, 토큰 모양 문자열, 개인 일정이 섞일 수 있습니다. 모든 artifact가 블로그나 Discord에 그대로 올라가도 되는 것은 아닙니다.
여기서 흔한 함정은 처음부터 완벽한 observability platform을 만들려고 하는 것입니다.
- 모든 tool call을 OTEL span으로 변환
- 비용/latency/token을 전부 수집
- UI 대시보드 제작
- replay 기능
- evaluation harness
- prompt diff viewer
이 방향은 나쁘지 않습니다. 하지만 작은 개인 agent framework에서는 처음부터 너무 큽니다. 더 좋은 시작점은 반복 workflow 단위의 작은 trace입니다.
workflow_trace
- key
- title
- source_ref
- loaded_skills
- input_contract
- changed_artifacts
- verification_json
- outcome
- failure_classification
- reusable_rule_candidate
이 정도만 있어도 충분히 쓸모가 있습니다.
- 04:00 publish가 몇 번 성공했나?
- 실패는 주로 install, lint, route probe, public deploy 중 어디서 났나?
- 어떤 workflow는 이미 skill로 승격할 만큼 반복됐나?
- 어떤 report style이 8AM briefing에서 중복을 줄였나?
즉, 처음부터 “관측 플랫폼”이 아니라 “다음 실행을 덜 틀리게 만드는 기록”이면 됩니다.
Trace가 강해지면 모든 것을 memory에 넣고 싶어집니다. 하지만 이것도 위험합니다.
memory:
- 오래 유지되는 사용자 선호
- 환경의 안정적 사실
- 반복적으로 필요한 규칙
trace:
- 특정 실행의 입력/출력/검증
- 특정 날짜의 실패/성공
- artifact와 로그
- 다음 개선 후보
예를 들어 이런 것은 memory가 아닙니다.
2026-06-23에 Day 13을 push했다.
commit SHA는 ...
이번 build는 13초 걸렸다.
오늘 OCR이 unavailable이었다.
이것들은 trace나 ticket report에 남기는 편이 맞습니다. 반대로 이런 것은 memory나 skill 후보가 될 수 있습니다.
SEOJing scheduled publish는 canonical checkout이 dirty면 origin/main worktree에서 한다.
content-only MDX publish에서 OCR이 unsupported_ext면 content review gate로 대체한다.
새 글은 cover/inline visual suitability step을 명시적으로 보고한다.
Trace는 memory를 대체하지 않습니다. Memory가 trace를 대체하지도 않습니다. 둘의 관계는 이렇게 보는 편이 안전합니다.
trace many times
-> repeated pattern confirmed
-> skill/reference/memory candidate
-> human-readable operating rule
에이전트 운영에서는 실패를 숨기면 다음 실행이 더 나빠집니다. 실패 trace는 특히 다음 정보를 남겨야 합니다.
failure_point:
- discovery
- tool execution
- dependency install
- verification
- external service
- permission/auth
- report/delivery
recoverability:
- retried and succeeded
- blocked on user/browser auth
- safe to retry later
- requires code/config change
blast_radius:
- no file changed
- local draft changed only
- committed but not pushed
- external side effect happened
예를 들어 public route probe가 실패했을 때, 단순히 “배포 실패”라고 쓰면 안 됩니다.
route probe 404 immediately after push
-> GitHub Actions deploy not complete yet일 수 있음
-> CI run을 먼저 poll해야 함
이런 실패 trace가 쌓이면 다음 workflow가 바뀝니다.
old:
push -> curl route -> 404면 실패
new:
push -> GitHub Actions deploy 완료까지 poll -> browser-like GET -> title readback
이 변화가 바로 trace의 가치입니다. 과거의 실패가 다음 실행의 순서를 바꿉니다.
에이전트 프레임워크를 직접 만든다면 trace는 어디에 붙어야 할까요?
너무 바깥에 있으면 단순 로그 수집기가 됩니다.
Agent Runtime -> stdout -> log collector
너무 안쪽에만 있으면 사용자/티켓/산출물과 연결되지 않습니다.
LLM call span only
실제로 필요한 위치는 runtime 전체를 가로지르는 층입니다.
User Request
-> Planner / Goal Interpreter
-> Context Builder
-> Tool Dispatcher
-> Artifact Store
-> Verification Gate
-> Reporter
-> Memory/Skill Candidate Extractor
Trace Layer
records: intent, evidence, actions, artifacts, verification, outcome
이 trace layer는 LLM 호출만 보는 것이 아니라, LLM이 무엇을 근거로 도구를 썼고 그 결과를 어떻게 보고했는지까지 연결해야 합니다.
그래야 이런 질문에 답할 수 있습니다.
이 보고서의 “pass”는 어떤 command result에서 왔나?
이 ticket done은 어떤 artifact를 근거로 했나?
이 memory write는 어떤 반복 trace에서 승격됐나?
이 external publish는 어떤 standing approval 아래 실행됐나?
OkayJing 관점에서 trace는 이미 여러 조각으로 존재합니다.
- sessions: 대화 흐름
- hermes-ticket: 작업 상태와 결과 보고
- cron output: scheduled run 결과
- work-ledger: spoke 계약과 artifact pointer
- git history: 코드/콘텐츠 변경
- workflow traces: 반복 workflow 후보
- skills: 검증된 절차의 압축본
문제는 이 조각들이 늘 하나의 증거 사슬로 읽히지는 않는다는 점입니다. 그래서 OkayJing의 방향은 “새 observability 플랫폼 설치”보다 먼저 다음에 가깝습니다.
1. 중요한 workflow마다 source_ref를 남긴다.
2. 결과 보고에 실제 검증 command를 붙인다.
3. 반복 성공/실패를 workflow trace로 기록한다.
4. trace가 쌓이면 skill/reference/eval로 승격한다.
5. memory에는 오래갈 규칙만 남긴다.
이렇게 하면 04:00 Dreaming, 08:00 briefing, SEOJing publish, Notjing gate, Local 서식지 worker run 같은 루틴이 같은 언어로 정리됩니다.
무슨 요청이었나?
어떤 증거를 읽었나?
무엇을 바꿨나?
어떻게 검증했나?
무엇을 다음 실행 규칙으로 흡수할까?
이 다섯 질문이 trace의 핵심입니다.
SEOJing 글 발행 workflow를 trace-first로 보면 이런 pseudo-flow가 됩니다.
run_publish_workflow(task):
trace.start(workflow_key="seojing-agent-framework-study-publish")
trace.record_intent(task.standing_approval)
repo_state = git.status()
trace.record_source("repo_state", repo_state)
if repo_state.has_unrelated_changes:
worktree = create_worktree("origin/main")
trace.record_decision("use isolated worktree", reason="protect unrelated local work")
next_day = inspect_existing_posts()
trace.record_source("latest_post", next_day - 1)
draft = write_mdx(next_day)
trace.record_artifact("draft", draft.path)
visual = run_visual_suitability_check(draft)
trace.record_decision("visual", visual.status)
checks = run(["prettier", "format:check", "lint", "build"])
trace.record_verification(checks)
if checks.pass:
commit = git.commit(scope=draft.files)
push = git.push("origin", "HEAD:main")
trace.record_artifact("commit", commit.id)
trace.record_external_effect("push", push.status)
else:
trace.fail(classification="verification_failure")
return
route = probe_public_url(next_day)
trace.record_verification("public_route", route.status)
trace.finish(outcome="success")
이 pseudo-flow의 포인트는 자동화를 많이 한다는 것이 아닙니다. 각 단계가 “나중에 읽을 수 있는 증거”를 남긴다는 점입니다.
Trace를 로그로만 보면 에이전트 운영은 계속 불안합니다.
로그:
- 어떤 일이 있었는지 보여준다.
trace:
- 왜 그 일이 필요했는지
- 무엇을 근거로 판단했는지
- 어떤 산출물이 생겼는지
- 검증은 실제로 무엇을 증명했는지
- 다음 실행 규칙이 어떻게 바뀌어야 하는지 보여준다.
에이전트 프레임워크를 설계할 때 trace는 사후 디버깅 기능이 아닙니다. 처음부터 runtime의 중심 계약입니다.
Agent Card가 “맡길 수 있는 경계”라면,
Trace는 “맡긴 뒤 믿고 이어받을 수 있는 증거”다.
다음 글에서는 이 증거 사슬을 바탕으로, workflow와 agent를 어떻게 분리해야 하는지 더 정리해볼 수 있습니다. 반복 가능한 일은 deterministic workflow로 두고, 어디부터 열린 agent loop로 넘길지 정하는 문제가 프레임워크 설계의 핵심이 되기 때문입니다.
에이전트가 서로 협업하려면 "무엇을 잘한다"는 소개가 아니라 능력, 입력, 산출물, 권한, 실패 모드, 사람 개입 조건을 적은 실행 가능한 경계선이 필요하다는 점을 reverse-engineering합니다.