[에이전트] 3. 피벗 : Java로 시작해 이틀 만에 TypeScript+Bun으로 갈아엎다

폐쇄망 사내 에이전트 3편 - 피벗

작성자

카테고리:

* 이 게시물은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

(폐쇄망 사내 에이전트 ③) 피벗

커밋 로그에서 제일 극적인 지점은 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와의 인프로세스 추론 통합도 결정적이었습니다.

코멘트

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다