Split View: 하네스 지문과 버전 관리 — 기록 없는 변경을 추적 가능하게
하네스 지문과 버전 관리 — 기록 없는 변경을 추적 가능하게
- 다시, 커밋 없는 6퍼센트포인트
- 하네스는 버전이 붙는 배포물입니다
- 지문 만들기: 정규화가 절반입니다
- 무엇이 들어가고, 무엇이 빠지나
- 결과에 지문이 붙어야 비교가 성립합니다
- 롤백과 회귀 이등분
- 직접 연습하기
- 참고 자료
다시, 커밋 없는 6퍼센트포인트
이 시리즈의 출발점이 된 이전 글은 구성한 사례 하나로 시작했습니다. 성공률이 지난주보다 6퍼센트포인트 올랐는데 프롬프트 커밋도 모델 변경도 없고, 알고 보니 도구 설명 한 줄, 재시도 상한, 파일 읽기 줄 수 상한이 각기 다른 사람 손에 바뀌어 있었다는 이야기입니다. 다음 주에 성공률이 떨어지면 무엇을 되돌려야 할까요. 기록이 없으면 답할 수 없습니다.
이 글은 그 사건을 답 가능하게 만드는 최소 장치, 하네스 지문을 다룹니다. 미리 말해 두면, 지문이라는 장치는 Lilian Weng의 하네스 글이 제안한 것이 아니라, 그 글이 그어 준 경계를 운영에 옮기기 위해 이 블로그가 구성한 접근입니다.
하네스는 버전이 붙는 배포물입니다
지문의 전제는 관점 전환입니다. 하네스를 흩어진 설정 모음이 아니라 하나의 배포물로 봅니다. 배포물이면 버전이 있어야 하고, 버전이 있으면 두 시점의 차이를 물을 수 있습니다. Weng의 정의가 유용한 이유가 여기 있습니다. 모델의 사고와 행동, 컨텍스트, 산출물, 평가를 조율하는 층 전부라는 경계가 있어야, "이 변경이 하네스 변경인가"라는 질문에 일관되게 답할 수 있습니다. 도구 설명 한 줄을 다듬는 것도, 재시도 상한을 올리는 것도 경계 안이므로 버전을 움직이는 변경입니다.
지문 만들기: 정규화가 절반입니다
지문 자체는 짧습니다. 하네스를 구성하는 결정들을 직렬화해 해시를 뜨면 됩니다. 어려운 절반은 정규화입니다. 의미가 같은 하네스는 같은 지문을 가져야 하는데, 순진하게 직렬화하면 도구 등록 순서만 바뀌어도 지문이 달라집니다. 그래서 도구 목록은 이름으로 정렬하고, JSON 키는 정렬된 순서로 고정하고, 의미에 영향을 주지 않는 공백이나 필드 순서는 직렬화 전에 통일합니다. 반대로 실행마다 달라지는 값, 타임스탬프나 실행 ID 같은 것은 지문에 넣으면 안 됩니다. 넣는 순간 모든 실행이 서로 다른 하네스가 되어 버립니다.
무엇이 들어가고, 무엇이 빠지나
들어가는 것은 에이전트의 행동을 바꾸는 모든 결정입니다. 모델 식별자, 시스템 프롬프트, 도구 스키마 전체, 재시도 상한과 정지 조건, 컨텍스트 정책, 권한 범위, 그리고 평가자의 버전까지. 평가자를 넣는 이유는 5편과 6편에서 본 그대로입니다. 채점 기준이 바뀌면 같은 하네스도 다른 점수를 받으므로, 채점 기준의 변경도 추적 대상입니다.
빠지는 것은 두 부류입니다. 하나는 실행 속성입니다. 과제 입력, 실행 시각, 난수 시드는 실행의 메타데이터이지 하네스가 아닙니다. 다른 하나가 더 중요합니다. 로깅과 관측 설정은 지문에서 뺍니다. 당신이 무엇을 볼 수 있는지는 에이전트가 무엇을 하는지를 바꾸지 않기 때문입니다. 관측 설정을 지문에 넣으면, 행동이 같은 결과들이 비교 불가능한 조각으로 쪼개집니다. 이 원칙은 이 블로그의 하네스 RPG에도 그대로 구현되어 있습니다. 관측 장비는 진행에 따라 열리지만 지문은 움직이지 않습니다.
결과에 지문이 붙어야 비교가 성립합니다
지문의 용도는 평가 결과에 도장을 찍는 것입니다.
{
"run_id": "2026-08-12T03:14:07Z-a41",
"harness_fingerprint": "3f9c1d2ab714",
"suite": "issues-50",
"success_rate": 0.62,
"counter_metrics": { "deleted_tests": 0, "avg_tool_calls": 11.4 }
}
이 한 줄이 생기면 규칙 두 개가 따라옵니다. 첫째, 비교는 같은 지문끼리만 성립합니다. 지문이 다른 두 성공률을 나란히 놓는 것은 비교가 아니라 실험이고, 실험이면 무엇이 달랐는지 diff를 말할 수 있어야 합니다. 둘째, 지문을 움직이는 모든 변경은 평가를 다시 돌려야 하는 변경입니다. 도구 설명 한 줄의 수정이 프롬프트 개편과 같은 급으로 취급되기 시작하고, 그것이 정확히 의도된 효과입니다.
롤백과 회귀 이등분
성공률 그래프에 계단이 생기면, 지문 이력이 용의자 목록을 만들어 줍니다.
def regression_boundary(runs):
"""성공률이 꺾인 지점의 지문 경계를 찾는다 (구성한 예시)."""
runs = sorted(runs, key=lambda r: r["ts"])
for prev, cur in zip(runs, runs[1:]):
if cur["success_rate"] < prev["success_rate"] - 0.03: # 잡음 여유
return prev["harness_fingerprint"], cur["harness_fingerprint"]
return None
# 경계의 두 지문에 해당하는 스펙의 diff가 곧 용의자 목록이다.
# 용의자가 여럿이면 절반씩 되돌리며 이등분 탐색을 한다.
두 지문 사이의 스펙 diff가 후보 변경들입니다. 후보가 하나면 그것을 되돌려 재평가하고, 여럿이면 git bisect와 같은 요령으로 절반씩 좁힙니다. 롤백도 같은 이력 위에서 정의됩니다. 이전 지문의 스펙을 다시 배포하고, 배포된 것의 지문이 목표 지문과 일치하는지 확인하면 롤백이 완료된 것입니다. 지문이 없던 시절의 "아마 이 설정이었을 것"이 "해시가 일치한다"로 바뀝니다.
직접 연습하기
하네스 엔지니어링 RPG의 1티어 "관측과 지문"의 첫 시나리오가 정확히 이 글의 서두 사건입니다. 지난주의 6퍼센트포인트를 재현하기 위해 하네스를 조립하다 보면, 지문이 어떤 변경에 반응하고 어떤 변경에 반응하지 않는지 몸으로 익히게 됩니다.
참고 자료
- Harness engineering for self-improvement — Lilian Weng, 2026-07-04 — 하네스의 경계, 즉 무엇이 하네스 안의 변경인지에 대한 정의가 이 글에 있습니다.
- 이 블로그의 이전 글: 하네스는 설정이 아니라 배포물입니다 — 지문 접근의 첫 소개와 스펙 직렬화 코드가 있습니다.
- 하네스 지문이라는 장치 자체와 본문의 6퍼센트포인트 사례, 코드 예시는 모두 원문의 내용이 아니라 이 블로그가 운영을 위해 구성한 것입니다.
Harness Fingerprints and Versioning — Making Unrecorded Changes Traceable
- Once more: six points and no commit
- The harness is a deployable that carries a version
- Building the fingerprint: normalization is half the work
- What goes in, and what stays out
- Results must carry the fingerprint for comparisons to hold
- Rollback and bisecting regressions
- Practice it yourself
- References
Once more: six points and no commit
The earlier post this series grew out of opened with one constructed case. The success rate rose six percentage points over last week with no prompt commit and no model change — and it turned out one tool description line, the retry cap, and a file-read line cap had each been changed by different hands. If the success rate drops next week, what do you roll back? Without a record, there is no answer.
This post covers the minimal device that makes that incident answerable: the harness fingerprint. To say it upfront — the fingerprint is not something Lilian Weng's harness post proposed. It is an approach this blog constructed to carry the boundary that post drew into operations.
The harness is a deployable that carries a version
The premise of the fingerprint is a shift of viewpoint: see the harness not as a scattered pile of settings but as one deployable. A deployable has a version, and with versions you can ask what changed between two points in time. This is where Weng's definition earns its keep. Only with a boundary — the whole layer that orchestrates how the model thinks and acts, its context, its artifacts, its evaluation — can you answer "is this change a harness change" consistently. Polishing one tool description line and raising the retry cap both sit inside the boundary, so both are version-moving changes.
Building the fingerprint: normalization is half the work
The fingerprint itself is short: serialize the decisions that make up the harness and hash them. The hard half is normalization. Two harnesses with the same meaning must produce the same fingerprint, but a naive serialization changes the hash when nothing but the tool registration order changed. So sort the tool list by name, fix JSON keys in sorted order, and unify whitespace and field order that carry no meaning before serializing. Conversely, values that differ per run — timestamps, run IDs — must not enter the fingerprint. The moment they do, every run becomes its own incomparable harness.
What goes in, and what stays out
What goes in is every decision that changes the agent's behavior: the model identifier, the system prompt, the full tool schemas, retry caps and stopping conditions, the context policy, the permission scope, and the version of the evaluator. The evaluator goes in for exactly the reasons parts 5 and 6 showed: when grading criteria change, the same harness earns a different score, so grading changes are tracked changes too.
What stays out comes in two kinds. One is run attributes: task inputs, execution time, random seeds are metadata of a run, not of the harness. The other matters more. Logging and observability settings stay out of the fingerprint, because what you can see does not change what the agent does. Put observability into the fingerprint and results with identical behavior shatter into incomparable fragments. This rule is implemented as-is in this blog's harness RPG: observation equipment unlocks as you progress, and the fingerprint does not move.
Results must carry the fingerprint for comparisons to hold
The use of the fingerprint is stamping evaluation results.
{
"run_id": "2026-08-12T03:14:07Z-a41",
"harness_fingerprint": "3f9c1d2ab714",
"suite": "issues-50",
"success_rate": 0.62,
"counter_metrics": { "deleted_tests": 0, "avg_tool_calls": 11.4 }
}
Once this line exists, two rules follow. First, comparisons hold only between equal fingerprints. Placing two success rates with different fingerprints side by side is not a comparison but an experiment — and an experiment must be able to state its diff. Second, every change that moves the fingerprint is a change that reruns the evaluation. Editing one tool description line starts being treated at the same rank as a prompt overhaul, and that is precisely the intended effect.
Rollback and bisecting regressions
When a step appears in the success-rate graph, the fingerprint history builds the suspect list.
def regression_boundary(runs):
"""Find the fingerprint boundary where the success rate broke (constructed example)."""
runs = sorted(runs, key=lambda r: r["ts"])
for prev, cur in zip(runs, runs[1:]):
if cur["success_rate"] < prev["success_rate"] - 0.03: # noise margin
return prev["harness_fingerprint"], cur["harness_fingerprint"]
return None
# The spec diff between the two boundary fingerprints is the suspect list.
# With several suspects, bisect: revert half at a time.
The spec diff between the two fingerprints is the candidate set of changes. With one candidate, revert it and re-evaluate; with several, narrow by halves in the same manner as git bisect. Rollback is defined over the same history: redeploy the spec of the earlier fingerprint, confirm the fingerprint of what got deployed matches the target, and the rollback is done. The old "it was probably this configuration" becomes "the hash matches".
Practice it yourself
The first scenario of tier 1, "Observation and fingerprints", in the harness engineering RPG is exactly the incident at the top of this post. As you assemble a harness to reproduce last week's six points, you learn by hand which changes the fingerprint reacts to and which it ignores.
- Previous in the series: Reward hacking — the metric rises while the task fails
- Next in the series: Growing into a harness engineer — why the job exists and what to practice
References
- Harness engineering for self-improvement — Lilian Weng, 2026-07-04 — the boundary of the harness, that is, the definition of what counts as a change inside the harness, is in this post.
- Earlier post on this blog: A harness is not configuration but a deployable — the first introduction of the fingerprint approach and the spec serialization code.
- The harness fingerprint device itself, the six-point case, and the code examples in the body are not from the original article; this blog constructed them for operations.