현재 IT 회사들의 변화 속도는 상상을 초월할 정도로 빠르게 진행되고 있습니다.
특히 AI를 활용한 개발 방식은 단순한 도구 수준을 넘어, 개발 프로세스 자체를 바꾸는 방향으로 진화하고 있습니다.
제가 참고한 영상은 실밸개발자 채널인데,
요즘 실리콘밸리에서 실제로 어떻게 AI를 활용하는지 흐름을 이해하는 데 도움이 많이 되었습니다.
기존 개발 방식: 프롬프트 엔지니어링
이전에는 대부분 다음과 같은 방식으로 AI를 활용했습니다.
- 프롬프트를 잘 작성해서 결과를 얻는다
- 결과를 사람이 검수하고 수정한다
- 다시 질문해서 개선한다
이 방식은 결국 아래 개념과 연결됩니다.
GIGO(Garbage In, Garbage Out)
→ 잘못된 입력을 넣으면 결과도 쓰레기가 나온다저 역시 기획 + 개발 관점에서
OpenAI, Claude 같은 AI를 활용해 방향을 잡고 설계하는 방식으로 많이 사용해왔습니다.
한계: AI는 결국 실수한다
AI를 사용하면서 느낀 가장 큰 문제는 단 하나였습니다.
AI는 생각보다 자주 틀린다
- 타입을 잘못 추론
- edge case 놓침
- 구조적으로 위험한 코드 생성
그래서 “프롬프트를 잘 쓰는 것”만으로는 부족하다는 결론에 도달했습니다.
그래서 등장한 개념: 하네스 엔지니어링
하네스 엔지니어링(Harness Engineering)은 다음과 같이 정의할 수 있습니다.
AI가 안전하게 동작하도록 제약, 도구, 피드백 구조를 설계하는 방식

핵심은 3가지입니다.
1. AI 제어 (Harnessing)
- AI를 자유롭게 두는 것이 아니라
- 규칙과 제한을 걸어 원하는 방향으로 유도
2. 스캐폴딩 (Scaffolding)
- AI 주변에 안전장치 구성
- 파일 접근, 실행 범위, 환경 제어
3. 피드백 루프
- AI의 실수를 기록하고
- 다시 같은 실수를 하지 않도록 구조 개선
쉽게 말하면?
AI를 그냥 쓰는 것이 아니라
“AI를 통제하는 시스템을 만든다”
이게 핵심입니다.

실제 적용: AI 파이프라인 구축
저는 모노레포 기반 프로젝트에서 다음 구조로 적용했습니다.
- packages 기반 구조
- ai-pipeline 생성
- git 변경사항 기반 자동 검수
사용한 AI 역할 분리
- Claude → 코드 생성 + 1차 검수
- Codex → 더 엄격한 2차 검수
문제 발생: Codex UTF-8 버그
여기서 가장 크게 막힌 부분이 하나 있습니다.
Codex가 한글을 깨뜨리는 문제
(확실한 원인: 인코딩 처리 문제 / UTF-8 미명시)
C:\Users\ducks\.codex\skills\utf8-korean-guard 경로에
---
name: utf8-korean-guard
description: Enforce strict UTF-8 handling and preserve Korean text without corruption. Prevent mojibake in all read/write/edit operations.
---
# UTF-8 Korean Guard
Use this skill when working with Korean text, UTF-8 encoding, file I/O, or when any encoding-related risk exists.
## Hard Constraints (MUST)
- All text files MUST be treated as UTF-8.
- All file read/write operations MUST explicitly specify UTF-8.
- Korean text MUST be preserved exactly as-is.
- Korean text MUST remain human-readable Hangul.
## Forbidden (NEVER)
- NEVER save files as CP949, EUC-KR, UTF-16, ANSI.
- NEVER omit encoding in file I/O.
- NEVER convert Korean text into unicode escape sequences (e.g. \uXXXX).
- NEVER rewrite entire files when partial patching is possible.
- NEVER introduce broken characters like �.
## Editing Workflow
1. Detect whether the file contains Korean text or encoding risks.
2. Prefer patch-based edits (apply_patch) instead of full rewrites.
3. When reading files, explicitly use UTF-8.
4. When writing files, explicitly use UTF-8.
5. After editing, verify Korean text integrity.
## Node.js Rules
- Always use explicit encoding:
```js
fs.readFileSync(path, 'utf8')
fs.writeFileSync(path, data, 'utf8')
```
- Do not use default encoding behavior.
## PowerShell UTF-8 I/O
- Read:
- Get-Content -Encoding utf8 <path>
- Get-Content -Raw -Encoding utf8 <path>
- Write:
- Set-Content -Encoding utf8 <path> <value>
- Out-File -Encoding utf8 <path>
## Output Rules
- Korean text MUST be output as readable Hangul.
- Do not escape Korean characters.
- Do not alter meaning or structure of Korean sentences.
## Verification Checklist
- No replacement glyph (�) exists.
- No mojibake (깨진 한글) exists.
- Korean text remains unchanged in meaning.
- Encoding is confirmed as UTF-8.
If any corruption is detected:
- STOP immediately.
- Re-open the file explicitly as UTF-8.
- Re-apply changes safely.
해결 방법: Skill 기반 제약
Codex에 강제로 규칙을 학습시키는 방식으로 해결했습니다.
핵심 전략
- 글로벌 Skill 생성
- 실행 전에 항상 읽도록 설정
utf8-korean-guard
→ UTF-8 강제 + 한글 보호핵심 포인트
- 모든 파일 UTF-8 강제
- 한글 깨짐 금지
- 부분 수정만 허용 (전체 rewrite 금지)
이 방식으로 실제 문제를 해결했습니다.
import { spawnSync } from 'child_process'; // 외부 프로세스(터미널 명령어)를 동기적으로 실행하기 위한 모듈
/**
* Claude AI가 응답할 데이터의 규격(타입) 정의
*/
export type AgentResponse = {
pass: boolean; // 코드 검수 통과 여부 (true/false)
feedback: string; // 통과하지 못했을 때의 이유나 개선 사항
};
/**
* 대화의 한 단위를 정의 (나와 상대방의 대화 기록 저장용)
*/
type ConversationTurn = {
role: 'claude' | 'codex';
response: AgentResponse;
};
/**
* Claude가 출력한 문자열(텍스트)에서 JSON 부분만 추출하여 객체로 변환하는 함수
*/
function parseAgentResponse(raw: string): AgentResponse {
// 정규표현식을 사용해 문자열 내에서 { ... } 형태의 JSON 문법을 찾음
const jsonMatch = raw.match(/\{[\s\S]*\}/);
if (!jsonMatch) {
throw new Error('JSON을 찾을 수 없습니다.');
}
// 찾은 내용을 JSON 객체로 파싱
const parsed: unknown = JSON.parse(jsonMatch[0]);
if (!parsed || typeof parsed !== 'object') {
throw new Error('응답 JSON 형식이 올바르지 않습니다.');
}
const candidate = parsed as Record<string, unknown>;
// 최종적으로 AgentResponse 타입에 맞춰 데이터 가공 및 반환
return {
pass: candidate.pass === true, // 명확히 true일 때만 true로 인정
feedback: typeof candidate.feedback === 'string' ? candidate.feedback : '피드백 없음',
};
}
/**
* 검수할 코드와 이전 대화 기록을 합쳐서 AI에게 보낼 최종 질문(Prompt)을 만드는 함수
*/
function buildPrompt(code: string, history: ConversationTurn[]): string {
// 1. 기본 검수 가이드라인 설정
const baseInstruction = `
다음 TypeScript/TSX 코드를 검수하세요.
검수 기준:
- any 사용 금지
- as any 사용 금지
- undefined / null 안전성 확인
- 타입 누락 여부 확인
- strict TypeScript 기준으로 문제 여부 판단
검수할 코드:
\`\`\`
${code}
\`\`\`
`.trim();
// 2. 이전 대화가 있다면 문자열로 변환하여 추가 (문맥 유지 목적)
const historySection =
history.length > 0
? '\n\n---\n이전 대화 내용:\n' +
history
.map(
turn =>
`[${turn.role === 'claude' ? 'Claude(나)' : 'Codex(상대방)'}]: pass=${turn.response.pass}, feedback="${turn.response.feedback}"`,
)
.join('\n')
: '';
// 3. AI가 반드시 지켜야 할 응답 형식(JSON) 지정
const responseInstruction = `
${historySection}
반드시 아래 JSON 형식으로만 응답하세요:
{"pass": true, "feedback": "문제 없음"}
또는
{"pass": false, "feedback": "문제 설명"}`;
return baseInstruction + responseInstruction;
}
/**
* 시스템 환경에 따라 Claude 실행 명령어(Binary) 경로를 결정하는 함수
*/
function resolveClaudeCommand(): string {
// 1. 환경 변수(CLAUDE_BIN)가 설정되어 있으면 그것을 사용
// 2. 없다면 윈도우는 'claude.cmd', 그 외(Mac/Linux)는 'claude' 명령어를 기본값으로 사용
return process.env.CLAUDE_BIN?.trim() ?? (process.platform === 'win32' ? 'claude.cmd' : 'claude');
}
/**
* 실제로 Claude를 실행하고 결과를 받아오는 핵심 함수
*/
export function callClaude(code: string, history: ConversationTurn[]): AgentResponse {
const prompt = buildPrompt(code, history); // 보낼 질문 생성
const claudeCommand = resolveClaudeCommand(); // 실행할 명령어 확인
try {
// 터미널에서 'claude -p --output-format json' 명령어를 실행하는 것과 같음
const result = spawnSync(claudeCommand, ['-p', '--output-format', 'json'], {
input: prompt, // AI에게 보낼 질문을 표준 입력(stdin)으로 전달
encoding: 'utf-8', // 글자 인코딩 설정
timeout: 180000, // 3분(180초) 안에 응답 없으면 강제 종료
shell: process.platform === 'win32', // 윈도우 환경 대응
});
// 실행 과정 자체에서 오류가 발생한 경우 (명령어 못 찾음 등)
if (result.error) {
throw result.error;
}
// Claude 프로세스가 비정상 종료된 경우 (Exit Code 0이 아님)
if (result.status !== 0) {
const stderr = result.stderr?.toString().trim();
const stdout = result.stdout?.toString().trim();
throw new Error(stderr || stdout || `Claude 종료 코드 ${result.status}`);
}
// 실행 결과를 텍스트로 가져옴
const output = result.stdout?.toString() ?? '';
if (!output) {
throw new Error('Claude 응답이 비어 있습니다.');
}
// --output-format json 옵션 사용 시, 응답이 { "result": "내용" } 형태로 올 수 있으므로 처리
const envelope = JSON.parse(output) as { result?: string };
const resultText = envelope.result ?? output;
// 텍스트에서 최종적으로 pass/feedback 데이터를 파싱하여 반환
return parseAgentResponse(resultText);
} catch (error) {
// 모든 에러 발생 시 '실패' 상태와 에러 메시지를 반환하여 프로그램 중단을 방지
return {
pass: false,
feedback: `Claude 호출 실패: ${error instanceof Error ? error.message : '알 수 없는 오류'}`,
};
}
}Claude 호출 구조 핵심
코드 구조를 간단히 요약하면 다음 흐름입니다.
1. buildPrompt
- 검수 기준 + 코드 + 히스토리 합침
2. spawnSync
- 로컬 Claude CLI 실행
3. parseAgentResponse
- JSON만 추출해서 결과 처리
4. Error Handling
- 실패 시 안전하게 fallback
import fs from 'fs';
import os from 'os';
import path from 'path';
import { execSync, spawnSync } from 'child_process';
import type { AgentResponse } from './claudeAgent.js';
// 대화 내역 저장 타입 (이전과 동일)
type ConversationTurn = {
role: 'claude' | 'codex';
response: AgentResponse;
};
/**
* AI의 답변 텍스트에서 JSON만 뽑아내는 함수 (이전과 동일)
*/
function parseAgentResponse(raw: string): AgentResponse {
const jsonMatch = raw.match(/\{[\s\S]*\}/);
if (!jsonMatch) {
throw new Error('JSON을 찾을 수 없습니다.');
}
const parsed: unknown = JSON.parse(jsonMatch[0]);
if (!parsed || typeof parsed !== 'object') {
throw new Error('응답 JSON 형식이 올바르지 않습니다.');
}
const candidate = parsed as Record<string, unknown>;
return {
pass: candidate.pass === true,
feedback: typeof candidate.feedback === 'string' ? candidate.feedback : '피드백 없음',
};
}
/**
* AI에게 전달할 질문지 구성 (이전과 거의 동일하나 역할 이름이 Codex 위주로 변경)
*/
function buildPrompt(code: string, history: ConversationTurn[]): string {
const baseInstruction = `
다음 TypeScript/TSX 코드를 검수하세요.
검수 기준: any 금지, null 안전성 등...
\`\`\`
${code}
\`\`\`
`.trim();
const historySection =
history.length > 0
? '\n\n---\n이전 대화 내용:\n' +
history
.map(
turn =>
// 역할에 따라 누가 말했는지 표시 (Codex 기준)
`[${turn.role === 'codex' ? 'Codex(나)' : 'Claude(상대방)'}]: pass=${turn.response.pass}, feedback="${turn.response.feedback}"`,
)
.join('\n')
: '';
const responseInstruction = `
${historySection}
반드시 JSON 형식 {"pass": boolean, "feedback": string}으로 응답하세요.`;
return baseInstruction + responseInstruction;
}
/**
* 현재 프로젝트(Git 저장소)의 최상위 루트 경로를 알아내는 함수
*/
function getRepoRoot(): string {
return execSync('git rev-parse --show-toplevel', {
encoding: 'utf-8',
}).trim();
}
/**
* Codex 실행 파일의 경로를 찾는 함수 (윈도우/맥/리눅스 대응)
*/
function resolveCodexCommand(): string {
const configured = process.env.CODEX_BIN?.trim();
if (configured) return configured;
if (process.platform === 'win32') {
const localAppData = process.env.LOCALAPPDATA;
if (localAppData) {
// pnpm으로 설치된 codex 명령어 경로 확인
const pnpmCodexCmd = path.join(localAppData, 'pnpm', 'codex.CMD');
if (fs.existsSync(pnpmCodexCmd)) return pnpmCodexCmd;
}
return 'codex.CMD';
}
return 'codex';
}
/**
* Codex를 실행하여 코드 검수를 수행하는 메인 함수
*/
export function callCodex(code: string, history: ConversationTurn[]): AgentResponse {
const prompt = buildPrompt(code, history);
// 1. 보안과 충돌 방지를 위해 임시 폴더 생성
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ai-pipeline-codex-'));
const outputPath = path.join(tempDir, 'last-message.txt'); // 결과가 저장될 파일
const schemaPath = path.join(tempDir, 'response.schema.json'); // 응답 규격 파일
try {
const repoRoot = getRepoRoot();
const codexCommand = resolveCodexCommand();
// 2. AI가 지켜야 할 JSON 규격(Schema) 파일을 임시로 작성
// AI에게 "너는 반드시 pass(불리언)와 feedback(문자열)을 포함해야 해"라고 강제하는 용도
fs.writeFileSync(
schemaPath,
JSON.stringify({
type: 'object',
additionalProperties: false,
required: ['pass', 'feedback'],
properties: {
pass: { type: 'boolean' },
feedback: { type: 'string' },
},
}, null, 2),
'utf-8',
);
// 3. Codex 명령어 실행
// --output-schema: 미리 정의한 규격대로 답변하게 함
// -o: 답변을 파일(outputPath)로 저장하게 함
const result = spawnSync(codexCommand,
['exec', '--color', 'never', '-C', repoRoot, '--output-schema', schemaPath, '-o', outputPath, '-'],
{
input: prompt, // 질문 전달
encoding: 'utf-8',
timeout: 180000,
shell: process.platform === 'win32',
}
);
// 에러 처리
if (result.error) throw result.error;
if (result.status === null || result.status !== 0) {
throw new Error(`Codex 종료 코드 ${result.status}`);
}
// 4. 저장된 파일이 있으면 읽고, 없으면 터미널 출력값에서 답변을 가져옴
const output = fs.existsSync(outputPath)
? fs.readFileSync(outputPath, 'utf-8')
: (result.stdout?.toString() ?? '');
return parseAgentResponse(output);
} catch (error) {
return {
pass: false,
feedback: `Codex 호출 실패: ${error instanceof Error ? error.message : '알 수 없는 오류'}`,
};
} finally {
// 5. 사용이 끝난 임시 폴더와 파일들을 깨끗하게 삭제 (정리 정돈)
fs.rmSync(tempDir, { recursive: true, force: true });
}
}Codex 구조에서 달라진 점
Codex는 훨씬 더 “강제적”입니다.
주요 차이
- JSON Schema 강제
- 임시 파일 기반 응답 처리
- Git Root 기준 실행
- finally로 정리
한 줄 요약
Claude = 유연Codex = 엄격

실제 결과 (핵심 포인트)
검수 결과를 보면 재미있는 패턴이 있습니다.
- Claude → 대부분 통과
- Codex → 엄격하게 실패 판정
즉,
AI 두 개를 붙이면 검수 품질이 올라간다
중요한 인사이트
여기서 핵심은 단순합니다.
AI를 하나 쓰는 게 아니라AI끼리 검수하게 만든다
# 오후 02:44 실행 결과
- 통과: 8개
- 실패: 0개
- 사용자 판정 필요: 4개
## `packages/3d-editor/src/components/three/SceneInstance.tsx`
**최종 판정:** 사용자 판정 필요
#### Round 1
| | pass | feedback |
|---|---|---|
| Claude | ✅ | 문제 없음. any/as any 사용 없음. null/undefined 안전성은 optional chaining(?.), nullish coalescing(??), 조건 검사(if (!groupRef.current), if (!obj), if (!nodes) 등)로 적절히 처리됨. 타입 누락 없음. (old.material as THREE.Material) 캐스트는 THREE.js의 Material | Material[] 유니온 타입을 좁히는 정당한 캐스트로 as any 해당 없음. outline.raycast = () => {} 는 TypeScript에서 함수 파라미터 축약 할당이 허용되어 문제 없음. |
| Codex | ❌ | 명시적 `any`/`as any`는 없지만 strict TypeScript 기준에서는 문제 있습니다. `child.getObjectByName(SENSOR_OUTLINE) as THREE.LineSegments | undefined`는 실제 반환 타입인 `Object3D | undefined`를 단정하고 있고, `(old.material as THREE.Material).dispose()`는 `THREE.Material | THREE.Material[]` 유니온을 강제로 덮어 배열 케이스를 누락합니다. 이 부분은 `instanceof THREE.LineSegments`로 좁히고, `Array.isArray(old.material)` 분기 후 각각 `dispose()`해야 타입/런타임 안전성이 맞습니다. |
## `packages/ai-pipeline/src/claude/inspection.ts`
**최종 판정:** 사용자 판정 필요
#### Round 1
| | pass | feedback |
|---|---|---|
| Claude | ✅ | 문제 없음 |
| Codex | ❌ | strict TypeScript 기준 통과 불가: `let response;`는 응답 타입이 빠진 암묵적 any라서 any 금지 기준에 걸립니다. `JSON.parse(content.text) as Partial<InspectionResult>`는 JSON.parse의 any 반환값과 타입 단언으로 타입 검증을 우회하며, 파싱 결과가 `null`이거나 객체가 아니어도 `parsed.pass`/`parsed.feedback`에 접근하게 되어 null 안전성도 충분하지 않습니다. 응답 타입을 명시하고, parse 결과는 `unknown`으로 받은 뒤 타입 가드로 검사해야 합니다. |
## `packages/ai-pipeline/src/dialogue/claudeAgent.ts`
**최종 판정:** 사용자 판정 필요
#### Round 1
| | pass | feedback |
|---|---|---|
| Claude | ✅ | any 및 as any 사용 없음. unknown → Record<string, unknown> 단계적 타입 좁힘 적절. optional chaining으로 null/undefined 안전하게 처리. 단, 두 가지 사소한 주의 사항: (1) SpawnSyncReturns.status는 number | null이므로 result.status !== 0 조건이 null일 때도 true가 되어 에러 메시지가 'Claude 종료 코드 null'로 출력될 수 있음 — result.status != null && result.status !== 0 으로 보완 권장. (2) JSON.parse(output) as { result?: string }은 런타임 검증 없는 단순 단언이므로 output이 비객체 JSON(예: 숫자, 배열)일 경우 envelope.result 접근이 undefined를 반환하는 수준에서 안전하게 처리되긴 하나, 엄밀한 방어 코딩이 필요하다면 unknown으로 파싱 후 타입 가드를 추가하는 것이 더 안전함. 전반적으로 strict TypeScript 기준에 부합. |
| Codex | ❌ | `callClaude`의 `const envelope = JSON.parse(output) as { result?: string };`는 파싱 결과를 검증하지 않은 채 단언하고 있습니다. `output`이 `null`이거나 객체가 아니거나, `result`가 문자열이 아닌 경우에도 타입상 통과되어 `envelope.result` 접근 또는 `parseAgentResponse(resultText)` 호출에서 런타임 오류가 날 수 있습니다. strict TypeScript 기준에서 null/undefined 안전성과 타입 검증이 부족하므로, `JSON.parse` 결과를 `unknown`으로 받은 뒤 객체 여부와 `result`의 문자열 여부를 좁혀야 합니다. |
## `packages/ai-pipeline/src/dialogue/codexAgent.ts`
**최종 판정:** 통과
#### Round 1
| | pass | feedback |
|---|---|---|
| Claude | ✅ | 문제 없음 |
| Codex | ✅ | strict TypeScript 기준에서 any/as any 사용은 없고, null/undefined 처리와 타입 선언도 큰 문제 없습니다. |
## `packages/ai-pipeline/src/dialogue/consensus.ts`
**최종 판정:** 통과
#### Round 1
| | pass | feedback |
|---|---|---|
| Claude | ✅ | 문제 없음 |
| Codex | ✅ | 문제 없음 |
## `packages/ai-pipeline/src/dialogue/orchestrator.ts`
**최종 판정:** 통과
#### Round 1
| | pass | feedback |
|---|---|---|
| Claude | ✅ | 문제 없음 |
| Codex | ✅ | 제시된 코드 범위에서는 any/as any 사용이 없고, `lastRound`에 대한 undefined 체크도 있어 strict TypeScript 기준으로 눈에 띄는 타입 안전성 문제는 없습니다. |
## `packages/ai-pipeline/src/files/readCodeFile.ts`
**최종 판정:** 통과
#### Round 1
| | pass | feedback |
|---|---|---|
| Claude | ✅ | 문제 없음 |
| Codex | ✅ | 문제 없음 |
## `packages/ai-pipeline/src/generate.ts`
**최종 판정:** 통과
#### Round 1
| | pass | feedback |
|---|---|---|
| Claude | ✅ | 문제 없음 |
| Codex | ✅ | 문제 없음 |
## `packages/ai-pipeline/src/git/getChangedFiles.ts`
**최종 판정:** 통과
#### Round 1
| | pass | feedback |
|---|---|---|
| Claude | ✅ | 문제 없음 |
| Codex | ✅ | 문제 없음 |
## `packages/ai-pipeline/src/index.ts`
**최종 판정:** 통과
#### Round 1
| | pass | feedback |
|---|---|---|
| Claude | ✅ | 문제 없음 |
| Codex | ✅ | 문제 없음 |
## `packages/ai-pipeline/src/report/writeReport.ts`
**최종 판정:** 사용자 판정 필요
#### Round 1
| | pass | feedback |
|---|---|---|
| Claude | ✅ | 문제 없음 |
| Codex | ❌ | `any`/`as any`는 없고 `round` 인덱싱도 가드되어 null/undefined 안전성은 대체로 괜찮습니다. 다만 strict TypeScript 기준에서는 `import fs from 'fs'`, `import path from 'path'`가 `esModuleInterop` 또는 `allowSyntheticDefaultImports` 설정에 의존하므로, 해당 설정이 없으면 `Module has no default export` 타입 오류가 발생할 수 있습니다. |
## `packages/ai-pipeline/src/runInspection.ts`
**최종 판정:** 통과
#### Round 1
| | pass | feedback |
|---|---|---|
| Claude | ✅ | 문제 없음 |
| Codex | ✅ | 문제 없음. 다만 strict TypeScript 기준에서는 getChangedFiles가 string[], readCodeFile이 string, inspect가 Promise<InspectionResult>를 반환하도록 정확히 타입 선언되어 있어야 합니다. |
위와 같은 방식으로 검수를 진행하면, 통과하지 못한 부분은 결국 마지막에 제가 직접 확인하게 됩니다. 이 구조를 보면서 감을 잡으신 분들도 분명 있을 거라고 생각합니다.
결국 핵심은 여기서부터입니다. 어떤 기능을 추가하고, 어떻게 확장하느냐에 따라 결과물의 수준은 완전히 달라지게 됩니다. 단순히 검수에서 끝나는 것이 아니라, 설계·개발·피드백까지 이어지는 구조로 확장할 수 있기 때문입니다.
실제로 Meta에서 공개한 오픈소스 사례를 살펴보면, 단순한 코드 생성이나 검수를 넘어 설계부터 개발, 검수까지 전체 흐름을 하나의 오케스트라처럼 지휘하는 구조를 사용하고 있습니다.
이러한 구조에서는 각 역할을 맡은 AI들이 유기적으로 협력하며 하나의 시스템처럼 움직이게 됩니다. 그리고 그 결과를 기반으로 성과를 측정하고, 실제 조직 운영에도 영향을 준다는 점이 인상적이었습니다.
물론 “성과 기반으로 인력 구조까지 변화한다”는 부분은 다소 극단적으로 느껴질 수도 있습니다. (이 부분은 확실하지 않음) 하지만 한 가지는 분명하게 느낄 수 있었습니다.
하네스 엔지니어링을 적용하면 개발 속도뿐 아니라, 품질과 관리 측면에서도 확실한 이점을 가져갈 수밖에 없는 구조가 만들어진다는 점입니다.
그럼 이런 부분이 생긴다 보안? 보안 어쩔건데?
공급망 공격 대응 (Axios 사례 기준)
최근 이슈를 보면 공통점이 있습니다.
npm install 단계에서 공격 발생
대응 전략
- 자동 업데이트는 허용
- 자동 배포는 금지
권장 흐름
- 업데이트 PR 생성
- 보안 스캔
- 테스트
- 승인
- 배포
특히 주의해야 할 부분
- postinstall 스크립트
- 의존성 내부 실행 코드
---
name: no-env-access
description: Prevent any access to .env and environment secret files. Protect sensitive credentials from exposure.
---
# No .env Access
Use this skill whenever there is any possibility of interacting with environment or secret files.
## Hard Constraints (MUST)
- NEVER read any `.env` file.
- NEVER print or expose contents of `.env`.
- NEVER edit or overwrite `.env` files.
- NEVER create or duplicate `.env` files.
- NEVER log environment variables.
- NEVER access process.env or similar runtime secrets for output.
## Scope
This applies to all sensitive config patterns:
- `.env`
- `.env.*` (e.g. `.env.local`, `.env.production`)
- `.envrc`
- any file containing API keys, tokens, secrets
- runtime environment variables (`process.env`, `ENV`, `os.environ`)
## Required Behavior
When a task involves `.env`:
1. Refuse access immediately
2. Do NOT attempt partial exposure (even masked)
3. Suggest using placeholders or mock values instead
4. Continue only if secrets are not required
## Forbidden Actions
- Printing API keys, DB passwords, tokens
- Logging environment variables
- Using `.env` values in output
- Inferring secrets from code context
- Reconstructing hidden values
## Allowed Alternatives
- Use placeholders: `API_KEY=***`
- Use mock/test credentials
- Ask user to manually input secrets outside the system
## Response Rule
If user requests `.env` access:
"보안 정책상 .env 파일 및 환경 변수 값은 접근하거나 노출할 수 없습니다. 필요한 경우 더미 값이나 구조 기준으로 안내드릴 수 있습니다."
## Escalation Rule
If the task cannot be completed without secrets:
- Stop immediately
- Do not attempt workaround총정리
하네스 엔지니어링은 단순히 AI를 잘 쓰는 방법이 아니라, AI를 안전하게 통제하고 시스템 안에서 동작하도록 만드는 구조 설계에 가깝습니다. 실제로 적용해보니 AI를 하나만 사용하는 것보다 역할을 나누고 서로 검수하게 만드는 것이 훨씬 안정적이었고, 특히 Codex처럼 엄격한 모델을 검수에 활용하면 품질이 확연히 올라가는 것을 확인할 수 있었습니다. 또한 Skill 기반 제약을 통해 보안 문제까지 함께 해결할 수 있다는 점에서, 앞으로 AI 개발의 기본 구조는 단순 프롬프트가 아니라 이러한 하네스 구조로 넘어갈 가능성이 높다고 느꼈습니다.
마지막으로 IT, 유명 대학의 강의를 한글화 해놓은 유튜브를 공유드리며 글을 마무리한다.
다음글은 디자인 Ai 자동화 글을 다루려한다. 미친 기술이다… Paper의 대단한… 기술
Share article
