휴먼 인 더 루프 (HITL)
이 가이드에서는 SDK의 승인 기반 휴먼인더루프 (HITL) 흐름을 다룹니다. 도구 호출에 승인이 필요하면 SDK는 실행을 일시 중지하고 interruptions를 반환하며, 나중에 동일한 RunState에서 실행을 재개할 수 있도록 합니다.
이 승인 적용 범위는 현재 최상위 에이전트에만 국한되지 않고 전체 실행에 적용됩니다. 도구가 현재 에이전트, 핸드오프를 통해 도달한 에이전트 또는 중첩된 agent.asTool() 실행 중 어디에 속하든 동일한 패턴이 적용됩니다. 중첩된 agent.asTool()의 경우에도 인터럽션(중단 처리)은 외부 실행에 표시되므로, 외부 result.state에서 승인하거나 거부한 다음 원래 루트 실행을 재개합니다.
agent.asTool()에서는 서로 다른 두 계층에서 승인이 발생할 수 있습니다. 에이전트 도구 자체가 asTool({ needsApproval })을 통해 승인을 요구할 수 있으며, 중첩 실행이 시작된 후 중첩된 에이전트 내부의 도구가 자체 승인을 요청할 수도 있습니다. 두 경우 모두 동일한 외부 실행 인터럽션(중단 처리) 흐름을 통해 처리됩니다.
이 페이지에서는 interruptions를 통한 수동 승인 흐름을 중점적으로 설명합니다. 애플리케이션이 코드에서 결정할 수 있는 경우, 일부 도구 유형은 프로그래밍 방식 승인 콜백도 지원하므로 실행을 일시 중지하지 않고 계속할 수 있습니다. agent.asTool() 자체를 설정하려면 도구를 참고하세요. 이 페이지에서는 해당 실행 계층 구조의 도구 중 하나라도 승인이 필요할 때 발생하는 동작을 다룹니다.
승인 흐름
섹션 제목: “승인 흐름”needsApproval 옵션을 true 또는 불리언을 반환하는 비동기 함수로 설정하여 승인이 필요한 도구를 정의할 수 있습니다.
import { tool } from '@openai/agents';import z from 'zod';
const sensitiveTool = tool({ name: 'cancelOrder', description: 'Cancel order', parameters: z.object({ orderId: z.number(), }), // always requires approval needsApproval: true, execute: async ({ orderId }, args) => { // prepare order return },});
const sendEmail = tool({ name: 'sendEmail', description: 'Send an email', parameters: z.object({ to: z.string(), subject: z.string(), body: z.string(), }), needsApproval: async (_context, { subject }) => { // check if the email is spam return subject.includes('spam'); }, execute: async ({ to, subject, body }, args) => { // send email },});- 도구 호출이 실행되기 직전에 SDK는 승인 규칙(
needsApproval또는 이에 해당하는 호스티드 MCP 규칙)을 평가합니다. - 승인이 필요하지만 아직 저장된 결정이 없으면 도구 호출은 실행되지 않습니다. 대신 실행에
RunToolApprovalItem이 기록됩니다. - 해당 턴이 끝나면 실행이 일시 중지되고, 보류 중인 모든 승인이 실행 결과의
interruptions배열에 반환됩니다. 여기에는 중첩된agent.asTool()실행 내부에서 요청된 승인도 포함됩니다. - 보류 중인 각 항목을
result.state.approve(interruption)또는result.state.reject(interruption)로 처리합니다. 실행의 나머지 부분에서 동일한 도구를 계속 승인하거나 거부하려면{ alwaysApprove: true }또는{ alwaysReject: true }를 전달합니다. 거부할 때{ message: '...' }를 전달하여 해당 도구 호출에 관해 모델로 전송되는 거부 문구를 제어할 수도 있습니다. - 업데이트된
result.state를runner.run(agent, state)에 다시 전달하여 재개합니다. 여기서agent는 해당 실행의 원래 최상위 에이전트입니다. SDK는 중첩된 에이전트 도구 실행을 포함하여 인터럽션(중단 처리)이 발생한 지점부터 계속합니다.
기본적으로 함수 도구 입력 가드레일은 승인 후 도구가 실행되기 직전에만 실행됩니다. 보류 중인 승인이 표시되기 전에 동일한 입력 가드레일로 로컬 함수 도구 호출을 검증하려면 run() 또는 Runner에 toolExecution: { preApprovalInputGuardrails: true }를 전달합니다. 사전 승인 가드레일이 거부하면 SDK는 승인 인터럽션(중단 처리)을 생성하는 대신 가드레일 메시지를 도구 출력으로 모델에 반환합니다. 호출을 허용하면 실행은 여전히 승인을 위해 일시 중지되며, 대기 중에 도구 호출이 안전하지 않은 상태로 바뀌었을 가능성에 대비해 승인 후 입력 가드레일이 다시 실행됩니다.
needsApproval이 함수이면 SDK는 도구 인수가 검사 가능한 객체로 파싱된 후에만 해당 함수를 호출합니다. 잘못된 형식의 JSON과 객체가 아닌 값은 실패 시 차단됩니다. SDK는 콜백을 호출하거나 도구를 실행하지 않고 승인을 요청합니다. 해당 호출을 승인해도 도구는 실행되지 않으며, 일반적인 인수 파싱 오류 경로로 계속 진행됩니다. Realtime 함수 도구에도 동일한 규칙이 적용되며, 잘못된 호출에 대해 tool_approval_requested를 내보냅니다.
{ alwaysApprove: true } 또는 { alwaysReject: true }로 생성한 고정 결정은 실행 상태에 저장되므로, 나중에 동일한 일시 중지 실행을 재개할 때 toString() / fromString()을 거쳐도 유지됩니다.
GA 모델에서는 컴퓨터 도구 인터럽션(중단 처리)이 하나의 computer_call에 포함된 작업 배치를 나타낼 수 있습니다. SDK는 실행 전에 작업별로 needsApproval을 평가하므로, 하나의 보류 중인 승인이 이동 + 클릭과 같은 작업 시퀀스를 포함할 수 있습니다. UI를 렌더링하기 위해 interruption.rawItem을 검사한다면 GA의 actions 배열과 레거시 단일 action 필드를 모두 처리해야 합니다.
직렬화된 RunState는 현재의 computer 도구 이름과 레거시 computer_use_preview 이름 모두에 대한 컴퓨터 승인을 보존하므로, 프리뷰에서 GA로 마이그레이션하는 동안에도 일시 중지된 실행을 문제없이 재개할 수 있습니다.
message를 제공하지 않으면 SDK는 구성된 toolErrorFormatter가 있는 경우 이를 사용하고, 그다음 기본 거부 문구를 사용합니다.
보류 중인 모든 승인을 한 번에 처리할 필요는 없습니다. 일부 항목만 승인하거나 거부한 후 다시 실행하면, 처리된 호출은 계속 진행되고 처리되지 않은 호출은 interruptions에 남아 실행을 다시 일시 중지합니다.
alwaysApprove: true 또는 alwaysReject: true로 생성한 고정 결정은 이후 동일한 도구 호출에 적용되는 기본값입니다. 하나의 호출 ID에 대한 개별 결정은 해당 고정 기본값보다 우선합니다. 다른 호출의 기본값은 변경하지 않은 채 고정 승인 상태에서 호출 하나를 거부하거나 고정 거부 상태에서 호출 하나를 승인할 수 있습니다. 나중에 해당 개별 결정을 대체하면 새 개별 결정이 해당 호출에 적용되고, 나머지 호출에는 고정 기본값이 계속 적용됩니다.
재개 전 입력 추가
섹션 제목: “재개 전 입력 추가”실행이 일시 중지된 동안 새로운 사용자 입력이 도착하여 다음 모델 호출을 재개하기 전에 반영해야 한다면 RunState.addInput()을 사용합니다. 승인을 처리하고 동일한 상태를 Runner.run()에 다시 전달하기 전에 입력을 추가합니다. 준비된 입력은 직렬화된 상태의 일부이므로, 처리되지 않은 승인이나 로컬 도구 작업으로 인해 해당 모델 호출이 지연되더라도 toString() / fromString()을 거쳐 유지됩니다.
준비된 항목의 복제된 스냅샷을 검사하려면 state.pendingInput을 읽고, 재개 전에 모든 항목을 제거하려면 state.clearPendingInput()을 호출합니다. addInput()은 문자열 또는 입력 항목 배열을 받습니다. 터미널 상태, 남은 턴이 없는 상태 또는 도구 결과가 실행을 종료할 수 있는 인터럽션(중단 처리)처럼 상태가 다음 모델 호출에 안전하게 도달할 수 없는 경우 UserError가 발생합니다.
반영된 준비 입력의 각 항목은 실행의 newItems에 RunInputItem으로 추가되고, 해당 원문 입력 항목은 history에 포함됩니다. 로컬 session을 사용할 경우 SDK는 모델 요청을 시작하기 전에 반영된 입력을 한 번 저장합니다. conversationId 또는 previousResponseId를 사용할 경우 서버가 응답을 수락할 때까지 입력은 보류 상태로 유지되므로, 수락 전에 발생한 것으로 확인된 실패는 입력을 잃지 않고 재개할 수 있습니다. 요청이 이미 수락되었을 가능성이 있다고 공급자가 보고하면 SDK는 해당 항목에 체크포인트를 설정하고, 이를 조용히 재실행하는 대신 실패 시 차단합니다. 별도의 명시적인 안전하지 않은 재실행 재정의는 모델 재시도를 참고하세요.
자동 승인 결정
섹션 제목: “자동 승인 결정”수동 interruptions가 가장 일반적인 패턴이지만 유일한 방식은 아닙니다.
- 로컬
shellTool()및applyPatchTool()은onApproval을 사용하여 코드에서 즉시 승인하거나 거부할 수 있습니다 - 호스티드 MCP 도구는
requireApproval과onApproval을 함께 사용하여 동일한 방식의 프로그래밍 방식 결정을 내릴 수 있습니다 - 일반 함수 도구는 이 페이지에서 설명하는 수동 인터럽션(중단 처리) 흐름을 사용합니다
이러한 콜백이 결정을 반환하면 사람의 응답을 기다리기 위해 일시 중지하지 않고 실행이 계속됩니다. Realtime 세션 API의 경우 음성 에이전트 구축의 승인 흐름을 참고하세요.
스트리밍 및 세션
섹션 제목: “스트리밍 및 세션”동일한 인터럽션(중단 처리) 흐름을 스트리밍 실행에도 사용할 수 있습니다. 스트리밍된 실행이 일시 중지되면 stream.completed를 기다리고 stream.interruptions를 읽어 각 항목을 처리한 다음, 재개된 출력도 계속 스트리밍하려면 { stream: true }로 run()을 다시 호출합니다. 이 패턴의 스트리밍 버전은 스트리밍 중 휴먼인더루프 (HITL)를 참고하세요.
session도 사용 중이라면 RunState에서 재개할 때 동일한 session을 계속 전달합니다. 그러면 입력을 다시 준비하지 않고 재개된 턴이 세션 메모리에 추가됩니다. 세션 수명 주기에 관한 자세한 내용은 세션을 참고하세요.
다음은 터미널에서 승인을 요청하고 상태를 파일에 임시로 저장하는 보다 완전한 휴먼인더루프 (HITL) 흐름의 예제입니다.
// This local CLI trusts its saved state. Browser/mobile approval UIs should keep// snapshots on the server; see human-in-the-loop-server.ts in agent-patterns.import { z } from 'zod';import readline from 'node:readline/promises';import fs from 'node:fs/promises';import { Agent, run, tool, RunState, RunResult } from '@openai/agents';
const getWeatherTool = tool({ name: 'get_weather', description: 'Get the weather for a given city', parameters: z.object({ location: z.string(), }), needsApproval: async (_context, { location }) => { // forces approval to look up the weather in San Francisco return location === 'San Francisco'; }, execute: async ({ location }) => { return `The weather in ${location} is sunny`; },});
const dataAgentTwo = new Agent({ name: 'Data agent', instructions: 'You are a data agent', handoffDescription: 'You know everything about the weather', tools: [getWeatherTool],});
const agent = new Agent({ name: 'Basic test agent', instructions: 'You are a basic agent', handoffs: [dataAgentTwo],});
async function confirm(question: string) { const rl = readline.createInterface({ input: process.stdin, output: process.stdout, });
const answer = await rl.question(`${question} (y/n): `); const normalizedAnswer = answer.toLowerCase(); rl.close(); return normalizedAnswer === 'y' || normalizedAnswer === 'yes';}
async function main() { let result: RunResult<unknown, Agent<unknown, any>> = await run( agent, 'What is the weather in Oakland and San Francisco?', ); let hasInterruptions = result.interruptions?.length > 0; while (hasInterruptions) { // Store the current run state await fs.writeFile( 'result.json', JSON.stringify(result.state, null, 2), 'utf-8', );
// At this point, another process could review the saved state
// Read the saved state later const storedState = await fs.readFile('result.json', 'utf-8'); const state = await RunState.fromString(agent, storedState);
for (const interruption of result.interruptions) { const confirmed = await confirm( `Agent ${interruption.agent.name} would like to use the tool ${interruption.name} with "${interruption.arguments}". Do you approve?`, );
if (confirmed) { state.approve(interruption); } else { state.reject(interruption); } }
// Resume execution from the restored state result = await run(agent, state); hasInterruptions = result.interruptions?.length > 0; }
console.log(result.finalOutput);}
main().catch((error) => { console.dir(error, { depth: null });});실제로 작동하는 엔드 투 엔드 버전은 전체 예제 스크립트를 참고하세요.
장시간 승인 대기 처리
섹션 제목: “장시간 승인 대기 처리”휴먼인더루프 (HITL) 흐름은 서버를 계속 실행하지 않고도 장시간 인터럽션(중단 처리) 상태를 유지할 수 있도록 설계되었습니다. 요청을 종료하고 나중에 계속해야 한다면 상태를 직렬화한 후 나중에 재개할 수 있습니다.
result.state.toString() 또는 JSON.stringify(result.state)을 사용하여 상태를 직렬화할 수 있습니다. 나중에 RunState.fromString(agent, serializedState)에 직렬화된 상태를 전달하여 재개할 수 있으며, 여기서 agent는 전체 실행을 시작한 에이전트 인스턴스입니다.
승인된 함수 도구 결과가 실행의 최종 출력이 될 수 있는 경우 출력 가드레일은 더 엄격한 재개 경계를 적용합니다. 현재 RunState 스키마는 SDK가 확실하게 판단할 수 있는 경우 현재 응답에서 생성된 항목의 정확한 소유권을 기록하므로, 지원되는 출력 포함 부분 승인 체크포인트는 직렬화된 상태를 통해 왕복 변환될 수 있습니다. SDK는 추가적인 모델, 도구 또는 세션 부수 효과가 발생하기 전에 해당 소유권을 검증합니다. 소유권 정보가 누락되었거나 잘못되었거나 모호한 이전 스냅샷 및 체크포인트는 여전히 UserError와 함께 실패 시 차단됩니다. 이런 경우 안전한 입력으로 새 실행을 시작하세요. 동일한 에이전트 그래프를 다시 구축하고 원래의 toolUseBehavior를 유지해야 합니다. 원문 항목을 재실행하여 소유권 오류를 우회하지 마세요.
RunState가 직렬화되면 SDK는 핸드오프 및 Agent.asTool() 그래프에 대해 안정적인 에이전트 ID를 기록합니다. 따라서 실행을 재개하는 프로세스에서 동일한 에이전트 그래프를 다시 구축한다면, 서로 다른 에이전트가 동일한 name을 공유하더라도 일시 중지된 실행을 재개할 수 있습니다.
RunState.fromString(agent, serializedState)에 전달되는 agent는 다시 구축된 그래프의 루트입니다. 역직렬화 중 SDK는 해당 에이전트의 핸드오프와 Agent.asTool() 참조를 순회한 다음, 상태에 직렬화된 모든 에이전트 참조를 다시 구축된 그래프와 대조하여 해석합니다. 여기에는 현재 에이전트와 생성 항목, 처리된 모델 응답 및 대기 중인 다음 단계가 보유한 중첩 참조가 포함됩니다.
다른 런타임에서 모델이나 도구를 래핑한 에이전트처럼 대체된 그래프로 재개해야 한다면, 원래 그래프로 상태를 역직렬화하고 다시 직렬화한 다음 대체된 루트 에이전트로 해당 문자열을 역직렬화합니다. state.setCurrentAgent(agent)를 호출하면 활성 에이전트만 변경되며, 역직렬화 중 이미 해석된 중첩 참조는 다시 작성되지 않습니다.
재개된 프로세스에 새로운 컨텍스트 객체를 주입해야 한다면 RunState.fromStringWithContext(agent, serializedState, context, { contextStrategy })를 대신 사용합니다.
contextStrategy: 'merge'(기본값)는 제공된RunContext를 유지하고 직렬화된 승인 상태를 병합하며, 새 컨텍스트에 아직 정의되지 않은 경우 직렬화된toolInput을 복원합니다contextStrategy: 'replace'는 제공된RunContext를 그대로 사용하여 실행을 다시 구축합니다
직렬화된 실행 상태에는 애플리케이션 컨텍스트뿐 아니라 승인, 사용량, 중첩된 toolInput, 보류 중인 중첩 에이전트 도구 재개와 같이 SDK가 관리하는 런타임 메타데이터도 포함됩니다. 직렬화된 상태를 저장하거나 전송할 계획이라면 runContext.context를 영속 데이터로 취급하고, 상태와 함께 전달하려는 경우가 아니라면 그 안에 비밀 정보를 넣지 마세요.
기본적으로 트레이싱 API 키는 직렬화된 상태에서 제외되므로 실수로 비밀 정보를 영속화하지 않습니다. 상태와 함께 트레이싱 자격 증명을 의도적으로 이동해야 할 때만 result.state.toString({ includeTracingApiKey: true })를 전달하세요.
직렬화된 상태는 서버 측 데이터베이스와 같이 애플리케이션에서 제어하는 스토리지에 저장하세요.
서버의 승인 상태 유지
섹션 제목: “서버의 승인 상태 유지”직렬화된 RunState에는 승인 결정, 보류 중인 도구 호출, 도구 인수 및 애플리케이션 컨텍스트를 포함한 실행 상태가 들어 있습니다. RunState.fromString()은 해당 상태를 복원하며, 이 메서드는 스냅샷이나 이를 제출한 사람을 인증하지 않습니다. 신뢰할 수 있는 스토리지의 스냅샷만 역직렬화하거나, 애플리케이션에서 전체 스냅샷의 무결성, 소유권 및 재실행 방지를 검증한 후 역직렬화하세요. 스키마 검증과 도구 호출 지문은 스냅샷을 인증하지 않습니다. 에이전트 또는 생성 항목 소유권에 관한 SDK 참조도 애플리케이션 사용자를 인가하지 않습니다.
브라우저 또는 모바일 승인 인터페이스에서는 전체 스냅샷을 애플리케이션이 제어하는 서버 스토리지에 보관하세요. 검토자에게는 보류 중인 결정에 관한 인가된 표시 정보와 불투명 식별자만 전송합니다. 실행 ID 또는 결정 ID는 인가 증명이 아닙니다. 도구 이름과 인수는 신뢰할 수 없는 표시 콘텐츠로 취급해야 합니다. 민감한 값을 필터링하고 HTML을 렌더링할 때 콘텐츠를 이스케이프하세요. 전체 실행 결과와 오류는 서버에 보관하고, 애플리케이션에서 선택한 출력만 클라이언트에 반환합니다.
결정이 도착하면 서버는 다음 작업을 수행해야 합니다.
- 애플리케이션의 세션 또는 인증 미들웨어를 사용하여 검토자를 인증합니다. 승인 요청 본문에서 인증된 ID를 가져오면 안 됩니다.
- 저장된 실행과 선택된 보류 중 호출을 기준으로 검토자를 인가합니다.
- 서버에 저장된 보류 중 요청을 기준으로 결정 식별자와 불리언 결정을 검증합니다. 클라이언트에서 대체 도구 호출, 인수, 승인 기록 또는 직렬화된 상태를 받지 마세요.
- 역직렬화 및 재개된 실행 전에 소유권을 원자적으로 확인하고 보류 중 요청을 소비 처리합니다. 동시에 제출되거나 재실행된 제출이 동일한 스냅샷을 두 번 재개해서는 안 됩니다. 공유 스토리지에서는 트랜잭션 또는 이에 상응하는 원자적 조건부 전환을 사용합니다.
- 서버 소유 스냅샷을 로드하고
state.getInterruptions()로 보류 중 항목을 가져온 다음, 실행을 재개하기 전에 해당 항목에만state.approve()또는state.reject()를 적용합니다.
다음 예제에서는 배치의 보류 중인 모든 호출에 대해 하나씩 결정을 요구합니다. 이는 애플리케이션 정책이며, SDK는 앞서 설명한 부분 승인도 지원합니다. 스토어는 단일 프로세스의 하나의 이벤트 루프로 제한됩니다. 소유자 확인, 검증 및 소비 처리 사이에는 await가 없습니다. 역직렬화나 실행이 실패하거나 실행이 취소되더라도 요청은 소비된 상태로 유지됩니다. 소비 처리는 이 스냅샷의 재제출을 방지하지만 도구의 부수 효과가 정확히 한 번만 발생함을 보장하지는 않습니다. 복구 또는 다른 실행을 시작하기 전에 완료되었거나 결과가 불확실한 도구 부수 효과를 조정하세요.
import { randomUUID } from 'node:crypto';import { Agent, Runner, RunState, type RunResult } from '@openai/agents';import { z } from 'zod';
export type PendingApproval = { kind: 'approval'; requestId: string; prompts: { decisionId: string; toolName: string; arguments: string }[];};
type StoredRun = { ownerId: string; snapshot: string; decisionIds: string[];};
// Simulation only: one event loop in one process. Production storage needs an// atomic owner-checked consume operation and bounded retention. Consumption is// permanent even after failure/cancellation; reconcile side effects before retry.export class ApprovalServer { #agent: Agent; #runner = new Runner({ tracingDisabled: true }); #pending = new Map<string, StoredRun>();
constructor(agent: Agent) { this.#agent = agent; }
// An HTTP adapter must obtain this identity from trusted authentication // middleware and apply request/CSRF protections. Never read it from the body. async start(authenticatedUserId: string, message: string) { const result = await this.#runner.run(this.#agent, message); return this.#save(authenticatedUserId, result); }
#save<TContext, TAgent extends Agent<any, any>>( ownerId: string, result: RunResult<TContext, TAgent>, ) { const interruptions = result.state.getInterruptions(); if (interruptions.length === 0) { // RunResult stays on the server; the app selects what the client may see. return { kind: 'completed' as const, output: result.finalOutput }; } const requestId = randomUUID(); const decisionIds = interruptions.map(() => randomUUID()); this.#pending.set(requestId, { ownerId, snapshot: result.state.toString(), decisionIds, }); const response: PendingApproval = { kind: 'approval', requestId, // Detached display values only. Filter arguments for the reviewer's access // policy; this demo uses synthetic weather data. Escape HTML in a web UI. prompts: interruptions.map((item, index) => ({ decisionId: decisionIds[index], toolName: item.name ?? 'unknown_tool', arguments: item.arguments ?? '', })), }; return response; }
async decide( authenticatedUserId: string, requestId: string, decisions: unknown, signal?: AbortSignal, ) { const stored = this.#pending.get(requestId); if (!stored || stored.ownerId !== authenticatedUserId) { throw new Error('Approval request is unavailable.'); } // Validate JSON request data, not client-supplied calls, state, or identity. const parsed = z.record(z.string(), z.boolean()).safeParse(decisions); if ( !parsed.success || Object.keys(parsed.data).length !== stored.decisionIds.length || !stored.decisionIds.every((id) => Object.prototype.hasOwnProperty.call(parsed.data, id), ) ) { throw new Error( 'Provide one boolean decision for every pending tool call.', ); } // No await between the owner check and consume. The parsed decisions are a // detached copy, so client mutation while deserialization awaits has no effect. this.#pending.delete(requestId); const state = await RunState.fromString(this.#agent, stored.snapshot); const interruptions = state.getInterruptions(); for (const [index, interruption] of interruptions.entries()) { if (parsed.data[stored.decisionIds[index]]) { state.approve(interruption); } else { state.reject(interruption); } } const result = await this.#runner.run(this.#agent, state, { signal }); return this.#save(authenticatedUserId, result); }}전체 예제는 CLI 클라이언트/서버 시뮬레이션을 참고하세요. 이는 배포 가능한 HTTP 서비스가 아닙니다. 프로덕션 애플리케이션에서는 신뢰할 수 있는 인증, 표시된 세부 정보와 선택된 호출에 대한 인가, 해당하는 경우 요청 및 CSRF 보호, 제한된 스토리지 보존 기간, 공유 스토리지의 원자적 소비 처리, 복구 정책을 제공해야 합니다. CLI는 합성 도구 데이터를 사용하고 트레이싱을 비활성화합니다. 실제 애플리케이션 스냅샷과 진단 정보에는 민감한 데이터가 포함될 수 있습니다.
contextStrategy: 'replace'를 사용하는 RunState.fromStringWithContext() 또는 직렬화된 승인 기록만 제거하는 방식으로는 신뢰할 수 없는 스냅샷을 안전하게 만들 수 없습니다. 다른 필드도 여전히 실행을 제어합니다. 클라이언트가 전체 스냅샷을 전송한다면 역직렬화 전에 전체 스냅샷의 무결성을 검증하고, 인가된 사용자 및 실행에 바인딩하며, 재실행을 방지하세요. 무결성 검증은 스냅샷을 암호화하거나 클라이언트로부터 내용을 숨기지 않습니다.
보류 중 작업의 버전 관리
섹션 제목: “보류 중 작업의 버전 관리”승인 요청에 시간이 오래 걸리고 에이전트 정의의 버전을 의미 있게 관리하거나 Agents SDK 버전을 올리려는 경우, 현재는 패키지 별칭을 사용해 두 버전의 Agents SDK를 병렬로 설치하고 자체 분기 로직을 구현하는 방식을 권장합니다.
실제로는 자체 코드에 버전 번호를 할당하고 직렬화된 상태와 함께 저장한 다음, 올바른 코드 버전으로 역직렬화되도록 안내해야 합니다.