예상 읽기 시간: 20~30분
Day 1에서는 에이전트를 모델 하나가 아니라 harness, 즉 실행 환경 전체로 봤습니다. Day 2에서는 도구를 함수가 아니라 계약(contract) 으로 봤고, Day 3에서는 컨텍스트를 실행 상태(state) 로 봤습니다. Day 4에서는 실행을 다시 읽을 수 있게 하는 관측 가능성(observability) 을 봤고, Day 5에서는 반복 가능한 워크플로와 열린 에이전트 판단의 경계를 나눴습니다. Day 6에서는 사람 개입을 예외가 아니라 위험도 기반 설계 표면으로 봤습니다.
오늘은 그 모든 것을 묶는 질문입니다.
에이전트가 일을 끝냈다는 말은 무엇으로 증명되는가?
답변 하나면 충분한가?
아니면 작업의 중간 사건, 산출물, 검증 결과, 다음 상태까지 남아야 하는가?
프레임워크를 처음 만들 때는 보통 대화 중심으로 생각합니다.
user message -> model response -> tool call -> final answer
이 구조는 데모에는 충분합니다. 하지만 실제 작업으로 들어가면 금방 부족해집니다. 블로그 글을 발행했다면 파일이 있어야 하고, 테스트 결과가 있어야 하고, 커밋이 있어야 하고, 배포 확인이 있어야 합니다. 로컬 운영 작업이라면 티켓 상태, 로그, 스크립트 출력, 실패 원인, 재시도 기준이 남아야 합니다.
그래서 오늘의 문장은 이겁니다.
에이전트 작업은 답변이 아니라 이벤트와 산출물의 수명주기로 설계해야 한다.
여기서 이벤트는 “무슨 일이 일어났는지”입니다. 산출물은 “그 일이 남긴 물건”입니다. 둘을 분리해야 에이전트가 실제로 일했는지, 어디서 실패했는지, 다음 실행이 무엇을 이어받아야 하는지 판단할 수 있습니다.
채팅 기반 에이전트는 자연스럽습니다. 사용자가 말하고, 에이전트가 답합니다. 그런데 작업이 길어질수록 대화만으로는 상태를 보존하기 어렵습니다.
예를 들어 이런 작업을 생각해봅시다.
SEOJing에 agent-framework Day 7 글을 작성하고,
포맷/린트/빌드를 통과시킨 뒤,
main에 커밋하고,
배포 URL을 확인해서 아침 브리핑에 넣어라.
최종 답변만 보면 이렇게 보일 수 있습니다.
완료했습니다. Day 7 글을 작성했고 검증도 통과했습니다.
하지만 운영 입장에서는 이 답변만으로는 부족합니다.
- 어떤 파일이 만들어졌는가?
- 포맷은 실제로 실행했는가?
- lint/build가 통과했는가, 아니면 생략했는가?
- 커밋에는 다른 파일이 섞이지 않았는가?
- push 후 origin/main과 로컬 HEAD가 같은가?
- 공개 URL은 실제로 열리는가, 아니면 배포 대기 중인가?
- 다음 08:00 브리핑은 어떤 근거를 인용해야 하는가?
이 질문에 답하지 못하면 “완료”라는 말은 운영 상태가 아니라 인상에 가깝습니다.
에이전트 프레임워크가 실제 업무를 맡으려면 답변을 잘 쓰는 것보다 먼저 해야 할 일이 있습니다.
작업의 흔적을 구조화한다.
이벤트와 산출물을 섞으면 시스템이 흐려집니다.
Post Q&A
에이전트 프레임워크 스터디 Day 7: 작업은 답변이 아니라 이벤트와 산출물로 남아야 한다 전체를 기준으로 질문과 피드백을 받아요.답을 본 뒤에는 이 내용을 댓글로 달아서 서징에게도 물어볼 수 있어요. 작성자가 직접 볼 수 있어요!
이벤트(event)
- 작업이 시작됨
- 파일을 생성함
- 테스트를 실행함
- 리뷰에서 지적을 받음
- 커밋을 만들었음
- 배포 확인이 실패함
- 사람이 승인함
산출물(artifact)
- 생성된 MDX 파일
- 테스트 로그
- 빌드 결과
- 커밋 SHA
- 배포 URL
- 스크린샷
- 리뷰 리포트
- 티켓 최종 보고서
이벤트는 시간의 흐름에 가깝고, 산출물은 참조 가능한 물건에 가깝습니다.
event는 "무슨 일이 있었는가"를 말한다.
artifact는 "그 일을 무엇으로 다시 확인할 수 있는가"를 말한다.
둘 중 하나만 남으면 문제가 생깁니다.
산출물만 남으면 맥락이 사라집니다.
파일은 있는데 왜 만들었는지 모른다.
로그는 있는데 어떤 요구사항 검증인지 모른다.
커밋은 있는데 어떤 티켓을 닫는지 모른다.
이벤트만 남으면 증거가 약합니다.
테스트를 실행했다고 적혀 있는데 로그가 없다.
배포를 확인했다고 적혀 있는데 URL과 응답이 없다.
리뷰를 통과했다고 적혀 있는데 어떤 diff를 봤는지 없다.
좋은 프레임워크는 둘을 같이 다룹니다.
Event: build started
Artifact: build log path
Event: build passed
Artifact: exit code, summary, generated route list
Event: final report saved
Artifact: ticket report id / markdown body
대화형 루프는 보통 이렇게 보입니다.
while not done:
call model
if tool_call:
run tool
else:
return answer
하지만 운영형 프레임워크에서는 이 루프만으로 부족합니다. 각 단계가 event stream으로 남아야 합니다.
RunStarted
request_id
user_goal
profile
risk_level
PlanCreated
steps
assumptions
no_touch_areas
ToolRequested
tool_name
input_schema_version
risk_level
approval_required
ToolCompleted
exit_code
output_artifact
redaction_applied
ArtifactCreated
type
path_or_url
owner
retention_policy
VerificationCompleted
command
result
evidence
RunCompleted
status
final_report
next_state
이렇게 보면 “답변 생성”은 실행의 마지막 이벤트 중 하나일 뿐입니다. 중요한 것은 실행이 어떤 상태 전이를 거쳤는지입니다.
대화 중심 관점:
사용자에게 무엇을 말할까?
이벤트 중심 관점:
시스템은 지금 어떤 상태이며, 무엇이 그 상태를 증명하는가?
이 차이는 큽니다. 특히 에이전트가 길게 일하거나, 중간에 끊기거나, 다른 프로필/작업자가 이어받아야 할 때 차이가 드러납니다.
티켓 하나를 예로 들어봅시다.
inbox -> in_progress -> review_required -> done
-> blocked
이건 단순한 칸반 보드가 아닙니다. 에이전트가 어떤 행동을 할 수 있는지 결정하는 상태 기계입니다.
inbox
- 아직 실행하지 않는다.
- 요구사항 정리가 필요할 수 있다.
in_progress
- 도구 실행과 파일 변경이 가능하다.
- 중간 산출물을 붙인다.
review_required
- 자동 진행을 멈춘다.
- 사용자/리뷰어/Notjing 판단이 필요하다.
blocked
- 더 실행하지 않는다.
- 빠진 입력, 권한, 외부 장애를 명시한다.
done
- 검증과 최종 보고가 있어야 한다.
- 이후에는 새 작업으로 이어받는다.
이 상태 전이가 없으면 에이전트는 대화 흐름에만 기대게 됩니다. 그러면 재시작 후 이런 일이 생깁니다.
- 이미 done인 작업을 다시 한다.
- blocked인데 계속 실행한다.
- review_required인데 다음 티켓을 시작한다.
- 사용자가 승인하지 않은 외부 side effect를 진행한다.
프레임워크 관점에서는 상태 전이가 곧 안전장치입니다.
상태가 없으면 기억에 기대고,
기억에 기대면 재시작과 병렬 작업에서 무너진다.
모든 산출물을 영원히 같은 장소에 두면 저장소가 쓰레기장이 됩니다. 반대로 아무것도 남기지 않으면 다음 작업이 매번 처음부터 시작합니다.
그래서 산출물에는 최소한 세 가지 속성이 필요합니다.
owner
- 이 산출물의 책임 주체는 누구인가?
- hub가 보관할 것인가, spoke가 버릴 것인가?
retention
- 영구 보존인가?
- 며칠 뒤 지워도 되는 임시 파일인가?
- 공개 블로그/문서로 승격될 것인가?
promotion rule
- 어떤 조건에서 더 durable한 위치로 옮기는가?
- 어떤 조건에서는 그냥 폐기하는가?
OkayJing식으로 말하면 이런 구분입니다.
facts/preferences -> memory
procedures -> skills
work state -> ticket / work ledger
conversations -> sessions
repeated execution -> cron
public learning artifact -> SEOJing post
raw temporary output -> cron output / temp artifact
이 구분은 깔끔한 정리를 위한 취향이 아닙니다. 다음 실행의 품질을 결정합니다.
예를 들어 “Day 7 글을 썼다”는 사실은 메모리에 넣으면 안 됩니다. 시간이 지나면 stale해지고, 특정 작업 상태일 뿐입니다. 대신 티켓 보고서와 Git history, 공개 URL에 남기면 됩니다.
반대로 “SEOJing scheduled publishing은 dirty canonical checkout에서 직접 stage하지 말고 origin/main worktree를 써야 한다”는 절차는 반복되는 운영 규칙이므로 스킬/운영 문서에 있어야 합니다.
산출물의 위치는 그 정보의 수명과 재사용 방식을 반영해야 한다.
작업형 에이전트 루프를 조금 더 현실적으로 쓰면 이렇습니다.
input: user_goal
1. classify_goal(goal)
- answer_only?
- local_read?
- reversible_write?
- external_side_effect?
2. create_or_attach_work_item(goal)
- ticket_id
- work_ledger_id if needed
- session_id
3. plan_with_artifact_targets()
- expected_files
- expected_logs
- expected_verification
- expected_final_report
4. execute_steps()
- emit events
- create artifacts
- attach evidence
5. verify_artifacts()
- do files exist?
- do checks pass?
- does diff match scope?
- does public route respond if needed?
6. close_or_block()
- done with report
- blocked with missing input
- review_required with exact question
핵심은 3번입니다. 좋은 프레임워크는 실행 전에 “어떤 산출물이 남아야 완료인가”를 압니다.
나쁜 실행 계획은 이렇게 말합니다.
글을 작성한다.
검증한다.
보고한다.
조금 더 나은 계획은 이렇게 말합니다.
- apps/web/content/study/agent-framework/day7.mdx를 생성한다.
- Prettier로 해당 파일을 포맷한다.
- pnpm format:check / lint / build 로그를 남긴다.
- git diff --check로 whitespace를 확인한다.
- commit에는 day7.mdx만 포함한다.
- push 후 origin/main과 HEAD 일치를 확인한다.
- 공개 URL 또는 배포 대기 상태를 티켓 보고서에 남긴다.
두 계획의 차이는 실행 가능성입니다. 전자는 말로는 맞지만 검증 지점이 흐립니다. 후자는 자동화와 재시작에 강합니다.
모델 컨텍스트는 휘발성입니다. 길어지면 압축되고, 세션이 바뀌면 사라지고, 다른 프로필은 볼 수 없습니다.
그래서 중요한 실행 흔적은 모델 컨텍스트 밖에 있어야 합니다.
- SQLite ticket DB
- cron output files
- git commits
- local markdown reports
- workflow trace DB
- dashboard state JSON
- session store
이것은 “저장 많이 하기”가 아닙니다. 저장의 목적은 다음 실행에서 같은 판단을 반복하지 않게 하는 것입니다.
좋은 저장:
- 다음 실행의 시작 시간을 줄인다.
- 검증 근거를 다시 찾을 수 있다.
- 실패 원인을 분류할 수 있다.
- 반복 워크플로를 스킬/스크립트로 승격할 수 있다.
나쁜 저장:
- 임시 진행상황을 메모리에 넣는다.
- 원문 로그를 끝없이 쌓지만 검색/승격 기준이 없다.
- hub에 spoke의 모든 중간 대화를 흡수한다.
- 검증 없는 요약을 사실처럼 재사용한다.
에이전트 프레임워크가 강해지려면 “무엇을 저장할까”보다 “저장된 것이 다음 실행에서 어떤 판단을 개선하는가”가 더 중요합니다.
실제 작업은 하나의 로그가 아니라 그래프에 가깝습니다.
User Request
-> Ticket
-> Work Item
-> Session
-> File Diff
-> Test Log
-> Review Finding
-> Commit
-> Public URL
-> Final Report
각 노드는 다른 시스템에 살 수 있습니다.
Ticket: SQLite
Session: state.db / transcript
File Diff: git
Test Log: terminal output / CI
Review Finding: OCR / CodeRabbit / Notjing report
Public URL: deployed site
Final Report: ticket report / morning briefing context
이 그래프를 만들면 좋은 점이 있습니다.
첫째, 재시작이 쉬워집니다.
세션이 끊겨도 티켓과 산출물로 이어받을 수 있다.
둘째, 병렬 작업이 안전해집니다.
hub는 계약과 결과만 보고,
spoke의 전체 중간 대화를 흡수하지 않아도 된다.
셋째, 나중에 학습 데이터 후보를 만들 수 있습니다.
성공한 반복 워크플로의 event/artifact 패턴을 모으면
스킬 개선, 평가셋, 로컬 정책 모델 후보가 된다.
여기서 중요한 제한도 있습니다. 도구 실행 자체나 승인 우회 같은 위험한 행동을 모델 가중치에 넣으면 안 됩니다. 학습 후보가 될 수 있는 것은 안정적인 판단 패턴과 보고 스타일, 라우팅 기준 정도입니다. 실행과 권한은 계속 deterministic runtime에 남아야 합니다.
성공 산출물만 남기면 프레임워크는 똑똑해지지 않습니다. 실패도 구조화되어야 합니다.
예를 들어 빌드가 실패했다면 이렇게 남기는 것이 좋습니다.
FailureEvent
type: build_failed
command: pnpm build
exit_code: 1
scope: new_mdx_route
suspected_cause: frontmatter parse error
evidence: build log excerpt
recovery: fixed date format and reran build
final_status: recovered
이렇게 남기면 다음에 비슷한 실패가 왔을 때 단순히 “빌드 실패”가 아니라 패턴을 볼 수 있습니다.
- MDX frontmatter 문제인가?
- dependency 설치 문제인가?
- 기존 repo가 dirty/behind인 문제인가?
- public route probe가 배포 완료 전에 너무 빨랐는가?
- 승인 scanner 때문에 cron에서 명령이 막혔는가?
실패 분류가 쌓이면 스킬이 좋아집니다. 반대로 실패를 최종 답변에서만 짧게 언급하고 사라지게 하면 다음 실행은 다시 같은 구덩이에 빠집니다.
에이전트 UI를 만들 때도 이 관점이 중요합니다. 단순 채팅 UI는 최종 메시지를 중심으로 설계됩니다. 하지만 작업형 UI는 상태와 산출물을 중심으로 설계되어야 합니다.
채팅 UI 중심
- 메시지 목록
- 입력창
- 도구 실행 로그 일부
- 최종 답변
작업 UI 중심
- 현재 작업자/세션
- 연결된 티켓/워크아이템
- 생성/수정 파일
- 검증 결과
- 승인 대기 항목
- 산출물 미리보기
- 다음 상태 전이 버튼
OkayJing Local 같은 운영 UI가 Discord를 대체하려면 단순히 예쁜 채팅창이 아니라 이런 표면이 필요합니다.
Office: 누가/무엇이 일하고 있는가
Sessions: 대화와 실행 흐름은 어디에 연결되는가
Code: 파일, diff, 커밋, 체크포인트는 무엇인가
Artifacts: 보고서, URL, 이미지, 문서, 로그는 어디에 있는가
Approvals: 어떤 위험도 때문에 멈췄는가
이 구조가 있어야 사용자가 “지금 무슨 일이 일어나는지”를 대화 추측이 아니라 화면의 상태로 볼 수 있습니다.
에이전트 프레임워크를 직접 설계한다고 가정하면, event/artifact lifecycle에 대해 최소한 이 질문들을 해야 합니다.
Run
- 실행 하나를 식별하는 run_id가 있는가?
- parent run / child run을 연결할 수 있는가?
- 중단 후 이어받을 수 있는가?
Event
- 각 도구 호출과 상태 전이가 이벤트로 남는가?
- 사람 승인/거절도 이벤트로 남는가?
- 실패 이벤트가 분류되는가?
Artifact
- 파일, 로그, URL, 스크린샷, 보고서가 참조 가능한가?
- owner와 retention이 있는가?
- public/private 경계가 있는가?
Verification
- 완료 조건이 산출물로 검증되는가?
- 검증 생략은 이유와 함께 남는가?
- 최종 답변이 검증보다 앞서지 않는가?
Promotion
- 반복되는 성공 패턴을 스킬/스크립트/평가셋으로 승격하는가?
- 일회성 작업 상태를 메모리에 넣지 않는가?
- hub가 spoke 전체 맥락을 흡수하지 않고 결과와 규칙만 흡수하는가?
이 질문들이 없으면 프레임워크는 “말은 잘하는데 운영이 약한” 상태에 머뭅니다.
아주 단순한 자료구조로 시작해도 됩니다.
type Run = {
id: string;
goal: string;
status: "running" | "blocked" | "review_required" | "done" | "failed";
startedAt: string;
endedAt?: string;
parentRunId?: string;
ticketId?: string;
};
type Event = {
id: string;
runId: string;
type: string;
at: string;
summary: string
처음부터 거대한 tracing platform을 붙일 필요는 없습니다. 중요한 것은 프레임워크의 내부 언어가 “답변”에서 “상태 전이와 산출물”로 바뀌는 것입니다.
나중에 필요하면 이 구조는 OpenTelemetry, LangSmith, Langfuse, local SQLite, MCP resources, A2A artifacts 같은 표준으로 연결할 수 있습니다. 하지만 시작점은 작아도 됩니다.
작은 local schema
+ 정확한 상태 전이
+ 검증 가능한 산출물
+ 반복 패턴 승격
이 조합이 프레임워크를 실제 업무 시스템으로 만듭니다.
오늘 본 내용은 한 줄로 줄이면 이렇습니다.
에이전트의 결과물은 final answer가 아니라
event + artifact + verification + next state다.
조금 더 풀면 이렇습니다.
1. 채팅 답변만으로는 작업 완료를 증명하기 어렵다.
2. 이벤트는 무슨 일이 있었는지, 산출물은 무엇으로 확인할 수 있는지를 말한다.
3. 작업은 상태 전이로 설계해야 재시작과 병렬 실행에 강해진다.
4. 산출물에는 owner, retention, promotion rule이 필요하다.
5. 실패도 구조화된 artifact로 남겨야 반복 개선이 가능하다.
6. UI도 메시지 중심이 아니라 작업 상태와 산출물 중심으로 가야 한다.
7. 반복 성공 패턴은 스킬/스크립트/평가셋 후보가 되지만, 권한과 도구 실행은 runtime에 남아야 한다.
이 관점은 OkayJing 같은 개인 운영 에이전트에 특히 중요합니다. 개인 작업은 회사 시스템보다 더 다양한 형태로 흩어집니다. Discord 대화, 로컬 티켓, SEOJing 글, git diff, LMS 일정, Google Calendar, cron output, Mac mini 서비스 로그가 모두 섞입니다.
그래서 프레임워크가 할 일은 하나입니다.
흩어진 실행 흔적을
검증 가능한 작업 그래프로 묶는다.
다음 글에서는 이 그래프를 더 확장해서, 에이전트 간 협업과 handoff를 어떻게 설계해야 하는지 보겠습니다. 단순히 “여러 에이전트를 만든다”가 아니라, 어떤 정보와 권한과 산출물이 넘어가야 안전한 handoff가 되는지 reverse-engineering할 겁니다.