레슨 4: 구조화된 상태: 에이전트는 작업이 어디까지 왔는지 어떻게 기억하는가
학습 목표:
- 작업 진행 상황을 대화 문장 속에 묻어 두는 방식이 왜 신뢰할 수 없는지, 그리고 왜 그것이 구조화된 상태가 되어야 하는지 설명하기
- 하나의 todo가 생성부터 제거까지 거치는 전체 생명주기를 말하기
- 처음부터 다시 시작하는 비용과 중단된 지점부터 이어서 하는 비용을 구분하기
- 어떤 작업 상태를 세션 히스토리에 두어야 하고, 어떤 것을 별도의 체크포인트에 기록해야 하는지 판단하기
전제: 레슨 3을 마치고 외부 메모리의 두 가지 모드를 이해하고 있을 것 | 이전: 레슨 3 << | 다음: 레슨 5 >>
프로세스 재시작 이후, 에이전트는 자신이 어느 단계까지 왔는지 여전히 아는가
한 에이전트가 여러 단계로 이루어진 작업을 진행하고 있습니다. 어떤 모듈을 리팩터링하는 작업으로, 네 개의 단계로 나뉩니다 — '타입 정의 업데이트', '호출부 업데이트', '테스트 실행', '문서 업데이트'. 두 번째 단계를 막 끝낸 순간 예기치 못한 재시작으로 프로세스가 중단됩니다. 다시 올라왔을 때, 에이전트는 똑같은 작업 설명을 마주합니다. 첫 번째 단계부터 다시 해야 할까요, 아니면 이미 앞의 두 단계를 끝냈다는 사실을 알고 곧바로 세 번째 단계부터 이어서 할 수 있을까요?
답은 한 가지로 귀결됩니다. 작업의 진행 상황이 대화 문장 더미 여기저기에 흩어져 있는 대신 구조화된 상태로 기록되었는가입니다. '타입 정의는 이미 업데이트됐다'가 그저 모델의 앞선 답변 어딘가에 있는 한 줄의 자연어, 수십 개의 메시지 사이에 묻힌 한 줄일 뿐이라면, 호스트 코드는 거기서 '지금 몇 번째 단계인가'라는 구체적 사실을 신뢰할 수 있게 뽑아낼 방법이 없습니다. 하지만 그 진행 상황이 고정된 필드를 가진 todo 목록으로 — 모든 작업이 명시적인 상태 표시를 지니는 형태로 — 표현되어 있다면, 호스트 코드는 그것을 바로 읽어낼 수 있습니다. 1단계와 2단계는 completed, 3단계는 아직 시작되지 않았다고 말입니다.
이것이 이번 레슨이 다루는 핵심 질문입니다. 레슨 1, 2, 3은 모두 콘텐츠를 관리하는 방법 — 대화 히스토리, 메모리 파일 — 에 관한 것이었습니다. 이번 레슨은 진행 상황을 표현하는 방법에 관한 것으로, 에이전트나 그 호스트 애플리케이션이 중단 이후 작업이 정확히 어디까지 왔는지 알 수 있게 하는 것입니다.
Todo 생명주기: 생성, 활성화, 완료, 제거
Claude Code의 작업 추적 도구를 예로 들어 봅시다. 공식 문서는 실행 중에 하나의 todo가 거치는 전체 생명주기를 제시합니다 — 네 단계입니다.1
- 생성(Created): Claude는 작업을 식별하면 그 todo를 pending으로 추가합니다
- 활성화(Activated): Claude는 작업을 시작할 때 todo를 in_progress로 설정합니다
- 완료(Completed): Claude는 작업이 성공적으로 끝나면 completed로 표시합니다
- 제거(Removed): Claude는 더 이상 필요 없는 todo를 TaskUpdate 호출에서 status: "deleted"로 설정해 삭제합니다1
이 네 단계는 자연어의 모호한 '다 했어요'나 '하고 있어요'가 아닙니다. 네 개의 명시적인 상태 값입니다. pending, in_progress, completed, 그리고 제거를 뜻하는 deleted입니다. 모든 상태 변화는 모델이 답변 텍스트에서 슬쩍 언급하는 것이 아니라, 명시적인 도구 호출을 통해 일어납니다.
문서는 또한 이 메커니즘이 대화에서 실제로 어떻게 드러나는지도 명확히 밝힙니다: "In a session that has the task-tracking tools, Claude keeps a written todo list, updating each item's status as it works. You see each change in the message stream as a structured tool call."1 (작업 추적 도구를 갖춘 세션에서 Claude는 작성된 todo 목록을 유지하며, 작업을 진행하는 동안 각 항목의 상태를 업데이트합니다. 각 변화는 메시지 스트림 안에서 구조화된 도구 호출로 나타납니다.) 이 문장은 핵심적인 구분을 못 박습니다. 진행 상황은 대화 속에 수동적으로 '반영'되는 것이 아니라, 그 자체로 식별하고 파싱할 수 있는 도구 호출로 능동적으로 기록됩니다. 그것이 바로 구조화된 상태와 문장 속에 흩어진 진행 상황 설명의 진짜 차이입니다.
체크포인트: 복구를 처음부터 다시 시작하는 것이 아닌 무언가로 만들기
구조화된 todo 목록을 갖추고 나면, 다음 질문은 이것입니다: 그 목록 자체는 어디에 사는가? 그것이 오직 이번 세션의 대화 히스토리에만 존재한다면, 세션이 진짜로 끝나는 순간(짧은 중단이 아니라 레슨 3의 '세션이 끝나면 윈도우 안의 모든 것은 사라진다'는 개념처럼 완전히 닫히는 것) 진행 기록도 함께 사라집니다. 근본적으로 API는 상태를 유지하지 않습니다: "The Messages API is stateless, which means that you always send the full conversational history to the API."2 (Messages API는 상태를 유지하지 않으며, 이는 항상 전체 대화 히스토리를 API에 보낸다는 뜻입니다.)
바로 그것이 체크포인트가 해결하는 문제입니다: 어느 시점의 작업 상태 — 어떤 단계가 끝났는지, 어떤 단계가 현재인지, 어떤 단계가 남았는지 — 를 하나의 데이터로 기록하고, 세션의 수명 바깥 어딘가에 영속화하는 것입니다. 체크포인트는 레슨 3의 외부 메모리와 같은 밑바탕 메커니즘(파일에 쓰고 나중에 다시 읽기)을 씁니다. 차이는 체크포인트가 '기억할 가치가 있는 지식'을 저장하는 것이 아니라 '작업이 어디까지 왔는지' — 실행을 재개하는 데 바로 쓸 수 있는 종류의 상태 — 를 저장한다는 점입니다.
체크포인트가 자리를 잡으면 마침내 복구 가능성이 성립합니다: 프로세스 재시작 이후 에이전트는 '내가 어디까지 했더라'를 추측할 필요가 없습니다. 가장 최근의 체크포인트를 읽고 '1단계와 2단계는 completed, 3단계는 in_progress'라는 것을 확인한 뒤, 1단계와 2단계를 다시 하지 않고 3단계부터 이어서 합니다.
여기서 비용 비교는 구체적입니다. 리팩터링 작업에서 '타입 정의 업데이트' 단계는, 그것이 멱등적(다시 실행해도 같은 결과가 나오는)이라면 처음부터 다시 해도 낭비되는 시간만 듭니다. 하지만 어떤 단계가 '데이터베이스에 마이그레이션 레코드를 삽입한다'처럼 멱등적이지 않은 연산이라면, 처음부터 다시 하는 것은 중복 레코드를 두 개 삽입하고 심지어 데이터를 손상시킬 수도 있습니다. 체크포인트가 아끼는 것은 시간만이 아닙니다 — 그런 종류의 멱등적이지 않은 연산을 실수로 다시 실행할 위험입니다.
구조화된 상태는 중단을 견뎌내기 위해 존재한다
이번 레슨을 연 시나리오로 돌아가 봅시다: 프로세스 재시작 이후 에이전트는 처음부터 다시 시작해야 할까요, 아니면 중단된 지점부터 이어서 해야 할까요? 이제 우리는 그것을 분명히 답할 수 있습니다 — 두 가지가 이뤄졌는지에 달려 있습니다. 작업의 진행 상황이 (대화 문장 속에 흩어지지 않고) 구조화된 상태로 표현되었는가, 그리고 그 상태가 (이번 한 세션의 히스토리에만 살지 않고) 체크포인트에 기록되었는가입니다. 둘 다 필요합니다. 구조화된 상태만 있고 체크포인트가 없으면 상태는 세션이 끝날 때 여전히 사라지고, 체크포인트는 있지만 구조화된 상태가 없으면 체크포인트에 기록된 것 자체가 모호한 자연어여서, 다시 읽어도 작업이 어느 단계까지 왔는지 신뢰성 있게 알 수 없습니다.
이번 레슨은 진행 상황을 표현하고 보존하는 방법에 관한 것이었습니다. 다음 레슨은 그에 못지않게 중요한 또 다른 질문으로 향합니다: 이렇게 보존된 상태와 메모리가 공격자의 표적이 될 수 있는가 — 공격자가 체크포인트나 메모리 파일에 콘텐츠를 쓸 수 있다면 어떤 일이 벌어지는가입니다.
정리
- 작업 진행 상황은 구조화된 상태가 되어야만 호스트 코드가 신뢰성 있게 읽어낼 수 있다. 자연어 답변에 흩어진 진행 상황 설명은 '지금 몇 번째 단계인가'로 안정적으로 파싱될 수 없다
- 하나의 todo의 전체 생명주기는 네 단계다 — 생성(pending), 활성화(in_progress), 완료(completed), 제거(deleted) — 그리고 각 단계는 메시지 스트림에서 관찰할 수 있는 명시적인 구조화된 도구 호출을 통해 일어난다
- 구조화됨과 영속적임은 서로 다른 두 가지다: 구조화는 '프로그램이 읽을 수 있는가'를 해결하고, 체크포인트는 '프로세스 재시작이나 세션 종료 후에도 상태가 여전히 남아 있는가'를 해결한다 — 둘 다 필요하다
- 체크포인트는 어느 시점의 작업 상태를 세션의 수명 바깥 어딘가에 기록하여, 복구를 '처음부터 다시 시작'이 아니라 '중단된 지점부터 이어서'로 바꾸며, 특히 멱등적이지 않은 연산의 실수 재실행을 피하게 한다
- 구조화된 상태도 외부 메모리와 마찬가지로 일단 영속화되면 그 자체로 공격 표적이 된다 — 다음 레슨의 주제다. 더 많이 보존할수록 지켜야 할 경계도 더 많아진다
>> 레슨 5: 메모리의 경계와 안전