이 글은 작업이 끝난 뒤 붙인 멋있는 이름이 아니다. 실제 source, pull request, 검증 기록을 다시 읽고 반복되던 작업 방식을 Harness Engineering이라는 관점으로 복원한 기록이다. Figma에서 레이아웃을 직접 설계했다는 부분은 내 작업 회고에 근거하며, repository source로 확인한 사실과 구분한다. 회사 내부 정보는 구조를 설명하는 데 필요한 수준으로 줄였고 DB 접속 정보, 실제 업무 데이터, 비공개 repository URL은 싣지 않았다.
먼저 결론: 화면 하나를 만든 것과 하네스를 만든 것은 다르다
backend XML을 작성하고 DB에 데이터를 넣은 다음 Figma에서 레이아웃을 잡고 frontend를 구현했다. 이것만 놓고 보면 full-stack에 가까운 vertical slice 개발이다. 자동으로 Harness Engineering이 되는 것은 아니다.
하네스는 그 일을 다음 화면에서도 반복할 수 있게 만드는 실행 환경이다.
- AI가 무엇을 먼저 읽어야 하는지 정한다.
- 화면이 지켜야 할 계약을 기계가 읽을 수 있는 형태로 남긴다.
- 사용할 component와 data flow를 제한한다.
- DB read와 write, 시스템 데이터와 업무 데이터를 구분한다.
- test, type check, build, browser 동작으로 완료를 판정한다.
- 실패 원인을 다음 작업의 rule, test, document로 되돌린다.
따라서 내 작업에 더 정확한 표현은 이렇다.
XML·DB·Figma·Frontend는 제품을 이루는 네 개의 계약이고, 이 계약을 AI가 읽고 구현하고 스스로 틀렸음을 발견하게 만든 반복 구조가 내가 만든 경량 frontend harness였다.
OpenAI도 Harness Engineering의 중심을 “코드를 대신 써 주는 모델”보다 사람이 환경을 설계하고 의도를 명세하며 feedback loop를 만드는 일에 둔다. AI가 작성한 코드의 양은 결과일 뿐, 재현성을 만드는 장치는 repository-local context, tools, architecture constraints, tests, browser, review loop다. OpenAI의 공식 Harness Engineering 글도 underspecified environment를 모델 성능이 아니라 시스템 설계 문제로 다룬다.
내가 AI를 바라본 방식: 똑똑한 동료보다 불확실한 compiler에 가깝다
내가 frontend 작업에서 AI를 잘 쓸 수 있었던 이유는 AI가 화면을 잘 꾸민다고 믿어서가 아니었다. 반대로 AI가 무엇을 모르고도 그럴듯하게 채우는지를 경계했기 때문이다.
내가 생각한 AI coding agent는 대략 이런 함수에 가깝다.
후보 구현 = Agent(현재 context, 명시한 계약, 사용 가능한 도구, 기존 code pattern)
여기서 출력은 정답이 아니라 후보 구현이다. 정답 판정은 별도다.
채택 가능
= backend 계약 일치
AND DB 데이터 계약 일치
AND visual/layout 계약 일치
AND 사용자 동작 계약 일치
AND regression 검증 통과
이 관점은 역할을 선명하게 나눴다.
| 영역 | AI에 맡긴 일 | 사람이 끝까지 가진 일 |
|---|---|---|
| 조사 | 유사 화면, mapper, component, 변경 영향 탐색 | 어떤 원본을 진실로 볼지 결정 |
| 명세 | source에서 column·action·validation 후보 추출 | 업무 의미, 허용 동작, 우선순위 확정 |
| 구현 | 반복 JSX, config, validator, test 초안 생성 | 구조 선택과 과도한 추상화 차단 |
| 디자인 | 기존 component 조합과 layout variant 제안 | Figma에서 정보 위계와 사용 흐름 결정 |
| 검증 | test·type check·build·browser 실행과 실패 분류 | “업무상 맞다”는 최종 승인 |
| 운영 | read-only 조사, diff와 rollback 정보 정리 | production write와 데이터 영향 승인 |
AI는 구현 속도를 높였지만 요구사항의 소유자가 아니었다. 특히 MES 관리자 화면에서는 column 하나가 단순한 표의 칸이 아니다. DB key인지, 수정 가능한 값인지, 다국어 code인지, 권한에 따라 보이는 action인지가 backend와 DB에 흩어져 있다. AI가 빈칸을 상식으로 메우면 보기 좋은 오답이 나온다.
그래서 나는 “이 화면을 만들어 줘”보다 다음 순서로 일을 좁혔다.
- 실제 mapper가 읽고 쓰는 field를 찾는다.
- DB에서 화면을 구성하는 시스템 metadata와 업무 row를 구분한다.
- 기존 frontend primitive로 표현할 수 있는 page shape를 고른다.
- Figma에서 정보 위계와 사용 순서를 확정한다.
- AI가 route-local implementation과 validator를 만든다.
- 독립된 검증 단계가 source와 runtime을 다시 비교한다.
좋은 prompt를 한 번 쓰는 방법이 아니라, 모델이 추측할 공간을 계속 줄이는 방법이었다.
문제의 뿌리: 화면의 진실이 frontend 한 곳에 있지 않았다
관리자 화면을 정상화할 때 가장 어려운 점은 React가 아니었다. 화면의 진실이 여러 층에 나뉘어 있다는 점이었다.
MyBatis XML
└─ query key, 반환 field, CRUD column, cast, join
DB system data
└─ menu, column metadata, label, button/action, role permission
Figma
└─ 정보 위계, grid 배치, 기본/상세 흐름, 시각적 밀도
Frontend
└─ route, config, component composition, validation, interaction
frontend만 보고 화면을 고치면 backend에 없는 API를 호출할 수 있다. XML만 보고 만들면 동작은 해도 사람이 쓰기 어려운 그리드 나열이 된다. DB metadata를 빼먹으면 code가 있어도 메뉴, label, column, button이 노출되지 않는다. Figma만 따라가면 실제 query가 반환하지 않는 field를 편집 가능하게 만들 수 있다.
이 네 층을 동시에 만족시키는 것이 page의 실제 contract였다.
Contract 1. XML은 API 문서가 아니라 실행되는 계약이었다
OpenAPI 문서가 완전하게 준비된 greenfield가 아니었다. 실제 MyBatis mapper의 namespace, query id, SELECT alias, INSERT·UPDATE·DELETE column, type cast가 frontend가 사용할 수 있는 범위를 정했다.
예를 들어 그리드 컬럼 관리 화면인 MOSIZ010을 만들 때는 backend의 gridColumn mapper를 먼저 기준으로 삼았다. 조회 결과에는 menu ID, grid sequence, data field, label code, 정렬 순서, 표시 여부, 너비와 높이 같은 값이 있었다. 저장 SQL이 integer나 Y/N으로 기대하는 값은 frontend validator에서도 같은 범위로 좁혔다.
여기서 중요한 것은 field 목록을 복사한 일이 아니다.
- SELECT에만 있고 UPDATE에 없는 field는 읽기 전용으로 둔다.
- WHERE를 구성하는 field는 key로 본다.
- backend cast보다 먼저 frontend에서 잘못된 숫자를 차단한다.
- mapper에 없는 action은 UI에도 만들지 않는다.
실제로 다른 메뉴 관리 화면을 검토할 때, frontend에는 권한 관련 grid와 API 호출이 있었지만 현재 backend XML에는 대응 query가 없었다. 그 경우 “언젠가 필요할 수 있다”는 이유로 남기지 않고 제거하거나 backend 후속 결정으로 돌렸다. AI가 만든 기능의 양보다 현재 계약으로 설명할 수 있는 line만 남기는 것이 우선이었다.
Contract 2. DB에는 화면 설정과 업무 데이터가 함께 있었다
DB 작업도 “화면이 안 보이니 row를 넣는다”로 끝내지 않았다. 데이터의 성격을 먼저 나눴다.
| 분류 | 예 | 기본 처리 |
|---|---|---|
| 시스템 데이터 | menu, label, column property, button/action, role permission | versioned seed와 migration 대상으로 관리 |
| 업무 데이터 | 사용자가 실제로 생성·변경하는 domain row | 명시적 요구 없이는 변경하지 않음 |
| 샘플 데이터 | test 화면을 채우기 위한 예시 row | 시스템 데이터와 분리하고 기본 배포에서 제외 |
왜 이렇게까지 나눴을까. 화면을 살리려고 넣은 샘플 row가 실제 업무 데이터처럼 남거나, 운영 DB에서 직접 수정한 metadata가 다음 배포 때 사라지면 “지금 보인다”는 사실이 재현성을 보장하지 못하기 때문이다.
그래서 반복 가능한 경로는 직접 SQL 한 번이 아니라 CSV template과 migration changeset, primary key 대조, 적용 전 backup, 적용 후 read 검증이었다. 모든 변경이 최종 merge된 것은 아니지만, 작업 산출물에는 시스템 데이터와 업무·샘플 데이터를 분리하려는 기준이 명확히 남았다.
Contract 3. Figma는 pixel 명세보다 인간의 판단을 보존하는 층이었다
나는 AI에게 business screen의 정보 구조까지 맡기고 싶지 않았다. Figma에서 직접 layout을 잡은 이유는 예쁜 색을 고르기 위해서가 아니었다.
- 사용자가 처음 봐야 하는 grid는 무엇인가.
- 상세 정보는 항상 노출할지, 필요할 때만 열지.
- 조회 조건과 action을 어느 덩어리로 묶을지.
- 여러 grid 사이의 master-detail 관계를 어떻게 읽히게 할지.
- 긴 업무 화면에서 무엇을 고정하고 무엇을 scroll하게 할지.
예를 들어 권한 관리 화면은 여러 tab을 늘어놓는 대신 기본 grid를 먼저 보여 주고, 별도·상세 관리가 필요할 때 하나의 toggle action으로 전환하도록 정리했다. 이 결정은 component 교체가 아니라 사용 흐름에 대한 판단이다. AI는 여러 variant를 빠르게 구현할 수 있지만 어느 정보가 기본이어야 하는지는 조직의 업무를 아는 사람이 정해야 했다.
Figma는 여기서 “완성 화면을 code로 변환하는 입력”만이 아니었다. backend와 DB가 허용하는 범위 안에서 사람이 사용 순서를 결정하고, AI 구현을 눈으로 판정할 수 있게 만드는 visual oracle이었다.
Contract 4. Frontend는 자유 창작이 아니라 제한된 조립이었다
화면마다 새로운 component를 만들게 두면 AI는 빠르게 중복을 만든다. 규격에 맞는 page를 반복해서 만들 수 있었던 핵심은 기존 primitive를 조립하도록 자유도를 줄인 데 있었다.
Tab
├─ PageTitle
├─ Filter.Body
│ └─ Filter.Item × N
└─ FieldSection
├─ Buttons.Add/Delete/Reset/Save
└─ Grid.Inline | Grid.Tree | Grid.Bucket
MOSIZ010도 이 문법을 따랐다. query key는 상수로 고정하고, filter 정의와 grid column 규격은 config에 모으고, save 전 validation은 pure function으로 분리했다. page component는 이들을 조립한다. 새 화면이 기존 화면과 비슷해지는 이유는 AI가 디자인 감각을 학습해서가 아니라, 선택 가능한 문법 자체를 제한했기 때문이다.
이 방식은 hardcoding을 무조건 없애는 것과도 다르다. 모든 화면을 거대한 범용 schema engine으로 만들지 않았다. 반복되는 metadata는 config로 올리되, 한 route에만 필요한 validation과 interaction은 route-local로 뒀다. shared abstraction은 실제로 두 번 이상 공유되는 동작에만 허용했다.
실제 page 하나가 만들어지는 순서
MOSIZ010을 예로 들면 작업의 입력과 출력은 다음처럼 연결됐다.
1단계. page contract를 먼저 만든다
구현 전에 아래 표를 채울 수 있어야 했다.
| 계약 항목 | MOSIZ010에서 확인한 내용 |
|---|---|
| page ID | 관리자용 grid column 관리 page |
| query namespace | backend gridColumn mapper |
| 조회 조건 | menu ID, grid sequence |
| primary key 후보 | menu ID, grid sequence, data field |
| 수정 가능 값 | label code, 정렬, 표시 flag, 크기, Excel 관련 flag |
| 읽기 전용 값 | 조회 join으로 계산되는 header text 등 |
| action | 조회, 추가, 수정, 삭제 |
| layout | filter 1개 영역 + inline grid 1개 영역 |
| 검증 | required, integer 범위, Y/N, 0/1, key whitespace |
| 완료 조건 | focused test, TypeScript, production build, diff check |
이 표가 비어 있는데 JSX부터 쓰면 AI는 빈칸을 가장 그럴듯한 값으로 메운다. 반대로 표가 채워지면 구현은 조립 문제로 작아진다.
2단계. backend의 허용 범위를 frontend type과 validation으로 번역한다
XML의 cast는 runtime에서 실패할 수 있는 마지막 방어선이다. 사용자가 문자열이나 범위를 벗어난 숫자를 입력한 뒤 DB 오류를 보는 대신, frontend validator가 더 가까운 곳에서 차단하게 했다.
여기서 validation은 UX 장식이 아니다. backend contract를 frontend가 실행 전에 검사하는 작은 evaluator다.
XML: grid_seq → bigint
Frontend: 공백 제거 → 정수 형식 확인 → safe integer와 최소값 확인
XML: view_flag → Y/N
Frontend: checkbox boolean ↔ Y/N 정규화 → 그 외 값 거부
3단계. 기존 component 문법으로 skeleton을 만든다
AI에는 새 component를 발명하는 대신 기존 page를 읽고 같은 primitive로 skeleton을 만들게 했다. 이 단계에서 잘하는 일은 import, config 연결, 반복 JSX, handler 골격처럼 구조가 분명한 작업이다.
4단계. Figma와 실제 업무 흐름으로 layout을 좁힌다
code가 build된다는 이유로 layout을 확정하지 않았다. filter와 action의 위치, grid 높이, 상세 전환 방식은 Figma와 실제 사용 순서로 다시 판단했다. AI의 첫 결과는 visual hypothesis이고, 사람이 만든 layout이 acceptance 기준이었다.
5단계. evaluator를 한 층씩 통과시킨다
source diff
↓
focused validator/config test
↓
TypeScript type check
↓
production build
↓
browser에서 조회·선택·저장 흐름 확인
↓
backend log/query와 DB read 결과 대조
각 단계가 잡는 실패가 다르다.
| Evaluator | 주로 잡는 실패 | 잡지 못하는 것 |
|---|---|---|
| source/XML 대조 | 없는 query, 잘못된 field·editable 범위 | 실제 visual 흐름 |
| unit test | validation 경계, config regression | backend와 결합된 동작 |
| TypeScript | type·import·contract mismatch | 업무 의미 |
| build | bundling, route, server/client 경계 | 실제 사용자 interaction |
| browser | rendering, click, console error, end-to-end 흐름 | 보이지 않는 데이터 의미 전체 |
| DB read | 저장 결과와 metadata 존재 | 화면 사용성 |
하나의 test를 “검증 완료”라고 부르지 않은 이유다. browser가 통과해도 잘못된 row를 수정할 수 있고, DB 값이 맞아도 화면이 쓸 수 없을 수 있다.
한 페이지의 요령이 여러 frontend 작업의 규격으로 바뀌었다
이 방식은 MOSIZ010 한 장에서 끝나지 않았다. 2026년 6월부터 7월까지 merge된 변경을 다시 분류하면, 새 page 구현에서 UX 정상화, 다국어, test page, module 조립으로 검증 범위가 확장됐다.
| 작업 | 실제로 바꾼 것 | harness에 남은 기준 |
|---|---|---|
MOSIZ010 그리드 컬럼 관리 | mapper 기반 single-grid CRUD, config, validator | XML field·cast를 frontend contract로 번역 |
MOSIZ009 사용자 권한 관리 | 사용자·role·권한·상세권한의 4-grid layout, null row 방어 | multi-grid 관계와 빈 JOIN 결과를 acceptance case로 추가 |
MOSIZ008 메뉴 관리 | one-grid CRUD, 계층 renderer, filter | page shape는 단순하게 두고 domain 표현만 route-local로 확장 |
| 권한 화면 UX 정상화 | 기본 grid 고정, 상세 관리 toggle, clipping·edit 보정 | Figma와 사용자 흐름을 build와 별개인 visual contract로 취급 |
| single/multi-grid test page | PK, editable field, copy default, master-detail refresh 보정 | page archetype별 config regression test 도입 |
| 관리자 화면 다국어 | label literal과 fallback 제거, metadata seed 분리 | 화면 문자열도 DB·translation contract로 검사 |
| Frame·MOS module 조립 | 공용 UI/store/API와 업무 page ownership 분리 | 단독·부분·전체 module 조합의 build/test matrix 도입 |
작업이 누적되면서 “새 page를 만드는 방법”도 달라졌다. 초기에는 기존 화면 하나와 XML 하나를 읽고 route를 완성하는 데 집중했다. 이후에는 null row, translation literal, module ownership, 여러 조합의 build처럼 한 page 밖에서 발생하는 regression까지 완료 조건에 포함했다.
이것이 내가 말하는 compounding이다. 작업량이 많아졌다는 사실보다, 앞선 작업의 실패가 다음 page의 기본 검증으로 승격됐다는 사실이 중요하다.
내가 만든 경량 harness의 구조
내 작업 방식을 도식으로 줄이면 다음과 같다.
┌──────────────────────────┐
│ Human intent │
│ 업무 의미 · Figma · 승인 │
└─────────────┬────────────┘
│ acceptance criteria
▼
┌──────────────┐ ┌──────────────────────────┐
│ XML / schema │──────▶│ Page contract │
│ system data │ │ field · action · layout │
└──────────────┘ └─────────────┬────────────┘
│
▼
┌────────────────────────┐
│ Planner / Generator │
│ 기존 pattern으로 구현 │
└────────────┬───────────┘
│ candidate diff
▼
┌────────────────────────┐
│ Evaluator │
│ test · build · browser │
│ XML · DB read 대조 │
└────────────┬───────────┘
│ failure class
▼
┌────────────────────────┐
│ Rule / test / document │
│ 다음 작업의 context │
└────────────────────────┘
이 하네스는 하나의 거대한 program이 아니다. 다음 여섯 층이 맞물린 운영 구조다.
1. Context harness: 원본까지 가는 짧은 지도
긴 instruction 하나에 모든 내용을 넣지 않았다. repository entry rule에는 “무엇을 어디서 찾는가”를 두고, 실제 계약은 XML, source, schema, page별 document에서 읽게 했다.
OpenAI가 AGENTS.md를 백과사전보다 목차로 사용하고, 구조화된 docs/를 system of record로 둔 이유와 같다. context가 길다고 정확해지는 것이 아니라, 지금 작업에 필요한 원본으로 빨리 이동할 수 있어야 한다.
2. Specification harness: page마다 같은 질문을 묻는다
page ID, query, key, editable field, action, layout, validation, system data, 완료 조건을 반복해서 확인했다. 이것이 사실상 page schema였다. 모든 page를 JSON 하나로 생성하지 않아도, 같은 질문으로 설계하면 결과의 모양은 안정된다.
3. Component harness: 사용할 문법을 제한한다
Tab, Filter, FieldSection, Grid, Buttons, 공통 API와 store를 먼저 사용한다. 기존 primitive로 표현할 수 없다는 사실을 확인한 뒤에만 공통 component를 확장한다. 이 제약이 CSS와 state management의 변종을 줄였다.
4. Safety harness: AI가 넘어서는 안 될 경계를 코드와 절차로 막는다
내 로컬에는 planner, generator, evaluator 역할과 remote write 차단 rule, build/status command를 묶으려 한 harness prototype도 남아 있다. remote SSH write, DB write, container를 통한 DB write를 차단하는 rule과 smoke script까지 작성했다.
그러나 정확히 말하면 이것은 완료된 사내 공용 플랫폼이 아니다. 현재 검증 script를 다시 실행했을 때 일부 repository link와 compiled hook binary가 없어 전체 audit가 실패했다. 당시 설계 문서도 shared infrastructure는 보류하고 개인 convention과 lean harness만 채택한다고 명시한다.
이 실패는 오히려 중요한 경계다. rule source가 존재하는 것과 모든 작업에 자동 적용되는 것은 다르다. 이 글에서 “구축했다”는 표현은 source와 운영 규칙을 설계하고 일부 작업에 사용했다는 뜻이지, 전사 rollout을 끝냈다는 뜻이 아니다.
5. Evaluation harness: 완료라는 말을 여러 증거의 교집합으로 만든다
focused test, TypeScript, build, browser, XML 대조, DB read, PR review를 역할별로 분리했다. 특히 browser verification은 code만 보고는 알 수 없는 문제를 잡는다. Anthropic의 long-running agent 실험에서도 명시적으로 browser automation을 요구했을 때 web app의 end-to-end 확인이 개선됐다. Anthropic의 공식 연구 기록은 feature list, incremental progress, Git history, progress artifact, browser test를 서로 다른 실패를 막는 장치로 설명한다.
6. Compounding harness: 한 번의 실패를 다음 실행의 기본값으로 바꾼다
AI가 같은 실수를 반복하면 “다음에는 잘해”라고 말하지 않았다.
- backend에 없는 action을 만들었다 → XML 우선 rule과 PR checklist로 만든다.
- 복잡한 validation이 component 안에 섞였다 → pure helper와 focused test로 분리한다.
- label을 화면에 직접 썼다 → translation metadata와 literal regression test로 바꾼다.
- module 조합마다 build 결과가 달랐다 → 조합 matrix test를 추가한다.
- 시스템 데이터와 샘플 데이터가 섞였다 → PR의 DB 영향 범위에서 분리한다.
이것이 compound engineering과 닿는 지점이다. Every의 공식 Compound Engineering workflow도 brainstorm, plan, work, simplify, review 뒤에 learning을 다시 reusable artifact로 남기는 loop를 둔다. 공식 저장소는 핵심을 ceremony가 아니라 “다음 작업을 더 쉽게 만드는 leverage”라고 설명한다.
왜 prompt-only 생성이나 전면 수작업을 선택하지 않았나
페이지를 반복해서 만든다고 해서 모든 화면을 하나의 schema로 자동 생성하려 하지는 않았다. 반대로 매번 빈 파일에서 수작업으로 시작하지도 않았다. 내가 필요했던 것은 대량 code 출력 자체가 아니라, 불완전한 backend 계약과 새 UX 판단을 함께 다루면서도 다음 page에서 같은 실수를 줄이는 방법이었다.
| 접근 | 강한 문제 | 약한 문제 |
|---|---|---|
| prompt-only AI generation | 작은 prototype과 layout variant를 빠르게 탐색 | source contract 누락, 일관성, 재현성 |
| 내가 사용한 lean contract harness | XML·DB·visual·frontend 계약을 맞추며 정상화 | contract 표 작성과 evaluator 유지 비용 |
| 완전 수작업 | 사람이 모든 예외를 직접 판단 | 반복 JSX와 대조 작업의 속도·누락 |
내가 선택한 조합은 반복 가능한 부분과 판단이 필요한 부분을 분리하는 것이었다.
기존 component와 config가 반복 구조를 제한한다
+
AI agent가 source를 조사하고 후보 구현을 만든다
+
human이 업무 의미와 layout을 결정한다
+
evaluator가 같은 계약으로 돌아왔는지 검사한다
즉 자동화의 목표는 사람을 제거하는 것이 아니라, 사람이 매번 같은 반복 작업을 하지 않고 판단이 필요한 차이만 보게 하는 것이다.
실제로 자동화한 것, 자동화하지 않은 것
자동화한 것
- 기존 page와 mapper에서 field·action 후보 찾기
- 정해진 primitive로 page skeleton과 config 만들기
- validator의 경계값 test 생성과 실행
- type check, build, formatting, diff 검증
- 여러 module 조합의 registry, build, test matrix
- browser에서 route 진입, console error, 주요 action 확인
- 실패를 issue, rule, test, PR 설명으로 구조화
- remote write처럼 위험한 command를 차단하는 hook prototype
의도적으로 자동화하지 않은 것
- 업무상 어떤 field를 사용자가 수정해도 되는지 최종 결정
- 상세 정보를 기본 화면에 보여 줄지에 대한 UX 판단
- 운영 DB write 승인
- 시스템 데이터와 업무 데이터의 의미 분류
- Figma의 최종 정보 위계
- test가 모두 통과한 변경의 배포 승인
여기서 human-in-the-loop는 AI가 부족해서 임시로 사람이 끼어드는 구조가 아니다. 책임과 비용이 큰 판단을 의도적으로 사람에게 남기는 architecture다.
잘못됐던 접근과 최소 반례
“화면 캡처만 주고 똑같이 만들어 줘”
캡처에는 query key와 primary key가 없다. 메뉴 이름은 같아 보여도 save 대상이 다른 table일 수 있다. 결과는 시각적으로 비슷하지만 저장이 틀린 page가 된다.
대안: screenshot은 visual oracle로만 쓰고 XML·DB contract를 별도 입력으로 준다.
“backend에 곧 생길 테니 UI를 먼저 남겨 두자”
대응 query가 없는 action은 사용자가 누르는 순간 실패한다. 더 위험한 경우는 이름이 비슷한 다른 query에 잘못 연결되는 것이다.
대안: 현재 executable contract에 없는 기능은 제거하거나 disabled future spec으로 분리한다.
“config-driven이면 무조건 좋은 구조다”
화면 하나만 쓰는 복잡한 interaction까지 schema에 넣으면 schema가 또 하나의 programming language가 된다. AI는 그 DSL의 예외를 계속 늘리고, debugging 경로는 길어진다.
대안: 반복되는 field metadata만 config로 올리고 route-specific behavior는 가까운 source에 둔다.
“build가 통과했으니 끝났다”
build는 button이 실제로 잘못된 row를 삭제하는지 모른다. browser test도 업무상 잘못된 권한을 판정하지 못한다.
대안: evaluator마다 잡는 실패를 명시하고 교집합으로 완료를 정의한다.
“하네스 source를 만들었으니 자동으로 적용된다”
현재 내 로컬 prototype audit가 보여 준 최소 반례다. hook source와 rule이 있어도 binary, symlink, repository wiring이 빠지면 guard는 실행되지 않는다.
대안: setup 자체를 smoke test하고, “source 존재”와 “enforcement 활성” 상태를 별도 지표로 둔다.
다음에 다시 만든다면: Page Contract를 먼저 제품으로 만들겠다
지금까지의 흐름을 한 단계 더 밀어붙인다면 agent prompt를 더 길게 쓰기보다 page-contract.yaml 같은 작은 명세를 만들고 싶다.
pageId: MOSIZ010
mapper: gridColumn.gridColumn
layout: single-grid
filters:
- field: menuId
type: select
- field: gridSeq
type: integer
grid:
sequence: 1
mode: inline
keys: [menuId, gridSeq, dataField]
actions: [read, create, update, delete]
verification:
- contract-test
- typecheck
- build
- browser-crud
목표는 YAML로 모든 React를 생성하는 것이 아니다. 다음 evaluator를 자동화하는 중간 계약으로 쓰는 것이다.
- mapper의 field와 contract field가 어긋나면 실패한다.
- frontend config의 key·editable 값이 contract와 어긋나면 실패한다.
- DB system seed에 menu·label·action이 없으면 경고한다.
- browser scenario를 contract에서 생성한다.
- Figma node나 승인 screenshot을 visual evidence로 연결한다.
이렇게 되면 AI model을 바꿔도 page contract와 evaluator는 남는다. 좋은 harness는 특정 모델의 prompt 요령이 아니라, 모델이 교체되어도 유지되는 product knowledge와 feedback loop여야 한다.
측정해야 할 것은 생성 line 수가 아니다
이 글에는 “AI로 열 배 빨라졌다”는 숫자가 없다. 내 작업 기록만으로는 통제된 비교 실험을 만들 수 없기 때문이다. 대신 다음 작업부터는 아래 지표를 남기는 편이 정직하다.
| 지표 | 질문 |
|---|---|
| first-pass contract pass rate | 첫 구현이 XML·config 대조를 몇 개 통과했나 |
| browser rework count | browser 검증 뒤 수정이 몇 번 발생했나 |
| unsupported action count | backend에 없는 UI/API를 몇 개 만들었나 |
| escaped regression | merge 뒤 발견된 page regression은 몇 개인가 |
| human decision time | 반복 coding이 아니라 판단에 쓴 시간은 얼마인가 |
| harness enforcement coverage | rule source가 아니라 실제로 guard가 실행되는 repo 비율은 얼마인가 |
| compounding rate | 재발한 실패 중 rule·test·document로 승격된 비율은 얼마인가 |
AI 생산성은 생성 속도만 재면 과대평가된다. 빨리 만든 잘못된 page를 사람이 오래 고쳤다면 leverage가 아니다. 내가 원하는 지표는 “몇 line을 생성했나”가 아니라 같은 실패를 다시 설명하지 않아도 되는가다.
내가 얻은 결론
처음에는 AI로 frontend를 많이 만든 사실이 핵심처럼 보였다. 작업을 다시 읽어 보니 핵심은 다른 곳에 있었다.
나는 AI에게 디자인과 업무 의미를 통째로 넘기지 않았다. backend XML을 실행 계약으로, DB system data를 노출 계약으로, Figma를 사람의 visual judgment로, frontend primitive를 구현 문법으로 두었다. AI는 그 사이를 빠르게 연결했고 test와 browser가 후보 구현을 다시 밀어냈다.
그래서 이 작업은 “AI가 그럴듯한 화면을 만들어 줬다”보다 다음 문장으로 설명하는 편이 정확하다.
나는 화면을 자동 생성한 것이 아니라, AI가 틀릴 수 있는 지점을 계약과 evaluator로 둘러싸서 규격에 맞는 화면을 반복해서 만들 수 있는 환경을 설계했다.
아직 완성된 전사 harness는 아니다. local guard prototype은 현재 audit를 통과하지 못했고, Figma 판단은 이 글에서 독립된 version history까지 공개하지 않았다. 그럼에도 이미 달라진 점은 분명하다. 실패를 prompt의 운으로 다시 시도하지 않고, source, rule, test, document, browser scenario 중 하나로 바꾸기 시작했다.
내가 생각하는 Harness Engineering은 바로 그 전환이다. AI에게 더 잘 부탁하는 기술이 아니라, 한 번 내린 좋은 판단이 다음 agent 실행에도 남게 하는 엔지니어링이다.
현재 한계와 다음 실험
이 글은 실제 작업 기록을 복원한 case study이지 통제된 생산성 실험은 아니다. 그래서 속도 배수나 절감 시간을 주장하지 않았다. 다음 page에서는 contract 작성 시작 시점부터 browser 재작업 횟수, unsupported action 수, human decision time을 함께 기록할 것이다.
local guard prototype도 source 존재와 실제 enforcement 활성 상태를 분리해 다시 측정해야 한다. wiring audit를 통과하기 전에는 공용 harness라고 부르지 않는다. Figma는 공개 가능한 화면과 version evidence가 생겼을 때 visual regression 기준까지 연결하는 것이 다음 단계다.
Reference
- Ryan Lopopolo, OpenAI, Harness engineering: leveraging Codex in an agent-first world, 2026-02-11.
- Justin Young et al., Anthropic, Effective harnesses for long-running agents, 2025-11-26.
- Every, Compound Engineering Plugin — Official Repository, GitHub, 2026-08-07 확인.
댓글