하네스 불가지론적(harness-agnostic) 오케스트레이션 라이브러리에서 에이전트별 격리 및 환경 라이프사이클을 처리하는 방법
요약
에이전트 오케스트레이션 라이브러리 설계 시 에이전트별 격리 및 환경 라이프사이클을 관리하는 아키텍처를 다룹니다. 워크스페이스와 런타임을 추상화하여 프로비저닝부터 은퇴까지의 상태를 정의하고 관리하는 방법을 제시합니다.
핵심 포인트
- 워크스페이스와 런타임의 추상화를 통한 환경 격리 구현
- 에이전트의 4가지 상태(not-provisioned, provisioned, started, retired) 정의
- 프로비저닝과 런타임 인터페이스를 분리하여 구현체 독립성 확보
- sync 및 ensure 명령을 통한 선언적 상태 조정(Reconciliation) 방식
이 글은 에이전트를 위한 오케스트레이션 라이브러리 설계에 관한 저의 세 번째 게시물입니다. 진행 과정에서 내린 아키텍처 결정 사항들을 공유하고, 여러분도 동일한 문제를 겪고 있을 경우를 대비해 해결책을 제시하고자 하며, 동시에 여러분의 의견도 듣고 싶습니다.
에이전트의 환경: 워크스페이스 (workspace), 런타임 (runtime), 그리고 디렉토리 (directories)
설정 파일 (Configuration files)
환경 라이프사이클 (Environment Lifecycle)
이 포스트는 에이전트 환경의 라이프사이클에 관한 내용입니다. 이는 종종 간과되거나 단순히 '워크스페이스 + 스레드' 정도로 단순화되곤 하는 부분입니다.
그래서 저는 여러 환경과 런타임을 지원하고 싶었고, 이는 이를 추상화할 방법이 필요함을 의미했습니다. 저는 첫 번째 포스트에서 정의했던 방식을 고안해냈습니다:
워크스페이스 (workspace): 에이전트가 작업할 장소를 보장함
런타임 (runtime): 에이전트가 실행될 수 있는 환경을 보장함
따라서 에이전트는 프로비저닝(provision)되어야 하는 워크스페이스와 시작(start)되어야 하는 런타임을 가집니다. 이 단계들은 자연스럽게 순차적이며, 다음과 같은 네 가지 상태를 가집니다:
프로비저닝되지 않음 (not-provisioned): 워크스페이스가 없음. 이 상태에 도달하는 방법은 두 가지입니다:
- 프로비저닝된 적 없음 (DB 레코드 없음, 레터(letter) 없음): 에이전트가 아직 엔티티로서 존재하지 않으며, 단지 설정 텍스트일 뿐임.
- 이전에 프로비저닝되었으나 이후 프로비저닝 해제됨 (레코드 + 레터 유지): 워크스페이스는 사라졌지만 정체성은 유지됨.
프로비저닝됨 (provisioned): 워크스페이스와 git 브랜치가 디스크에 존재함. 런타임은 없음.
시작됨 (started): 런타임 레이어의 관점에서 런타임이 "활성화(up)"된 상태임 (이는 런타임마다 다름). 토큰이 발급됨. 메시지를 수신할 수 있음. 이때 에이전트가 실행됨. 이 상태는 순수한 형태에서 에이전트가 실제로 실행 중인지 여부는 실제로 알지 못한다는 점에 유의하세요 (에이전트 자체에 관한 노트 참조).
은퇴함 (retired): 영구적으로 해체됨. DB 레코드 + 레터가 영구적으로 보관됨 (이벤트 로그는 항상 하나의 레터를 하나의 에이전트에 매핑하며, 레터는 절대 재사용되지 않음).
중요한 점은 프로비저닝(provision)과 런타임(runtime)이 각각 인터페이스(interface) 뒤에 있으며, 모든 구현체는 스스로를 시작하고, 실행 중인지 확인하고, 프로비저닝하는 등의 방법을 알고 있다는 것입니다. 라이프사이클 로직은 자신이 어떤 구현체와 통신하고 있는지 신경 쓰지 않습니다.
참고:
start/stop은 런타임(runtime)마다 의미가 다릅니다.
provision은 런타임에 무관(runtime-agnostic)합니다.
저는 에이전트가 프로비저닝(provisioning) 시점에 생성되도록 결정했습니다. 별도의 "create" 명령은 없습니다. agents.yaml에 선언된 영구적인 에이전트는 provision이 실행될 때까지는 단순한 설정 텍스트일 뿐입니다. provision이 실행되는 행위가 바로 DB 레코드를 생성하고, 해당 레터(letter)를 할당하며, 환경을 구축하는 작업입니다.
조정(Reconciliation) 명령: sync 및 ensure
sync: 현실과 일치하도록 DB를 하향식(downward)으로 조정합니다.
ensure: agents.yaml에 선언된 에이전트별 최소 기준(floor, 목표가 아님)까지 에이전트를 상향식(upward)으로 끌어올립니다.
agents: atlas: ensure: started # 필요 시 provision + start 수행
backend: ensure: provisioned # provision만 수행, 런타임은 시작하지 않음
provision 및 멱등성(idempotency)에 관한 참고 사항
Provision은 멱등하며 복구 작업(repair operation)의 역할도 겸합니다. 모든 단계는 "ensure" / 누락 시 생성(create-if-missing) 방식입니다: 워크스페이스(workspace) 보장, 브랜치(branch) 보장, 아티팩트/비밀(artifacts/secrets) 디렉토리 보장, on_provision 실행. 그 결과는 다음과 같습니다:
삭제된 워크스페이스는 provision을 재실행함으로써 복구됩니다.
provision 도중 충돌(crash)이 발생하면 재실행을 통해 수정됩니다.
현재 존재하는 것을 절대 덮어쓰지 않습니다: 이미 존재하는 워크스페이스는 그대로 두고, 누락된 경우에만 재생성합니다. 이를 통해 재프로비저닝(re-provision)을 언제든 안전하게 실행할 수 있습니다.
이전에 프로비저닝된 에이전트를 재프로비저닝하면 기존 레코드와 레터를 재사용합니다.
명령 테이블
| 명령 | 참고 사항 |
|---|---|
| provision | 재시도/중복을 처리함 |
| unprovision | --remove-branch, --remove-artifacts, --remove-secrets 옵션 사용 |
| start | 설정을 위해 agents.yaml을 로드함 |
| stop | yaml이 필요 없음 |
| retire | yaml이 필요 없음 |
| sync | yaml은 선택 사항; 하향식(downward)으로만 작동 |
| ensure | yaml이 필요함; 최소 기준(floor)까지 상향식(upward)으로 작동 |
| promote | ephemeral(휘발성) → permanent(영구적)로 승격; yaml을 작성함 (프로그래밍 방식의 yaml 쓰기만 허용) |
레터(Letter)
Provision은 생성 이벤트입니다. agents.yaml에 정의된 영구적인 에이전트는 provision이 실행될 때까지 단순한 설정 텍스트일 뿐이며, 별도의 create 명령은 존재하지 않습니다. Provision은 DB 레코드를 생성하고, 레터를 할당하며, 환경을 구축합니다.
한 번도 프로비저닝되지 않은 에이전트(YAML만 있는 상태)는 레코드와 레터가 없습니다.
프로비저닝(provisioned)되면, 레터(letter)는 미프로비저닝(unprovision), 재프로비저닝(re-provision), 그리고 은퇴(retire) 과정을 거치며 지속됩니다. 한 번 할당된 레터는 절대 해제되지 않습니다 (이벤트 로그는 레터를 하나의 에이전트에 영구적으로 매핑해야 합니다).
따라서 미프로비저닝(unprovision)은 레코드와 레터를 유지한 채 에이전트를 미프로비저닝 상태로 되돌리며, 재프로비저닝(re-provision)은 동일한 식별자를 재사용합니다.
호스트(host) vs 도커(docker)
이것은 설계의 핵심 부분이라기보다는 구현 세부 사항에 가깝지만, 런타임(runtime)에 따라 시작(start)과 중지(stop)의 의미가 달라집니다. 호스트는 지속적인 런타임 프로세스가 없는 반면, 도커는 프로세스가 존재하기 때문입니다. 도커에서 시작(start)은 docker run을 의미하며 컨테이너가 지속적인 객체가 됩니다. 반면 호스트에서 시작(start)은 주로 토큰을 발행하고 새로운 상태를 설정하는 것을 의미합니다.
이는 호스트에서 "실행 중인가?(is it running?)"라는 질문에 단순히 true를 반환한다는 것을 의미합니다. 확인해야 할 프로세스가 없기 때문입니다. 즉, 호스트에서의 시작(start)은 사실상 장부상의 기록(토큰이 발행되었다는 주장)에 불과합니다.
에이전트 자체에 관한 참고 사항
이 부분은 제가 고민했던 지점인데, 다음과 같은 깨달음을 얻었습니다.
에이전트 자체(즉, 실제로 작업을 수행하는 LLM 또는 하네스(harness))는 단지 서브프로세스(subprocess)일 뿐이므로, 실제로는 라이프사이클(lifecycle)을 갖지 않습니다. 그것은 작동 중이거나, 작동 중이 아니거나 둘 중 하나입니다.
그래서 시작(start) 상태에 대한 하위 상태(substate)를 생각해보기도 했지만, 이는 환경(environment)과는 관련이 없습니다.
에이전트 자체에 대해 이야기할 것이 아주 많고 제가 이를 다소 무시하고 있는 것처럼 보일 수 있지만, 이는 나중에 중심 주제가 될 것입니다. 지금은 그 주변의 모든 것들을 먼저 설정하고 있습니다.
또한 저는 기존의 하네스들을 대체하려는 것이 아님을 밝혀둡니다. opencode, claude code 등은 모두 매우 잘 작동하며, 이들과 대등한 무언가를 만드는 것은 어려울 것입니다. 일부는 이미 원격 제어(control remote), 서브 에이전트(sub-agents) 등을 지원하고 있습니다.
핵심은 에이전트를 오케스트레이션(orchestrate)하기 쉽게 만들고, 하네스 불가지론적(harness-agnostic)이며, 커스텀 엔드포인트(custom endpoints)와 로컬 모델(local models) 실행까지 허용하는(이에 대해서는 이미 초안을 가지고 있습니다) 라이브러리를 만드는 것입니다. 이 모든 것들은 라이브러리 입장에서 보면 claude code를 실행하는 것과 마찬가지입니다. 즉, 대화할 수 있고, 무언가를 수행하게 할 수 있으며, 다른 에이전트들과 통신할 수 있는 하나의 에이전트일 뿐입니다.
다음 포스트는 스킬(skills)에 관한 것입니다. 스킬은 상당히 보편화되었기에 이를 지원하고 싶지만, 현재의 매우 자유로운 접근 방식은 실질적인 보안 위험을 초래한다고 생각하여 선호하지 않습니다. 게시가 완료되었을 때 알고 싶다면 저를 팔로우해 주세요.
submitted by /u/facu_75
[link] [comments]
AI 자동 생성 콘텐츠
본 콘텐츠는 r/LocalLLaMA의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기