제 12 장

워크플로와 에이전트의 제어

개발팀이 티켓을 조회하고 등록하는 도구를 붙이자 회의 분위기가 달라졌다. 조회도 되고 등록도 되니 봇에게 맡기는 일만 남은 셈이었고, 이제 알아서 처리하게 두면 되지 않겠냐는 말이 나왔다.

그런데 도구를 쥐여 줬다고 봇이 알아서 일하지는 않는다. 조회 결과를 받은 뒤 끝낼지, 다른 자료를 찾을지, 확인을 요청할지 누군가 정해야 한다. 그 결정을 모델에 맡기면 입력에 따라 유연하게 움직이지만 호출 횟수와 실패 경로도 늘어난다. 개발팀은 고정된 티켓 업무 흐름과 제한된 도구 선택 루프를 나누고, 제어 로직은 가짜 계획기로 검증하기로 했다. 실제 모델이 어떤 행동을 제안하든 서버는 허용 목록에 있는 실행만 통과시킨다.

정해진 경로와 동적인 경로

결정권이 어디에 있나워크플로 · 티켓 등록 · 코드가 정한다초안 작성 · 모델이 써도 된다확인 대기 · 무조건 멈춘다호스트가 등록한다에이전트 루프 · 조회 · 계획기가 고른다관찰계획기 제안검증 · 실행finish다시 관찰허용: get_ticket · prepare_ticket · finish목록에 없음: create_ticket · approve · 권한 변경초안 생성 뒤에는 어느 쪽이든 확인 대기로 멈춘다. 등록은 계획기의 행동이 아니다

워크플로는 다음 단계가 코드의 규칙으로 정해진 흐름이고 이 책의 에이전트 루프는 관찰 결과를 받아 모델이 다음 행동을 제안하는 구조다. 이 구분은 Anthropic의 설계 안내에서도 쓴다. 제품 이름이 무엇이든 다음 단계를 코드가 정하는지 모델이 정하는지를 본다. 한울연구소의 티켓 등록은 고정 흐름이다. 초안 생성 뒤에는 무조건 확인 대기로 멈추고 확인 이벤트 뒤에 호스트가 등록 메서드를 호출한다. 조회 단계에서만 계획기가 종료 여부를 제안한다. 허용 행동은 티켓 조회, 티켓 초안 작성, 종료 셋이고, 도구 이름이 목록에 없으면 실행하지 않는다. 워크플로와 에이전트의 구분

한 단계의 순서

루프 한 단계의 순서질문 + 관찰다음 행동 제안JSON행동 · 인자 검증Action 스키마실행 허용 검사한도 · 반복 · 마감도구 호출결과 검증구조 · 크기종료 또는다음 관찰관찰은 복사해서 넘긴다 · finish는 자유 문장을 받지 않고 마지막으로 검증한 도구 결과를 돌려준다

ReAct 연구는 추론과 행동을 교차시키며 환경의 결과를 쓰는 접근을 제시했다. 다만 그 벤치마크나 내부 추론 서술을 따라갈 것 없이, 밖에서 확인할 수 있는 행동 요청과 도구 결과만으로 제어를 짤 수 있다. 실행을 추적하는 데 긴 사고 과정까지 저장할 필요는 없다. 행동 스키마는 추가 필드를 거부하고 도구 인자는 선택된 도구의 스키마로 다시 검사한다. 등록 도구를 요청하거나 중복 JSON 키로 도구 이름을 덮어쓰려는 출력도 거부한다. ReAct 논문

도구 결과도 그대로 관찰에 넣지 않는다. 티켓 조회는 티켓 ID·접수 상태·분류·제목·설명을, 초안 작성은 초안 ID·미리보기·확인 대기 상태를 검사하고 결과 크기에도 한도를 둔다. 계획기에 넘기는 관찰은 복사본이라 계획기가 접수 상태를 해결 완료로 고쳐도 원본은 그대로다. 종료 행동은 자유로운 성공 문장을 받지 않는다. 관찰이 있으면 마지막으로 검증한 도구 결과를 돌려주고 아무 도구도 실행하지 않았으면 no_action이다. completed는 이 제한된 루프가 결과를 얻고 끝났다는 뜻이다. 사용자의 복합 요구까지 다 채웠는지는 이 루프가 판단하지 않는다.

루프를 멈추는 한도

루프를 멈추는 네 가지 한도단계 한도4계획 제안 횟수크레딧12계획 1 · 도구 실행 2시간30초단조 증가 시계 · 마감반복 지문=같은 도구 · 같은 인자크레딧 12단위가 소모되는 두 예 (experiments/agent-control.json)조회 후 종료completed · 2단계 · 4크레딧크레딧 1만 남음budget · 도구 호출 없음계획 1도구 실행 2없는 예산 · 실행 전에 검사하므로 도구를 부르지 않는다

모델이 끝났다고 말해도 루프는 서버가 정한 기준으로 멈춘다. 크레딧은 제어 로직을 검증하려고 정한 정책 예산이다. API 가격이나 실제 토큰 비용은 크레딧에 넣지 않았다. 계획 요청 전에 1단위가 남았는지, 계획을 받은 뒤 도구 실행에 필요한 2단위가 있는지 검사한다. 실행한 뒤에 잔액을 줄이면 한도를 넘긴 작업이 이미 끝나 있다. 실제 금액 예산으로 바꾸려면 사용량과 단가를 계산한 다음, 예상 최대 비용을 먼저 예약해 두고 작업이 끝나면 정산한다. 단계 한도는 제안 횟수를, 반복 지문은 같은 도구·같은 인자의 요청을 잡는다. 인자가 달라지는 반복은 지문으로 못 잡아서 단계 한도를 함께 둔다. 시간은 단조 증가 시계로 마감을 계산하고 계획과 도구 호출의 앞뒤에서 확인한다.

마감을 넘겼다고 실행 중인 호출이 끊기지는 않는다. 계획기가 60초 동안 멈추면 30초에 함수가 돌아오는 것이 아니라, 호출이 끝난 뒤 마감 초과를 확인해 다음 도구를 실행하지 않는다. 그래서 실행 시간을 강제하려면 클라이언트 타임아웃, 취소 가능한 비동기 작업, 프로세스 격리를 함께 설계한다. 도구가 늦게 돌아오면 execution_unknown이 된다. 시간을 넘긴 결과라 정상 완료로 내보내지 않는다. 그렇다고 외부 부작용이 없었다고 볼 수도 없다.

종료 상태뜻
awaiting_confirmation초안 생성 후 사용자 확인을 기다린다
completed검증한 도구 결과를 얻고 종료했다
no_action도구 결과 없이 종료를 제안했다
repeated_action같은 도구·인자 요청이 반복됐다
step_limit허용한 계획 횟수를 소진했다
budget다음 계획 또는 도구 실행 예산이 부족하다
deadline마감 이후 다음 단계를 시작하지 않는다
execution_unknown호출 이후 결과를 확정할 수 없다

부분 실패와 복구

재시도를 정하려면 결과가 불명확한 작업과 실행하지 않은 작업을 나눠야 한다. 예산 부족으로 도구를 호출하지 않았으면 실행되지 않은 것이 확실하다. 반면 도구 함수가 예외를 던졌으면 내부에서 상태를 바꾼 뒤 실패했을 수도 있다. 이 루프는 도구 예외 뒤 자동으로 재시도하지 않고 execution_unknown과 실행 시작 기록을 남긴다. 업무 상태를 조회하거나 같은 멱등성 키로 복구할 길이 있을 때 별도 경로를 고른다. 오류 문자열을 모델에게 주고 아무 도구나 다시 고르게 두면 같은 실패를 한 번 더 부른다.

초안을 만들어도 티켓은 아직 생기지 않는다. 다만 초안을 만든 직후 연결이 끊기면 확인되지 않은 초안이 남고, 작업을 재시작하며 새 초안을 만들면 초안이 겹친다. 이 메모리 루프는 프로세스 재시작 뒤 상태를 복원하지 않는다. 운영 구현에서는 요청 ID·단계·선택 행동·도구 실행 식별자·확인 대기 상태와 결과를 체크포인트로 남긴다. 저장된 단계에서 다시 시작할 때는 외부 쓰기를 무조건 재실행하지 않고, 완료 결과가 저장돼 있는지와 실제 업무 상태를 먼저 대조한다. 확인을 기다리는 동안 권한이 바뀔 수 있어서 재개 시 현재 권한을 다시 검사하고, 승인된 초안의 본문이 달라졌으면 새 확인을 받는다.

실행 기록과 평가

실행 기록은 무엇을 시도했는지와 무엇을 확인했는지를 나눈다. 이벤트에는 단계, 도구 이름, started·returned·unknown·late 같은 상태가 들어가고, 계획기의 내부 사고나 사용자 본문은 감사 로그에 복사하지 않는다. 도구 결과는 다음 계획의 관찰로 쓰고 운영 로그에는 따로 남긴다. 권한이 다른 작업에 전체 관찰을 재사용하면 이전 요청의 티켓 정보가 섞이니 요청별로 격리한다.

평가는 업무 성공과 제어 준수를 따로 본다. 올바른 티켓 상태를 돌려줬는가, 확인 전에 등록이 일어나지 않았는가, 반복과 예산 초과를 막았는가, 실패 뒤 중복 상태를 만들지 않았는가를 항목마다 따로 센다.

개발팀은 가짜 계획기를 실제 티켓 구현에 붙여 초안 작성 뒤 티켓이 하나도 안 생기는지부터 봤다. 그다음 확인 이벤트를 넣어 등록하고 조회와 종료 두 단계를 돌렸다. 조회 후 종료는 2단계에 4크레딧으로 끝났고, 반복 조회는 두 번째 실제 호출 전에 멈췄다. 크레딧이 1단위뿐인 초안 요청은 계획만 받고 도구를 호출하지 않았다. 가짜 계획기는 테스트에 필요한 행동을 일부러 돌려주므로 이 실험은 제어 로직까지만 잰다. 모델이 자연어 요청에서 적절한 도구를 고르는지는 가짜 계획기를 걷어내야 보인다. 실제 모델을 붙일 때는 같은 제어 테스트를 유지하고 도구 선택과 인자 정확성 평가를 따로 둔다.

에이전트가 필요 없는 경우

티켓 하나의 상태를 묻는 일이라면 입력한 ID로 조회해서 결과를 보여 주면 된다. 사용자가 이미 명확한 ID를 줬는데 모델이 조회 여부와 종료 여부를 매번 판단하면 호출만 늘어난다. 초안 확인·등록도 고정 경로로 충분하다. 모델은 증상 설명을 정리하는 데만 쓰고 확인과 등록은 애플리케이션이 한다. 필요한 자료나 도구가 입력에 따라 달라지고 관찰을 보고 다음 행동을 바꿔야 할 때만 제한된 동적 선택을 검토한다. 그때도 작은 허용 목록과 단계 한도로 시작해, 업무 성공과 지연, 운영 복잡도를 고정 흐름 쪽과 견줘 본다. 에이전트라는 이름은 채택 근거가 되지 못한다. 같은 평가 질문에서 무엇을 더 잘 처리하고 어떤 비용을 더 내는지 말할 수 없으면 더 단순한 경로가 답이다.

다중 에이전트

위임 · 예산 · 격리상위 요청 · 전체 예산 12 · 시간 30초 · 조직 A위임 메시지: 하위 작업 · 필요한 자료 · 허용 도구 · 배분 예산 · 반환 형식하위 A · 문서 검색도구: search만예산 4 · 조직 A 자료만하위 B · 티켓 조회도구: get_ticket만예산 4 · 조직 A 티켓만결과 검증 → 상위 문맥하위마다 10단계씩 개수 제한 없이 주면 전체 한도가 사라진다 · 하위의 출력으로 권한을 넓힐 수 없다 · 조직 B의 캐시·초안은 보이지 않는다

여러 에이전트로 나누는 설계는 작업을 독립적으로 분해할 수 있을 때 검토한다. 두 역할이 같은 전체 대화와 같은 넓은 도구 권한을 공유하면 이름만 나눈 셈이다. 문서 검색 담당에게 티켓 등록 권한을 줄 이유가 없고 하위 작업의 결과도 검증한 뒤 상위 문맥에 넣는다. 한 에이전트의 주장이 원문이나 업무 시스템보다 우선하지 않는다. 예산은 상위 요청 전체에서 관리한다. 하위 작업에 배분한 비용·시간·동시 실행 수를 합산하고 남은 예산에서만 추가 위임하며, 종료한 작업의 결과와 취소된 작업의 부작용을 구분한다. 격리에는 문맥뿐 아니라 자격 증명과 저장 공간이 들어간다. 이번 구현은 단일 루프고 여러 에이전트를 함께 돌려 보지는 않았다. 심화로는 한 요청을 독립 조회 둘로 나누고 같은 권한·전체 예산으로 단일 실행과 비교한다.

직접 구현과 SDK

확인할 것같은 이름이 가리킬 수 있는 다른 범위
최대 턴모델 호출 수인가, 도구 호출 수인가
타임아웃전체 요청인가, 개별 호출인가
자동 재시도어느 단계에 붙는가, 외부 쓰기에도 붙는가
체크포인트어느 단계에 기록되고 재개 시 무엇이 다시 실행되는가

직접 구현한 루프는 허용 행동과 종료 조건이 코드에 드러나는 대신 실행 상태의 저장, 재개, 스트리밍, 도구 프로토콜 연결을 직접 관리한다. SDK나 워크플로 엔진은 이런 공통 기능을 주지만 업무별 멱등성·확인·권한 정책을 대신 정하지 않는다. LangGraph 문서에는 체크포인트로 실행 상태를 보존하는 방법이 나와 있는데, 그렇게 저장한 상태가 외부 티켓 API가 실제로 커밋한 결과와 어긋날 수 있다. 설정값만 옮기지 말고 기존 실패 시나리오를 새 구현에서 다시 돌린다. 개발팀은 SDK를 설치해 동적 실행을 해 보지는 않았다. 대표 구현은 Python 루프다. MCP 연결과 결합하려면 비동기 호출·연결 종료·시간 제한·오류 변환이 더 필요하다. LangGraph 실행 상태 보존 안내

알아서 처리하게 두자고들 했지만 결국 개발팀은 어디까지 맡길지를 하나하나 코드로 적었다. 봇이 고를 수 있는 행동과 멈춰야 할 자리를 사람이 먼저 정해 두어야 했다.

답변에서 업무 실행으로12 / 21