WEBBOOK CHAPTER

Codex로 구축하는 AI 개발팀: 4장. 첫 번째 최소 공장

4장. 첫 번째 최소 공장

다음은 여러 현장에서 반복된 패턴을 합친 합성 사례로, 등장하는 숫자는 설명을 위한 예시 수치다.

플랫폼 팀이 에이전트 공장을 만들겠다며 분기 전체를 썼다. 멋진 대시보드와 작업 큐, 여러 모델 선택 화면이 생겼지만 실제 제품 저장소를 끝까지 바꾼 작업은 없었다. 반대편의 작은 팀은 폴더 네 개와 스크립트 하나로 시작했다. 요구 하나를 읽고, 격리된 복사본을 만들고, 결정론적 변경을 적용하고, 테스트 결과와 이벤트를 남겼다. 볼품은 없었지만 어느 부분이 약한지 배울 수 있었다.

최소 공장은 기능이 적은 플랫폼이 아니다. 입력부터 검증된 결과까지 하나의 얇은 수직 흐름이 실제로 닫힌 시스템이다.

이번 장의 약속

  • 최소 공장의 완료 조건을 정의한다.
  • 외부 AI 없이 하니스와 모델 변동성을 분리해 검증한다.
  • 성공 경로와 실패 경로를 모두 실행한다.
  • 산출물만으로 한 실행을 재구성한다.

먼저 한 작업만 통과시킨다

첫 공장은 다음 여덟 단계를 가진다.


1. 작업 계약 읽기
2. 명세 존재와 입력 검증
3. 격리 작업 공간 준비
4. 에이전트 어댑터 실행
5. 허용 경로 검사
6. 필수 품질 게이트 실행
7. 결과와 인계 문서 생성
8. 이벤트와 요약 저장
작업 계약, 입력 검증, 격리 공간, 모의 에이전트, 경로 검사, 품질 게이트, 인계, 이벤트와 요약이 위에서 아래로 연결되는 최소 공장 흐름.
작업 계약, 입력 검증, 격리 공간, 모의 에이전트, 경로 검사, 품질 게이트, 인계, 이벤트와 요약이 위에서 아래로 연결되는 최소 공장 흐름.

그림 4-1. 기능이 많은 플랫폼보다 입력·격리·실행·검증·인계·기록이 한 번 닫히는 수직 단면을 먼저 만든다.

처음부터 넣지 않을 것도 명시한다.

  • 클라우드 큐와 분산 실행
  • 여러 실제 모델의 자동 라우팅
  • 조직 전체 권한 관리 화면
  • 자동 운영 배포
  • 자연어로 무엇이든 수행하는 범용 작업

이 기능은 필요할 수 있다. 그러나 단일 작업의 계약과 판정이 검증되지 않은 상태에서 분산시키면 실패 원인만 늘어난다.

예제 공장의 네 경계

실습 저장소의 실제 구조는 다음과 같습니다.


03-labs/
├── specs/             # 다중 작업 실행 명세와 DAG
├── tasks/             # 한 기능용 실행 명세
├── fixtures/templates # 공장이 조립할 주문 앱 원본
├── src/               # 하니스·큐·작업 공간·게이트 구현
├── test/              # 하니스·보안·구조 자동 테스트
├── capture/           # 실제 실행에서 만든 출판 화면 원천
└── .factory/          # 실행 때 생기는 작업 공간·릴리스·증거

specs/tasks/는 의도·계획 층, 루트의 src/는 교육용 제어면입니다. .factory/runs/<runId>/workspaces/ 아래의 src/가 작업면이고, 같은 실행 루트의 events.jsonl과 보고서가 관측 기록입니다. 원본과 생성 결과의 src/를 혼동하지 않습니다.

모의 에이전트가 필요한 이유

실제 모델은 같은 입력에도 다른 행동을 할 수 있고, 서비스 상태·가격·모델 버전의 영향을 받는다. 공장 코드와 모델을 동시에 처음 실행하면 오류가 다음 중 어디에 있는지 알기 어렵다.

  • 작업 계약 파서
  • 작업 공간 복사와 경로 검사
  • 에이전트의 행동 선택
  • 파일 쓰기 도구
  • 테스트와 게이트
  • 이벤트 기록

결정론적 모의 에이전트는 정해진 작업 ID에 정해진 변경 또는 실패를 낸다. 이것으로 공장 배관을 먼저 시험한다. 현재 참조 구현은 이 작업자를 Factory 안에서 직접 만든다. 실제 에이전트를 연결하려면 작업자 주입 경계를 먼저 추가하고, 현재 completed·handoff 의미와 제품별 응답을 변환하는 어댑터를 구현해야 한다.

다음은 src/mock-agent.mjs의 핵심 발췌입니다. 전체 파일과 자동 테스트가 예제 저장소에 있습니다.

실행 가능 — 원본: 03-labs/src/mock-agent.mjs; 검증: cd 03-labs && npm test.


async run({ task, attempt, workspace, previousHandoffs = [] }) {
  await this.eventLog.emit('agent.invoked', {
    taskId: task.id,
    attempt,
    adapter: 'deterministic-mock',
    resumedFromHandoff: previousHandoffs.length > 0
  });

  if ((task.scenario?.handoffOnAttempts ?? []).includes(attempt)) {
    const checkpoint = {
      completed: ['명세 읽기', '출력 경로 검증'],
      remaining: ['파일 생성', '품질 게이트 통과'],
      resumeHint: `${task.id} 작업을 같은 명세로 다시 실행하세요.`
    };
    return { status: 'handoff', checkpoint };
  }

  for (const operation of workspace.operations) {
    await this.workspaceManager.applyOperation(workspace.path, operation);
  }
  return { status: 'completed', changedPaths: workspace.outputs };
}

발췌는 파일 쓰기 이벤트와 체크포인트 파일 저장 부분을 생략했습니다. 모의 작업자는 명세의 연산을 적용하거나 정해진 attempt에서 인계합니다. 수용 기준 누락과 최종 RED 판정은 작업자가 아니라 하니스의 acceptance-tests 게이트가 담당합니다.

완료 증거를 파일로 만든다

콘솔의 “성공” 한 줄은 프로세스가 끝나면 사라지고 다른 도구가 읽기 어렵다. 각 실행은 다음 산출물을 남긴다.


.factory/runs/<runId>/
├── input.json        # 정규화된 작업과 기준 버전
├── events.jsonl      # 시간순 상태 변화
├── gate-results.json # 최종 status와 게이트별 판정·상세
├── change.patch      # 기준선 대비 변경
├── handoff.md        # 결과, 남은 위험, 다음 행동
└── summary.json      # 최종 상태와 주요 지표

input.json은 실행 당시의 계약을 보존한다. 원본 작업 파일이 나중에 바뀌어도 무엇을 실행했는지 알 수 있다. change.patch는 결과 증거, events.jsonl은 과정 증거, gate-results.json은 판정 증거다. handoff.md는 사람이 긴 로그를 읽지 않고도 리뷰를 시작하게 한다.

실습 1: 환경과 전체 검증

예제 디렉터리로 이동해 의존성을 확인하고 전체 테스트를 실행한다.


cd agent-software-factory/03-labs
npm test

핵심 경로는 외부 패키지를 설치하지 않는다. 성공하면 테스트 요약과 함께 종료 코드 0이 반환되어야 한다. 정확한 테스트 개수는 원고 개정에 따라 달라질 수 있으므로 숫자보다 fail 0과 프로세스 종료 코드를 확인한다.

실패하면 다음을 확인한다. Git clone으로 받은 저장소라면 마지막 명령도 실행한다.


node --version
pwd
git rev-parse --is-inside-work-tree && git status --short

Node.js가 20 미만이거나 현재 경로가 03-labs가 아니면 먼저 바로잡는다. Git 저장소에서 사용자 변경이 보이면 지우지 말고 새 복사본에서 실습한다. ZIP이나 비Git 복사본에서는 Git 상태 검사를 건너뛴다.

실습 2: 성공 경로 실행

실습 저장소의 package.json에 정의된 캡스톤 실행 명령을 사용한다.


npm run factory -- --task tasks/add-order-total.json
add-order-total 최소 공장 터미널. 작업 하나가 격리 작업공간에서 시도 1회로 리뷰·구문·구조 검사를 통과하고 GREEN으로 끝난다.
add-order-total 최소 공장 터미널. 작업 하나가 격리 작업공간에서 시도 1회로 리뷰·구문·구조 검사를 통과하고 GREEN으로 끝난다.

그림 4-2. 실제 최소 공장 명령은 주문 합계 작업 하나를 격리·리뷰·검증·통합해 GREEN으로 끝낸다.

실행기의 실제 핵심 출력은 다음과 같습니다. 절대 경로만 <LAB_ROOT>로 정규화했습니다.


에이전트 소프트웨어 공장: 주문 합계 기능 추가
실행 ID: add-order-total | 작업 1개 | 어댑터 deterministic-mock | 동시성 auto

[배치 1] add-order-total
  → add-order-total 시도 1: 격리 작업공간 생성
  ✓ add-order-total: 리뷰·구문·구조 검사 통과 후 통합

GREEN ✓ — 주문 합계 기능 추가
보고서: <LAB_ROOT>/.factory/runs/add-order-total/report.md
이벤트: <LAB_ROOT>/.factory/runs/add-order-total/events.jsonl

최종 보고 상태는 GREEN이고 summary.json의 작업/시도는 1/1, 세 최종 게이트는 모두 통과해야 합니다.


npm run report -- --latest
주문 합계 기능 추가 보고서 화면. 상태 GREEN, 작업 1, 총 시도 1, 재시도·인계·리뷰 반려 0과 세 품질 게이트 PASS가 보인다.
주문 합계 기능 추가 보고서 화면. 상태 GREEN, 작업 1, 총 시도 1, 재시도·인계·리뷰 반려 0과 세 품질 게이트 PASS가 보인다.

그림 4-3. 같은 최소 공장 실행의 로컬 보고서는 작업·시도 각 1회와 세 필수 게이트 PASS를 보여 준다.

다음 질문에 답할 수 있는지 확인한다.

  • 어떤 task 파일과 run ID를 사용했는가?
  • 어떤 파일이 바뀌었는가?
  • required-files, architecture, acceptance-tests는 각각 무엇을 확인했는가?
  • 작업 공간과 승인된 release/는 어떻게 분리됐는가?

답을 찾기 위해 모델 대화 전문이 필요하다면 이벤트와 요약이 아직 부족하다.

실습 3: 의도적으로 경계를 넘는다

경로 탈출 공격 fixture를 실행한다.


npm run test:security -- --case path-traversal

검사 안의 모의 작업은 ../와 작업 공간 밖 경로를 요청한다. 바깥 파일을 실제로 바꾸지 않고 정책이 공격을 거부해야 합니다. 명령 자체의 기대 종료 코드는 0입니다. 공격이 성공해서가 아니라 모든 공격 사례가 예상대로 차단됐기 때문에 보안 검사가 통과합니다.

다음을 확인한다.

  1. 작업 공간 밖 파일이 생성·변경되지 않았다.
  2. 부모 경로 탈출은 [SEC_PATH_TRAVERSAL], 절대 경로는 [SEC_PATH_ABSOLUTE]로 분류된다. 파일 시스템 경계 검사에서는 [SEC_PATH_OUTSIDE][SEC_SYMLINK_PATH]도 사용한다.
  3. 오류 메시지에 비밀이나 불필요한 호스트 경로가 노출되지 않는다.
  4. 같은 결정적 위반을 무의미하게 재시도하지 않는다.

안전 장치는 문서에 쓰인 금지가 아니라 실제 공격 fixture를 거부한 증거로 확인한다.

실습 4: 명세가 부족하면 멈춘다

수용 기준이 빠진 작업을 실행한다.


npm run factory -- --task tasks/missing-acceptance.json

교육용 CLI의 최종 상태는 RED, 종료 코드는 1입니다. 작업 파일은 만들어졌지만 acceptance-tests 게이트가 “실행 가능한 인수 테스트가 없습니다”라는 이유로 릴리스 승인을 차단합니다. 2장에서 정의한 작업 상태로 해석하면 ‘검증 증거 부족으로 통합이 차단된 상태’입니다. CLI의 교통신호 상태와 작업 수명주기 상태를 같은 말로 섞지 않습니다.

차단은 처리량을 낮추는 결함처럼 보일 수 있습니다. 하지만 검증할 수 없는 구현을 배포한 뒤 재작업하는 비용을 앞 단계에서 드러낸 것입니다.

실행기의 핵심 판정 순서

게이트는 순서가 있습니다. 현재 교육용 하니스의 실제 순서는 다음과 같습니다.


명세 스키마·경로 검증
  → 시도별 작업 공간
  → 독립 리뷰
  → 변경 파일 구문 검사
  → 지역 구조 검사
  → release 통합
  → 필수 파일·전체 구조·인수 테스트

경로 탈출은 명세 로드와 파일 연산에서 거부합니다. 독립 리뷰는 TODO, 동적 코드 실행, child process, 간접 지시 문자열 같은 제한된 패턴을 검사합니다. 이 예제가 범용 정적 보안 분석이나 OS 샌드박스를 제공한다고 해석하지 않습니다.

한 실행을 사후 재구성한다

터미널 출력을 보지 않은 동료에게 실행 디렉터리만 건넨다. 동료는 다음 양식을 채운다.


작업 목표:
기준 버전:
최종 상태:
변경 파일:
통과/실패 게이트:
정책 위반:
재시도 수:
사람이 결정할 것:

다섯 분 안에 채울 수 없다면 기록을 더 늘리기보다 요약 구조와 이벤트 이름을 다듬는다. 관측의 목적은 데이터를 많이 저장하는 것이 아니라 결정을 빨리 복원하는 것이다.

왜 실패하는가

대시보드부터 만든다

아직 어떤 상태와 실패 이유가 중요한지 모르는 상태에서 UI를 만들면 잘못된 개념이 굳는다. JSON과 명령줄로 한 흐름을 검증한 뒤 반복되는 질문을 화면으로 올린다.

성공 경로만 데모한다

실제 운영 비용은 차단, 타임아웃, 경로 위반, 테스트 불안정에서 발생한다. 최소 공장은 적어도 성공, 명세 차단, 정책 위반, 재시도 가능 오류를 재현해야 한다.

실제 모델로 모든 것을 시험한다

같은 오류가 재현되지 않아 하니스 회귀 테스트가 불안정해진다. 결정론적 어댑터로 제어 흐름을 검증하고, 실제 모델 평가는 별도 평가 세트에서 한다.

작업 공간을 재사용한다

이전 실행의 파일과 캐시가 다음 실행에 섞이면 같은 입력의 결과를 비교할 수 없다. 각 runId는 새 작업 공간과 산출물 디렉터리를 가진다.

운영 판단: 최소 공장을 실제 모델에 연결할 때

다음 조건을 모두 만족한 뒤 실제 코딩 에이전트 어댑터를 추가한다.

  • 성공·차단·정책 위반·일시 오류가 결정론적으로 테스트된다.
  • 작업 공간 밖의 쓰기가 실행기에서 거부된다.
  • 필수 게이트가 하나라도 실패하면 성공으로 표시되지 않는다.
  • 실행 산출물만으로 상태를 재구성할 수 있다.
  • 비밀이 이벤트와 오류에 기록되지 않는 검사가 있다.
  • 같은 작업을 깨끗한 환경에서 반복 실행할 수 있다.

목표 어댑터의 계약은 예를 들어 run({ task, workspace, tools })처럼 설계할 수 있다. 이는 현재 코드에 구현된 API가 아니다. 작업자 의존성 주입과 상태 변환을 구현·계약 테스트한 뒤 사용하며, 모델 고유 설정은 어댑터 내부에 가두고 공장의 작업·게이트·이벤트 의미는 유지한다.

연습문제

  1. 최소 공장에서 클라우드 큐보다 먼저 검증해야 할 수직 흐름을 한 문장으로 적어라.
  2. events.jsonl만 있고 input.json이 없을 때 재현에 생기는 문제를 설명하라.
  3. 테스트 명령이 종료 코드 0이지만 테스트 수가 0일 때 사용할 종료 상태와 이유 코드를 설계하라.
  4. 경로 위반 시나리오 외에 안전 장치를 실제로 검증할 실패 주도 테스트 두 개를 추가하라.

체크포인트

  • 외부 AI 서비스 없이 전체 공장 테스트가 통과한다.
  • 한 작업이 입력, 격리 실행, 경로 검사, 품질 게이트, 요약으로 끝까지 흐른다.
  • 성공, 명세 차단, 정책 위반의 산출물을 비교했다.
  • 실행 기록만으로 동료가 상태와 다음 행동을 복원했다.

1부에서 우리는 병목의 이동, 에이전트 루프, 균형 지표, 최소 수직 흐름을 만들었다. 2부에서는 공장의 연료인 저장소 지식을 다룬다. 에이전트가 읽을 수 있는 저장소, 실행 가능한 명세, 충돌을 줄이는 작업 그래프, 오래가는 컨텍스트를 설계한다.