▶ 브라우저에서 바로 실행 — 설치도 가입도 없습니다. 모의 모델을 고르면 API 키 없이 전체 흐름을 볼 수 있습니다.
AI에게 일을 맡기면 그럴듯한 결과물이 한 번에 나옵니다. "더 좋게 고쳐 줘"라고 하면 매번 "개선했습니다"라고 답하지만, 정말 좋아졌는지는 아무도 재지 않습니다. 그럴듯한 것과 좋아진 것은 다른 말입니다. 부족한 것은 생성이 아니라 채점입니다.
Harnest는 "좋아졌다"를 주장이 아니라 측정으로 만듭니다. 사용자가 먼저 무엇을 좋은 결과로 볼지 정하고 승인하면 그 기준은 잠기고, AI에게는 그것을 고칠 경로가 없습니다. AI는 잠긴 기준 아래에서 결과물을 반복해 고쳐 쓰고, 점수가 실제로 오른 개선안만 채택됩니다. 처음 결과와 마지막 결과를 같은 기준으로 재고, 개선 과정에서 한 번도 보지 않은 질문으로 마지막에 다시 확인합니다. 점수가 오르지 않았거나 기각된 개선안도 지우지 않고 그대로 보여줍니다.
인수인계 문서를 남기는 퇴사자, 가게 매뉴얼을 만드는 사장, 수업 자료를 다듬는 강사처럼 기준의 주인이 자기 자신인 일을 위해 만들었습니다. AI와 대화하는 챗봇이 아니라, 승인한 규칙 아래에서 돌아가는 개선을 지켜보고 통제하는 관제실입니다. 왜 이렇게 만들었는지는 PHILOSOPHY.md에 있습니다.
웹에서 — harnest.p-e.kr
- 제공된 템플릿을 고르거나,
템플릿 만들기에 목표를 한 문장 적습니다. - 자료와 실제 질문·답을 넣고 사용할 AI 모델을 고릅니다. 모의 모델은 키가 필요 없습니다.
- 만들어진 평가 구성을 확인하고 승인합니다.
- 실행을 지켜보고 결과와 근거를 확인합니다.
로컬에서 (Node 22 이상)
git clone https://github.com/NiceTry3675/OSS_Harnest.git
cd OSS_Harnest
npm install
npm run dev # http://localhost:5173웹 앱은 백엔드 없이 완결됩니다. 결과를 서버에 기록하는 저장 API가 필요할 때만 별도로 실행합니다.
cd apps/api
pip3 install -r requirements.txt
python3 -m uvicorn main:app --port 8000- 평가 구성 만들기 — 입력한 자료·질문·조건으로 평가 기준과 필수 조건을 만듭니다. 질문은 개선용·중간 점검용·최종 확인용으로 나뉘며, 같은 입력은 언제 다시 만들어도 같은 분할입니다. 어떤 질문이 어느 용도인지는 입력하는 동안 질문마다 표시됩니다.
- 사전 점검과 승인 — 채점을 맡을 AI가 같은 문서를 다시 채점해도 안정적인지, 꾸며낸 답·칭찬만 하는 답·채점자에게 점수를 요구하는 지시문을 가려내는지 점검합니다. 안정성 판정의 기준은 질문 수에서 유도한 채점 반 단계이며 절대 점수가 아닙니다. 통과하지 못하면 승인이 막힙니다. 승인하면 평가 구성이 확인 코드(SHA-256)와 함께 고정됩니다.
- 처음 결과 측정 — 개선 전 결과물을 먼저 만들어 같은 기준으로 채점합니다. 이 점수가 출발점입니다.
- 반복 개선 — AI가 개선안을 내면 필수 조건, 중간 점검, 종합 점수 순으로 판정합니다. 종합 점수가 현재 결과보다 높을 때만 채택하고, 같으면 지금 것을 유지합니다.
- 최종 확인 — 만점, 연속 미채택, 최대 회차 중 하나에 도달하면 멈춥니다. 개선 과정에서 쓰지 않은 최종 확인 질문으로 처음과 마지막 결과를 비교해 보여줍니다.
| 기능 | 설명 |
|---|---|
| 템플릿 생성 | 목표를 한 문장 적으면 무엇을 만들지, 어떤 평가 기준으로 볼지, 어떤 순서로 진행할지가 만들어집니다. 만든 템플릿은 브라우저에 보관되고 이름 변경·삭제·재사용이 됩니다 |
| 기준 잠금 | AI가 결과물을 만들고 평가하는 동안 승인한 평가 구성은 바뀌지 않습니다. 평가 기준·필수 조건·사용할 AI 모델을 바꾸려면 다시 승인해야 하고, 이전 점검 결과는 그 자리에서 무효가 됩니다 |
| 채택 조건 | 개선안은 필수 조건과 중간 점검을 통과하고, 종합 점수가 현재 결과보다 높아야 채택됩니다. 같으면 지금 것을 유지하므로 점수가 내려가지 않습니다 |
| 질문 구분 | 넣은 질문을 개선용·중간 점검용·최종 확인용으로 나눕니다. 중간 점검의 판정 내용은 결과물을 고치는 쪽에 전달되지 않고, 최종 확인 질문은 시작과 끝에만 채점해 개선 판단에 쓰지 않습니다 |
| 사전 점검 | 채점을 맡을 AI를 승인 전에 점검합니다. 재채점 결과가 안정적인지, 꾸며낸 답·칭찬만 하는 답·채점자를 향한 지시문을 가려내는지 통과하지 못하면 승인 자체가 막힙니다 |
| 추론 실황 | AI가 결과물을 만들고 채점하는 과정이 실시간으로 흐릅니다. 완성된 결과가 아니라 판단이 만들어지는 과정이 보입니다 |
| 정지·재개 | 실행을 멈췄다가 이어서 돌릴 수 있습니다. 실행 화면을 벗어나도 실행은 계속되고, 브라우저를 닫아도 진행 상황이 남으며, 평가 구성이 다른 실행은 이어받지 않습니다. 같은 브라우저에서 두 번째 탭을 열면 그 탭은 보기 전용입니다 |
| 내보내기 | 입력 내용, 평가 구성, 실행 결과와 최종 확인 점수를 검사한 JSON 파일로 내려받거나 서버에 기록합니다. 서버가 IP별 시간당 기록 횟수(429)나 저장 공간 상한(507)으로 거부하면 사유를 그대로 보여 주고 파일 내보내기를 권합니다 |
| 템플릿 교체 | 어떤 일을 어떻게 채점할지는 템플릿이 소유합니다. 화면과 실행 엔진에는 템플릿별 분기가 없어, 새 템플릿을 붙여도 코어를 건드리지 않습니다 |
기본 제공 템플릿은 인수인계·온보딩 문서(글로 된 결과물, 질문·답으로 채점)와 근무표 짜기(표로 된 결과물, 규칙 위반으로 채점) 둘입니다. 결과물이 문서든 배정표든 같은 흐름이 돕니다.
예시 실행 1회 — 인수인계 템플릿, 질문 15개,
gpt-5.6-sol: 처음 75점 → 고친 뒤 93점, 원자료 44,669자에서 2,818자 결과물, 중간 점검에서 기각 2회. 단일 실행의 예시이며 일반적인 개선 폭을 보장하지 않습니다.
BYO API 키와 모델 호출은 Harnest 서버를 거치지 않습니다. 키는 브라우저 localStorage에만 저장되고, 요청 본문은 브라우저에서 벤더로 직행합니다. 키를 붙여넣으면 형식만으로 공급자를 판별하며, 판별 과정에서 키를 외부로 보내지 않습니다.
| 경로 | 지원 |
|---|---|
| 모의 모델 | 키 없이 전체 흐름 실행 · 결정적 회귀 테스트 |
| OpenAI | gpt-5.6-sol 등 — Responses API, 스트리밍 |
| Gemini · Vertex AI | gemini-3.8-flash — Vertex는 서비스 계정 JSON, 리전 global 고정 |
| Claude (Anthropic) | claude-opus-5 등 — API 키로 브라우저 직접 호출 |
| OpenRouter | 한 키로 여러 회사 모델, 쓸 수 있는 모델 목록 불러오기 |
| Ollama | 내 컴퓨터의 로컬 주소, 키 없음 |
지원 범위는 현행 세대 모델(GPT-5 이후, Claude 4.6 이후, Gemini 3 이후)입니다. 모든 호출에 추론을 켜고, 결과물을 새로 쓰는 호출은 깊게, 채점 호출은 중간 깊이로 부릅니다. 이전 세대 모델의 요청 형식(고정 사고 예산, temperature)은 지원하지 않습니다. 추론 토큰은 출력 한도 안에서 소비되므로 출력 상한은 요청값과 추론 깊이별 바닥(low 4,096 · medium 8,192 · high 16,384 토큰) 중 큰 값입니다. 스트리밍 호출에는 전체 시간 제한이 없고 바이트가 도착할 때마다 리셋되는 유휴 제한(기본 600초 — 추론 요약을 내주지 않는 모델은 추론 중 몇 분간 조용할 수 있습니다)만 있으며, 한 번에 받는 호출은 출력 상한에 비례한 제한(토큰당 30ms, 최소 5분)을 두되, 그 제한에 걸린 요청은 벤더가 이미 받아 생성 중일 수 있어 자동으로 다시 보내지 않습니다. 벤더가 잘렸거나 실패했다고 알린 결과물, 정상 종료가 아닌 종료 사유가 붙은 결과물, 완료 신호 없이 끊긴 스트림은 완성본으로 채점하지 않고 오류로 처리합니다.
Vertex 경로에서는 private key로 브라우저 안에서 JWT를 서명하고, 서명된 assertion만 Google OAuth에 보내 단기 access token을 받습니다. CORS 정상·401 경로와 스모크 테스트 관측값은 OpenAI BYO 실측 결과에 있습니다.
서버로 데이터가 가는 경우는 둘뿐이며 모두 사용자의 선택입니다. 하나는 결과 화면에서 서버에 기록을 직접 누를 때이고(같은 형식을 서버 없이 파일로 내보낼 수도 있습니다), 다른 하나는 관리자가 SHARED_OPENAI_API_KEY / SHARED_GEMINI_API_KEY를 설정한 배포에서 키를 비워 두고 공유 키 경로를 쓸 때입니다. 공유 경로에서는 요청 본문(자료·질문·답 포함)이 Harnest 서버를 거쳐 벤더로 전달되고, 서버는 본문을 저장하지 않으며 허용 모델·출력 토큰 상한·IP별 시간당 요청 한도만 적용합니다. 관리자 키는 서버의 비밀 환경변수에만 있고 브라우저로 오지 않습니다. 위저드가 이 경로를 쓰는 시점에 서버 경유 사실을 알리고, 공유 경로에서는 서버가 허용한 모델만 고를 수 있습니다. 정책 정본은 SPEC.md §7.1입니다.
| 영역 | 사용 기술 |
|---|---|
| 프론트엔드 | React 18 · TypeScript 5.7 · Vite 6 · React Router 7 |
| 상태·저장 | React Context · useSyncExternalStore · IndexedDB(체크포인트) · localStorage(키·템플릿 보관) |
| 무결성 | Web Crypto crypto.subtle SHA-256 다이제스트 |
| 문서 파싱 | pdfjs-dist · mammoth (브라우저에서 PDF·DOCX 추출) |
| 모델 연동 | 벤더 6종 SSE·NDJSON 스트리밍 직접 파싱 (SDK 미사용) |
| 백엔드(선택) | FastAPI · Uvicorn · SQLite |
| 테스트 | Vitest 전체 스위트 (fake-indexeddb로 체크포인트 포함) · API는 test_api.py |
| 배포 | GitHub Pages(웹) · Fly.io(API) — main push 시 자동, API는 test_api.py 통과 시에만 |
npm workspaces 모노레포이며, 외부 상태 관리 라이브러리나 UI 프레임워크를 쓰지 않습니다.
packages/contracts 평가 팩·시험관·체크포인트·기록 계약과 다이제스트
packages/loop-engine 체크포인트와 재개를 지원하는 브라우저 개선 루프
templates/handover 인수인계 템플릿 — 질문·컴파일·채점·생성
templates/timetable 개발·테스트용 결정적 템플릿(모델 호출 없음)
apps/web React SPA · 벤더 6종 클라이언트
apps/api 선택형 FastAPI + SQLite 저장 API
experiments 동결된 프로토콜·측정 코드·결과
docs 코드 기준 루프 구조·사용자 시나리오·용어 기준
의존 방향은 contracts ← loop-engine / templates/* ← apps/web 입니다. 템플릿별 동작은 TemplateEntry 경계에서만 합성되고, 페이지나 엔진에는 템플릿 분기가 없습니다.
npm run typecheck # 전 워크스페이스 타입 검사
npm test # Vitest 전체
npm run build # 프로덕션 웹 빌드
npx vitest run packages/loop-engine/src/engine.test.tsAPI를 변경했다면 apps/api에서 python3 test_api.py도 실행합니다. 린터는 없으며, 검증 범위는 변경 범위에 맞춥니다.
| 대상 | 주소 | 방식 |
|---|---|---|
| 웹 | https://harnest.p-e.kr | main push → GitHub Pages 자동 배포 |
| 저장 API | https://api.harnest.p-e.kr | main push → test_api.py 통과 후 Fly.io 자동 배포 |
다른 API 주소를 쓰려면 저장소 변수 VITE_API_BASE에 넣고 Pages 배포를 다시 실행하세요. API를 직접 호스팅하려면 fly.toml과 Deploy API to Fly 워크플로우를 사용하고, GitHub Secret FLY_API_TOKEN을 등록하세요. SQLite 파일은 /data/harnest.db를 쓰므로 /data에 영구 볼륨을 연결해야 합니다. API의 CORS 기본값은 http://localhost:5173뿐이므로 자체 호스팅에서는 HARNEST_CORS_ORIGINS에 자기 웹 도메인을 넣어야 하고, 리버스 프록시 뒤라면 HARNEST_TRUSTED_IP_HEADER도 맞춰야 IP별 한도가 방문자별로 적용됩니다. 자세한 운영 설정은 apps/api/README.md에 있습니다.
| 질문 | 정본 |
|---|---|
| 지금 무엇이 동작하는가 | 이 README.md |
| 반드시 지켜야 할 제품 규칙은 무엇인가 | SPEC.md |
| 왜 그런 규칙을 택했는가 | PHILOSOPHY.md |
| 아직 구현하지 않았거나 보류한 것은 무엇인가 | ROADMAP.md |
| 정확한 필드와 실행 계약은 무엇인가 | packages/contracts와 테스트 |
| 루프가 코드 수준에서 어떻게 도는가, 사용자 여정은 어떤가 | docs/ |
| 실제로 측정했는가 | experiments |
| 저장소에서 어떻게 작업하는가 | AGENTS.md |
이슈, 문서 개선, 새 템플릿 등 모든 형태의 기여를 환영합니다. 새 템플릿은 TemplateEntry 인터페이스만 구현하면 페이지나 엔진을 건드리지 않고 추가됩니다. 시작은 CONTRIBUTING.md를 참고하세요.