LogoSEO Jing
  • All Posts
  • SEO Jing
  • okayJing
  • KD Team
  • CLab CoreTeam
  • Study

Contact Me

© 2026 SEOJing. All rights reserved.

에이전트 프레임워크AI 개발ArtifactEventDurable StateWorkflowObservability시스템 설계

에이전트 프레임워크 스터디 Day 7: 작업은 답변이 아니라 이벤트와 산출물로 남아야 한다

2026년 6월 17일·18분 읽기

예상 읽기 시간: 20~30분

오늘의 목표

Day 1에서는 에이전트를 모델 하나가 아니라 harness, 즉 실행 환경 전체로 봤습니다. Day 2에서는 도구를 함수가 아니라 계약(contract) 으로 봤고, Day 3에서는 컨텍스트를 실행 상태(state) 로 봤습니다. Day 4에서는 실행을 다시 읽을 수 있게 하는 관측 가능성(observability) 을 봤고, Day 5에서는 반복 가능한 워크플로와 열린 에이전트 판단의 경계를 나눴습니다. Day 6에서는 사람 개입을 예외가 아니라 위험도 기반 설계 표면으로 봤습니다.

오늘은 그 모든 것을 묶는 질문입니다.

text
에이전트가 일을 끝냈다는 말은 무엇으로 증명되는가?
답변 하나면 충분한가?
아니면 작업의 중간 사건, 산출물, 검증 결과, 다음 상태까지 남아야 하는가?

프레임워크를 처음 만들 때는 보통 대화 중심으로 생각합니다.

text
user message -> model response -> tool call -> final answer

이 구조는 데모에는 충분합니다. 하지만 실제 작업으로 들어가면 금방 부족해집니다. 블로그 글을 발행했다면 파일이 있어야 하고, 테스트 결과가 있어야 하고, 커밋이 있어야 하고, 배포 확인이 있어야 합니다. 로컬 운영 작업이라면 티켓 상태, 로그, 스크립트 출력, 실패 원인, 재시도 기준이 남아야 합니다.

그래서 오늘의 문장은 이겁니다.

에이전트 작업은 답변이 아니라 이벤트와 산출물의 수명주기로 설계해야 한다.

여기서 이벤트는 “무슨 일이 일어났는지”입니다. 산출물은 “그 일이 남긴 물건”입니다. 둘을 분리해야 에이전트가 실제로 일했는지, 어디서 실패했는지, 다음 실행이 무엇을 이어받아야 하는지 판단할 수 있습니다.


1. 채팅 답변만 남는 시스템의 한계

채팅 기반 에이전트는 자연스럽습니다. 사용자가 말하고, 에이전트가 답합니다. 그런데 작업이 길어질수록 대화만으로는 상태를 보존하기 어렵습니다.

예를 들어 이런 작업을 생각해봅시다.

text
SEOJing에 agent-framework Day 7 글을 작성하고,
포맷/린트/빌드를 통과시킨 뒤,
main에 커밋하고,
배포 URL을 확인해서 아침 브리핑에 넣어라.

최종 답변만 보면 이렇게 보일 수 있습니다.

text
완료했습니다. Day 7 글을 작성했고 검증도 통과했습니다.

하지만 운영 입장에서는 이 답변만으로는 부족합니다.

text
- 어떤 파일이 만들어졌는가?
- 포맷은 실제로 실행했는가?
- lint/build가 통과했는가, 아니면 생략했는가?
- 커밋에는 다른 파일이 섞이지 않았는가?
- push 후 origin/main과 로컬 HEAD가 같은가?
- 공개 URL은 실제로 열리는가, 아니면 배포 대기 중인가?
- 다음 08:00 브리핑은 어떤 근거를 인용해야 하는가?

이 질문에 답하지 못하면 “완료”라는 말은 운영 상태가 아니라 인상에 가깝습니다.

에이전트 프레임워크가 실제 업무를 맡으려면 답변을 잘 쓰는 것보다 먼저 해야 할 일이 있습니다.

text
작업의 흔적을 구조화한다.

2. 이벤트와 산출물은 다르다

이벤트와 산출물을 섞으면 시스템이 흐려집니다.

Post Q&A

오케이징에게 물어보기

에이전트 프레임워크 스터디 Day 7: 작업은 답변이 아니라 이벤트와 산출물로 남아야 한다 전체를 기준으로 질문과 피드백을 받아요.답을 본 뒤에는 이 내용을 댓글로 달아서 서징에게도 물어볼 수 있어요. 작성자가 직접 볼 수 있어요!

0/500

포스트 목록

/study/agent-framework
파일 7개, 폴더 0개
에이전트 프레임워크 스터디 Day 1: 프레임워크보다 먼저 실행 환경을 설계하기에이전트 프레임워크 스터디 Day 2: 도구는 함수가 아니라 계약이다에이전트 프레임워크 스터디 Day 3: 컨텍스트는 자료 더미가 아니라 실행 상태다에이전트 프레임워크 스터디 Day 4: 관측 가능해야 에이전트가 개선된다에이전트 프레임워크 스터디 Day 5: 워크플로와 에이전트의 경계를 나누는 법에이전트 프레임워크 스터디 Day 6: 사람 개입은 예외가 아니라 설계 표면이다에이전트 프레임워크 스터디 Day 7: 작업은 답변이 아니라 이벤트와 산출물로 남아야 한다
text
이벤트(event)
- 작업이 시작됨
- 파일을 생성함
- 테스트를 실행함
- 리뷰에서 지적을 받음
- 커밋을 만들었음
- 배포 확인이 실패함
- 사람이 승인함

산출물(artifact)
- 생성된 MDX 파일
- 테스트 로그
- 빌드 결과
- 커밋 SHA
- 배포 URL
- 스크린샷
- 리뷰 리포트
- 티켓 최종 보고서

이벤트는 시간의 흐름에 가깝고, 산출물은 참조 가능한 물건에 가깝습니다.

text
event는 "무슨 일이 있었는가"를 말한다.
artifact는 "그 일을 무엇으로 다시 확인할 수 있는가"를 말한다.

둘 중 하나만 남으면 문제가 생깁니다.

산출물만 남으면 맥락이 사라집니다.

text
파일은 있는데 왜 만들었는지 모른다.
로그는 있는데 어떤 요구사항 검증인지 모른다.
커밋은 있는데 어떤 티켓을 닫는지 모른다.

이벤트만 남으면 증거가 약합니다.

text
테스트를 실행했다고 적혀 있는데 로그가 없다.
배포를 확인했다고 적혀 있는데 URL과 응답이 없다.
리뷰를 통과했다고 적혀 있는데 어떤 diff를 봤는지 없다.

좋은 프레임워크는 둘을 같이 다룹니다.

text
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

3. 에이전트 실행을 event stream으로 보면 무엇이 달라지는가

대화형 루프는 보통 이렇게 보입니다.

text
while not done:
  call model
  if tool_call:
    run tool
  else:
    return answer

하지만 운영형 프레임워크에서는 이 루프만으로 부족합니다. 각 단계가 event stream으로 남아야 합니다.

text
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

이렇게 보면 “답변 생성”은 실행의 마지막 이벤트 중 하나일 뿐입니다. 중요한 것은 실행이 어떤 상태 전이를 거쳤는지입니다.

text
대화 중심 관점:
사용자에게 무엇을 말할까?

이벤트 중심 관점:
시스템은 지금 어떤 상태이며, 무엇이 그 상태를 증명하는가?

이 차이는 큽니다. 특히 에이전트가 길게 일하거나, 중간에 끊기거나, 다른 프로필/작업자가 이어받아야 할 때 차이가 드러납니다.


4. 상태 전이로 작업을 설계하기

티켓 하나를 예로 들어봅시다.

text
inbox -> in_progress -> review_required -> done
                         -> blocked

이건 단순한 칸반 보드가 아닙니다. 에이전트가 어떤 행동을 할 수 있는지 결정하는 상태 기계입니다.

text
inbox
- 아직 실행하지 않는다.
- 요구사항 정리가 필요할 수 있다.

in_progress
- 도구 실행과 파일 변경이 가능하다.
- 중간 산출물을 붙인다.

review_required
- 자동 진행을 멈춘다.
- 사용자/리뷰어/Notjing 판단이 필요하다.

blocked
- 더 실행하지 않는다.
- 빠진 입력, 권한, 외부 장애를 명시한다.

done
- 검증과 최종 보고가 있어야 한다.
- 이후에는 새 작업으로 이어받는다.

이 상태 전이가 없으면 에이전트는 대화 흐름에만 기대게 됩니다. 그러면 재시작 후 이런 일이 생깁니다.

text
- 이미 done인 작업을 다시 한다.
- blocked인데 계속 실행한다.
- review_required인데 다음 티켓을 시작한다.
- 사용자가 승인하지 않은 외부 side effect를 진행한다.

프레임워크 관점에서는 상태 전이가 곧 안전장치입니다.

text
상태가 없으면 기억에 기대고,
기억에 기대면 재시작과 병렬 작업에서 무너진다.

5. 산출물에는 소유자와 수명이 필요하다

모든 산출물을 영원히 같은 장소에 두면 저장소가 쓰레기장이 됩니다. 반대로 아무것도 남기지 않으면 다음 작업이 매번 처음부터 시작합니다.

그래서 산출물에는 최소한 세 가지 속성이 필요합니다.

text
owner
- 이 산출물의 책임 주체는 누구인가?
- hub가 보관할 것인가, spoke가 버릴 것인가?

retention
- 영구 보존인가?
- 며칠 뒤 지워도 되는 임시 파일인가?
- 공개 블로그/문서로 승격될 것인가?

promotion rule
- 어떤 조건에서 더 durable한 위치로 옮기는가?
- 어떤 조건에서는 그냥 폐기하는가?

OkayJing식으로 말하면 이런 구분입니다.

text
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를 써야 한다”는 절차는 반복되는 운영 규칙이므로 스킬/운영 문서에 있어야 합니다.

text
산출물의 위치는 그 정보의 수명과 재사용 방식을 반영해야 한다.

6. artifact-first 에이전트 루프

작업형 에이전트 루프를 조금 더 현실적으로 쓰면 이렇습니다.

text
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번입니다. 좋은 프레임워크는 실행 전에 “어떤 산출물이 남아야 완료인가”를 압니다.

나쁜 실행 계획은 이렇게 말합니다.

text
글을 작성한다.
검증한다.
보고한다.

조금 더 나은 계획은 이렇게 말합니다.

text
- 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 또는 배포 대기 상태를 티켓 보고서에 남긴다.

두 계획의 차이는 실행 가능성입니다. 전자는 말로는 맞지만 검증 지점이 흐립니다. 후자는 자동화와 재시작에 강합니다.


7. 이벤트 로그는 모델 컨텍스트보다 오래 살아야 한다

모델 컨텍스트는 휘발성입니다. 길어지면 압축되고, 세션이 바뀌면 사라지고, 다른 프로필은 볼 수 없습니다.

그래서 중요한 실행 흔적은 모델 컨텍스트 밖에 있어야 합니다.

text
- SQLite ticket DB
- cron output files
- git commits
- local markdown reports
- workflow trace DB
- dashboard state JSON
- session store

이것은 “저장 많이 하기”가 아닙니다. 저장의 목적은 다음 실행에서 같은 판단을 반복하지 않게 하는 것입니다.

text
좋은 저장:
- 다음 실행의 시작 시간을 줄인다.
- 검증 근거를 다시 찾을 수 있다.
- 실패 원인을 분류할 수 있다.
- 반복 워크플로를 스킬/스크립트로 승격할 수 있다.

나쁜 저장:
- 임시 진행상황을 메모리에 넣는다.
- 원문 로그를 끝없이 쌓지만 검색/승격 기준이 없다.
- hub에 spoke의 모든 중간 대화를 흡수한다.
- 검증 없는 요약을 사실처럼 재사용한다.

에이전트 프레임워크가 강해지려면 “무엇을 저장할까”보다 “저장된 것이 다음 실행에서 어떤 판단을 개선하는가”가 더 중요합니다.


8. 산출물 그래프: 작업은 선형 로그가 아니라 연결망이다

실제 작업은 하나의 로그가 아니라 그래프에 가깝습니다.

text
User Request
  -> Ticket
    -> Work Item
      -> Session
        -> File Diff
        -> Test Log
        -> Review Finding
        -> Commit
        -> Public URL
      -> Final Report

각 노드는 다른 시스템에 살 수 있습니다.

text
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

이 그래프를 만들면 좋은 점이 있습니다.

첫째, 재시작이 쉬워집니다.

text
세션이 끊겨도 티켓과 산출물로 이어받을 수 있다.

둘째, 병렬 작업이 안전해집니다.

text
hub는 계약과 결과만 보고,
spoke의 전체 중간 대화를 흡수하지 않아도 된다.

셋째, 나중에 학습 데이터 후보를 만들 수 있습니다.

text
성공한 반복 워크플로의 event/artifact 패턴을 모으면
스킬 개선, 평가셋, 로컬 정책 모델 후보가 된다.

여기서 중요한 제한도 있습니다. 도구 실행 자체나 승인 우회 같은 위험한 행동을 모델 가중치에 넣으면 안 됩니다. 학습 후보가 될 수 있는 것은 안정적인 판단 패턴과 보고 스타일, 라우팅 기준 정도입니다. 실행과 권한은 계속 deterministic runtime에 남아야 합니다.


9. 실패를 artifact로 남기기

성공 산출물만 남기면 프레임워크는 똑똑해지지 않습니다. 실패도 구조화되어야 합니다.

예를 들어 빌드가 실패했다면 이렇게 남기는 것이 좋습니다.

text
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

이렇게 남기면 다음에 비슷한 실패가 왔을 때 단순히 “빌드 실패”가 아니라 패턴을 볼 수 있습니다.

text
- MDX frontmatter 문제인가?
- dependency 설치 문제인가?
- 기존 repo가 dirty/behind인 문제인가?
- public route probe가 배포 완료 전에 너무 빨랐는가?
- 승인 scanner 때문에 cron에서 명령이 막혔는가?

실패 분류가 쌓이면 스킬이 좋아집니다. 반대로 실패를 최종 답변에서만 짧게 언급하고 사라지게 하면 다음 실행은 다시 같은 구덩이에 빠집니다.


10. UI도 이벤트/산출물 관점으로 봐야 한다

에이전트 UI를 만들 때도 이 관점이 중요합니다. 단순 채팅 UI는 최종 메시지를 중심으로 설계됩니다. 하지만 작업형 UI는 상태와 산출물을 중심으로 설계되어야 합니다.

text
채팅 UI 중심
- 메시지 목록
- 입력창
- 도구 실행 로그 일부
- 최종 답변

작업 UI 중심
- 현재 작업자/세션
- 연결된 티켓/워크아이템
- 생성/수정 파일
- 검증 결과
- 승인 대기 항목
- 산출물 미리보기
- 다음 상태 전이 버튼

OkayJing Local 같은 운영 UI가 Discord를 대체하려면 단순히 예쁜 채팅창이 아니라 이런 표면이 필요합니다.

text
Office: 누가/무엇이 일하고 있는가
Sessions: 대화와 실행 흐름은 어디에 연결되는가
Code: 파일, diff, 커밋, 체크포인트는 무엇인가
Artifacts: 보고서, URL, 이미지, 문서, 로그는 어디에 있는가
Approvals: 어떤 위험도 때문에 멈췄는가

이 구조가 있어야 사용자가 “지금 무슨 일이 일어나는지”를 대화 추측이 아니라 화면의 상태로 볼 수 있습니다.


11. 프레임워크 설계 체크리스트

에이전트 프레임워크를 직접 설계한다고 가정하면, event/artifact lifecycle에 대해 최소한 이 질문들을 해야 합니다.

text
Run
- 실행 하나를 식별하는 run_id가 있는가?
- parent run / child run을 연결할 수 있는가?
- 중단 후 이어받을 수 있는가?

Event
- 각 도구 호출과 상태 전이가 이벤트로 남는가?
- 사람 승인/거절도 이벤트로 남는가?
- 실패 이벤트가 분류되는가?

Artifact
- 파일, 로그, URL, 스크린샷, 보고서가 참조 가능한가?
- owner와 retention이 있는가?
- public/private 경계가 있는가?

Verification
- 완료 조건이 산출물로 검증되는가?
- 검증 생략은 이유와 함께 남는가?
- 최종 답변이 검증보다 앞서지 않는가?

Promotion
- 반복되는 성공 패턴을 스킬/스크립트/평가셋으로 승격하는가?
- 일회성 작업 상태를 메모리에 넣지 않는가?
- hub가 spoke 전체 맥락을 흡수하지 않고 결과와 규칙만 흡수하는가?

이 질문들이 없으면 프레임워크는 “말은 잘하는데 운영이 약한” 상태에 머뭅니다.


12. 작은 구현 모델

아주 단순한 자료구조로 시작해도 됩니다.

ts
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 같은 표준으로 연결할 수 있습니다. 하지만 시작점은 작아도 됩니다.

text
작은 local schema
+ 정확한 상태 전이
+ 검증 가능한 산출물
+ 반복 패턴 승격

이 조합이 프레임워크를 실제 업무 시스템으로 만듭니다.


13. 오늘의 요약

오늘 본 내용은 한 줄로 줄이면 이렇습니다.

text
에이전트의 결과물은 final answer가 아니라
event + artifact + verification + next state다.

조금 더 풀면 이렇습니다.

text
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 서비스 로그가 모두 섞입니다.

그래서 프레임워크가 할 일은 하나입니다.

text
흩어진 실행 흔적을
검증 가능한 작업 그래프로 묶는다.

다음 글에서는 이 그래프를 더 확장해서, 에이전트 간 협업과 handoff를 어떻게 설계해야 하는지 보겠습니다. 단순히 “여러 에이전트를 만든다”가 아니라, 어떤 정보와 권한과 산출물이 넘어가야 안전한 handoff가 되는지 reverse-engineering할 겁니다.

;
artifactIds?: string[];
riskLevel?: "low" | "medium" | "high";
};
type Artifact = {
id: string;
runId: string;
kind: "file" | "log" | "url" | "diff" | "report" | "screenshot";
ref: string;
owner: "hub" | "spoke" | "project" | "external";
retention: "temporary" | "ticket" | "project" | "public" | "archive";
verified: boolean;
};