← Ascendy EN

meta

자율성과 fail-closed는 상충하지 않는다 — 하네스가 스스로 돌기 시작했다

· Ascendy Engineering


TL;DR

이 글에 대하여. 공개 저장소(Apache-2.0)에서 검증 가능한 실제 작업 기록이다. 과장하지 않으려 애썼다 — “자율”이라는 단어를 쓰되, 사람이 어디에 남아 있는지도 같이 적었다. 같은 결의 loop engineering의 핵심은 검증, 리뷰 도구가 저자를 잡았다와 이어진다.

자율성과 fail-closed는 반대말이 아니다

에이전트에게 자율성을 준다고 하면, 보통 “게이트를 없애서 알아서 하게 둔다”를 떠올린다. 그런데 우리가 하네스를 자율로 만들며 배운 건 정반대였다.

자율을 늘리는 방법은 검증을 없애는 게 아니라, 판단 없는 일만 위임하고 계층을 오히려 하나 더 얹는 것이다. 이 글은 그 두 면 — 자율 실행더 두꺼워진 방어 — 이 어떻게 같은 설계의 앞뒷면인지에 대한 이야기다.

골 하나 주면 끝까지 — 자율 드라이버

먼저 자율 쪽. 예전 하네스는 “분해하고 시작”까지 한 번 돌고 멈췄다. 파이프라인이 멈출 때마다 사람이 상태 파일을 열어 진단해야 했다. 그게 실제 페인 포인트였다.

그래서 골 하나를 주면 끝까지 굴러가는 드라이버로 다시 설계했다. 이제는 goal 하나를 던지면 에이전트가:

  1. 상태를 읽고 (어느 태스크가 막혔는지, 왜 미뤄졌는지)
  2. 진단하고
  3. 허용된 조치만 스스로 하고 (일시적 인프라 복구, 오래된 브랜치 정리, 원인이 해소된 보류의 재개, 잘못 적힌 브리프 수정)
  4. 다시 이어서 돌린다

— 모든 태스크의 초안 PR이 열릴 때까지 이 루프를 돈다.

여기서 두 가지가 fail-closed를 지킨다.

하네스가 자기 릴리스를 스스로 컷했다

같은 원리가 릴리스에도 적용됐다. 이번에 하네스는 자기 릴리스(v0.7.0)를 스스로 컷한 첫 사례를 남겼다. 이전 버전은 손으로 태그를 달았지만, 이번엔 릴리스 PR이 머지되자 워크플로가 버전 정합성을 검사하고, 사람의 승인 클릭 한 번을 기다린 뒤, 태그와 릴리스 노트까지 자동으로 마쳤다.

핵심은 “자동화가 판단을 대체한다”가 아니다. 정반대다 — 사람의 결정(머지, 승인 클릭)만 남기고, 그 결정 사이의 기계적 단계를 전부 없앤 것이다. 버전이 어긋나면 막고(드리프트 가드), 승인자가 아니면 막는다(actor 게이트). 자동화의 방향이 판단을 지우는 게 아니라 잡일을 지우는 쪽이다.

그런데 — 검증을 빼지 않고, 하나 더 얹었다

여기까지는 자율 쪽이다. 진짜 하고 싶은 이야기는 이제부터다. 자율로 돌기 시작하면서, 우리는 적대적 검증 계층을 오히려 하나 더 얹었다. 그리고 그게 값을 했다. 세 개의 장면.

장면 1 — 자율성을 부여하는 문서조차 리뷰에 까였다

자율 드라이버에게 “이런 상황에선 이렇게 해도 된다”고 알려주는 권한 플레이북을 썼다. 그 초안에 “오래된 브랜치는 삭제하라”는 조항이 있었다.

크로스-모델 리뷰어(코드를 쓴 모델과 다른 계열)가 이걸 major로 깠다. “엔진은 원래 가드에 걸리면 삭제하지 않고 보류한다. 에이전트에게 무조건 삭제를 허용하면, 아직 머지 안 된 작업이 날아가는 데이터 손실 경로가 된다.” 그래서 조항은 “조상 커밋인지 확인한 뒤에만 삭제, 아니면 이름만 바꿔 치워두기”로 고쳐졌다.

교훈이 선명하다. 에이전트에게 자율성을 주는 문서마저, 적대적 리뷰를 통과해야 한다. 권한을 넓히는 바로 그 순간이 방어를 가장 두껍게 해야 할 순간이다.

장면 2 — 리뷰의 범위(scope)가 리뷰의 품질을 결정한다

한 태스크가 파이프라인의 크로스-모델 리뷰를 APPROVED로 통과했다. 그런데 통과 후에 따로 돌린 스택 통합 리뷰(그 태스크만이 아니라 쌓인 전체를 한 번 더 적대적으로 보는 계층)가 무결성 결함을 잡았다.

왜 태스크별 리뷰는 못 봤을까? 그 리뷰는 자기 태스크의 변경분만 본다. 그런데 이 결함은 새로 넣은 예외 로직과 커밋 귀속 방식의 상호작용에서 생겨서, 태스크 하나만 봐서는 안 보이고 스택 전체를 봐야 드러났다.

고칠 때도 원칙을 지켰다. 인라인으로 직접 패치하지 않고(보안 경계 + 워커와 오퍼레이터가 같은 모델이라), 결함을 워커에게 리뷰 피드백으로 되돌려 고치게 하고, 다른 모델이 다시 검증했다. 심판은 끝까지 다른 모델이었다.

교훈: 태스크별 게이트가 모두 통과해도, 합쳐진 결과 전체를 한 번 더 적대적으로 보는 계층이 값을 한다. 리뷰의 범위가 리뷰의 품질을 정한다.

장면 3 — fail-closed는 워커의 “거부”에도 있다

한 태스크의 요구 사항에 서로 모순되는 두 항목이 들어갔다. “기존 테스트는 손대지 말고 통과시켜라”“그 테스트가 고정하고 있는 규칙을 바꿔라”가 동시에 있었던 것이다.

구현을 맡은 워커는 이걸 세 번 연속 거부하고 모순을 보고했다. 억지로 통과시키는 길(예: 테스트를 몰래 고쳐서 초록불을 만드는 것)을 택하지 않았다. 사람이 모순을 풀어준 뒤에야 진행됐다.

fail-closed는 게이트에만 있는 게 아니다. 워커의 거부에도 있다. 막히면 억지로 뚫는 대신 멈추고 알리는 것 — 그게 자율 에이전트에게 가장 필요한 성질이다.

정직하게 — 이건 “완전 자율”이 아니다

과장하지 않겠다. 위의 자율 런들은 완전 자율이 아니다. 사람이 각 런의 시작(골 작성)과 끝(PR 머지)에 손을 댔고, 도는 중에 드러난 엔진의 빈틈을 우리가 손으로 수습했다.

그런데 흥미로운 건 — 그 수습들이 곧 다음 개선의 재료가 됐다는 점이다. 자율로 돌리다 막힌 지점이 이슈로 남고, 그 이슈를 다시 (부분적으로) 자율로 고치고… 그렇게 순환이 돈다. 정확한 그림은 “완전 자율”이 아니라 “자율 + 사람의 수습, 그리고 그 수습이 다음 재료가 되는 순환”이다.

가져갈 것

자율성은 “사람을 빼는 것”이 아니다. 사람이 어디에 남아야 하는지를 더 정확히 아는 것에 가깝다. 그 자리를 정확히 남길수록, 나머지를 더 과감하게 맡길 수 있다.


저작·인용: 이 글은 Ascendy Engineering이 작성했으며 출처 표기 시 재인용 가능합니다. 잘못된 정보를 발견하면 GitHub 이슈로 알려주세요.


Tags: ai, agents, code-review, autonomy, fail-closed, opinion