Loop, Interaction, Approval의 경계
-LLM의 자유도를 높이기 전에 판단과 실행의 책임을 나눈 경험-
단계별로 동작하던 여러 LLM 기능을 하나의 흐름으로 연결하면서, 처음에는 다음 기능을 자동으로 호출하면 Agentic Workflow가 될 것이라고 생각했습니다. 하지만 실제로 필요한 것은 호출 순서만이 아니었습니다. 현재 상태에서 실행 가능한 행동을 판단하고, 필요한 사용자 결정을 요청하며, 사용자가 승인한 결과와 실제 실행 대상이 같은지 검증하는 구조가 필요했습니다. 이러한 판단과 검증을 일관되게 수행하기 위해 Workflow Loop와 Domain Adapter, 구조화 Interaction, 스냅샷 승인의 경계를 나누었습니다.
1. 배경과 문제
처음에는 기능들이 각각 독립적인 단계로 동작했습니다. 사용자가 입력을 주면 한 단계가 결과를 만들고 그 결과를 다음 단계의 입력으로 전달한 뒤 다음 실행을 선택했습니다. 결과 생성과 검증, 수정, 저장 기능이 모두 있어도 전체 흐름을 이어 가는 일은 사용자에게 남아 있었습니다. 기능은 완성됐지만 목표 단위의 실행 경험은 끊겨 있었습니다.
여러 API를 정해진 순서로 호출하는 것만으로는 부족했습니다. 사용자 판단에 따라 요청이 달라지고 검증 결과에 따라 이전 단계를 보완하거나 재실행해야 했습니다. 분기와 재시도, 사용자 응답 대기를 단순한 반복문과 조건문으로 연결하면 흐름이 길어질수록 상태 관리와 재개가 복잡해집니다. 그래서 현재 상태와 다음 행동을 관리하는 Agentic Workflow를 도입했습니다. 필요한 정보를 요청하고 실행 결과를 되돌리기 어려운 작업 앞에서는 승인을 기다린 뒤 중단 지점에서 실행을 재개하도록 했습니다.
Agentic Workflow를 설계하면서 가장 먼저 나눈 것은 Workflow를 수행하는 LLM과 도메인 기능을 수행하는 LLM의 책임이었습니다. Workflow LLM은 현재 실행 상태에서 다음 행동 후보를 선택합니다. 도메인 LLM은 각 Tool 안에서 결과를 생성하고 검증하거나 수정합니다. Workflow LLM이 도메인 결과나 최종 Tool 입력까지 만들면 검증된 상태와 다른 값을 참조할 수 있습니다. 반대로 도메인 LLM이 다음 단계까지 결정하면 기능마다 실행 규칙이 겹칩니다. 그래서 Workflow Loop는 실행 순서와 대기·재개를 관리했습니다. Domain Adapter는 현재 허용되는 행동과 Tool 입력을 검증한 뒤 도메인 Tool을 호출했습니다. 이렇게 나누면 도메인 규칙이 바뀌어도 공통 Workflow를 손대지 않고 Adapter와 Tool 내부에서 대응할 수 있습니다.
2. 실행 책임의 분리
전체 구조는 공통 실행 절차와 특정 업무 목표를 분리합니다. Workflow Loop는 실행 순서와 대기·재개를 관리하고 다음 행동을 판단합니다. Domain Adapter는 업무 목표에 맞는 기능을 골라 Domain Tool 호출을 준비합니다. 사용자와 Loop 사이는 Interaction·Approval로, Loop와 Adapter 사이는 Decision·Tool 호출 인터페이스로 연결했습니다. 두 Session은 식별자로만 연결해 실행 제어 정보와 업무 결과를 분리했습니다.
사용자 -- Interaction / Approval --> Workflow Loop
Workflow Loop <-> Domain Adapter -> Domain Tool
Workflow Session: 실행 / 대기 / 재개 | Domain Session: 업무 결과 / 검증 / 버전
Workflow Session에는 현재 Decision, 대기 중인 Interaction·Approval, 완료·실패·정책 차단 여부를 저장합니다. Loop는 이를 읽어 Tool을 호출하거나 사용자 응답을 기다리고, 호출 뒤에는 성공·실패와 다음 실행 위치를 기록합니다. 여기서 Workflow의 상태는 업무 결과의 내용이 아니라 실행이 어디까지 진행됐고 무엇을 기다리는지를 뜻합니다. Loop는 업무 결과의 의미를 해석하지 않습니다.
Domain Session에는 특정 업무 목표를 수행하며 만들어진 결과, 결과 버전, 검증 여부와 수정 대상을 저장합니다. Adapter는 이를 읽어 실행할 Tool과 사용자 판단 필요 여부를 정하고, 검증된 결과로 최종 입력을 조립합니다. 승인된 결과와 실행하려는 결과가 같은지도 Adapter가 확인합니다. 여기서 Domain의 상태는 업무가 어떤 결과를 만들었고 그 결과를 신뢰할 수 있는지를 뜻합니다.
정리하면 Loop는 '어떻게 순환하는가'를, Adapter는 '지금 무엇을 해도 되는가'를 판단합니다. Loop는 Decision의 종류에 따라 공통 처리를 선택하고, Adapter는 Domain Session의 검증 결과에 따라 수정 Tool을 허용할지 사용자 확인을 먼저 받을지 정합니다.
2.1 Decision과 Policy
Workflow Loop와 Domain Adapter는 Decision으로 다음 행동을 주고받습니다. Adapter가 Policy로 허용 행동을 계산해 Decision을 반환하면 Loop가 실행합니다. Decision은 Tool 호출, 사용자 질문, 승인 요청, 완료, 실패, 대기를 나타냅니다. Policy는 Adapter가 반환할 행동을 제한합니다. 검증 실패 시 수정 Tool만 허용하고, 승인받지 않은 변경 Tool은 차단합니다.
여기서 LLM은 Workflow LLM입니다. 처음에는 LLM 없이 Policy 규칙만으로 허용·차단 흐름을 테스트했습니다. 이후 Workflow LLM은 허용된 행동 중 하나를 고르거나 짧은 의도만 제안합니다. 최종 Tool 입력은 Adapter가 Domain Session의 검증된 결과와 Workflow Session의 승인 정보를 읽어 조립합니다. LLM은 이 값을 직접 작성하지 않습니다.
기존 단계 기능도 다시 작성하지 않았습니다. Tool Registry가 이미 검증한 기능을 호출하도록 감쌌고, Workflow는 Registry를 통해서만 실행했습니다. 덕분에 새 실행 흐름을 도입하면서도 기존 기능의 동작을 비교할 수 있었습니다. 공통 모듈은 처음부터 크게 추상화하지 않고 실제 흐름 안에서 계약을 검증한 뒤 판단하기로 했습니다.
책임 기준 Workflow Loop는 실행 생명주기를, Domain Adapter는 업무 결과 해석과 허용 행동을, Domain Tool은 실제 기능 수행을 담당합니다.
3. 구조화 Interaction
실행 책임을 분리한 뒤에는 사용자 입력 계약의 빈틈이 보였습니다. 처음에는 Workflow가 사용자에게 보낼 질문을 문자열 메시지로만 반환했습니다. 간단한 질문에는 충분했지만 선택지 고르기, 대상 지정, 변경 내용 검토처럼 사용자 편의 향상을 위한 소통 방법을 표현하기 어려웠습니다. 백엔드는 질문이 필요하다는 사실만 알렸고, 화면은 사용자가 실제로 무엇을 해야 하는지 추측해야 했습니다.
이를 해결하기 위해 Interaction을 화면 문구가 아니라 사용자 행동의 의미로 정의했습니다. type, target, options, required 등으로 구성된 인터페이스를 사용했습니다. type은 일반 입력인지 선택인지 검토인지 등을 나타내고, target은 응답이 어떤 작업 결과에 적용되는지 식별합니다. options는 선택지 고르기 형식의 선택 가능한 값이며, required는 응답 없이 진행할 수 있는지를 나타냅니다. 화면은 이 계약을 채팅 입력, 선택 카드, 변경 비교 화면처럼 상황에 맞는 컴포넌트로 표현합니다.
{
"type": "REVIEW_DIFF",
"target": "result",
"required": true
}
4. 스냅샷 승인과 상태
사용자 결정 중에서도 데이터를 변경하거나 외부 작업을 실행하는 Tool은 더 강한 승인 계약이 필요했습니다. 승인 여부를 Boolean 값 하나로 저장하면 사용자가 승인 버튼을 눌렀다는 사실만 남습니다. 어떤 결과를 어떤 상태에서 승인했는지는 알 수 없습니다. 승인한 뒤 결과가 수정되거나 검증 상태가 바뀌어도 같은 승인 값을 재사용할 수 있다는 뜻입니다.
그래서 승인을 실행 대상의 스냅샷에 결합했습니다. 승인 정보에는 대상 Tool, 결과 버전과 해시, 검증 결과 해시, 멱등성 키를 포함했습니다. 실행 직전에는 현재 결과가 승인 당시 결과와 같은지 다시 비교합니다. 대상 Tool이나 버전, 해시가 하나라도 다르면 실행하지 않습니다. 사용자는 단순히 '실행해도 된다'고 승인하는 것이 아니라 식별 가능한 특정 결과를 승인합니다.
이 검사는 승인 시점과 실행 시점 사이의 변경을 막는 장치이기도 합니다. 사용자가 화면에서 결과를 확인한 직후 다른 요청이 같은 결과를 수정할 수 있고, 실행 큐에서 대기하는 동안 검증 결과가 달라질 수도 있습니다. 승인 순간에만 조건을 확인하면 이런 변화를 놓칩니다. 실행 직전에 동일성을 다시 확인해야 사용자가 본 내용과 실제로 저장되는 내용이 같다는 조건을 유지할 수 있습니다.
실행 가능 = 승인됨
&& 대상 Tool 일치
&& 결과 버전 / 해시 일치
&& 검증 결과 해시 일치
&& 멱등성 키 유효
불일치 상황은 Tool 실행 실패와도 구분했습니다. 승인이 없거나 오래됐거나 현재 결과와 다르면 Tool을 호출하기 전에 Policy가 차단합니다. 반면 허용된 Tool을 호출한 뒤 내부 또는 외부 오류가 발생한 경우는 실행 실패입니다. 두 상태를 나누면 재시도가 가능한 오류인지, 사용자의 재승인이나 상태 갱신이 필요한 상황인지 판단하기 쉬워집니다.
4.1 감사와 멱등성
승인 요청, 승인 결과, Tool 호출, 정책 차단은 Audit Event로 남겼습니다. 감사 기록은 사후 로그만을 뜻하지 않습니다. 다음 Decision이 이전 실행 결과를 해석하고, 운영 중에 특정 실행이 왜 진행되거나 멈췄는지 확인하는 근거가 됩니다. 상관관계가 있는 이벤트를 같은 실행 흐름으로 묶으면 사용자 응답과 Tool 결과를 함께 추적할 수 있습니다.
멱등성 키는 같은 승인 요청이 두 번 실행되는 일을 막는 데 사용했습니다. 네트워크 재시도나 사용자의 반복 클릭이 발생하더라도 이미 처리한 키라면 다시 실행하지 않습니다. 중요한 점은 멱등성 검사를 Tool 내부에만 맡기지 않고 승인과 실행 계약에도 포함한 것입니다. 그래야 Workflow 관점에서 같은 의도가 한 번만 처리됐음을 설명할 수 있습니다.
4.2 영속 상태
초기에는 Workflow 상태 일부를 요청과 응답에 실어 전달했습니다. 짧은 기능 검증에는 편리했지만 새로고침, 서버 재시작, 장시간 승인 대기에서는 안정적인 재개를 보장하기 어려웠습니다. 승인과 감사 정보가 클라이언트가 들고 있는 상태에 의존하면 서버가 현재 실행의 기준 상태를 스스로 판단하기도 어렵습니다.
후속 작업에서는 Workflow Session과 승인, 감사 상태를 JPA로 영속화했습니다.
5. 적용 기준과 마무리
이번 작업에서는 Agentic Workflow를 설계하며 다음과 같은 기준을 적용했습니다. 첫째, 실행 루프와 도메인 판단을 먼저 분리합니다. 경계를 정할 때는 해당 로직이 업무 결과의 내용을 알아야 하는지 확인했습니다. 결과의 의미나 허용 규칙을 알아야 한다면 Adapter에 두고, 대기·재개·완료처럼 업무와 관계없이 반복되는 절차라면 Loop에 둡니다. 이 기준이 없으면 새로운 흐름을 추가할수록 Loop의 조건문과 상태 해석이 늘어납니다.
둘째, LLM은 허용된 범위에서 제안하게 하고 최종 Tool 입력을 소유하지 않게 합니다. Policy가 가능한 행동을 계산하고 Adapter가 검증된 결과와 승인 정보로 입력을 조립합니다. Workflow LLM은 그 범위 안에서 행동을 고를 뿐 결과 내용이나 승인 값을 새로 만들지 않습니다. 이렇게 나누면 어떤 값이 LLM의 제안이고 어떤 값이 애플리케이션이 확인한 정보인지 구분할 수 있습니다. 자율성을 높이는 일과 실행 통제를 포기하는 일은 같지 않습니다.
셋째, 사용자에게 보낼 내용을 문자열로만 전달하지 않고 행동의 형식으로 표현합니다. 일반 입력, 선택, 검토에는 서로 다른 안내와 응답 방식이 필요합니다. type으로 행동의 종류를 알리고 target으로 응답이 적용될 작업 결과를 지정합니다. 선택이 필요한 경우에는 options로 가능한 값을 전달하고, required로 응답 없이 진행할 수 있는지도 알립니다. 구조화 Interaction의 목적은 화면을 통제하는 것이 아니라 사용자와 Workflow 사이의 소통 방법을 분명히 하는 데 있습니다.
넷째, 승인은 승인 당시 실행 대상의 스냅샷에 묶습니다. 승인 버튼 클릭만 저장해서는 승인 이후의 변경과 중복 실행을 막을 수 없습니다. 대상 Tool, 결과 버전과 해시, 검증 상태, 멱등성 키를 실행 직전에 다시 확인해야 합니다. 조건이 맞지 않으면 Tool을 호출하지 않고 정책 차단으로 기록합니다. 반대로 조건을 통과한 뒤 Tool 내부에서 오류가 발생했다면 실행 실패로 남깁니다. 두 결과를 구분해야 재승인이 필요한지 재시도할 수 있는지 판단할 수 있습니다.
중단과 재개에 필요한 Workflow Session, 승인, 감사 상태는 후속 작업에서 JPA로 영속화했습니다.
이번 작업에서 Agentic Workflow는 LLM 호출을 자동으로 잇는 구조가 아니라 판단과 실행의 책임을 나누는 구조로 정리했습니다. Loop와 Adapter의 역할을 분리하고, Interaction으로 사용자 응답이 적용될 대상을 분명히 하며, Approval로 승인한 결과와 실제 실행 대상을 맞췄습니다. 결국 중요한 것은 LLM이 더 많은 일을 하게 만드는 것이 아니라 누가 무엇을 판단하고 어떤 근거로 실행하는지 분명히 하는 일이었습니다.
DEVKC