• AG-UI 이벀트 μ‹œμŠ€ν…œμ€ μ—μ΄μ „νŠΈ-UI κ°„ μ‹€μ‹œκ°„ 톡신을 μœ„ν•œ typed 이벀트 슀트림 ν”„λ‘œν† μ½œ
  • 28개 ν™œμ„± 이벀트 νƒ€μž…κ³Ό 3κ°€μ§€ 섀계 νŒ¨ν„΄μœΌλ‘œ κ΅¬μ„±λœ ν‘œμ€€ν™”λœ μ—μ΄μ „νŠΈ μΈν„°νŽ˜μ΄μŠ€
  • Thread(λŒ€ν™” μ„Έμ…˜) > Run(μ‚¬μš©μž 1ν„΄) > Step(선택적 단계) > Streaming Item 의 4계측 ꡬ쑰

ν•΄λ‹Ή κ°œλ…μ΄ ν•„μš”ν•œ 이유

  • LangGraph, CrewAI λ“± λ‹€μ–‘ν•œ ν”„λ ˆμž„μ›Œν¬λ₯Ό ν•˜λ‚˜μ˜ UIμ—μ„œ μ—°κ²°ν•  ν‘œμ€€ μΈν„°νŽ˜μ΄μŠ€ λΆ€μž¬ ν•΄κ²°
  • LLM μ‘λ‹΅μ˜ μ‹€μ‹œκ°„ μŠ€νŠΈλ¦¬λ°μ„ μœ„ν•œ 이벀트 기반 비동기 톡신 ν•„μš”
  • MCP(μ—μ΄μ „νŠΈ 도ꡬ) Β· A2A(μ—μ΄μ „νŠΈ κ°„ 톡신)와 ν•¨κ»˜ μ—μ΄μ „νŠΈ ν”„λ‘œν† μ½œ μŠ€νƒμ˜ UI 계측 λ‹΄λ‹Ή

AS-IS β€” REST μš”μ²­/응닡 방식

sequenceDiagram
    autonumber
    participant User
    participant UI
    participant Agent

    User->>UI: λ©”μ‹œμ§€ μž…λ ₯
    UI->>Agent: HTTP POST /chat
    Note over Agent: 전체 응닡 생성 (waiting...)
    Agent-->>UI: μ™„μ„±λœ 응닡 λ°˜ν™˜
    UI->>User: 응닡 ν‘œμ‹œ (ν•œ λ²ˆμ—)

TO-BE β€” AG-UI 이벀트 슀트리밍

sequenceDiagram
    autonumber
    participant User
    participant UI
    participant Agent

    User->>UI: λ©”μ‹œμ§€ μž…λ ₯
    UI->>Agent: RunAgentInput (threadId + runId)
    Agent-->>UI: RUN_STARTED
    Agent-->>UI: TEXT_MESSAGE_START
    Agent-->>UI: TEXT_MESSAGE_CONTENT (delta)
    Agent-->>UI: TEXT_MESSAGE_CONTENT (delta)
    Agent-->>UI: TEXT_MESSAGE_END
    Agent-->>UI: RUN_FINISHED
    UI->>User: μ‹€μ‹œκ°„ 슀트리밍 ν‘œμ‹œ

4계측 ꡬ쑰: Thread > Run > Step > Item

flowchart TD
    subgraph THREAD["Thread (threadId β€” λŒ€ν™” μ„Έμ…˜)"]
        subgraph RUN["Run (runId β€” μ‚¬μš©μž 1ν„΄)"]
            RUN_STARTED([RUN_STARTED])
            RUN_FINISHED([RUN_FINISHED])
            RUN_ERROR([RUN_ERROR])
            subgraph STEP["Step (선택적, N개 순차)"]
                STEP_STARTED[STEP_STARTED]
                STEP_FINISHED[STEP_FINISHED]
                subgraph MSG["Text Message"]
                    TMS[TEXT_MESSAGE_START]
                    TMC[TEXT_MESSAGE_CONTENT]
                    TME[TEXT_MESSAGE_END]
                end
                subgraph TOOL["Tool Call"]
                    TCS[TOOL_CALL_START]
                    TCA[TOOL_CALL_ARGS]
                    TCE[TOOL_CALL_END]
                    TCR[TOOL_CALL_RESULT]
                end
                subgraph REASON["Reasoning"]
                    RST[REASONING_START]
                    RMC[REASONING_MESSAGE_CONTENT]
                    REN[REASONING_END]
                end
            end
        end
    end
    RUN_STARTED --> STEP_STARTED
    STEP_STARTED --> TMS
    STEP_STARTED --> TCS
    STEP_STARTED --> RST
    TMS --> TMC
    TMC -->|반볡| TMC
    TMC --> TME
    TCS --> TCA
    TCA -->|반볡| TCA
    TCA --> TCE
    TCE --> TCR
    RST --> RMC
    RMC -->|반볡| RMC
    RMC --> REN
    TME --> STEP_FINISHED
    TCR --> STEP_FINISHED
    REN --> STEP_FINISHED
    STEP_FINISHED -->|λ‹€μŒ Step| STEP_STARTED
    STEP_FINISHED --> RUN_FINISHED
    RUN_STARTED -->|치λͺ…적 μ—λŸ¬| RUN_ERROR
    RUN_FINISHED -->|λ‹€μŒ μ‚¬μš©μž ν„΄| RUN_STARTED

State Management β€” μ—­ν• κ³Ό 라이프사이클

λͺ©μ : μ—μ΄μ „νŠΈμ™€ ν”„λ‘ νŠΈμ—”λ“œ κ°„ μ–‘λ°©ν–₯ 곡유 μƒνƒœ 동기화. λ‹¨μˆœ μ €μž₯이 μ•„λ‹ˆλΌ μ–‘μͺ½ λͺ¨λ‘ 읽고 μ“Έ 수 μžˆλŠ” collaborative state둜, Human-in-the-Loop μ›Œν¬ν”Œλ‘œμš°μ˜ 기반이 λœλ‹€.

핡심 νŠΉμ„±

  • λ°©ν–₯: μ–‘λ°©ν–₯ (agent ↔ frontend λͺ¨λ‘ μˆ˜μ • κ°€λŠ₯)
  • λͺ©μ : μ—μ΄μ „νŠΈμ˜ μ œμ•ˆ(proposal)을 UIκ°€ ν‘œμ‹œ β†’ μ‚¬μš©μžκ°€ 승인/κ±°λΆ€/μˆ˜μ • β†’ λ‹€μ‹œ μ—μ΄μ „νŠΈμ— 반영
  • UI 반영: 메인 UI (폼, 에디터, λ¬Έμ„œ λ·° λ“±)
  • 지속성: Run 경계λ₯Ό λ„˜μ–΄ λˆ„μ 

μ‹€μ „ μ˜ˆμ‹œ: Shared State β€” λ ˆμ‹œν”Ό 에디터

AI와 μ‚¬μš©μžκ°€ 같은 λ ˆμ‹œν”Όλ₯Ό ν•¨κ»˜ νŽΈμ§‘ν•˜λŠ” μ‹œλ‚˜λ¦¬μ˜€.

state = {
    "recipe": {
        "skill_level": "Advanced",
        "ingredients": [{"name": "chicken breast", "amount": "1"}, ...],
        "instructions": ["Season chicken...", "Sear until...", ...]
    }
}
yield StateSnapshotEvent(type=EventType.STATE_SNAPSHOT, snapshot=state)

CopilotKit의 useCoAgent hook이 이 stateλ₯Ό μ–‘λ°©ν–₯으둜 λ°”μΈλ”©ν•œλ‹€:

const { state: agentState, setState: setAgentState } = useCoAgent({
  name: "agent",
  initialState: { someProperty: "initialValue" },
})
  • μ—μ΄μ „νŠΈκ°€ λ ˆμ‹œν”Ό μ΄ˆμ•ˆμ„ STATE_SNAPSHOT으둜 곡유
  • UIκ°€ 폼으둜 λ Œλ”λ§ β†’ μ‚¬μš©μžκ°€ 재료 μˆ˜μ •
  • setAgentState둜 변경이 μ—μ΄μ „νŠΈμ— μ „λ‹¬λ˜μ–΄ λ‹€μŒ 생성에 반영

μ™œ ν•„μš”ν•œκ°€: λ©”μ‹œμ§€λ‘œλŠ” β€œμž¬λ£Œ λͺ©λ‘, 쑰리 단계, λ‚œμ΄λ„β€ 같은 κ΅¬μ‘°ν™”λœ 곡동 νŽΈμ§‘ λ¬Έμ„œλ₯Ό ν‘œν˜„ν•  수 μ—†λ‹€.

라이프사이클 λ‹€μ΄μ–΄κ·Έλž¨

sequenceDiagram
    autonumber
    participant Agent
    participant UI
    participant User

    Note over Agent: Run μ‹œμž‘
    Agent->>UI: STATE_SNAPSHOT (초기 전체 μƒνƒœ)
    UI->>User: 폼 λ Œλ”λ§

    Note over Agent: 일뢀 ν•„λ“œ μ—…λ°μ΄νŠΈ
    Agent->>UI: STATE_DELTA (JSON Patch)
    UI->>User: ν•΄λ‹Ή ν•„λ“œλ§Œ κ°±μ‹ 

    User->>UI: ν•„λ“œ μˆ˜μ •
    UI->>Agent: setState (μ—­λ°©ν–₯ 반영)

    Note over Agent: λŒ€κ·œλͺ¨ λ³€κ²½/μž¬λ™κΈ°ν™” ν•„μš”
    Agent->>UI: STATE_SNAPSHOT (μž¬λ™κΈ°ν™”)
    UI->>User: 전체 μž¬λ Œλ”λ§

Activity β€” μ—­ν• κ³Ό 라이프사이클

λͺ©μ : μ±„νŒ… λ©”μ‹œμ§€ 사이에 κ΅¬μ‘°ν™”λœ μ§„ν–‰ 상황을 ν‘œμ‹œ. μ—μ΄μ „νŠΈκ°€ ν˜„μž¬ ν•˜κ³  μžˆλŠ” μž‘μ—…μ„ ν”„λ‘œκ·Έλ ˆμŠ€ λ°”, μΉ΄λ“œ, ν”Œλžœ 트리 같은 별도 UI μ»΄ν¬λ„ŒνŠΈλ‘œ μ‹œκ°ν™”ν•˜κΈ° μœ„ν•œ 단방ν–₯ 슀트림.

핡심 νŠΉμ„±

  • λ°©ν–₯: 단방ν–₯ (agent β†’ frontend, μ‚¬μš©μž μˆ˜μ • λΆˆκ°€)
  • λͺ©μ : μž‘μ—… μ§„ν–‰ μƒν™©μ˜ κ΅¬μ‘°ν™”λœ 읽기 μ „μš© λ·°
  • UI 반영: λ©”μ‹œμ§€ 슀트림 쀑간 (Plan λ°•μŠ€, 검색 κ²°κ³Ό νŒ¨λ„ λ“±)
  • 지속성: role: "activity" λ©”μ‹œμ§€λ‘œ νžˆμŠ€ν† λ¦¬ λˆ„μ , Run을 λ„˜μ–΄ 보쑴

μ‹€μ „ μ˜ˆμ‹œ: Plan λͺ¨λ“œ

μ—μ΄μ „νŠΈκ°€ μž‘μ—… κ³„νšμ„ μˆ˜λ¦½ν•˜λ©΄μ„œ UI에 λ³„λ„μ˜ Plan λ°•μŠ€λ‘œ μ‹€μ‹œκ°„ ν‘œμ‹œ.

{
  type: EventType.ACTIVITY_SNAPSHOT,
  messageId: "activity-1",
  activityType: "PLAN",
  content: { tasks: ["task 1"] }
}
  • activityType이 "PLAN"이면 UIλŠ” Plan μ „μš© λ Œλ”λŸ¬λ₯Ό 선택
  • 같은 messageId의 ACTIVITY_DELTA둜 tasks 배열에 μΆ”κ°€/μˆ˜μ •
  • Run이 λλ‚˜λ„ role: "activity" λ©”μ‹œμ§€λ‘œ νžˆμŠ€ν† λ¦¬μ— λ‚¨μŒ

μ™œ ν•„μš”ν•œκ°€:

  • λ©”μ‹œμ§€λŠ” μžμ—°μ–΄ ν…μŠ€νŠΈ 버블 β€” Plan/Search 같은 κ΅¬μ‘°ν™”λœ μ§„ν–‰ 뷰에 뢀적합
  • State ManagementλŠ” μ–‘λ°©ν–₯ νŽΈμ§‘μš©μ΄λΌ 읽기 μ „μš© μ§„ν–‰ 상황 ν‘œμ‹œμ—λŠ” κ³Όν•œ 섀계
  • Activity = β€œμ—μ΄μ „νŠΈλ§Œ μ“°κ³  μ‚¬μš©μžλŠ” 보기만 ν•˜λŠ” κ΅¬μ‘°ν™”λœ λ·°β€μ˜ μ „μš© ν”„λ¦¬λ―Έν‹°λΈŒ

λ©”μ‹œμ§€ vs State vs Activity 비ꡐ

ν•­λͺ©TEXT_MESSAGESTATEACTIVITY
μš©λ„λŒ€ν™” ν…μŠ€νŠΈκ³΅μœ  νŽΈμ§‘ μƒνƒœμ§„ν–‰ 상황 λ·°
λ‚΄μš©μžμ—°μ–΄ λ¬Έμžμ—΄κ΅¬μ‘°ν™”λœ JSONκ΅¬μ‘°ν™”λœ JSON
νŽΈμ§‘ λ°©ν–₯β€”μ–‘λ°©ν–₯단방ν–₯ (agentβ†’UI)
UI μœ„μΉ˜μ±„νŒ… 버블메인 UI (폼/에디터)λ©”μ‹œμ§€ 사이 (μΉ΄λ“œ/νŒ¨λ„)
증뢄 μ—…λ°μ΄νŠΈdelta (문자 append)JSON PatchJSON Patch
νžˆμŠ€ν† λ¦¬role: "assistant"μƒνƒœλ‘œ λˆ„μ role: "activity" λ©”μ‹œμ§€

라이프사이클 λ‹€μ΄μ–΄κ·Έλž¨

sequenceDiagram
    autonumber
    participant Agent
    participant UI
    participant User

    Note over Agent: μž‘μ—… μ‹œμž‘
    Agent->>UI: ACTIVITY_SNAPSHOT (activityType PLAN)
    UI->>User: Plan λ°•μŠ€ λ Œλ”λ§

    loop μž‘μ—… μ§„ν–‰
        Agent->>UI: ACTIVITY_DELTA (tasks μΆ”κ°€)
        UI->>User: Plan λ°•μŠ€ μ‹€μ‹œκ°„ κ°±μ‹ 
    end

    Note over Agent: μƒˆ Plan으둜 ꡐ체
    Agent->>UI: ACTIVITY_SNAPSHOT (replace true)
    UI->>User: Plan λ°•μŠ€ 전체 ꡐ체

    Note over Agent: Run μ’…λ£Œ 후에도 role activity λ©”μ‹œμ§€λ‘œ νžˆμŠ€ν† λ¦¬ 보쑴

28개 ν™œμ„± 이벀트 (μΉ΄ν…Œκ³ λ¦¬λ³„)

Lifecycle β€” 5개

μ΄λ²€νŠΈν•΅μ‹¬ ν•„λ“œμ˜λ―Έ
RUN_STARTEDthreadId, runIdμ—μ΄μ „νŠΈ μ‹€ν–‰ μ‹œμž‘
RUN_FINISHEDthreadId, runIdμ‹€ν–‰ 정상 μ™„λ£Œ
RUN_ERRORmessage, codeμ‹€ν–‰ 였λ₯˜ μ’…λ£Œ
STEP_STARTEDstepNameν•˜μœ„ 단계 μ‹œμž‘
STEP_FINISHEDstepNameν•˜μœ„ 단계 μ™„λ£Œ

Text Message β€” 4개

μ΄λ²€νŠΈν•΅μ‹¬ ν•„λ“œμ˜λ―Έ
TEXT_MESSAGE_STARTmessageId, roleν…μŠ€νŠΈ 슀트림 μ‹œμž‘
TEXT_MESSAGE_CONTENTmessageId, deltaν…μŠ€νŠΈ 청크 λˆ„μ 
TEXT_MESSAGE_ENDmessageIdν…μŠ€νŠΈ 슀트림 μ™„λ£Œ
TEXT_MESSAGE_CHUNKmessageId, deltaStart+Content+End μžλ™ ν™•μž₯ 편의 이벀트

Tool Call β€” 5개

μ΄λ²€νŠΈν•΅μ‹¬ ν•„λ“œμ˜λ―Έ
TOOL_CALL_STARTtoolCallId, toolCallName툴 호좜 μ‹œμž‘
TOOL_CALL_ARGStoolCallId, delta툴 인자 JSON 청크
TOOL_CALL_ENDtoolCallId툴 인자 전솑 μ™„λ£Œ
TOOL_CALL_RESULTtoolCallId, content툴 μ‹€ν–‰ κ²°κ³Ό (슀트리밍 μ•„λ‹˜)
TOOL_CALL_CHUNKtoolCallId, deltaStart+Args+End μžλ™ ν™•μž₯ 편의 이벀트

State Management β€” 3개

μ΄λ²€νŠΈν•΅μ‹¬ ν•„λ“œμ˜λ―Έ
STATE_SNAPSHOTsnapshot전체 μƒνƒœ (ν”„λ‘ νŠΈμ—”λ“œ ꡐ체)
STATE_DELTAdelta (RFC 6902)JSON Patch 증뢄 μ—…λ°μ΄νŠΈ
MESSAGES_SNAPSHOTmessagesλŒ€ν™” νžˆμŠ€ν† λ¦¬ 전체 μŠ€λƒ…μƒ·

Activity β€” 2개

μ΄λ²€νŠΈν•΅μ‹¬ ν•„λ“œμ˜λ―Έ
ACTIVITY_SNAPSHOTmessageId, activityType, contentμ§„ν–‰ ν™œλ™ 전체 μŠ€λƒ…μƒ· (PLAN, SEARCH λ“±)
ACTIVITY_DELTAmessageId, patchν™œλ™ JSON Patch 증뢄 μ—…λ°μ΄νŠΈ

Special β€” 2개

μ΄λ²€νŠΈν•΅μ‹¬ ν•„λ“œμ˜λ―Έ
RAWevent, sourceμ™ΈλΆ€ μ‹œμŠ€ν…œ 이벀트 pass-through
CUSTOMname, valueμ• ν”Œλ¦¬μΌ€μ΄μ…˜ ν™•μž₯ 이벀트

Reasoning β€” 7개

μ΄λ²€νŠΈν•΅μ‹¬ ν•„λ“œμ˜λ―Έ
REASONING_STARTmessageIdμΆ”λ‘  ν”„λ‘œμ„ΈμŠ€ μ‹œμž‘
REASONING_MESSAGE_STARTmessageId, roleμΆ”λ‘  λ©”μ‹œμ§€ 슀트림 μ‹œμž‘
REASONING_MESSAGE_CONTENTmessageId, deltaμΆ”λ‘  청크
REASONING_MESSAGE_ENDmessageIdμΆ”λ‘  λ©”μ‹œμ§€ μ™„λ£Œ
REASONING_MESSAGE_CHUNKmessageId, delta편의 이벀트 (μžλ™ ν™•μž₯)
REASONING_ENDmessageIdμΆ”λ‘  ν”„λ‘œμ„ΈμŠ€ μ™„λ£Œ
REASONING_ENCRYPTED_VALUEsubtype, entityId, encryptedValueμ•”ν˜Έν™”λœ chain-of-thought

Deprecated 5개: THINKING_* β†’ λͺ¨λ‘ REASONING_*으둜 λŒ€μ²΄ (v1.0.0 제거 μ˜ˆμ •)

3κ°€μ§€ 섀계 νŒ¨ν„΄

1. Start-Content-End νŒ¨ν„΄ (슀트리밍)
   TEXT_MESSAGE_START β†’ TEXT_MESSAGE_CONTENT (반볡) β†’ TEXT_MESSAGE_END
   TOOL_CALL_START    β†’ TOOL_CALL_ARGS (반볡)       β†’ TOOL_CALL_END
   REASONING_START    β†’ REASONING_MESSAGE_CONTENT   β†’ REASONING_END

2. Snapshot-Delta νŒ¨ν„΄ (μƒνƒœ 동기화)
   STATE_SNAPSHOT β†’ STATE_DELTA (반볡) β†’ STATE_SNAPSHOT (μž¬λ™κΈ°ν™” μ‹œ)

3. Lifecycle νŒ¨ν„΄ (μ‹€ν–‰ 경계)
   RUN_STARTED β†’ (Steps/Items) β†’ RUN_FINISHED | RUN_ERROR

AG-UI vs Codex vs Claude CLI 이벀트 λ§€ν•‘

의미AG-UICodexClaude CLI (NDJSON)
μ„Έμ…˜(λŒ€ν™”)threadId (RUN_STARTED 포함)thread.startedν”„λ‘œμ„ΈμŠ€ spawn
ν„΄ μ‹œμž‘RUN_STARTEDturn.startedsystem { subtype:"init" }
ν…μŠ€νŠΈ μ‹œμž‘TEXT_MESSAGE_STARTitem.startedcontent_block_start { type:"text" }
ν…μŠ€νŠΈ 청크TEXT_MESSAGE_CONTENTitem.updatedcontent_block_delta { type:"text_delta" }
ν…μŠ€νŠΈ μ’…λ£ŒTEXT_MESSAGE_ENDitem.completedcontent_block_stop
툴 μ‹œμž‘TOOL_CALL_STARTitem.startedcontent_block_start { type:"tool_use" }
툴 인자TOOL_CALL_ARGSitem.updatedcontent_block_delta { type:"input_json_delta" }
툴 μ’…λ£ŒTOOL_CALL_ENDitem.completedcontent_block_stop
툴 κ²°κ³ΌTOOL_CALL_RESULTβ€”μ—†μŒ (Claude λ‚΄λΆ€ μ‹€ν–‰)
μΆ”λ‘  μ‹œμž‘REASONING_START + REASONING_MESSAGE_STARTβ€”content_block_start { type:"thinking" }
μΆ”λ‘  청크REASONING_MESSAGE_CONTENTβ€”content_block_delta { type:"thinking_delta" }
μΆ”λ‘  μ’…λ£ŒREASONING_MESSAGE_END + REASONING_ENDβ€”content_block_stop
ν„΄ 성곡RUN_FINISHEDturn.completedresult { subtype:"success" }
ν„΄ μ‹€νŒ¨RUN_ERRORturn.failedresult { subtype:"error_*" }
μΈν„°λŸ½νŠΈ(μ»€μŠ€ν…€ 이벀트)β€”result { subtype:"error_during_execution" }
치λͺ…적 μ—λŸ¬RUN_ERRORerrorν”„λ‘œμ„ΈμŠ€ crash (stdout μ’…λ£Œ)
μƒνƒœ 동기화STATE_SNAPSHOT / STATE_DELTAβ€”β€”
단계 ꡬ뢄STEP_STARTED / STEP_FINISHEDβ€”β€”

섀계 μ² ν•™ 비ꡐ

ν•­λͺ©AG-UICodexClaude CLI
컨텐츠 νƒ€μž… κ΅¬λΆ„νƒ€μž…λ³„ 별도 μ΄λ²€νŠΈλ‹¨μΌ item.* 톡합content_block.type ν•„λ“œ ꡬ뢄
툴 κ²°κ³ΌTOOL_CALL_RESULT μ‘΄μž¬β€”μ—†μŒ (μ—μ΄μ „νŠΈ λ‚΄λΆ€ μ‹€ν–‰)
μΆ”λ‘ (Thinking)7개 μ „μš© μ΄λ²€νŠΈβ€”thinking 블둝 νƒ€μž…
μƒνƒœ λ™κΈ°ν™”μ „μš© 이벀트 3μ’…β€”β€”
Step κ°œλ…λͺ…μ‹œμ  이벀트 μŒβ€”β€”
μ„Έμ…˜ vs ν„΄ 뢄리threadId + runId λͺ…μ‹œ 뢄리thread + turn λΆ„λ¦¬ν”„λ‘œμ„ΈμŠ€=μ„Έμ…˜, result=ν„΄ μ’…λ£Œ

μ°Έκ³  λ¬Έμ„œ