
AI Agent를 몇 턴 사용하다 보면 한 가지 문제가 생긴다.
처음에는 Agent가 작업을 잘 이해하고 있다.
사용자
이 프로젝트의 로그인 문제를 조사해줘.
Architecture는 그대로 유지하고
최소 수정으로 해결해줘.
Agent가 Repository를 조사한다.
AuthManager
LoginViewModel
TokenStore
몇 차례 대화하면서 원인을 좁힌다.
1턴
→ Authentication 구조 확인
2턴
→ Race Condition 후보 발견
3턴
→ Token 갱신 흐름 확인
4턴
→ 수정 방향 결정
그런데 대화가 길어지면 이런 현상이 생긴다.
왜 이 파일을 조사했는지 다시 설명해야 한다.
이미 제외한 가설을 다시 검토한다.
앞에서 합의한 우선순위를 잊는다.
같은 Tool을 다시 호출한다.
이미 했던 판단을 다시 한다.
그래서 많은 Agent 시스템이 모든 것을 Context에 쌓기 시작한다.
System Prompt
+
Conversation
+
Repository 정보
+
Tool 결과
+
Task 상태
+
요약
+
Memory
+
이전 판단
그리고 Context Window가 커진다.
하지만 GPT-5.6에서는 여기서 한 가지 새로운 선택지가 생겼다.
Persisted Reasoning이다.
GPT-5.6은 이전 턴의 reasoning item을 다음 턴에서 다시 활용할 수 있도록 reasoning.context를 제공한다.
그래서 이제 Agent 상태를 최소 세 가지로 구분해서 생각할 필요가 있다.
Memory
Context
Persisted Reasoning
이 셋은 비슷해 보이지만 역할이 전혀 다르다.
이번 글에서는 이 세 가지를 어떻게 분리하고, 장시간 실행되는 Agent Runtime에서 어떻게 관리할지 정리해본다.
가장 간단하게 정리하면 이렇다.
Memory
→ 오래 기억해야 할 사실
Context
→ 지금 작업하는 데 필요한 정보
Persisted Reasoning
→ 앞선 판단을 이어가기 위한 추론 연속성
예를 들어 iOS Agent가 있다고 하자.
이 프로젝트는 Swift 6을 사용한다.
외부 라이브러리 추가에는 승인이 필요하다.
Generated 디렉터리는 직접 수정하지 않는다.
이런 정보는 여러 Task에서도 계속 유효할 수 있다.
현재 Task가
로그인 실패 시 사용자 메시지를 수정한다.
라면 필요한 Context는
LoginViewModel.swift
AuthErrorMapper.swift
관련 테스트
현재 Git Diff
Task 요구사항
정도다.
Task가 끝나면 대부분 필요 없어진다.
Agent가 조사하면서 이런 판단을 했다고 하자.
네트워크 계층 문제 가능성은 낮다.
AuthErrorMapper에서 서버 오류가 그대로 전달되고 있다.
Public API를 변경할 필요는 없다.
수정 범위는 ErrorMapper와 Test면 충분하다.
다음 턴에서도 같은 Task를 계속한다면 이런 판단의 연속성이 유용하다.
이 역할에 가까운 것이 Persisted Reasoning이다.
여기서 가장 많이 헷갈릴 수 있다.
이전 Reasoning을 유지한다.
=
Memory다.
가 아니다.
Memory는 보통 사실이나 장기 상태를 저장한다.
예를 들어
{
"project": {
"language": "Swift 6",
"architecture": "MVVM"
}
}
또는
{
"userPreferences": {
"preferSmallPR": true
}
}
처럼 명시적인 데이터를 저장할 수 있다.
반면 Persisted Reasoning은 GPT-5.6이 이전 응답에서 생성한 reasoning item을 다음 턴에서 활용하게 하는 기능이다.
중요한 차이가 있다.
Memory
Application이 관리하는 정보
Persisted Reasoning
Model의 이전 reasoning 상태를 이어가는 기능
따라서 Agent의 영구 데이터베이스를 Persisted Reasoning으로 대체해서는 안 된다.
Context는 모델에게 현재 요청을 처리하기 위해 제공하는 입력 정보다.
예를 들어
System Prompt
Task
Repository files
Tool results
Previous messages
같은 것들이다.
Context는 쉽게 말해
지금 모델이 보고 있는 작업 공간
이다.
반면 Persisted Reasoning은
이 작업을 이전에 어떻게 이해하고 있었는가
의 연속성에 가깝다.
둘은 서로 보완 관계다.
Context
→ 무엇을 보고 있는가
Reasoning
→ 그것을 어떻게 판단해 왔는가
GPT-5.6 Responses API에는 reasoning.context 설정이 있다.
현재 주요 선택지는 다음처럼 이해하면 된다.
auto
all_turns
current_turn
기본 동작을 모델과 API에 맡긴다.
reasoning.context = auto
특별한 이유가 없다면 시작점으로 사용할 수 있다.
앞선 턴의 Reasoning이 현재 작업에도 계속 중요할 때 사용한다.
reasoning.context = all_turns
예를 들어
대규모 Refactoring
복잡한 Debugging
Research
Migration
장시간 Agent
처럼 여러 턴 동안 같은 목표와 가정이 유지되는 작업이다.
과거 Reasoning을 이어갈 필요가 없을 때 사용한다.
reasoning.context = current_turn
새로운 Task로 전환했거나 앞선 판단이 더 이상 의미가 없다면 이쪽이 낫다.
all_turns를 사용할지 판단하는 가장 좋은 질문은 이것이다.
앞선 턴의 목표와 가정이
지금도 유효한가?
YES라면
all_turns
후보다.
NO라면
current_turn
으로 끊는 편이 좋다.
예를 들어
Task A
로그인 Race Condition 조사
를 계속하고 있다.
Turn 1
Architecture 분석
Turn 2
가능한 원인 후보
Turn 3
로그 확인
Turn 4
수정
Turn 5
Test
같은 Goal이다.
이때는 Reasoning 연속성이 유용하다.
하지만 사용자가 갑자기
이제 로그인 문제는 끝났고
설정 화면 UI를 만들어줘.
라고 한다.
Task가 완전히 달라졌다.
Auth Debugging Reasoning
↓
Settings UI
이전 추론을 계속 유지할 이유가 적다.
이때 Reasoning을 끊는 것이 자연스럽다.
Persisted Reasoning이라는 이름 때문에
항상 all_turns를 쓰면
더 똑똑하지 않을까?
라고 생각할 수 있다.
그렇지 않다.
오래된 판단이 현재 Task와 맞지 않을 수 있다.
예를 들어 Agent가 초기에 이렇게 판단했다.
원인은 TokenStore일 가능성이 가장 높다.
그런데 새로운 로그가 들어왔다.
TokenStore는 정상.
실제 원인은 Session Refresh.
Agent가 오래된 가설에 과도하게 매달리면 문제가 된다.
Reasoning도 Context처럼 수명이 있는 정보로 봐야 한다.
실전에서는 세 개보다 조금 더 세분화하는 편이 관리하기 좋다.
1. Durable Memory
2. Task Context
3. Execution State
4. Persisted Reasoning
구조로 나눈다.
Task가 끝나도 남는 정보다.
Repository 규칙
사용자 선호
프로젝트 Architecture
정책
팀 Convention
예:
{
"repository": {
"language": "Swift 6",
"architecture": "MVVM",
"generatedPath": "Generated/"
}
}
현재 작업에 필요한 자료다.
Issue
관련 코드
현재 Diff
Test
Reference
사용자의 현재 요구사항
예:
{
"taskId": "PAP-142",
"title": "Fix login error handling",
"files": [
"AuthErrorMapper.swift",
"LoginViewModel.swift"
]
}
Agent Runtime 자체의 상태다.
현재 Step
완료한 Tool
생성된 Artifact
Retry 횟수
Budget
Checkpoint
예:
{
"taskId": "PAP-142",
"step": "verification",
"completedSteps": [
"analysis",
"implementation"
],
"retryCount": 0
}
앞선 Model 판단을 이어가는 계층이다.
이건 애플리케이션에서
{
"reasoning": "..."
}
형태로 직접 저장하는 일반 Memory와 다르다.
Responses API에서 Reasoning Item을 유지하고 다음 턴에 다시 사용할 수 있도록 연결한다.
Agent Runtime
│
┌─────────────┼─────────────┐
│ │ │
Memory Context Execution
│ │ │
│ │ │
└───────┬─────┴─────┬───────┘
│ │
▼ ▼
Model Reasoning State
│
└─────┬─────┘
│
Result
각 계층은 책임이 다르다.
Responses API에서는 이전 Response를 이어갈 때 previous_response_id를 사용할 수 있다.
개념적으로 첫 요청은 다음과 같다.
const first = await openai.responses.create({
model: "gpt-5.6-sol",
reasoning: {
effort: "high",
context: "all_turns"
},
input: `
Investigate the login race condition.
Preserve the existing public API.
`
});
다음 요청은 앞선 Response를 연결한다.
const second = await openai.responses.create({
model: "gpt-5.6-sol",
previous_response_id: first.id,
reasoning: {
effort: "high",
context: "all_turns"
},
input: `
The latest test log is attached.
Continue the investigation.
`
});
같은 Task가 계속되기 때문에 앞선 Reasoning을 활용할 수 있다.
다음 사용자 요청이 완전히 다른 작업이라면
SettingsView를 새 디자인으로 만들어줘.
이전 Auth Reasoning을 끌고 갈 필요가 없다.
const response = await openai.responses.create({
model: "gpt-5.6-terra",
reasoning: {
effort: "medium",
context: "current_turn"
},
input: `
Implement the new SettingsView.
`
});
또는 새로운 Agent Run 자체를 시작한다.
핵심은
Conversation이 계속된다
≠
Reasoning도 반드시 계속돼야 한다
는 것이다.
Chat UI에서는 한 Conversation 안에서 여러 작업을 할 수 있다.
Conversation #123
├── 로그인 Bug
├── 설정 화면
├── Push Notification
└── Test 개선
하지만 Agent Runtime에서는 이것을 하나의 Reasoning Session으로 취급하면 좋지 않다.
Task별로 나눈다.
Conversation #123
Task A
Login Bug
Reasoning Session A
Task B
Settings UI
Reasoning Session B
Task C
Push Notification
Reasoning Session C
이 구조가 훨씬 명확하다.
예를 들어
type AgentTask = {
conversationId: string;
taskId: string;
runId: string;
reasoningPolicy:
| "auto"
| "all_turns"
| "current_turn";
};
실제 값은
{
"conversationId": "conv-71",
"taskId": "PAP-142",
"runId": "run-9912",
"reasoningPolicy": "all_turns"
}
처럼 관리한다.
사용자가 같은 Chat 화면에 있어도 Task가 바뀌면 새로운 Run을 만든다.
간단한 구조를 만든다면 다음처럼 할 수 있다.
type ReasoningSession = {
taskId: string;
previousResponseId?: string;
mode:
| "auto"
| "all_turns"
| "current_turn";
createdAt: number;
updatedAt: number;
};
Task Store에서 관리한다.
class ReasoningSessionStore {
private sessions =
new Map<string, ReasoningSession>();
get(taskId: string) {
return this.sessions.get(taskId);
}
save(session: ReasoningSession) {
this.sessions.set(
session.taskId,
session
);
}
clear(taskId: string) {
this.sessions.delete(taskId);
}
}
async function runAgent(
taskId: string,
input: string
) {
const session =
reasoningStore.get(taskId);
const response =
await openai.responses.create({
model: "gpt-5.6-sol",
previous_response_id:
session?.previousResponseId,
reasoning: {
effort: "high",
context:
session?.mode ?? "auto"
},
input
});
reasoningStore.save({
taskId,
previousResponseId: response.id,
mode: "all_turns",
createdAt:
session?.createdAt ?? Date.now(),
updatedAt: Date.now()
});
return response;
}
핵심은 Agent Runtime이
Task
↔
Response Chain
관계를 알고 있다는 것이다.
이렇게 만드는 것은 좋지 않다.
Agent에게 묻는다.
"이전 추론을 계속 기억할까요?"
Reasoning State 정책은 Runtime이 관리해야 한다.
Model
→ Task 내용 판단
Runtime
→ State Policy 결정
왜냐하면
보존 기간
비용
Task Boundary
Privacy
Retry
Failure Recovery
같은 정책은 Model이 아니라 시스템 책임이기 때문이다.
앞 글의 Model Router와 비슷하다.
type ReasoningTaskProfile = {
sameGoal: boolean;
assumptionsStable: boolean;
taskChanged: boolean;
previousReasoningUseful: boolean;
};
function selectReasoningContext(
task: ReasoningTaskProfile
):
| "all_turns"
| "current_turn" {
if (task.taskChanged) {
return "current_turn";
}
if (
task.sameGoal &&
task.assumptionsStable &&
task.previousReasoningUseful
) {
return "all_turns";
}
return "current_turn";
}
처음에는 이 정도 규칙이면 충분하다.
예를 들어 다음은 Reasoning을 이어가기 좋다.
Turn 1
AuthManager 분석
Turn 2
관련 Test 확인
Turn 3
실패 로그 추가
Turn 4
수정 구현
Turn 5
Test 실패 원인 분석
계속 같은 Goal이다.
Login Race Condition 해결
Reasoning Continuity의 가치가 높다.
같은 Task여도 중요한 가정이 바뀔 수 있다.
예를 들어 처음 Task는
NetworkClient를 수정할 수 있다.
였다.
그런데 사용자가 중간에 말한다.
NetworkClient는 공용 SDK라 수정하면 안 돼.
큰 제약이 바뀌었다.
이럴 때는 이전 Reasoning이 오히려 방해가 될 수 있다.
Reasoning Context를 Reset하고 새 제약으로 다시 판단하게 하는 것이 안전할 수 있다.
Critical Constraint Changed
→ Reasoning Reset
예:
Agent 초기 판단
TokenStore 문제
그런데 새로운 Evidence가 들어왔다.
Trace
TokenStore 정상
SessionManager에서 Race 발생
이때 두 가지 전략이 있다.
기존 가설
+
새 Evidence
→ 기존 판단 수정
새 Evidence
→ 처음부터 재평가
어느 쪽이 좋은지는 Task 성격에 따라 다르다.
그래서 Runtime에 Reset 조건을 둘 수 있다.
type ReasoningResetReason =
| "new_task"
| "critical_constraint_changed"
| "goal_changed"
| "invalidated_assumption"
| "manual_reset";
그리고
function shouldResetReasoning(
reason: ReasoningResetReason
): boolean {
return [
"new_task",
"critical_constraint_changed",
"goal_changed",
"invalidated_assumption",
"manual_reset"
].includes(reason);
}
처럼 단순하게 시작할 수 있다.
Persisted Reasoning과 Memory를 분리하는 가장 중요한 이유 중 하나다.
Reasoning에는 임시 가설이 있을 수 있다.
TokenStore가 문제일 가능성이 높다.
이걸 Memory에 저장하면 위험하다.
Memory에는 검증된 결과를 저장한다.
Confirmed:
AuthErrorMapper is the production error mapping path.
즉
Reasoning
가설
판단
탐색
우선순위
와
Memory
확정된 사실
지속되는 규칙
사용자 선호
정책
을 구분해야 한다.
Agent가 알아낸 모든 정보를 바로 Memory로 보내지 않는다.
Reasoning
↓
Finding
↓
Validation
↓
Memory Candidate
↓
Policy
↓
Durable Memory
예를 들어 Agent가 알아냈다.
LegacyAuthService는 이름과 달리
현재 Production에서 사용 중이다.
코드와 배포 설정으로 확인됐다.
이건 Memory Candidate가 될 수 있다.
반면
Token Refresh가 가끔 느린 것 같다.
정도는 Memory에 넣으면 안 된다.
type MemoryRecord = {
key: string;
value: string;
source:
| "repository"
| "user"
| "tool"
| "verified_agent";
confidence:
| "confirmed"
| "probable";
createdAt: number;
};
예:
{
"key": "auth.productionMapper",
"value": "AppErrorMapper",
"source": "repository",
"confidence": "confirmed",
"createdAt": 1785200000
}
Reasoning의 임시 판단과 구분하기 쉬워진다.
Context를 DB에 하나의 거대한 문자열로 저장할 필요도 없다.
Task마다 필요한 정보를 조립한다.
Context Builder
├── Task
├── Relevant Memory
├── Repository Files
├── Current Diff
├── Relevant Tool Results
└── Policy
예를 들어
type AgentContext = {
task: Task;
memories: MemoryRecord[];
files: string[];
artifacts: Artifact[];
policies: Policy[];
};
처럼 관리한다.
그리고 각 턴에 필요한 것만 선택한다.
이 부분이 중요하다.
관련 파일을 다시 찾는다
≠
이전 Reasoning을 다시 사용한다
Context Retrieval은
현재 Task에 필요한 자료는 무엇인가?
를 해결한다.
Persisted Reasoning은
앞선 판단을 계속 이어갈 필요가 있는가?
를 해결한다.
두 기능을 하나로 만들지 않는다.
장시간 Agent에서는 Tool Result가 계속 쌓인다.
git diff
test logs
search result
build output
database query
API response
전부 Conversation에 계속 넣으면 Context가 커진다.
Tool Result는 보통 Artifact Store에 보관한다.
Artifact Store
run-9912/
├── git-diff.patch
├── test-result.json
├── build.log
└── investigation.json
모델 Context에는 필요한 요약과 Reference만 넣는다.
Artifact는 작업의 결과물이다.
Patch
Test Report
Screenshot
Log
Review Result
Memory는 이후 판단에 사용할 지속 정보다.
예를 들어
test-result.json
은 Artifact다.
하지만
이 Repository에서 Integration Test는
Simulator iPhone 17 Pro에서 실행해야 한다.
같은 지속 규칙은 Memory가 될 수 있다.
type AgentRunState = {
task: {
taskId: string;
goal: string;
status: string;
};
reasoning: {
previousResponseId?: string;
context:
| "auto"
| "all_turns"
| "current_turn";
};
execution: {
step: string;
retryCount: number;
toolCalls: number;
};
context: {
memoryIds: string[];
artifactIds: string[];
filePaths: string[];
};
};
이 구조만 봐도 역할이 구분된다.
실제 Agent Runtime이라면 다음처럼 나눌 수 있다.
src/
├── runtime/
│ ├── AgentRuntime.ts
│ └── AgentRunState.ts
│
├── reasoning/
│ ├── ReasoningSession.ts
│ ├── ReasoningPolicy.ts
│ └── ReasoningResetPolicy.ts
│
├── memory/
│ ├── MemoryStore.ts
│ ├── MemoryRecord.ts
│ └── MemoryPromotion.ts
│
├── context/
│ ├── ContextBuilder.ts
│ └── ContextRetriever.ts
│
├── artifacts/
│ ├── ArtifactStore.ts
│ └── Artifact.ts
│
└── tasks/
├── TaskStore.ts
└── Task.ts
Reasoning, Memory, Context가 서로 다른 Module이다.
장시간 Agent에서는 Checkpoint가 필요하다.
예를 들어
Step 1
Repository 분석
Step 2
Plan
Step 3
Code 변경
Step 4
Test
Step 3에서 Process가 죽었다.
Agent가 다시 실행될 때
어디까지 했는가?
를 알아야 한다.
이것은 Persisted Reasoning보다 Execution Checkpoint 문제다.
{
"taskId": "PAP-142",
"completedSteps": [
"repository_analysis",
"implementation"
],
"nextStep": "verification"
}
이 데이터는 반드시 Runtime이 명시적으로 저장해야 한다.
Reasoning State만 믿으면 안 된다.
Agent가 Crash됐을 때 시스템이 알아야 하는 것은
무슨 생각을 하고 있었는가?
뿐만 아니다.
더 중요한 것은
무슨 작업이 실제로 완료됐는가?
다.
그래서
Reasoning
→ 판단 연속성
Checkpoint
→ 실행 연속성
으로 분리한다.
조금 더 확장하면 다음 다섯 가지가 된다.
Memory
Context
Reasoning
Execution
Artifact
장기 사실
현재 필요한 정보
앞선 판단의 연속성
작업 진행 상태
실제 만들어진 결과
이 다섯 가지를 분리하면 Agent Runtime이 훨씬 안정적이다.
Task:
FramePublisher의 Race Condition을 수정한다.
Agent가 시작한다.
Swift 6
Actor isolation 사용
외부 라이브러리 추가 금지
FramePublisher.swift
CameraSession.swift
관련 Test
Race는 callback queue와 actor boundary 사이에서 발생할 가능성이 높다.
analysis completed
implementation pending
아직 없음.
Agent가 코드를 수정한다.
analysis completed
implementation completed
verification pending
frame-publisher-fix.patch
테스트를 실행한다.
test-result.json
작업이 끝난다.
이제 Reasoning Session은 종료할 수 있다.
하지만 일부 사실은 Memory 후보가 될 수 있다.
Task가 완료됐다고 해서 모든 State를 남길 필요가 없다.
Task Done
↓
Reasoning Session Close
Durable Memory만 필요한 내용을 승격한다.
Artifact는 보관 정책에 따라 저장한다.
Execution State는 완료 상태로 고정한다.
Reasoning
→ 종료
Context
→ 폐기
Execution
→ completed
Artifact
→ 보존
Memory
→ 필요한 것만 Promotion
이 흐름이 깔끔하다.
사용자가 작업을 중단했다가 일주일 뒤 돌아올 수도 있다.
그때 이전 Reasoning을 그대로 이어가는 것이 항상 좋은 것은 아니다.
그래서 Session TTL을 둔다.
type ReasoningPolicy = {
maxIdleMs: number;
};
예:
const policy = {
maxIdleMs:
1000 * 60 * 60 * 4
};
4시간 이상 작업이 중단됐다면 새 Context를 만들고 Reasoning도 재검토한다.
TTL 값은 서비스 특성에 따라 다르게 설정한다.
예를 들어 Agent가 Branch A를 기준으로 Reasoning했다.
그 사이 대규모 Merge가 발생했다.
Reasoning
기준 SHA
abc123
현재 Repository
SHA
xyz981
코드 상태가 크게 달라졌다.
이전 Reasoning이 현재 Repository에 맞지 않을 수 있다.
그래서 Reasoning Session에 Base Revision을 기록하는 것도 방법이다.
type ReasoningSession = {
taskId: string;
previousResponseId?: string;
baseRevision?: string;
};
Revision 변화가 크면 Reasoning Reset을 검토한다.
Coding Agent에서는
Task
↔
Git Branch
↔
Reasoning Session
관계를 만들 수 있다.
예:
{
"taskId": "PAP-142",
"branch": "fix/PAP-142-auth-race",
"reasoningSession": "rs-291"
}
다른 Branch로 이동하면 새로운 Session을 만들 수 있다.
Planner와 Developer Agent가 있다고 하자.
Planner
→ Developer
Planner의 Reasoning 전체를 Developer에게 그대로 전달하고 싶어질 수 있다.
하지만 두 Agent의 역할은 다르다.
Planner Reasoning
Architecture 후보
Trade-off
Task 분해
Developer에게 필요한 것은 보통
Plan
Constraints
Acceptance Criteria
다.
Reasoning 자체를 공유하기보다 Artifact를 공유한다.
Planner
Reasoning
↓
Plan Artifact
↓
Developer
이 구조가 Agent 간 결합도를 낮춘다.
예:
type ImplementationPlan = {
goal: string;
files: string[];
constraints: string[];
steps: Array<{
id: string;
description: string;
}>;
acceptanceCriteria: string[];
};
Planner가 생성한다.
{
"goal": "Fix auth race condition",
"files": [
"FramePublisher.swift"
],
"constraints": [
"Do not change public API"
],
"steps": [
{
"id": "1",
"description": "Move state mutation into actor isolation"
}
],
"acceptanceCriteria": [
"Existing tests pass",
"Race regression test passes"
]
}
Developer는 이 Artifact를 Context로 사용한다.
Planner의 Persisted Reasoning을 공유할 필요가 없다.
Agent A가 잘못된 가설을 가질 수 있다.
그 Reasoning을 Agent B가 그대로 물려받으면 Bias도 전달된다.
예:
Agent A
"Network 문제다."
Agent B까지
Network 문제라고 가정하고 조사
하게 만들 수 있다.
대신
Evidence
Artifact
Contract
를 공유한다.
각 Agent는 자신의 Reasoning Session을 갖는다.
Developer가
이 구현이 가장 안전하다.
라고 판단했다고 해서 Reviewer도 같은 Reasoning을 이어받을 필요는 없다.
오히려 독립적으로 보는 것이 좋다.
Developer Reasoning
X
Reviewer Reasoning
공유하는 것은
Task
Diff
Test Result
Acceptance Criteria
정도다.
Reviewer가 독립적으로 판단한다.
Reasoning State는 감사 로그를 대체하지 않는다.
운영에서는 반드시 별도의 기록이 필요하다.
누가
언제
어떤 Tool을
어떤 입력으로
어떤 결과를 만들었는가
예:
{
"runId": "run-9912",
"events": [
{
"type": "tool_call",
"tool": "run_tests",
"status": "completed"
}
]
}
이런 정보가 Audit Log다.
Persisted Reasoning과 역할이 다르다.
여기서 중요한 점이 하나 있다.
Persisted Reasoning은
모델의 내부 생각을
사용자에게 그대로 공개한다.
는 기능이 아니다.
Responses API의 Reasoning Item을 모델의 다음 턴에서 재사용하는 기능으로 이해해야 한다.
UI에서 사용자에게 보여줄 필요가 있는 정보는 별도의 Summary나 Artifact로 만든다.
예:
{
"investigationSummary": {
"confirmed": [
"Race occurs during session refresh"
],
"excluded": [
"TokenStore"
],
"nextAction": "Patch SessionManager"
}
}
이런 형태가 더 안전하고 운영하기 쉽다.
장기 Agent라면
왜 이런 결정을 했지?
를 사람이 확인해야 할 때가 있다.
Reasoning State를 직접 쓰지 말고 Decision Log를 만든다.
type DecisionRecord = {
decision: string;
evidence: string[];
alternatives?: string[];
timestamp: number;
};
예:
{
"decision": "Modify SessionManager instead of TokenStore",
"evidence": [
"race-test-18.log",
"SessionManager.swift:142"
],
"alternatives": [
"TokenStore modification"
],
"timestamp": 1785200012
}
이건 사람이 읽고 검토할 수 있는 Artifact다.
Task가 매우 길다면 중간 Checkpoint마다 사람이 읽을 수 있는 Summary를 생성할 수 있다.
Checkpoint 1
Investigated
- AuthManager
- TokenStore
Excluded
- NetworkClient
Current hypothesis
- Session refresh race
Next
- Reproduce with concurrent refresh test
이 Summary는 Persisted Reasoning과 별개다.
Persisted Reasoning
→ Model Continuity
Reasoning Summary
→ Human / Runtime Visibility
역할을 나눈다.
OpenAI 공식 가이드에서는 직접 History를 관리하거나 store: false, Zero Data Retention 환경에서 동작할 때 이전 입력과 Response Output Item을 다시 제공하는 흐름을 설명한다.
GPT-5.6에서는 이런 경우 API가 반환하는 encrypted reasoning item을 재전달하는 방식도 고려해야 한다.
즉
Server-managed continuation
vs
Application-managed continuation
두 구조가 있다.
가장 간단한 흐름은
Response 1
↓
previous_response_id
↓
Response 2
↓
previous_response_id
↓
Response 3
이다.
Application은 Response ID 체인을 관리한다.
Task
→ latestResponseId
정도만 저장하면 된다.
일부 시스템에서는 모든 History를 직접 저장한다.
User Input
Response Output Items
Tool Calls
Tool Results
Reasoning Items
을 Application Store에 보관하고 다음 요청에 재구성한다.
장점은
State 관리가 명확함
재현 가능성
Custom Retention
Migration
이다.
대신 구현이 복잡하다.
작은 Agent라면
previous_response_id
방식부터 시작하는 것이 단순하다.
큰 Agent Platform에서는
Task Store
Artifact Store
Memory Store
Execution Store
Response State
를 애플리케이션에서 관리하는 방향을 고려할 수 있다.
특히
장기 실행
Multi-Agent
Resume
Replay
Audit
ZDR
요구가 커질수록 Application State가 중요해진다.
Tool Timeout으로 요청이 실패했다고 하자.
Runtime이 Retry한다.
여기서 잘못하면 같은 작업을 중복 실행할 수 있다.
Turn
Tool Call
Timeout
Retry
같은 Tool Call
그래서 Reasoning 연속성뿐 아니라 Tool의 Idempotency도 함께 관리해야 한다.
Reasoning State
+
Execution Checkpoint
+
Tool requestId
가 같이 필요하다.
장시간 Agent라면 이런 구조가 좋다.
Agent Run
↓
Checkpoint
↓
Model Reasoning
↓
Tool
↓
Artifact
↓
Checkpoint
Crash가 나면
Checkpoint Load
↓
Execution State 확인
↓
Reasoning Session 복원 또는 Reset 판단
↓
다음 Step 진행
한다.
Reasoning State 하나만 복원하는 것이 아니다.
앞선 글에서 Model Router와 Budget을 다뤘다.
Reasoning도 비용과 연결된다.
장기간 계속 all_turns를 유지하는 것이 모든 작업에 적합한 것은 아니다.
Runtime Policy에 이런 값을 둘 수 있다.
type ReasoningBudget = {
maxTurns: number;
maxIdleMinutes: number;
resetOnTaskChange: boolean;
};
예:
{
"maxTurns": 20,
"maxIdleMinutes": 240,
"resetOnTaskChange": true
}
20턴이 넘어가면 Summary와 Task State를 확인하고 새로운 Reasoning Session으로 넘어가는 식이다.
이 숫자가 정답이라는 의미는 아니다.
자신의 Eval을 기준으로 조정한다.
Context Budget
무엇을 얼마나 모델에게 보여줄까
Reasoning Budget
얼마나 오래 앞선 판단을 이어갈까
둘은 다른 문제다.
대규모 Context가 필요하지만 Reasoning 연속성은 짧을 수 있다.
반대도 가능하다.
작은 코드 몇 개
+
매우 복잡한 Debugging
+
10턴 동안 같은 가설 검증
에서는 Context는 작아도 Reasoning Continuity가 중요할 수 있다.
새 기능이라고 무조건 켜면 안 된다.
대표 Task를 준비한다.
.ai/
└── evals/
└── reasoning/
├── multi-turn-debugging.json
├── architecture-migration.json
├── research-task.json
└── task-switch.json
그리고 비교한다.
current_turn
vs
all_turns
측정 항목은
Task Success
Repeated Tool Calls
Turns to Completion
Total Tokens
Latency
Cost
Contradictions
Stale Assumption Rate
정도가 좋다.
Persisted Reasoning의 효과를 볼 때 유용한 지표가 있다.
같은 파일 재검색 횟수
같은 Tool 중복 호출
이미 제외한 가설 재검토
같은 질문 반복
동일 Plan 재생성
예를 들어
current_turn
평균 Tool Call
32회
중복 호출
8회
all_turns
평균 Tool Call
24회
중복 호출
2회
처럼 차이가 나타날 수 있다.
하지만 이것은 실제 프로젝트에서 측정해야 한다.
Persisted Reasoning의 부작용도 확인한다.
새 Evidence를 무시했는가
오래된 가설을 계속 유지했는가
변경된 Constraint를 놓쳤는가
Task 전환 후 이전 목표를 끌고 왔는가
이걸
Stale Assumption
지표로 볼 수 있다.
Reasoning Continuity가 높다고 항상 좋은 것은 아니다.
처음에는 간단하게
같은 Task
→ all_turns
새 Task
→ current_turn
으로 시작한다.
운영 데이터가 쌓이면 더 세분화한다.
Debugging
→ all_turns
일회성 Q&A
→ current_turn
Repository Migration
→ all_turns
Independent Review
→ current_turn
Reviewer Agent
→ 새 Reasoning Session
서비스에 맞게 최적화한다.
앞에서 만든 Model Router를 기억해보자.
Task
↓
Model Router
↓
Sol / Terra / Luna
이제 Reasoning Router가 추가된다.
Task
↓
Task Profiler
├── Model Router
│
│ Sol / Terra / Luna
│
└── Reasoning Router
│
auto
all_turns
current_turn
예를 들어
Simple Formatting
→ Luna
→ current_turn
일반 Bug Fix
→ Terra
→ all_turns
Complex Debugging
→ Sol high
→ all_turns
새로운 Independent Review
→ Sol
→ current_turn
처럼 구성할 수 있다.
지금까지 세 글의 내용을 합치면 구조가 보인다.
첫 번째 글:
Model Router
두 번째:
Tool Strategy Router
이번 글:
Reasoning State Router
전체는
Task
↓
Task Profiler
↓
┌─────────────────────┐
│ Model Router │
│ │
│ Sol / Terra / Luna │
└─────────────────────┘
↓
┌─────────────────────┐
│ Tool Strategy │
│ │
│ Direct │
│ Programmatic │
│ Hybrid │
└─────────────────────┘
↓
┌─────────────────────┐
│ Reasoning Policy │
│ │
│ auto │
│ all_turns │
│ current_turn │
└─────────────────────┘
↓
Agent Runtime
이제 Agent가 단순히
모델 하나 + Prompt 하나
가 아니다.
Task를 처음 받으면 다음 Profile을 만든다.
type AgentTaskProfile = {
taskId: string;
complexity:
| "simple"
| "standard"
| "complex";
horizon:
| "short"
| "long";
continuity:
| "new"
| "continuing";
toolStrategy:
| "direct"
| "programmatic"
| "hybrid";
risk:
| "low"
| "medium"
| "high";
};
예:
{
"taskId": "PAP-142",
"complexity": "complex",
"horizon": "long",
"continuity": "continuing",
"toolStrategy": "hybrid",
"risk": "medium"
}
Runtime이 이를 이용한다.
Model
GPT-5.6 Sol
Reasoning
all_turns
Tool
Hybrid
Memory
Repository Policy만 Retrieval
Execution
Checkpoint 활성화
이런 식이다.
다음과 같은 Agent를 만들지 않는 것이 좋다.
하나의 Conversation
↓
모든 Task
↓
모든 Memory
↓
모든 Tool Result
↓
모든 Reasoning
↓
끝없이 이어지는 Context
처음에는 편하다.
시간이 지나면 다음 문제가 생긴다.
Context 증가
오래된 정보
상충하는 규칙
잘못된 가설
Tool 중복 호출
Task 경계 붕괴
비용 증가
디버깅 어려움
가장 실용적인 기준 중 하나다.
Task Start
↓
Reasoning Session Start
Task Continue
↓
Reasoning Continue
Major Constraint Change
↓
Reasoning Reset 검토
Task Complete
↓
Reasoning Session Close
New Task
↓
New Reasoning Session
Task가 Reasoning의 Lifecycle을 결정한다.
실무에서
Agent가 기억해야 한다.
라고 말하면 너무 모호하다.
정확히 구분해야 한다.
사용자 선호를 기억한다.
→ Memory
현재 Diff를 알아야 한다.
→ Context
이전 Debugging 가설을 이어가야 한다.
→ Persisted Reasoning
Test까지 완료했다는 것을 알아야 한다.
→ Execution State
Test 결과 파일을 남겨야 한다.
→ Artifact
이 구분만 해도 Agent Architecture가 훨씬 명확해진다.
GPT-5.6의 Persisted Reasoning은 장기 Agent를 만들 때 꽤 중요한 변화다.
하지만
Reasoning을 오래 유지하면
Agent가 모든 것을 기억한다.
라고 이해하면 안 된다.
장기 Agent 상태는 최소한 다음처럼 분리하는 것이 좋다.
Memory
→ 오래 유지할 사실
Context
→ 현재 작업 자료
Persisted Reasoning
→ 앞선 판단의 연속성
Execution State
→ 실제 작업 진행 상태
Artifact
→ 작업으로 만들어진 결과
그리고 Reasoning에는 Lifecycle이 있어야 한다.
Task Start
→ Reasoning Start
Task Continue
→ all_turns
Goal / Constraint 변경
→ Reset 검토
Task Complete
→ Reasoning Close
New Task
→ current_turn / New Session
특히 중요한 것은 Conversation과 Task를 같은 것으로 보지 않는 것이다.
한 Conversation 안에서도 여러 Task가 존재할 수 있다.
Conversation
├── Task A
│ └── Reasoning Session A
│
├── Task B
│ └── Reasoning Session B
│
└── Task C
└── Reasoning Session C
Reasoning은 Conversation 전체의 영구 Memory가 아니라 Task에 종속된 작업 상태에 가깝게 다루는 편이 안전하다.
그리고 Agent 간에는 Reasoning 자체보다
Plan
Artifact
Evidence
Contract
Decision Log
를 전달한다.
한 줄로 정리하면 이렇다.
Memory는 무엇을 기억할지 결정하고,
Context는 지금 무엇을 볼지 결정하며,
Persisted Reasoning은
앞선 판단을 계속 이어갈지 결정한다.
장기 Agent가 안정적으로 동작하려면 많이 기억하는 것보다
무엇을 오래 보존하고, 무엇을 현재 작업에만 사용하며, 어떤 판단은 언제 끊을지를 명확하게 나누는 것이 더 중요하다.
OpenAI — GPT-5.6 Model Guidance
GPT-5.6에서 새롭게 제공되는 Persisted Reasoning과 reasoning.context의 auto, all_turns, current_turn 사용 기준을 확인할 수 있는 공식 문서.
OpenAI Responses API Documentation
previous_response_id를 이용한 Multi-turn Response 연결과 Response Item을 직접 관리하는 방식을 확인할 수 있는 공식 API 문서.
OpenAI — GPT-5.6 Models
GPT-5.6 Sol·Terra·Luna의 Reasoning 지원 범위와 Responses API 지원 여부를 확인할 수 있는 공식 모델 문서.
GPT-5.6의 Persisted Reasoning은 이전 턴의 reasoning item을 다음 턴에서 다시 활용할 수 있도록 하는 기능이다.
같은 목표·가정·우선순위가 여러 턴 동안 유지되는 작업에서는 reasoning.context: "all_turns"와 previous_response_id를 이용해 이전 Reasoning을 이어갈 수 있다.
반대로 이전 판단이 더 이상 관련 없거나 Task 자체가 달라졌다면 current_turn을 사용해 과거 Reasoning의 영향을 끊을 수 있다.
OpenAI는 직접 History를 관리할 경우 이전 User Input뿐 아니라 Response Output Item도 보존해 다시 전달하도록 안내하고 있으며, store: false 또는 Zero Data Retention 환경에서는 API가 반환하는 encrypted reasoning item을 재전달하는 방식도 지원한다.
따라서 Persisted Reasoning은 일반적인 사용자 Memory나 데이터베이스 상태를 대체하는 기능이 아니라 여러 턴에 걸쳐 동일 작업의 Reasoning Continuity를 유지하기 위한 별도의 Agent State 계층으로 보는 것이 적절하다.