인터럽트와 재개
인터럽트와 재개 (Interrupts and Resume)
실행(run)이 외부에서 무엇인가를 어떻게 요청하고, 다음 실행이 어떻게 답하는지 설명해 드릴게요 — 1.0 기준이에요. 실행은 가끔 오직 외부 세계만 줄 수 있는 것 — 승인, 자격 증명, 선택 — 이 필요해요.
출처: 문서
본문
실행은 가끔 오직 외부 세계만 줄 수 있는 것이 필요해요 — 승인, 자격 증명, 선택. 프로토콜에는 컨슈머로부터의 실행 중 채널이 없으므로, 실행은 기다리지 않아요: 무엇을 기다리는지 말하며 끝나고, 그것에서 계속되는 실행이 답을 지니고 갑니다.
인터럽트 (Interrupting)
외부 입력이 필요한 실행은 outcome이 인터럽트 결과인 RUN_FINISHED로 끝나며, 하나 이상의 Interrupt 객체를 담아요 — 적어도 하나는 담아야 해요, 답할 것이 없는 인터럽트 결과는 컨슈머에게 할 일이 없게 만들 테니까요.
- 프로듀서는 인터럽트된 실행을 성공으로 보고해서는 안 됩니다(MUST NOT): 인터럽트 결과는 묻기 위해 멈춘 실행을 끝내는 유일한 적합한 방식이에요 — 프로듀서가 무엇을 기다리는지 이름 붙이고, 재개 항목이 그것에 답하는 경우요. 프런트엔드 도구를 호출해서 멈춘 실행은 다른 평범한 경로예요: 그것은 성공으로 끝나고, 성공 결과의
pendingToolCallIds에 답하지 않은 호출을 이름 붙이며, 답은 다음 입력의 메시지를 타고 옵니다. 시키는 대로 멈춘 실행은 둘 다 아니에요: 그것은 cancelled outcome으로 끝나고 아무것도 요청하지 않습니다. - 각 인터럽트의
id는 실행 안에서 고유해야 합니다(MUST); 재개 항목은 이 id로 그것에 답합니다. - 인터럽트의
reason은 열린 문자열이에요 — 프로토콜은 에이전트가 입력을 필요로 할 모든 이유를 분류하려 하지 않아요.message는 답하는 사람을 위한 사람이 읽을 수 있는 프롬프트이고,toolCallId는 승인이 관련된 도구 호출을 이름 붙이며,responseSchema는 답의 예상 형태를 서술해요 — 컨슈머가 그것을 위한 폼을 만들 수 있도록 불투명하게 실어 나릅니다. - 서브에이전트 안에서 제기된 인터럽트는 그 서브에이전트의
subagentRunId를 담을 수 있어요(MAY); subagent rules이 귀속과 그것을 따르는 suspended 결과를 통치해요.
인터럽트된 실행은 닫힌 실행이에요. run lifecycle이 닫힌 실행에 대해 말하는 모든 것이 적용됩니다 — 그것이 인정하는 유일한 늦은 도착, 닫힌 뒤 표면화된 실패를 보고하는 RUN_ERROR를 포함해서 — 그리고 계속하는 것은 새 실행을 의미합니다.
재개 (Resuming)
계속되는 실행은 입력의 resume 목록에 답을 실어 나르며, 답해진 각 인터럽트마다 ResumeEntry 하나가 있어요:
- 각 항목의
interruptId는 계속되는 실행 — 스레드의 가장 최근 인터럽트된 실행 — 의 인터럽트를 이름 붙여야 합니다(MUST). - 항목의
status는 인터럽트가 답해졌는지 버려졌는지 말해 주고,payload는 에이전트가 요청한 답을 담아요 — 임의의 JSON 값;metadata는 답의 일부가 아니라 응답에 대한 봉투 정보예요. - 재개 목록은 계속되는 실행의 모든 인터럽트를 덮어야 합니다(MUST): 각각 답하거나, 버려진 상태의 항목으로 명시적으로 버려지거나. 생략은 버림이 아니에요. 덮는 것(coverage)은 컨슈머가 강제할 규칙이에요: 컨슈머가 닫는
RUN_FINISHED가 전달한 인터럽트를 들고 있고, 그것이 재개 목록을 조립하므로, 목록이 완전한지 항상 말할 수 있는 유일한 참여자예요. 컨슈머는 인터럽트가 덮이지 않은 채로 남는 재개 입력을 실행이 시작되기 전에 — 무엇이든 보내기 전에 — 거부해야 하고(MUST), 항목이 없는 인터럽트를 조용히 지나쳐서는 안 됩니다(MUST NOT). 프로듀서는 이걸 확인하도록 요구받지 않고, 적합하려면 인터럽트된 실행에서 아무것도 유지할 필요가 없어요; 결함 있는 컨슈머가 어쨌든 불완전한 목록을 보내면 프로듀서가 하는 것은 아래의 그것의 오류 처리예요. 버려진 인터럽트가 에이전트의 작업에 대해 무엇을 뜻하는지는 프로듀서의 몫이에요. expiresAt은 의도적으로 형식 제약이 없어요. 그래서 인터럽트가 만료됐는지 여부는 그것을 판단하는 컨슈머 자신의 읽기입니다 — 참조 구현은 그것을 날짜로 읽고 지금-또는-이전을 만료로 취급해요. 규칙은 스키마가 명시하기를 거부하는 파싱이 아니라 판단에 붙어요: 컨슈머가 만료로 판단한 인터럽트는 더 이상 답해질 수 없어요 — 컨슈머는 실행 시작 전에 그것을 해결하는 재개 항목을, 덮이지 않은 인터럽트를 거부하듯 거부합니다. 그것은 여전히 — 그리고 덮는 것이 필수이므로, 반드시 — 버려질 수 있는데, 그것이 스레드가 제때 답한 사람이 없는 인터럽트를 지나서 움직이는 방식이에요.- 스레드와 상태 연속성은 간극을 가로질러 유지돼요: 재개하는 실행은 같은
threadId, 누적된 메시지, 인터럽트된 실행이 남긴 상태를, 어떤 순차 실행이 그러하듯 정확히 담아요. - 토큰 사용량은 간극을 가로질러 이어지지 않아요: 재개하는 실행의
usage는 자기 자신이 만든 모델 호출만 덮습니다. 인터럽트된 실행은 이미 그것을 인터럽트한RUN_FINISHED에서 자기 것을 보고했어요.
재개 항목을 받는 프로듀서는 그것들을 자기가 멈춘 답으로 취급합니다. 목록은 무엇이 결정됐는지에 대한 컨슈머의 완전한 진술이고, 프로듀서는 그것을 그렇게 받아들여요: 이전 실행의 인터럽트를 기억하거나, 그것들과 목록을 비교하거나, 그 목적을 위해 실행 사이에 어떤 상태도 유지할 필요가 없어요. 간극을 가로질러 아무것도 실어 나르지 않는 프로듀서도 적합해요.
컨슈머 규칙의 두 위반이 그래도 프로듀서에 닿을 수 있어요. 이어지는 것은 그들에 대한 프로듀서의 오류 처리이지, 그것들을 보내라는 허가는 아니에요.
- 인식되지 않은 항목 — 프로듀서가 제기하지 않은 인터럽트를 이름 붙이는 것: 실행은 항목 없이 진행되어야 하고(SHOULD), 프로듀서는 물어보지도 않은 답으로 실행을 실패시키기보다 경고를 표면화해야 해요(SHOULD).
- 덮이지 않은 인터럽트 — 프로듀서가 여전히 열려 있다고 말할 수 있고 항목이 없는 것, 인터럽트된 실행의 체크포인트를 유지했기 때문에, 또는 메시지가 결과도 그것에 답하는 항목도 없는 승인 게이트 도구 호출을 담기 때문에.
프로듀서는 없는 항목을 근거로 인터럽트된 행동을 수행해서는 안 되고(MUST NOT), 그 생략을 버림으로 취급해서도 안 됩니다(MUST NOT) — 경고와 함께 호출을 버리고 성공으로 끝내면, 결함 있는 컨슈머가 아무도 버리기로 결정하지 않은 것을 버리게 하며, 그것이 덮기 규칙이 막으려는 결과예요. 알아차렸으면, 프로듀서는 인터럽트를 열어 둡니다. 실행 시작 전에 입력을 거부할 수 있거나(MAY)(어떤 무효 입력처럼 —
RUN_ERROR로 여는 스트림이든, 전송 자신의 요청 거부든), 또는 덮인 항목이 허용하는 것을 실행하고 다시 인터럽트 결과로 끝내면서, 여전히 열린 인터럽트를 담아 컨슈머가 다시 답할 기회를 얻을 수 있어요(MAY). 어느 쪽이든 프로듀서가 열려 있다고 아는 인터럽트가 답 없이 서 있는 동안 실행은 성공으로 끝나서는 안 됩니다(MUST NOT). 구분할 수 없는 프로듀서 — 위의 무상태 경우 — 는 여기 의무가 없어요, 알아차릴 것이 없기 때문이에요.
메시지 흐름
sequenceDiagram
participant User
participant Application
participant Agent
Application->>Agent: RunAgentInput (runId: "run-1")
Agent->>Application: RUN_STARTED
Agent->>Application: TOOL_CALL_START ("transfer_funds") …
Agent->>Application: RUN_FINISHED (outcome: interrupt,<br/>id: "int-1", toolCallId, responseSchema)
Application->>User: renders the approval
User->>Application: approves
Application->>Agent: RunAgentInput (runId: "run-2",<br/>resume: [{interruptId: "int-1", payload}])
Agent->>Application: RUN_STARTED … RUN_FINISHED
데이터 타입
RunFinishedInterruptOutcome, Interrupt, ResumeEntry는 schema reference로 정의됩니다. expiresAt는 있을 때 관례적으로 ISO 8601 타임스탬프를 담아요; 스키마는 의도적으로 그 형식을 제약하지 않습니다.
오류 처리
인터럽트를 담는 성공 결과는 스키마가 이미 거부하는 모순이에요 — 성공 결과는 닫혀 있으니까요. 인터럽트된 실행을 계속하지 않는 실행의 재개 목록은 아무것도 답하지 않아요; 프로듀서는 그 항목들을 위처럼 인식되지 않은 것으로 취급합니다.
더 알아보기 (Learn more)
- 스펙 개요 — AG-UI 1.0 프로토콜의 공식 요구사항을 확인해 보세요.
- 실행 라이프사이클 (Lifecycle) — 닫힌 실행과 취소된 실행의 규칙을 살펴보세요.
- 도구 호출 (Tool calls) — 프런트엔드 도구와 인터럽트의 관계를 확인해 보세요.