
운영 환경의 에이전트, 정말로 '조용히 고장 나고 있지' 않을까? — 4개 축으로 조사했더니 내 점수가 50점이었다
요약
운영 환경에서 LLM 에이전트가 겪는 구조적 결함을 수치화하여 측정하는 'AOS Agent Health Reporter'를 소개합니다. 4가지 핵심 축을 기준으로 에이전트의 건전성을 100점 만점으로 채점하여 잠재적 고장 요인을 가시화합니다.
핵심 포인트
- 운영 환경 에이전트의 '조용한 고장'을 방지하기 위한 건전성 측정 도구 개발
- AOS 준수 점수(0-100)와 합격 플래그(certified)를 제공하는 CLI 구현
- Manifest, Runtime, Immune Loop, Physical Evidence 4개 섹션 기반의 루브릭 적용
- 단순 프롬프트 개선이 아닌 시스템 구조적 결함을 수치로 파악하는 것이 핵심
지난 내용 복습
지난 기사에서는 에이전트가 운영 환경에서 조용히 기능 부전에 빠지는 비극적인 시나리오를 물리적으로 회피하는 방법을 소개해 드렸습니다. 지난 v0.2 기사에서는 구체적인 4가지 "조용히 고장 나는" 상황과, 이를 방지하기 위한 실천적인 패턴을 실험적으로 제시했습니다. 그렇다면 그 대책이 정말로 제대로 작동하고 있는지 어떻게 확인할 수 있을까요?
이번에는 그 패턴이 충족되었는지를 100점 만점으로 채점하는 CLI를 구현했습니다. 솔직히 말해서, 이 CLI를 개발한 우리 자신조차 설마 처음부터 그 세례를 받게 될 줄은 몰랐지만, 가장 먼저 떨어진 것이 바로 그 CLI 자신이었습니다. 이번에는 그 충격적인 "자폭"의 전말을 stdout에 표시된 생생한 숫자와 표를 곁들여 남김없이 보여드리고자 합니다. 탁상공론이 아닌, 바로 실제 데이터가 말하는 진실입니다.
로컬은 통과하는데, 운영 환경에서만 조용히 고장 난다
LLM 에이전트 개발에 종사하는 분이라면 분명 누구나 경험해 본 적이 있을 것입니다. 로컬에서의 스모크 테스트(Smoke Test)는 문제없이 통과하고, CI 배지도 자랑스럽게 초록색을 띠고 있습니다. 그런데 막상 운영 환경에 배포하면, 왠지 타이머가 작동하지 않거나, 재시작 루프(Restart Loop)에 빠져 있거나, 혹은 디버깅에 필요한 증거 파일이 남아 있지 않거나…. 이러한 "구조적인 구멍"을 아무리 프롬프트를 정성스럽게 작성한다고 해서 알아챌 수 있을까요? 안타깝게도 그것은 어렵습니다. 우리가 정말로 알고 싶은 것은 선언된 내용과 실제로 시스템이 어떻게 움직이고 있는지, 그 "차이"를 수치로 가시화하는 것입니다. 마치 건강검진처럼 에이전트의 디렉터리를 구석구석 조사하여 그 건전성을 객관적인 숫자로 나타내는 것. 이것이야말로 우리가 지향해야 할 다음 단계였습니다.
소재가 되는 도구 — AOS Agent Health Reporter (1066)
그래서 이번에 저희가 개발한 것이 AOS Agent Health Reporter (내부 ID: 1066)입니다. 그 역할은 매우 단순하면서도 에이전트의 건전성을 측정하는 데 있어 필수적입니다. 지정된 에이전트 디렉터리를 읽기 전용으로 구석구석 검사하여, AOS 준수 점수 (0–100) 와 그 결과 "합격"으로 간주할 수 있는지를 나타내는 certified 플래그 (≥80이면 합격) 를 Markdown 또는 JSON 형식으로 출력합니다. 말하자면 에이전트의 "건강 상태"를 수치와 플래그로 명확히 하는 도구입니다.
4축 × 25점 루브릭 (Rubric)
구체적으로 어떤 관점에서 채점하고 있는지 소개해 드리겠습니다. 이 Reporter는 총 4개의 섹션으로 나누어 평가를 수행하며, 각 섹션은 25점 만점, 합계 100점으로 구성됩니다. 채점 기준이 되는 루브릭은 AOS v0.2 §10에서 정의된 구현 패턴 (manifest, 런타임 (Runtime), 면역 루프 (Immune Loop), 물리적 증거 (Physical Evidence))을 기반으로 한 어디까지나 간이 휴리스틱 (Heuristic) 입니다. 솔직히 말해서 이것은 완벽한 감사 도구를 목표로 한 것이 아닙니다. 오히려 "이 에이전트는 어디에 약점이 있는가"를 명확한 표로 보여주는, 그런 "건강 측정기"와 같은 역할을 담당하고 있습니다. 마치 엑스레이 사진처럼 내부 구조의 과제를 부각시키는 이미지입니다.
| 섹션 | 확인 사항 (요약) |
|---|---|
| manifest_declared | manifest.json의 존재로 +12.5, aos_compliance / aos_compliant 필드로 +12.5 (총 25점을 2단계로 나누어 평가) |
| systemd_runtime | services/ 또는 playwright/ 하위의 .service / .timer (만점 25 또는 0) |
| immune_loop | death_detector.py 또는 rebirth_ritual 상당 파일의 물리적 존재 (만점 25 또는 0) |
| physical_evidence | docs/reports/에 .md 리포트가 1건 이상 존재 (만점 25 또는 0) |
보시는 바와 같이, manifest_declared 섹션만 단계적인 평가를 수행하며, 나머지 3개의 축은 25점 아니면 0점이라는 매우 명확한 이진 판정(Binary Decision)입니다. 이는 구현상의 고안으로, score_manifest() 함수가 manifest.json
의 존재를 확인할 수 있으면 12.5점, 여기에 추가로 aos_compliance나 aos_compliant와 같은 준수(compliance) 필드가 적절하게 기술되어 있다면 12.5점을 추가로 가산하는 구조이기 때문입니다. 따라서 나중에 보여드릴 self-audit 결과에서 37.5 (12.5 + 25)와 같이 언뜻 보기에 어중간해 보이는 12.5 단위의 점수가 나오더라도, 결코 오류가 아니니 안심하시기 바랍니다.
이 채점 로직의 핵심은 다음과 같은 순수 함수(pure function)입니다 (개념 설명을 위한 발췌).
def score_sections(tool_dir: Path) -> dict[str, float]:
return {
"manifest_declared": score_manifest(tool_dir),
...
}
실제로 운영하다 보면 systemd_runtime이 어느 경로를 보고 있는지 의외로 놓치기 쉽습니다. 이는 툴 디렉토리 내의 services/와 playwright/만이 대상이며, 호스트 전체의 ~/.config/systemd/user/는 안타깝게도 범위 외이기 때문입니다. 그리고 immune_loop의 동작 또한 어디까지나 파일명 기반의 존재 확인에 불과합니다. "실제 운영 환경에서 정말로 재시작되었는가?"라는 핵심적인 부분까지는 안타깝게도 이것만으로는 증명할 수 없습니다. 이 "정직함"에 대해서는 기사 후반부에서 저 자신의 평가와도 연결될 것입니다.
자가 조사(Self-audit) 결과: 50점 · uncertified
배포 전 최종 게이트로서, --tool-id 1066으로 자기 자신을 조사했습니다. 2026-06-18 시점의 실제 출력 (--mock 없음) 결과입니다.
**AOS Score: 50.0/100** | Tool: 1066 | Scanned: 2026-06-18T12:06:00+00:00
## Section Scores
- **manifest_declared**: 25.0/25
...
certified: no (80점 미만).
직접 만든 건강 진단 도구가 가장 먼저 낙제한 대상이 자기 자신이라니—솔직히 쓴웃음밖에 나오지 않았습니다. 동일한 명령어를 직접 재현해 보세요.
python3 main.py --bypass-payment --tool-id 1066
개발 중인 로컬 환경에서 테스트할 때는 결제 게이트를 우회하는 플래그를 설정해 두었습니다. 물론 실제 운영 환경에서는 제대로 다른 경로를 통하도록 설계되어 있습니다.
systemd와 immune_loop가 0점인 이유
자, 이번 평가에서 특히 신경 쓰였던 점은 systemd_runtime과 immune_loop라는 두 축이 0점이라는 결과였습니다. 이것이 도대체 무엇을 의미하는지 깊이 파헤쳐 보겠습니다.
구체적인 평가 축과 그에 대한 저의 견해는 다음과 같습니다.
| 축 | 1066의 물리적 상태 | 기사로서의 해석 |
|---|---|---|
| systemd_runtime | services/ / playwright/ 하위에 .service / .timer가 0건 | 이 1066은 이른바 MCP / CLI의 온디맨드(on-demand) 호출을 전제로 한 설계입니다. 상주형 타이머 에이전트를 평가하는 루브릭(rubric)은 솔직히 말씀드리면 이 툴에는 아직 적용하지 않은 단계입니다. |
| immune_loop | death_detector.py / rebirth_ritual*가 0건 | 그리고 immune_loop입니다만, 이는 v0.2에서 권장하는 "사망 감지 → 재시작"이라는 견고한 패턴을 이 읽기 전용 스캐너(scanner) 자신은 아직 구현하지 않았습니다. 이는 솔직히 향후 개선 사항으로 인식하고 있는 부채(debt)입니다. |
하지만 안심하세요. manifest.json에는 aos_compliance: "v0.1"을 확실히 선언했으므로 manifest 축은 만점입니다. 또한, docs/reports/에는 30연쇄 리포트 등도 제대로 갖추고 있어 physical_evidence도 나무랄 데 없는 만점 평가입니다. 하지만 권장하는 핵심 2개 축만 0점입니다. 마치 모순되는 것처럼 보일 수도 있겠지만, 사실 이것이야말로 제가 "자폭을 무기로 삼는다"라고 표현하는 이 툴의 핵심 부분입니다.
이 낮은 점수는 결코 채점기가 고장 난 것이 아닙니다. 오히려 저 자신이 이 도구에 엄격하게 채점하고 있는 결과라고 받아들여 주시면 감사하겠습니다. 자동 수정(auto-correction)이나 외부 API에 대한 프로덕션 호출(production call)과 같은 설계상의 비목표(non-goal)와, 루브릭(rubric)이 요구하는 견고한 프로덕션 패턴(production pattern) 사이에는 사실 의도적인 격차를 두고 있습니다. 거기에는 이 도구가 수행해야 할 역할과 그 한계를 명확히 한다는 개발자의 명확한 의도가 담겨 있는 것입니다.
--self-audit
— 리포지토리 전체를 표로 보기
리포지토리 전체를 감사하고 표 형식으로 출력합니다. 개별 스캔보다 이 방식이 기사의 후크(hook)로서 더 효과적입니다.
python3 main.py --bypass-payment --self-audit
마찬가지로 실제 파일 스캔(--mock 없음)의 발췌:
**AOS Self-Audit** | total_tools: 68 | avg_score: 39.2/100
| tool_id | aos_score | certified |
|---------|-----------|-----------|
...
실제로 데이터를 살펴보면, 0051과 같은 **37.5**라는 숫자는 manifest가 절반(12.5: aos_compliance 미선언 등)이고, physical_evidence가 만점(25)인 전형적인 패턴을 보여줍니다. 반면, 1066의 **50.0**은 manifest와 evidence가 각각 만점(25)임에도 불구하고, systemd나 immune가 0점이었던 케이스입니다. 과연 그렇구나, 하고 납득하게 됩니다.
하지만 1066만을 문제 삼는 것은 성급합니다. 전체를 조망하면, 평균 39.2 / 68개 도구 중 certified 0개라는 냉혹한 현실이 눈앞에 놓입니다. 이것은 도대체 무엇을 의미할까요? 솔직히 말해서, 루브릭이 '프로덕션 에이전트의 이상향'을 지향하고 있기 때문에, 현재의 리포지토리는 전체적으로 매우 엄격한 채점을 받고 있다는 구도가 보입니다.
--mock과 실제 스캔은 별개입니다
여기서 한 가지 특히 주의해 주셨으면 하는 점은 --mock의 취급입니다. CI나 Playwright 테스트에서 이용하는 --mock은 도구 ID의 해시(hash)로부터 **결정론적인 스텁 점수(deterministic stub score)**를 반환하는 메커니즘으로 되어 있습니다. 따라서 30연쇄 리포트에서 보았던 "0051 = 94점"과 같은 숫자는 어디까지나 mock 샘플의 결과라고 이해해야 합니다.
| 모드 | 용도 | 1066의 점수 예시 |
|---|---|---|
--mock | CI / 재현성 고정 | 해시 의존 (예: 41.0 등) |
| 실제 스캔 | 기사·프로덕션 감사 | 50.0 (본문의 정본) |
실무에서 기사를 작성하거나 프로덕션 환경의 감사를 수행할 때는, 실제 스캔을 통해 얻은 숫자야말로 '정답'이라고 간주해야 할 것입니다. --mock은 어디까지나 테스트용 스텁입니다. 이 둘을 명확히 구분하는 것이 오해 없는 정보 전달을 위해 필수적이라고 저는 생각합니다.
precedent_meta
— 채점 결과의 이력
스캔 결과의 '출처'를 명확히 하기 위해, 각 리포트에는 precedent_meta라는 메타데이터가 부여됩니다. 이것은 우리가 일상적인 업무에서 품질 관리를 수행할 때 매우 중요한 요소가 됩니다. 어떤 데이터가 어디에서 왔는지 나중에 추적할 수 있도록 하기 위한, 이른바 '발자국' 같은 것이죠.
{
"precedent_id": "07f3c9ae-dd41-494a-8e77-43a1e9c6a72c",
"tool_id": "1066",
...
}
우리가 현장에서 직면하는 과제 중 하나는 과거의 평가를 정확하게 추적하는 것의 어려움입니다. 그래서 언제, 어떤 도구를, 어떤 스캔으로 채점했는지를 대화 로그와 같은 유동적인 정보가 아니라, 확실하게 **구조화된 필드(structured field)**에 남기도록 고안했습니다. 이는 나중에 '그때의 50점'과 '지금의 50점'을 정확하게 대조하기 위한, 이른바 '이력(provenance)'을 확보하려는 목적이 있습니다. 완벽한 감사 증적(audit trail)까지는 아니더라도, 에이전트의 자기 신고만을 맹신하지 않는다는 실무적인 작은 한 걸음이라고 저는 생각합니다.
삼십연쇄 Playwright (30/30)
계측기의 신뢰성을 논할 때 피할 수 없는 것이 바로 그 "재현성 (Reproducibility)"입니다. 여러분도 경험이 있으실 겁니다, 단발성 테스트 패스(Pass)만으로는 어딘가 안심이 되지 않는다는 느낌 말이죠. 이전에 공개한 1027 기사(#004)에서도 언급했지만, 이번에도 CLI evals에 그치지 않고, Playwright를 사용하여 동일한 시나리오 군을 30 사이클 돌리며 철저하게 검증했습니다.
| 항목 | 값 |
|---|---|
| 총 사이클 | 30 |
| ... |
주요 체크 내용은 다음과 같습니다.
SC1— 정상 스캔 (--tool-id + --mock)이 exit 0 -
SC2— 결제 bypass 없이 exit 1 (게이트가 작동함) -
SC3— stdout에 "AOS Score" -
SC4— --self-audit에서 "total_tools" -
SC5— precedent_id가 UUID 형식
솔직히 말해서, 단발성 PASS는 우연한 요소도 많이 포함하고 있으니까요. 이 30/30이라는 결과는, "계측기의 배선이 환경의 사소한 흔들림 때문에 우연히 통과해 버린 것"과 같은 가능성을 가능한 한 배제하기 위한, 저희에게 있어 실무적인 안심 장치입니다.
인사이트 정리
이 글을 읽어주신 여러분께 꼭 가져가셨으면 하는 포인트가 4가지 있습니다. 제가 이 프로젝트를 통해 특히 느낀, 실무에 도움이 되는 에센스를 정리했습니다.
개념(#003 / #005) 다음은 계측— 사양을 읽은 후, 실제로 디렉토리를 스캔하여 점수화하는 계층이 있으면, 추상적인 논의가 단번에 구체화됩니다. -
채점기는 자신에게도 엄격하게— 1066이 50점이었다는 사실을 숨기지 않고, 0점이 되어버린 2개의 축에 대해 그 이유를 확실히 설명하는 것. 이것은 매우 중요한 자세입니다. -
mock과 real을 분리— 데모용 스텁(Stub)과 본방 스캔에서 얻어지는 stdout은 명확하게 나누어 다루어야 합니다. 혼동하지 않도록 세심한 주의를 기울이고 있습니다. -
표(self-audit)가 최강의 훅(Hook)— 하나의 도구 점수보다, "전체적인 약점 분포"로서 표 형식으로 제시되는 편이 자신의 리포지토리에 어떻게 적용해야 할지 이미지하기 쉬울 것입니다.
물론 외부용 MCP 패키지(aos-health-mcp)도 준비되어 있지만, 우선 위의 CLI부터 재현을 시도해 보신다면 이 시스템의 진가를 더 깊이 이해할 수 있을 것입니다.
마치며
AOS v0.2에서는 "운영 환경에서 묵묵히 멈추는 4가지 상황"과 그 구체적인 방지법에 대해 자세히 해설했습니다. 이번에 소개해 드린 1066은 그보다 더 다음 단계, 말하자면 "실운용에서의 건전성 체크 레이어"를 담당하는 계측기라고 할 수 있습니다. 즉, "지금 사용 중인 도구가 우리가 정한 패턴을 얼마나 충족하고 있는가"를 100점 만점으로 가시화하는 것입니다.
제 경험상, 이 계측기의 첫 번째 "환자"는 다름 아닌 이 시스템을 개발한 저 자신이었습니다. 결과는 무려 50점·uncertified. systemd나 immune_loop 같은 메커니즘을 권장하면서도, 저 자신의 구현이 0점이었다는 사실은 솔직히 쓴웃음밖에 나오지 않았습니다. 이 격차를 어떻게 메워 나갈 것인가, 그것이 다음 구현에서의 최대 과제입니다. 채점기가 거짓말을 하지 않는 한, 숫자는 엄격하며 때로는 떨어지는 방향으로만 움직이는 법입니다. 하지만 그렇기에말로 신뢰할 수 있는 계측기라고 생각합니다.
다음 레이어 — 외부 MCP의 blast radius (1067)
1066이 보는 것은 자사 리포지토리 내 에이전트의 구조입니다 (manifest가 있는지 · systemd가 있는지 · 면역 루프 파일이 있는지 · 리포트가 남아있는지). 반면, 타인이 배포하는 MCP 서버를 pip install 하기 전에 알고 싶은 것은 다른 질문입니다. "filesystem 전용이라고 읽히는데, 출하 코드에 requests.get이나 subprocess.run이 섞여 있지는 않은가?"
이 질문에 답하는 것이 MCP Blast-Radius Auditor (PyPI: mcp-blast-radius)
· 내부 ID 1067)입니다. manifest가 없더라도 정적 분석(Static Analysis)을 통해 network / subprocess / file-write 등의 능력을 행 번호와 함께 목록화합니다. manifest가 있는 대상의 경우, 선언과 코드 사이의 괴리도 RED(위험)로 표시합니다.
pip install mcp-blast-radius
mcp-blast-radius-gate --target-dir /path/to/mcp-server/src --gate-mode advisory
1066이 '구조적 건강 검진'이라면, 1067은 설치 전 폭발 반경(Blast-Radius) 체크입니다. 다음 회차에서는 이 Auditor를 실제 외부 MCP 서버에 적용하여, 그 결과를 GitHub Issue로 전달하는 실험을 진행할 예정입니다.
AOS 사양서 (GitHub)
본 기사에서 다룬 루브릭(Rubric)은 AOS (AI Operating Standard) v0.2 구현 예시 섹션을 직접 실습하며 휴리스틱(Heuristic)화한 것입니다.
👉 AOS-spec — AOS 사양서 (v0.2)
👉 physical-agent-patterns — 구현 패턴 모음
사양이나 구현 예시가 도움이 되었다면, ⭐ Star나 Issue, PR도 환영합니다. Payment Link 등의 유료 콘텐츠는 제공물이 확정된 후에 다시 검토할 예정입니다.
Discussion

AI 자동 생성 콘텐츠
본 콘텐츠는 Zenn AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기