* 이 게시물은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.
(폐쇄망 사내 에이전트 ③) 피벗
커밋 로그에서 제일 극적인 지점은 2일차 오후다. 전날까지 Java + LangChain4j로 Tool 31종을 올리고 전수 테스트까지 마쳤는데, 8월 2일 14시 5분에 이 커밋이 박힌다.
commit 9510570 (2026-08-02 14:05)
TypeScript + Bun 전면 재작성으로 교체 (Tool 35종)
810 files changed, 51237 insertions(+), 30853 deletions(-)
810개 파일, +51,237줄. 하루 반 동안 만든 Java 구현을 통째로 들어내고 TypeScript로 다시 쓴 것이다. 미련이 없었다면 거짓말이지만, 이유는 분명했다.
왜 갈아엎었나 — 폐쇄망 배포가 결정했다
Java 구현은 잘 돌았다. 문제는 배포였다. 폐쇄망 사무 PC에 반입하려면 JRE 설치·버전 관리부터가 일이고, 관리자 권한이 제한된 PC에서는 그것조차 벽이다. Bun은 이 문제를 한 방에 정리한다 — bun build --compile로 런타임까지 박힌 단일 실행 파일이 나온다. 복사하면 끝. 게다가 빌드 단계 없이 소스를 그대로 실행하니 개발 사이클도 짧다. 추론 엔진 쪽도 컸다: llama.cpp를 프로세스 안에서 직접 부르는 node-llama-cpp가 있어서(4편), 별도 추론 서버 없이 실행 파일 하나로 끝나는 그림이 그려졌다.
| Java (main 브랜치) | TypeScript + Bun (dev) | |
|---|---|---|
| 배포 | JRE 필요·설치 절차 | 단일 바이너리 복사 |
| 빌드 | Gradle 빌드 | 빌드 단계 없음(소스 실행) |
| 추론 | 외부 llama-server 프로세스 | 인프로세스(node-llama-cpp) |
| 규모 | Tool 31종 | Tool 35종·33패키지 |
재작성이 아니라 이식 — 패리티 규율
이 커밋에서 배울 점은 ‘갈아엎은 용기’보다 갈아엎는 방법이다. 무작정 새로 쓰지 않고, Java 구현을 ‘패리티 기준’으로 남겨두고 규약을 먼저 정했다.
- 사용자 대면 문자열(CLI 출력·승인 화면·오류 메시지)은 Java판과 글자 단위로 맞춘다.
- 설정 파일과 감사 DB(SQLite) 스키마는 서로 호환 — 갈아타도 기록이 이어진다.
- Java 원문이 실제 동작과 어긋난 자리는 사실 쪽에 맞추고, 어디를 왜 깼는지 규약 문서에 기록한다.
- 테스트 1,385개·tsc 오류 0을 옮긴 날 기준선으로 삼는다.
덕분에 이건 ‘두 번째 프로토타입’이 아니라 검증된 동작의 이식이 됐다. 재작성의 최대 리스크인 ‘미묘하게 달라진 동작’을 문자열 단위 패리티로 억눌렀고, 이후 기능 추가는 전부 이 기준선 위에서 이뤄진다. 참고로 원래의 Java 구현은 지금도 main 브랜치에 그대로 있다 — 지우지 않고 기준으로 남긴 것이다.
매몰비용을 넘어서
갈아탄 뒤의 뼈대 — 워크스페이스 33패키지
재작성 커밋이 세운 구조를 보면 ‘왜 모노레포인가’가 보인다. 경계(2편)가 디렉터리로 물리화돼 있다.
apps/ cli · server · desktop ← 조립하는 곳
packages/ agent · policy · approval · audit · tool-runtime …
← 도메인. 어느 Provider도 모른다
providers/ node-llama-cpp · openai-compatible · ollama
← ChatModel 구현들. 서로를 모른다
tools/ filesystem · shell · git · excel · document …
← 실행되는 것들. 전부 승인·감사를 지나서만
infrastructure/ process · platform · sqlite · security
포인트는 의존 방향이다. packages는 providers를 모르고, tools는 apps를 모른다. 그래서 “Java에서 TS로”라는 큰 수술이 사실상 같은 설계도의 재시공이 될 수 있었다 — 설계도(패키지 경계)가 언어보다 오래 산다.
패리티를 지키는 장치 — 규약 문서와 ‘의도적으로 깬 곳’
이식에서 제일 무서운 건 ‘모르고 달라진 동작’이다. 그래서 규약 문서(CONVENTIONS)에 원칙 1: 패리티가 기본, 깰 때는 기록을 박았다. Java 원문이 실제 동작과 어긋났던 자리 — 시스템 프롬프트 일부, 몇몇 tool description — 는 사실 쪽에 맞추되, 어디를 왜 깼는지 목록으로 남겼다. 나중에 “이거 Java랑 다른데?”가 나오면 그 목록부터 본다. 답이 있으면 의도, 없으면 버그다. 이 한 장이 이식 검증 비용을 크게 줄였다.
빌드도 확인해두자. 배포는 이 한 줄로 5개 타깃이 나온다 — 폐쇄망 반입(10편)의 출발점이다.
bun run distribution/build.ts --target all
# → windows-x64 · linux-x64 · linux-arm64 · macos-x64 · macos-arm64
# 각 타깃: air(CLI) + air-server 단일 바이너리 2종
적용해 보기 — 피벗 판단 체크리스트
- 지금 막힌 것이 구조적 제약(배포·플랫폼·성능 상한)인가, 익숙함의 문제인가.
- 지금까지의 산출물 중 설계 자산(도메인 모델·테스트 시나리오·API 계약)의 비율은 얼마나 되나 — 높을수록 이식이 싸다.
- 패리티 기준(문자열·데이터 호환·테스트 수)을 숫자로 정했나.
- 옛 구현을 지우지 않고 기준으로 남길 방법(브랜치·태그)이 있나.
하루 반짜리 결과물을 버리는 결정은 쉽지 않다. 도움이 된 건 두 가지였다. 하나, Java 작업의 대부분은 코드가 아니라 설계 자산(Tool 목록·정책 모델·승인 흐름·테스트 시나리오)이었고 그건 전부 살아서 넘어갔다. 둘, 프로젝트 초반일수록 갈아타는 비용이 싸다 — 사흘째였다면 고민이 훨씬 길었을 것이다. 자바 시리즈를 쓴 사람으로서 Java가 아까웠지만, 이 물건의 승부처는 언어가 아니라 배포와 추론 통합이었다. 도구는 문제에 맞춰 고르는 것이지, 정든 순서로 고르는 게 아니다.
이틀째의 전면 재작성이 가능했던 진짜 이유는 2편의 분리 설계다. 모델·프레임워크가 얇은 경계 뒤에 있었기에, 언어를 바꿔도 ‘무엇을 만들지’는 그대로였다.
자주 묻는 질문 (FAQ)
만들던 걸 갈아엎는 기준이 있나요?
바꿔서 얻는 것이 구조적(배포 방식·플랫폼 제약 해소)이고, 지금까지의 산출물 대부분이 설계 자산으로 이전 가능하며, 프로젝트가 아직 초반이라면 갈아타는 쪽이 쌉니다. 반대로 얻는 게 취향 수준이면 참는 게 맞습니다.
Bun을 고른 이유가 뭔가요?
폐쇄망 배포 때문입니다. bun build –compile이 런타임 포함 단일 실행 파일을 만들어 JRE·Node 설치 없이 복사만으로 배포됩니다. 빌드 단계 없이 소스를 직접 실행하는 개발 속도, node-llama-cpp와의 인프로세스 추론 통합도 결정적이었습니다.

답글 남기기