본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 23. 00:39

모델이 아닌 하네스(Harness)를 믿으세요: 로컬 에이전트가 스스로 가드레일을 구축한 주말의 기록

요약

로컬 27B 코딩 모델의 불안정한 출력을 보완하기 위해 모델 자체보다 견고한 '하네스(Harness)' 시스템을 구축하는 전략을 다룹니다. 모델을 확률적 구성 요소로 취급하고, 유닛 테스트, 린트, 빌드 검증 등 결정론적 가드레일을 통해 에이전트의 신뢰성을 확보하는 과정을 설명합니다.

핵심 포인트

  • 모델의 성능보다 오류를 잡아내는 파이프라인(Harness) 구축이 핵심
  • 로컬 모델의 가변성을 제어하기 위한 결정론적 가드레일 활용
  • 워크스페이스 복제 및 자동화된 테스트 게이트를 통한 검증 프로세스
  • 클라우드 API 없이 로컬 환경(AMD, Apple Silicon)에서 에이전트 운영

LLMKube 블로그에서 교차 게시되었습니다.

집에 있는 하드웨어에서 실행되는 로컬 27B 코딩 모델은 동전 던지기와 같습니다. 어떤 실행에서는 20분 만에 해결책을 완벽하게 찾아내기도 하지만, 어떤 실행에서는 잘못된 파일을 수정하고, 코드가 무엇을 하든 상관없이 통과하는 테스트를 작성한 뒤 완료되었다고 말하기도 합니다. LLMKube의 Foreman 뒤에 숨겨진 베팅은 제가 신뢰할 수 있을 만큼 좋은 로컬 모델을 찾을 수 있을지에 대한 것이 아니었습니다. 그것은 제가 그 어떤 단일 모델의 출력보다 더 신뢰할 수 있는 _하네스 (harness)_를 구축할 수 있는지에 대한 것이었습니다. 이번 주말은 그 베팅을 그 어떤 벤치마크보다 더 혹독하게 시험했습니다. 왜냐하면 하네스가 주말 내내 스스로의 가드레일 (guardrails)을 구축했기 때문입니다.

0.8.12 및 0.8.13 버전 동안 일어난 일의 요약본입니다. 저의 로컬 코더는 스스로를 위한 세 개의 새로운 게이트 (gates)를 구축했습니다. 그중 하나는 그것이 잡아내도록 작성된 바로 그 결함을 포함한 채 배포되었고, 리뷰 과정에서 그것이 발견되었습니다. 기계들이 작동하는 동안 세 명의 새로운 기여자(contributors)가 네 개의 깨끗한 풀 리퀘스트 (pull requests)를 보냈습니다. 동일한 모델이 AMD 박스와 Apple Silicon Mac에서 실행되었으며, Mac은 아무도 예상하지 못한 라운드에서 조용히 승리했습니다. 그리고 이 과정 중 단 1바이트도 클라우드 API에 닿지 않았습니다.

명확하게 기술된 논지

모델이 아닌 하네스 (harness)를 믿으세요. 로컬 모델 기반의 코딩 에이전트는 매우 가변적인 품질의 출력을 생성하며, 아무리 프롬프트 튜닝 (prompt tuning)을 해도 27B 모델을 프론티어 모델 (frontier model)만큼 신뢰할 수 있게 만들 수는 없습니다. 그래서 Foreman은 모델에게 신뢰할 수 있게 해달라고 요구하지 않습니다. 대신 모델을 신뢰할 수 있는 파이프라인 (pipeline)으로 감쌉니다. 즉, 코더는 복제된 워크스페이스 (cloned workspace)에서 작업하고, 워크스페이스 내의 빠른 게이트 (gate)가 gofmt, vet, 빌드 (build), 린트 (lint), 그리고 수정된 패키지에 대한 유닛 테스트 (unit tests)를 실행합니다. 리뷰어는 이슈 (issue)에 대한 디프 (diff)를 읽고, 클린룸 (clean-room) Kubernetes Job이 무엇인가가 'GO'라고 불리기 전에 전체 스위트 (full suite)를 다시 실행합니다. 이 모든 것 주변에는 범위 확인 (scope checks), 수정 없는 연속 기록 탐지 (edit-free-streak detection), 레포지토리 맵 컨텍스트 (repo-map context)와 같은 결정론적 레일 (deterministic rails)이 자리 잡고 있습니다. 모델은 구성 요소 자체가 신뢰할 수 없을 때조차 시스템의 판결을 신뢰할 수 있게 만드는 역할을 하는 시스템 내부의 확률적 구성 요소 (stochastic component)입니다.

흥미로운 질문은 결코 "모델이 좋은가"가 아닙니다. 그것은 "하네스 (Harness)가 모델이 나쁠 때 이를 잡아내는가"입니다. 이번 주말은 저에게 유난히 정직한 답을 주었습니다.

시작이 된 감사 (Audit)

회귀 (Regression) 현상과 함께 시작되었습니다. 저는 0.8.12 버전을 배포하고 전체 플릿 (fleet)에 적용했는데, 제 Mac에 있는 메탈 에이전트 (metal agent)가 작동을 멈췄습니다. 원인은 며칠 전 Foreman 자체가 작성했던 변경 사항 때문이었습니다. 해당 변경 사항은 에이전트가 서비스별 runtime 필드를 준수하도록 만들었지만, 에이전트는 자신의 llama.cpp 백엔드를 llama-server라는 키로 등록한 반면, 제 플릿의 모든 InferenceService (그리고 클러스터 내 컨트롤러, CRD 자체의 기본값)는 표준 값인 llamacpp를 사용하고 있었습니다. 코드베이스의 두 부분이 이름에 대해 서로 일치하지 않았던 것입니다. 하위 호환성이 깨진 상태였음에도 불구하고, 이는 게이트 (gate)를 통과했고, 리뷰를 통과했으며, 배포되었습니다.

그 일은 꽤 쓰라렸기에, 저는 같은 종류의 실수를 찾기 위해 그 주말 동안 Foreman이 반영한 모든 PR (Pull Request)을 감사했습니다. 그리고 두 번째 실수를 발견했습니다. 메트릭 (metrics) 변경 사항에서 첫 번째 토큰 생성 시간 (time-to-first-token) 히스토그램과 요청 오류 카운터 (request-error counter)를 등록했고, 기록 규칙 (recording rules)과 Grafana 패널까지 완벽하게 갖추었지만, 정작 그 어떤 프로덕션 코드도 이를 방출 (emit)하지 않았습니다. 대시보드는 확신에 찬 영구적인 '0'만을 보여주었을 것입니다.

두 버그는 모두 동일한 형태를 띠고 있었으며, 이는 에이전트형 하네스 (agentic harness)를 운영하는 사람이라면 밤잠을 설칠 만큼 위험한 형태입니다: 테스트가 아무것도 테스트하지 않은 채 통과해 버린 것입니다. 런타임 (runtime) 변경 사항은 전체 플릿이 사용하는 실제 값이 아닌, 임의로 만들어진 런타임 값으로 테스트되었습니다. 메트릭은 카운터를 스스로 증가시킨 뒤 값이 올라갔는지 확인하는 유닛 테스트 (unit test)를 통해 "테스트"되었습니다. 자기 확인적 테스트 (Self-confirming tests)였습니다. 게이트는 테스트를 실행하고 테스트가 통과(green)되면 만족합니다. 게이트는 코드가 잘못되었을 때 테스트가 실패할지 여부를 결코 묻지 않았습니다. 그것이 하네스의 사각지대이며, 확률적 모델 (stochastic model)은 충분한 실행 횟수를 제공할 때마다 매번 사각지대를 찾아낼 것입니다.

그래서 하네스가 스스로 가드레일을 구축했습니다

이번 주말의 모든 포착(catch)은 게이트(gate)로 이어졌습니다. 저는 감사(audit)를 통해 드러난 정확한 실패 클래스(failure classes)들에 대해 세 개의 이슈(issue)를 등록했고, 그러고 나서 이 프로젝트의 핵심인 작업을 수행했습니다. 바로 Foreman에게 그 이슈들을 다시 넘겨주어, 하네스(harness)가 스스로를 더 발전시킬 수 있는 게이트를 구축하도록 한 것입니다.

  • 범위 가드 (A scope guard). 레포지토리 맵(repo map)을 사용하여 이슈와 관련된 파일들의 점수를 매기고, 변경 사항(diff)이 해당 파일들과 전혀 겹치지 않는 GO(Go) 요청은 거부합니다. 이는 과거에는 제가 직접 감시해야 했던 "잘못된 서브시스템(subsystem)을 수정함"을 잡아내는 장치입니다.
  • 리뷰어 루브릭 (A reviewer rubric). 리뷰어가 반드시 적용해야 하는 두 가지 새로운 체크 항목입니다: 테스트가 플레이스홀더(placeholder)가 아닌 시스템이 프로덕션(production)에서 사용하는 실제 값을 사용하는가; 그리고 모든 새로운 메트릭(metric), 플래그(flag), 또는 필드(field)가 실제로 프로덕션 경로(production path)에 연결(wired)되어 있는가, 아니면 테스트에 의해서만 사용되는가입니다. 이는 감사(audit)에서 발견된 두 가지 실패 클래스(failure classes)를 규칙으로 명문화한 것입니다.
  • 바이트 체크 (A bite check). 가장 강력한 장치입니다. 새로 추가되거나 변경된 테스트는 변경 전의 코드에 대해 반드시 *실패(fail)*해야 합니다. 만약 테스트가 이전 코드와 새 코드 모두에서 통과(pass)한다면, 그것은 변경 사항을 테스트하고 있는 것이 아니며, 게이트는 이를 '물지 않는(non-biting) 테스트'로 간주하여 거부합니다. 이는 전체 자기 확인(self-confirming) 클래스에 대한 결정론적(deterministic) 포착 장치입니다.

처음 두 가지는 깔끔하게 안착했습니다. 코더(coder)는 제 책상 위에 있는 AMD Strix Halo 박스에서 Vulkan을 통해 실행되는 밀도 높은 27B 모델을 사용하여, 게이트 검증을 거친 두 항목을 첫 시도에 모두 생성해 냈습니다. 리뷰어 루브릭(reviewer rubric)은 심지어 즐거울 정도로 자기 참조적(self-referential)입니다. 그 자체의 "이것이 연결되어 있는가"라는 변경 사항이 실제로 연결되어 있었습니다. 제가 확인했습니다.

자신의 꼬리를 먹어치운 부분

바이트 체크(bite check) 단계에서 정직함이 드러났습니다. 저는 어떤 브랜치(branch)에도 승인(sign off)하기 전에, 하네스(harness)가 수행하는 것과 동일한 방식의 적대적 리뷰(adversarial review)인 심층 리뷰를 실행했습니다: 격리된 워크트리(worktree)를 생성하고, 구현 사항을 되돌린(revert) 후, 새 테스트를 다시 실행하여 기능(feature)이 없을 때 테스트가 실패하는지 확인하는 과정입니다. 범위 가드(scope guard)는 통과했습니다. 루브릭(rubric)도 통과했습니다. 하지만 바이트 체크(bite check)는 통과하지 못했습니다.

물지 않는(non-biting) 테스트를 거부하기 위해 구축된 게이트(gate)는 자체 6개 테스트 중 4개가 물지 않는 상태로 배포되었습니다. 이 테스트들은 베이스라인과 동일한 해피 패스(happy path)를 주장했기에, 해당 기능이 제거된 상태에서도 통과(green) 상태를 유지했습니다. 해당 기능이 잡아내기 위해 존재하는 바로 그 안티 패턴(anti-pattern)이, 기능 자체의 테스트 파일 안에 들어있었던 것입니다. 또한 실제 정답성 버그(correctness bug)도 있었습니다(새로 생성된 프로덕션 파일을 되돌릴 수 없어, 정당한 신규 파일 PR을 잘못 거부하게 됨). 그리고 이 기능은 클린룸(clean-room) 작업(Job)에 속해야 했음에도 빠른 게이트(fast gate)에 포함되어 구축되어 있었습니다.

분명히 말씀드리자면, 이것은 하네스(harness)가 실패했다는 이야기가 아닙니다. 오히려 그 반대입니다. 모델이 결함이 있는 게이트를 생성했고, (하네스의 일부인) 리뷰가 단 한 줄의 코드도 머지(merge)되기 전에 실증적인 증거를 가지고 이를 냉정하게 잡아냈습니다. 이것이 가장 날카롭게 증명된 전체 논지입니다: 모델이 하네스를 작성할 때조차, 당신은 모델보다 하네스를 믿어야 합니다. 저는 구체적인 수정 사항으로 문제를 다듬은 뒤, 다시 실행하도록 보냈습니다.

참고로, 재실행은 모델의 스트리밍 연결에서 발생한 예기치 않은 EOF(unexpected EOF), 즉 일시적인 네트워크 오류로 인해 16번째 턴에서 중단되었습니다. 그리고 하네스는 다시 한번 올바르게 동작했습니다. 해당 실행을 인프라 오류(infrastructure error)로 분류하고, 미완성으로 표시했으며, 아무것도 푸시(push)하지 않았습니다. 절반만 완성된 브랜치도, 잘못된 GO 신호도 없었습니다. 일시적인 오류(blip)는 버그가 아니며, 시스템은 그 차이를 알고 있었습니다. 저는 모델 서버가 정상임을 확인하고 다시 배포했습니다. 이것이 바로 "밤새도록 돌려두세요"라는 말을 실제로 할 수 있게 만드는, 화려하지는 않지만 필수적인 신뢰성 작업(reliability work)입니다.

두 명의 코더, 하나의 모델, 그리고 놀라운 결과

이 모든 것을 실행하는 플릿(fleet)은 의도적으로 이기종(heterogeneous)으로 구성되어 있습니다. 코더 모델은 27B 밀집(dense) 모델이며, 이번 주말 동안 저는 이를 매우 다른 두 대의 머신에서 서비스하도록 했습니다. 하나는 Vulkan 기반의 AMD Strix Halo 박스였고, 다른 하나는 Metal 기반의 Apple Silicon M5 Max였습니다. 동일한 모델, 동일한 양자화(quant), 하지만 공유하는 것이 거의 없는 두 개의 가속기(accelerator)였습니다.

전용 AMD 박스가 주력(workhorse) 역할을 하고, Mac은 느린 보조 차선이 될 것이라고 예상했습니다. 하지만 초기 수치는 그 반대를 말하고 있습니다. 현실적인 컨텍스트 깊이(context depth)에서 측정했을 때, Mac의 프롬프트 처리 처리량(prompt-processing throughput)은 Strix가 안정적인 구성에서 보여주는 성능을 훨씬 상회했으며, Strix의 가장 빠른 디코딩 경로(decode path)가 긴 컨텍스트에서 무너지는 지점에서도 Mac은 안정적이었습니다. 이것은 의도적으로 일치하지 않게 설정된 초기 측정값(서로 다른 KV 설정, 병렬 실행 아님)이며, 두 기기를 나란히 놓고 직접 테스트하기 전까지는 정확한 수치를 제시하지 않겠습니다. 하지만 방향성은 흥미롭습니다. 이 워크로드(workload)에서 작은 Apple 노드는 느린 쪽이 아닙니다.

하네스(harness)를 신뢰하는 또 다른 절반

여기는 제가 작성하게 될 줄 몰랐던 부분입니다. 기기들이 주말 내내 작업을 수행하는 동안, 마치 살아있는 생명체와 같은 리포지토리(repository)는 특유의 행동을 보였습니다. 바로 다른 사람들이 나타난 것입니다. 이전에 함께 작업해 본 적 없는 두 명의 기여자(contributor)가 LLMKube의 라우터(router) 및 추론(inference) API에 대해 세 개의 풀 리퀘스트(pull request)를 보냈습니다. 이는 추론 포드(inference pods)를 위한 일련의 보일러플레이트(boilerplate), 토폴로지 분산(topology-spread) 및 어피니티 패스스루(affinity passthrough)를 제거하는 기본 경로 전략(default-route strategy)과 배포를 위한 수정 이력 제한(revision-history-limit) 노브(knob)에 관한 것이었습니다. 세 개 모두 깔끔했습니다. 완전한 테스트, 두 CRD 복사본의 동기화, 문서 업데이트, CI 통과(green)까지 모두 갖춰져 있었습니다. 저는 각각을 면밀히 검토했으며, 제가 남긴 의견은 사소한 것뿐이었습니다. 그러다 제가 말 그대로 이 포스트를 초안 작성하고 있는 와중에, 세 번째 기여자가 네 번째를 열었습니다. modelCache.enabled: false를 설정해도 실제로 캐시가 비활성화되지 않는 Helm 차트 버그에 대한 깔끔한 수정 사항이었습니다. 원인 분석(Root-caused), 테스트, 승인까지 완료되었습니다. 똑같은 흐름이었습니다.

그리고 이것이 동일한 논지라는 점이 머릿속에 명확히 그려졌습니다. 동전 던지기 수준의 27B 모델을 신뢰할 수 있게 만드는 게이트(Gates)와 리뷰(Review)는, 신입 기여자의 풀 리퀘스트(Pull Request)가 깔끔하게 반영되도록 하는 게이트 및 리뷰와 동일합니다. 하네스(Harness)는 AI 기능이 아닙니다. 그것은 프로젝트의 품질 하한선(Quality floor)이며, 변경 사항(Diff)이 내 책상 위의 로컬 모델에서 왔는지 아니면 인터넷 너머의 사람으로부터 왔는지는 상관하지 않습니다. 이 모든 과정 중간에 누군가 우리 Discord에 메모를 남겼습니다: "정말 멋진 프로젝트네요. 여러분 덕분에 vllm 디버깅 시간을 2시간이나 아꼈습니다." 이것이 양쪽 측면 모두에서 우리가 추구하는 핵심입니다.

내가 현재 실제로 믿고 있는 것

로컬에서 실제 엔지니어링 작업을 수행하기 위해 자신의 하드웨어에 프론티어 모델(Frontier model)을 갖출 필요는 없습니다. 당신에게 필요한 것은 단일 모델의 출력보다 더 신뢰할 수 있는 하네스(Harness)입니다. 그것을 구축한다면, 데스크톱의 27B 모델은 유용하고 감독 가능한 동료가 됩니다. 당신이 자정에 모든 변경 사항(Diff)을 일일이 읽으며 실수를 잡아내는 대신, 시스템이 실수를 잡아내는 동료 말입니다. 그것을 구축한다면, 동일한 시스템은 커뮤니티가 당신의 프로젝트 위에서 무언가를 구축할 수 있게 해주는 토대가 됩니다.

이번 주말에 모델이 고장 난 게이트를 생성했습니다. 하지만 하네스(Harness)가 이를 잡아냈습니다. 세 명의 새로운 기여자가 프로젝트를 개선했고, 하네스는 모델의 작업에 보증을 서는 것과 동일한 방식으로 그들의 작업에 대해서도 보증을 섰습니다. 이것은 관리해야 할 모순이 아닙니다. 설계가 제대로 작동하고 있다는 증거입니다. 모델이 아닌 하네스를 믿으세요.

LLMKube는 Apache 2.0 라이선스이며 NVIDIA, Apple Silicon, AMD 환경의 Kubernetes 위에서 실행됩니다. 프로젝트는 GitHub에서 확인할 수 있으며, 우리는 Discord에 있습니다.

AI 자동 생성 콘텐츠

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

원문 바로가기
0

댓글

0