Agent 실행과 위임 권한의 수명주기 분리하기
앞선 글에서는 사용자의 권한을 Agent 자체에 복제하지 않고 특정 실행에 위임하는 구조를 다뤘습니다. 실행을 시작할 때 capability ceiling을 정하고, Tool을 호출할 때는 사용자의 현재 IAM 권한을 다시 확인하는 방식이었습니다.
이 구조를 background execution에 적용하자 다른 질문이 생겼습니다.
한번 위임한 권한은 언제까지 유효해야 할까요?
일반적인 웹 요청에서는 이 질문이 크게 드러나지 않습니다. 요청이 들어오면 credential을 검증하고 처리한 뒤 응답을 반환합니다. authorization decision과 실제 실행을 하나의 request boundary 안에서 다룰 수 있었습니다.
Agent는 조금 다릅니다. LLM 호출과 tool 실행이 반복되는 한 turn은 수 분에서 수십 분까지 이어지고, 브라우저의 HTTP 연결보다 오래 살아남을 수 있습니다.
브라우저 연결과 실행 수명의 분리#
현재 Agent runtime은 실행 상태를 background runner에서 관리합니다. 브라우저는 진행 상황을 구독해 보여줄 뿐이고, 실제 실행은 서버에서 계속됩니다. 실행 중 발생한 이벤트는 durable하게 저장되므로, 프로세스가 죽어도 어디까지 진행됐는지에 대한 기록은 남습니다.
작업의 소유권은 lease로 관리합니다. lease는 유효기간이 붙은 소유권입니다. runner는 heartbeat로 lease를 주기적으로 갱신하면서 자신이 이 작업을 처리하고 있음을 표시하고, runner가 죽어 갱신이 끊기면 lease는 저절로 만료됩니다. 만료된 작업은 다른 runner가 turn state를 reclaim해 저장된 이벤트를 기반으로 이어서 실행합니다.
브라우저나 SSE의 disconnect와 Agent turn의 종료는 같은 사건이 아닙니다.
사용자가 탭을 닫았든, 프로세스가 죽었든, 네트워크가 끊겼든 연결이 사라졌다는 사실만으로 진행 중인 실행을 중단할 수는 없습니다.
MCP Tasks 확장도 비슷한 상황을 다룹니다. 장기 실행 요청에는 최종 결과 대신 durable task handle을 반환하고, 클라이언트는 연결이 끊기거나 재시작된 뒤에도 같은 handle로 상태를 조회합니다. 이 구조가 우리의 authorization 방식을 정당화하는 것은 아니지만, 장기 실행 작업의 수명이 연결의 수명과 분리된다는 전제가 같아서 참고했습니다.
RUNNING 상태와 Tool 호출#
turn의 RUNNING은 작업이 아직 끝나지 않았다는 뜻입니다. 실행을 이어가고 외부 Tool을 호출하려면 다음 조건을 차례로 통과해야 합니다.
다른 runner가 turn state를 reclaim해도 delegation context가 없다면 authorization expired로 종료됩니다. 기존 runner가 실행을 계속하는 중이라도 사용자의 권한이나 delegation이 회수되면 이후 Tool 호출은 delegation 또는 현재 IAM 검사에서 거절됩니다.
delegation에는 Agent 실행 상태와 별도의 TTL, hard expiry, revoke 상태를 두었습니다. turn이 끝나거나 사용자의 authority가 바뀌면 active delegation을 회수하며, delegation credential이 유효한 경우에도 Tool 호출 시점에는 현재 IAM을 다시 확인합니다.
실행 중 authority 변경#
사용자가 Agent에게 여러 구성원의 평가 정보를 분석해 달라고 요청했다고 가정해보겠습니다. Agent가 여러 Tool을 순차적으로 호출하는 동안 관리자가 사용자의 역할을 변경하기도 합니다.
09:00 Agent turn 시작
performance.read 사용 가능
09:01 Tool A 호출 성공
09:02 관리자가 사용자 role 변경
09:03 Agent turn은 여전히 RUNNING
09:04 Tool B 호출
→ 현재 IAM에서 거절turn 시작 시점의 권한을 snapshot으로 만들어 종료할 때까지 신뢰하면 09:04의 호출도 성공합니다. 실행 시간만큼 stale authority가 남습니다.
현재 구조는 이 변화를 두 경로로 반영합니다. 일부 IAM 변경은 사용자의 active delegation을 revoke합니다. revoke hook이 실행되지 않았거나 변경과 Tool 호출 사이에 시간이 겹치더라도, 이후 요청은 authorization boundary에서 현재 IAM을 다시 평가합니다.
delegation credential은 “이 실행이 종료될 때까지 해당 권한을 보장한다”는 증명서가 아닙니다. 발급된 capability는 실행이 넘을 수 없는 상한이고, 실제 행동은 사용자가 그 권한을 현재도 가지고 있을 때만 허용됩니다.
다만 revoke가 이미 시작된 요청을 소급해서 취소하지는 않습니다. 요청이 authentication과 authorization을 통과한 뒤 domain operation을 수행하는 중이라면, 그 작업을 중단하려면 별도의 cancellation이나 transaction 경계가 필요합니다. 현재 모델이 보장하는 범위는 revoke 이후 authorization boundary에 도달한 Tool 호출을 거절하는 것까지입니다.
Runner reclaim 이후의 권한 확인#
기존 runner가 lease를 잃으면 다른 runner가 새 lease를 획득하고, durable event와 turn state를 읽어 실행 ownership을 넘겨받습니다. 여기까지 복구되는 것은 실행 상태입니다.
delegation credential은 turn state와 함께 복원되지 않습니다. 따라서 Runner B는 RUNNING 상태를 읽었더라도 유효한 delegation context 없이 재개 경로에 진입합니다. 현재는 이를 authorization expired로 처리하고 외부 Tool 호출 전에 실행을 종료합니다.
turn state에 남은 actor나 capability를 이용해 credential을 다시 만들 수 있지만, 그렇게 하지 않았습니다. 저장된 실행 상태는 어떤 작업이 남아 있는지는 보여주지만, 그 작업이 현재도 사용자를 대신해 행동할 수 있다는 사실까지 증명하지는 않기 때문입니다.
일반적인 background computation은 저장된 계산을 이어가는 것으로 복구가 끝날 수 있습니다. 반면 Agent는 복구 이후에도 새로운 Tool을 선택해 데이터를 읽거나 side effect를 만듭니다. 실행 상태를 복원하는 것과 외부 시스템에 다시 행동할 자격을 부여하는 것은 별도의 판단으로 남겼습니다.
Delegation credential 교체와 응답 유실#
여기서 교체하는 credential은 Agent가 Core internal API에 제출하는 opaque token입니다. 이 교체가 delegated authority 자체를 갱신하는 것은 아닙니다. credential의 TTL이 turn보다 짧기 때문에 실행을 계속하려면 만료 전에 새 값을 받아야 합니다.
Agent가 D1과 idempotency key로 credential 교체를 요청하면 Core는 D2를 발급하고 D1을 즉시 ROTATED 상태로 바꿉니다. 응답이 정상적으로 도착하면 Agent는 이후 요청부터 D2를 사용합니다.
문제는 D2를 발급한 뒤 응답만 유실되는 경우입니다.
응답이 유실되면 Core에서는 credential 교체가 끝났지만 Agent는 D2를 받지 못합니다. D1은 이미 사용할 수 없으므로 일반적인 재시도라면 복구되지 않습니다.
이전 credential과 새 credential을 일정 시간 함께 허용하는 방법도 있습니다. 구현은 단순하지만 교체 뒤에도 D1을 재사용할 수 있는 시간이 생깁니다. 현재 구조는 D1을 즉시 종료하고, 같은 idempotency key를 가진 교체 요청만 짧은 replay window 안에서 복구합니다. 동일한 D2를 다시 전달할 수 있도록 credential 값은 암호화해 제한된 시간만 보관합니다.
ROTATED는 delegated authority 전체가 아니라 D1이라는 개별 credential의 상태입니다. D2는 D1과 같은 root delegation, bound thread, hard expiry를 계승해 생성되며, D2의 짧은 expiry도 root의 hard expiry를 넘지 않습니다. credential은 바뀌어도 최초 delegation이 정한 실행 문맥과 최대 수명은 늘어나지 않습니다.
교체 과정에서 복구하려는 것은 authority가 아니라 D2의 전달 실패입니다. 실행 상태에서 새 authority를 만들어내지 않으면서 네트워크 응답 유실에는 동일 요청의 재전달로 대응했습니다. 이 때문에 delegation credential 교체에는 수명 관리뿐 아니라 응답 유실과 중복 요청을 처리하는 재시도 규칙도 필요했습니다.
Tool 호출 오류 처리#
실행과 authority의 lifecycle을 분리하고 나면 Tool 호출 실패도 단순한 HTTP 오류로만 다룰 수 없습니다. 현재 internal API와 Agent client 사이의 계약에서는 상태 코드에 따라 다음 행동이 달라집니다.
| 응답 | 현재 Agent가 해석하는 의미 | 다음 행동 |
|---|---|---|
400 | Tool argument 오류 | 인자를 교정한 뒤 재시도 가능 |
403 | 현재 사용자 권한으로 실행 불가 | 같은 호출의 반복을 중단 |
401 | delegation 만료, revoke, context mismatch 가능 | execution authority를 다시 판단해야 함 |
현재 Agent는 403을 permission denied로 모델에 전달하고, 400의 구체적인 오류는 argument를 수정하도록 ToolResult로 응답하도록 구현되어 있습니다. 반면 401은 delegation 만료와 revoke 등 서로 다른 원인을 충분히 구분하지 못하고 있습니다.
여러 모델과 Tool 호출로 실행이 길어지는 Agent에서는 authorization failure를 단순한 오류로만 처리할 수 없습니다. 권한이 사라진 turn을 실패 처리할지, 사용자 승인을 기다릴지, 새로운 authority를 받은 뒤 같은 task를 재개할지는 execution state와 별도로 결정해야 합니다.
Auth0의 Asynchronous Authorization은 background에서 동작하는 Agent가 민감한 행동을 수행하기 전에 별도의 기기와 채널을 통해 사용자 승인을 받는 흐름을 제공합니다. 현재 구조에 re-consent나 step-up authorization을 적용한 것은 아니지만, 고위험 Tool 실행 직전에 권한을 다시 얻는 방식은 별도의 lifecycle 설계 대상이 됩니다.
실행 복구와 권한 유효성#
Agent를 background execution으로 옮길 때는 연결이 끊겨도 실행을 계속하고, 프로세스가 종료돼도 상태를 잃지 않으며, 다른 runner가 작업을 이어받도록 만드는 데 초점을 맞췄습니다.
하지만 durable한 실행 상태는 현재의 실행 권한을 증명하지 않습니다. RUNNING은 아직 작업이 남았다는 뜻이고, lease는 어느 runner가 작업을 소유하는지를 나타냅니다. delegation의 유효성과 현재 IAM은 해당 실행이 사용자를 대신해 외부 시스템에 행동할지를 결정합니다.
Long-running Agent의 복구 모델에는 어떤 상태를 남길지뿐 아니라, 어떤 authority를 복구하지 않을지도 포함돼야 합니다. 실행이 계속되는 동안에도 사용자의 권한은 변경되며, 유효한 authority가 없다면 복구된 실행 역시 외부 행동을 이어가서는 안 됩니다.