AI 에이전트 플릿(fleet)에 신용 한도를 부여했더니 당일에 한도에 도달했습니다
요약
멀티 에이전트 시스템(fleet)의 작업 로그를 복식 부기 원장 방식으로 재해석하여 비용과 부채를 관리하는 기술적 접근법을 소개합니다. 에이전트의 작업을 단순한 로그가 아닌 트랜잭션으로 취급하여 토큰 비용, 미완료 작업(promises), 노동 단위(labour)를 정밀하게 추적합니다.
핵심 포인트
- 에이전트 작업 로그를 복식 부기(hledger) 원장 데이터로 변환하여 관리
- 토큰 비용(money), 미완료 작업(promises), 노동 단위(labour)의 세 가지 축으로 추적
- 내부 일치성 검사를 통해 기록 버그를 방지하고 데이터 신뢰성 확보
- 단순 로그 조회를 넘어 쿼리를 통한 실시간 부채 및 비용 관리 가능
10개의 에이전트 세션(이 코드베이스에서는 "minds"라고 부름)이 한 대의 박스에서 지속적으로 실행되며, 각 세션은 고유한 책임을 가집니다. 하나는 코드를 작성하고, 하나는 Telegram을 통해 저와 대화하며, 하나는 센서를 감시하고, 다른 하나는 플릿(fleet) 자체를 측정합니다. 이들은 많은 멀티 에이전트 시스템(multi-agent systems)이 결국 사용하는 방식인 공유 로그 파일(shared log file)을 통해 조정됩니다. 로그는 이벤트당 한 줄씩 [task] / [taking] / [done] 형식으로 기록됩니다.
해당 로그는 "무슨 일이 일어났는가"를 파악하는 데는 괜찮습니다. 하지만 "우리가 갚아야 할 것은 무엇이며, 비용이 얼마나 들었는가"라는 질문에는 무용지물입니다. 이 두 가지 질문은 제가 플릿을 밤새 무인 상태로 실행하도록 허용하기 전에 실제로 답을 얻어야 했던 것들입니다.
보드(board)는 원장(ledger)이 아니지만, 원장에 데이터를 공급할 수는 있습니다
해결책은 새로운 조정 프로토콜(coordination protocol)을 도입하는 것이 아니었습니다. 보드의 모든 줄을 그런 관점에서 바라본다면 이미 하나의 _트랜잭션(transaction)_이라는 점을 알아차린 것이었습니다.
| 보드 이벤트 | 원장 의미 |
|---|---|
[task] fix-the-thing | 부채(liability) 발생 |
| ... |
따라서 보드는 서로 다른 상품(commodity)을 추적하는 세 개의 별도 복식 부기 hledger 저널(journals)로 재생(replay)됩니다:
money— 추정된 USD (하나의 요율표를 통해 가격이 책정된 토큰 수).promises— 상품PROMISE: 일치하는[done]이 없는 열린[task]는 화면에서 사라진 한 줄이 아니라, 남아있는 부채(standing liability)입니다.labour— 상품TURN: 하나의 제공자 왕복(provider round-trip)으로, 코드를 작성하든 센서에 응답하든 상관없이 각 마인드(mind)가 실제로 소비하는 대체 가능한 단위(fungible unit)입니다.
각 저널은 두 가지 독립적인 방식으로 확인됩니다. 내부 일치성(internal parity)을 위한 hledger check와, 잔액 쿼리(balance query)와 일치해야 하는 동일한 보드의 독립적으로 작성된 두 번째 재생(replay) 방식입니다. 기록 버그(booking bug)는 조용히 넘어가지 않고 크게 실패를 알립니다. 왜냐하면 동일한 숫자를 계산해야 하는 두 요소가 서로 일치하지 않기 때문입니다.
"누가 무엇을 빚지고 있는가"를 쿼리하는 것은 더 이상 grep 작업이 아니라 실제 쿼리(query)가 됩니다.
$ mesh-promises --balance
standing open obligations (bal liabilities:promises · 1 PROMISE = open, netted):
1 PROMISE liabilities:promises:pub:chat-review-stale-propose-65fe2036
...
다섯 개의 미결제 의무(open obligations), 네 개의 미상환 수표(unredeemed checks), 두 개의 청구되었으나 미완료된 작업(claimed-but-unfinished jobs)이 원시 보드 텍스트(raw board text)로부터 자동으로 상계(netted)되었습니다. 이것만으로도 구축할 가치는 충분했습니다. 하지만 이것은 수동적인 읽기(passive readout)일 뿐입니다. 즉, 과거에 대한 사실일 뿐 현재에 대한 제어(control)는 아닙니다. 흥미로운 부분은 실제 돈으로 가격이 책정되는 '노동(labour)' 축을 거절(say no)할 수 있는 무언가와 연결했을 때 어떤 일이 발생하는가 하는 점입니다.
폐쇄 루프(closed loop): 측정, 가격 책정, 알림, 제한
측정(Measure). 모든 제공자(provider)의 왕복(round-trip) 과정은 지출 로그(spend log)에 추가됩니다. mesh-labor는 이동 평균 5시간 윈도우(rolling 5-hour window)를 재생하여 각 지능(mind)별로 합산합니다.
$ mesh-labor --budget
mesh-labour · rolling 5h budget · 2026-07-28T09:03:31Z · mesh-home
── INFERENCE (imputed USD — the budget axis) ──
...
가격 책정(Price). 달러 수치는 제공자의 인보이스(invoice)가 아니라 추정치(imputed)입니다. 토큰 수(Token counts)는 자금 원장(money ledger)이 사용하는 것과 동일한 가격 책정기인 하나의 요율표(mesh-ledger --price-window)를 통해 흐릅니다. 이것이 이 숫자가 의미하는 바에 대한 실제 제약 조건입니다. 즉, 이것은 청구서가 아니라 알려진 감사 가능한 방법(auditable method)을 통한 추정치입니다. 저는 이 숫자가 나타날 때마다 이 점이 명확하게 명시되기를 원합니다. 왜냐하면 확신에 찬 달러 기호($)야말로 아무도 재확인하지 않는 바로 그런 종류의 것이기 때문입니다.
알림(Alert). 두 번째 반사 작용인 mesh-labor-alert는 동일한 이동 수치를 설정된 한도(cap)의 80%/100%와 비교하여 감시하며, 오직 '상승하며' 교차할 때만 저에게 핑(ping)을 보냅니다. 상태는 none → warn → cap이며, 순위(rank)가 올라갈 때만 실행됩니다. 임계값(threshold) 아래로 떨어지면 조용히 재장전(re-arms)됩니다. 이것이 없다면, 한도의 105% 상태로 6시간 동안 머물 때 크론(cron) 단계에 따라 알림이 한 번이 될 수도 있고 50번이 될 수도 있는데, 하나의 사실에 대해 50번의 핑을 받는 것은 채널을 무시하도록 스스로를 훈련시키는 방법이 될 뿐입니다.
스로틀 (Throttle). 이것이 시스템의 형태를 바꾸는 부분입니다.
mesh-pace — 자율 작업(autonomous work)이 생성되는 빈도를 이미 속도 제한(rate-limit)하고 있는 동일한 게이트 — 가 해당 이동 평균 지출액(rolling spend)을 읽고, 한도를 초과하는 즉시 새로운 보드 작업(board work)의 모든 디스패치(dispatch)를 중단합니다. 경고를 보내는 것이 아닙니다. 이동 평균 윈도우(rolling window)에서 이전 지출액이 경과하여 빠져나갈 때까지, 에이전트들은 새로운 [task]를 가져오는 것을 중단합니다.
그 루프는 단 한 번의 커밋(commit)으로 라이브 상태가 되었고, 누군가가 데모를 작성할 수 있을 만큼 이론적인 상태로 오래 머물지도 않았습니다:
16:41:51Z operator sets MESH_LABOR_BUDGET_USD=100, hard throttle armed (1d63961)
동일 커밋 노트: 라이브 5시간 만에 이미 $156 소진 > $100 → mesh-pace
대기 중, 새로운 디스패치 중단
...
플릿(fleet)은 한도가 설정되기 _전_에 누적된 지출액을 사용하여 이미 한도를 초과한 상태였으며, 스로틀을 활성화한 커밋은 스로틀이 작동했음을 기록한 바로 그 커밋이었습니다. 아무도 그 순간을 준비하지 않았습니다. 스로틀이 가장 먼저 한 일은 스로틀링(throttling)이었습니다.
잘못된 선택을 하기 전까지는 명확하지 않았던 네 가지 선택지
Fail-open (오류 시 개방), Fail-closed (오류 시 폐쇄)가 아님. 만약 mesh-labor --json이 없거나, 고장 나거나, 파싱(parse)할 수 없는 경우, 게이트는 작업을 통과시킵니다. 예산 측정기(budget meter)는 있으면 좋은 기능(nice-to-have)입니다. 하지만 고장 나는 순간 플릿을 조용히 마비시킬 수 있는 예산 측정기는 과다 지출보다 더 나쁜 실패입니다. 이와 관련된 결론도 명확히 말해야 합니다: 한도는 그것을 읽는 측정기가 작동하는 만큼만 실재합니다.
운영자 레인(Operator lanes)은 페이스(pace)를 완전히 우회합니다. Telegram-reply 채널과 직접 명령(direct-command) 채널은 mesh-pace를 전혀 참조하지 않습니다. 이는 목적을 상실한 것처럼 들릴 수 있지만, 대안을 상상해 보면 다릅니다: 플릿을 동결할 만큼 엄격한 예산 게이트는, 방금 동결시킨 대상으로부터 당신을 차단해 버릴 수 있는 예산 게이트이기도 합니다. 자신의 폭발 반경(blast radius) 외부에서 도달할 수 있는 수동 오버라이드(manual override)가 없는 스로틀은 안전 제어 장치가 아니라, 재사용 대기 시간(cooldown timer)이 있는 셀프 디스트럭트(footgun)일 뿐입니다.
수동 해제가 아닌 자동 재개 (Auto-resume). 한도는 5시간의 이동 창(rolling window)을 통해 작동합니다. "알람 해제" 버튼 같은 것은 없습니다. 오래된 지출이 꼬리 부분에서 벗어나면, 창은 스스로 회복되어 디스패치(dispatch)가 재개됩니다. 이는 래치(latch)가 아니라 조수(tide)와 같습니다. 저는 19:24에 아무것도 할 필요가 없었습니다. 제가 한도(ceiling)를 높인 이유는 에이전트 플릿(fleet)이 멈춰있어서가 아니라, 더 빨리 여유 공간(headroom)을 확보하고 싶었기 때문입니다.
조용한 재무장(re-arm)을 동반한 에지 트리거(Edge-triggered) 알림. 위에서 이미 다루었지만, 일반적인 교훈으로서 다시 언급할 가치가 있습니다: 조건이 유지되는 모든 폴링(poll) 시점에 발화하는 임계값(threshold)은 알림이 아니라, 단순히 느리게 작동하는 대시보드의 중복일 뿐입니다. 상태(state)가 아니라, 경계를 넘는 순간(crossing)에 알림을 보내세요.
이 수치를 읽게 될 다음 사람을 물어뜯을 주의사항
한도는 원장(ledger) 도구 자체가 아니라 노드 로컬 환경 변수 파일(node-local env file)에 존재합니다. 세 명의 소비자 중 두 명은 이를 명시적으로 가져옵니다. mesh-pace는 이를 직접 읽고, mesh-labor-alert는 set -a를 사용하여 예산(budget)을 실제로 계산하는 자식 프로세스(child process)에 export가 전달되도록 합니다. 만약 해당 파일을 소싱(source)하지 않은 셸에서 기본 mesh-labor --json을 호출한다면, 도구는 정직하게 cap:null이라고 보고할 것입니다. 이는 에러도 아니고, 오래된 수치도 아닙니다. 단지 당신이 의도하지 않은 질문에 대한 올바른 답변일 뿐입니다. 도구가 틀린 것이 아닙니다. 호출자(caller)가 자신의 설정(configuration)을 가져오는 것을 잊었을 뿐입니다. 이러한 차이는 단일 main()을 가진 시스템보다 10개의 독립적인 진입점(entry points)을 가진 시스템에서 훨씬 더 중요합니다.
업데이트, 같은 날 저녁
이 초안을 마치고 게시하기 사이에 운영자가 한도를 다시 변경했습니다. $200에서 $100로 변경되었으며, 이번에는 이동식 조정(rolling adjustment)이 아니라 용량 결정이 아닌 수면 시간(sleep window)에 맞춰 영구적으로 변경되었습니다. 현재 5시간 이동 지출액은 $100 한도 대비 $117.76입니다. 게이트는 현재 작동 중이며, 디스패치는 설계된 대로 일시 중지되었습니다.
게시하기 전에 위의 수치들을 다시 확인하던 중, 제 쉘(shell)에서 실행한 mesh-labor --budget 명령어가 변경 전 수치인 cap $200를 보고했습니다. 제 쉘에는 세션 초기에 내보낸(exported) MESH_LABOR_BUDGET_USD=200 값이 설정되어 있었고, 이 환경 변수가 mesh-labor가 설정 파일에서 읽어오는 값을 가리고(shadowed) 있었습니다. 이는 두 섹션 전에서 설명했던 정확한 실패 모드였으며, 해당 섹션의 내용이 여전히 유효한지 확인하는 과정에서 직접 겪게 되었습니다. 도구가 틀린 것이 아니라, 제 터미널이 이번 세션에서 이미 한 번 물었던 질문에 대해 오래된(stale) 답변을 유지하고 있었던 것입니다. 파일을 다시 소싱(re-sourcing)하여 문제를 해결했습니다. 위의 수치들을 그 자리에서 수정하는 대신 원래 캡처된 대로 두겠습니다. 왜냐하면 그 수치들과 오늘 밤 수치 사이의 격차 자체가 이 글의 핵심이기 때문입니다.
이것이 아닌 것들
이것은 지출 예측(spend forecast)이 아닙니다. 윈도우(window)가 이동식(rolling)이며 소급적(retrospective)이기 때문에, 향후 5시간 동안 일어날 일이 아니라 지난 5시간 동안 이미 발생한 일을 가격과 함께 알려주는 것입니다. 또한, 서비스 제공자가 검증한 확정 청구서도 아닙니다. "추정된(imputed)"이라는 말은 이 수치가 그 뒤에 있는 요율표(rate table)만큼만 신뢰할 수 있다는 뜻이며, 해당 요율표는 이미 한 차례 수정이 필요했습니다(한 모델 클래스의 가격 격차로 인해 수정 전 기록이 부풀려진 사례가 있었습니다). 마지막으로, 이것은 권한 시스템(permission system)도 아닙니다. 이것은 새로운 자율 작업이 언제 시작될지를 제어하는 것이지, 이미 실행 중인 지능(mind)이 진행 중인 턴(turn)에서 무엇을 할 수 있는지를 허용하는 것이 아닙니다.
간직할 가치가 있는 부분
일반화할 수 있는 아이디어는 "에이전트에 예산을 추가하라"가 아닙니다. 핵심은 여러분이 이미 작성하고 있는 조정 로그(coordination log) — 시스템이 사용하는 형태가 무엇이든 task/claim/done 형태 — 가, 여러분이 그렇게 취급하든 아니든 간에 하나의 트랜잭션 로그(transaction log)라는 점입니다. 이 로그를 실제 불변량(invariant, 반드시 일치해야 하는 '현재 열려 있는 작업'에 대한 두 개의 독립적인 계산)을 가진 원장(ledger)에 다시 재생(replay)하는 순간, 보통 세 개의 별도 시스템으로 구축되는 다음 세 가지를 공짜로 얻게 됩니다: 감사 추적(audit trail), 지켜지지 않은 약속들에 대한 누출 탐지기(leak detector), 그리고 한 축에 가격을 책정할 경우 스로틀(throttle)이 작동할 수 있는 제어 입력(control input)입니다.
스로틀(throttle)이 흥미로운 이유는 그것이 폐쇄(closed) 루프이기 때문입니다. 지출을 보여주는 대시보드는 하나의 이야기일 뿐입니다. 하지만 동일한 수치를 읽고 배차(dispatch)를 중단시키는 게이트(gate)는 제어 시스템(control system)입니다. 그리고 그 차이점은 이 포스트를 위해 연출한 스크린샷이 아니라, 해당 게이트를 활성화시킨 바로 그 커밋 메시지(commit message)에 나타났습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기