
한 잔의 커피와 네 명의 에이전트: Warp로 커피 로스팅하기
요약
Warp의 에이전트형 개발 환경을 활용하여 MCP 서버와 AI 에이전트들을 통해 커피 로스팅 과정을 자동화한 실험 사례를 소개합니다. 다양한 모델과 하네스를 결합하여 데이터 파이프라인 구축부터 실시간 오디오 분석을 통한 로스팅 마일스톤 감지까지 구현했습니다.
핵심 포인트
- Warp를 활용한 에이전트형 개발 환경의 실전 활용 사례
- MCP 서버를 통한 하드웨어(로스터)와 AI 에이전트의 연결
- 다양한 모델(Gemini, Claude Code 등)과 하네스를 혼합 사용한 워크플로우
- Audio Spectrogram Transformer를 이용한 실시간 로스팅 이벤트 감지
우리는 이제 Warp가 무엇을 잘하는지 알고 있습니다. Warp는 '에이전트형 개발 환경 (Agentic Development Environment)'으로 요약될 수 있습니다. 즉, 작업이 이미 이루어지고 있는 터미널 내에서 계획을 세우고, 코드를 작성하며, 명령어를 실행하고, 도구를 사용하는 AI 에이전트입니다. Warp는 여러분이 어떤 모델이든, 어떤 하네스(harness)든 원하는 방식대로 사용할 수 있게 해주면서 기능을 출시하고, CI를 수정하며, 인프라를 관리합니다. 하지만 아무도 묻지 않았던 질문이 하나 있습니다. 과연 Warp가 커피를 로스팅할 수 있을까요?
적절한 MCP 서버를 제공한다면 가능합니다. 이 포스트는 그 서버와 이를 구축한 에이전트들, 그리고 Warp가 실제 드럼 로스터를 통해 두 번의 로스팅을 수행했던 오후에 관한 이야기입니다.
일요일, 주방 카운터. Hottop 드럼 로스터, 작은 스탠드에 놓여 로스터를 향하고 있는 USB 마이크, 그리고 MCP 서버를 통해 로스터와 연결되어 Warp를 실행 중인 노트북이 있었습니다. 지난 8분 동안 저는 원두 프로브(bean probe) 온도가 180 °C를 넘어 올라가는 동안
Part 3에서 훈련시킨 Audio Spectrogram Transformer는 25파운드짜리 USB 마이크를 대상으로 양자화된 (quantized) INT8 ONNX 모델로 실행되며, 1차 크랙 (first crack)을 감지했습니다. 18초 동안 5개의 양성 탐지 윈도우 (positive detection windows)를 누적하여 해당 이벤트를 로스팅 타임라인 (roast timeline)에 기록했습니다. 로스팅 전체 과정에서 수동 개입은 없었습니다. 로스팅 프로파일 (roast-profile) 결정(열, 팬, 배출 시점)은 제가 내렸지만, 런타임 (runtime)이 모든 마일스톤 (milestone)을 탐지했습니다.
이것은 그날의 두 번째 로스팅이었습니다. 그보다 앞선 오후에 더 엄격한 탐지 프로파일 (detector profile)로 진행했던 첫 번째 로스팅 역시 오디오를 통해 1차 크랙을 포착했습니다.
누가 이것을 만들었는지에 대해 정확히 말씀드리고 싶습니다. 왜냐하면 단 하나의 AI 에이전트가 만든 것이 아니기 때문입니다. 네 가지 서로 다른 역할을 가진 네 명의 에이전트가 시스템을 프로토타입 (prototype)에서 검증된 프로덕션 로스팅 (production roast) 단계로 끌어올렸습니다:
| 에이전트 | 역할 | 결과물 |
|---|---|---|
| Warp (Oz) | ML 구축: 데이터셋 파이프라인 (dataset pipeline), 훈련 (training), 튜닝 (tuning), ONNX 내보내기 (export), Hugging Face 게시 | coffee-first-crack-detection: 18개의 스토리, 10개의 PR, 약 11,000행 |
| ... |
해당 표에 대해 오해의 소지가 있을 수 있어 명확히 설명하자면, Warp가 두 번 등장하는데 이는 다른 에이전트들이 제가 갈아탄 경쟁 모델이 아니라는 뜻입니다. Oz와 Gemini 3.5 Flash는 서로 다른 모델을 실행하는 Warp 자체의 에이전트입니다. Codex와 Claude Code는 독립적인 하네스 (harness)이며, 저 역시 이들을 Warp 세션 (session) 내에서 실행했습니다. 구축부터 로스팅에 이르는 프로젝트 전체가 하나의 환경에서 이루어졌으며, 작업별로 모델과 하네스를 선택했습니다. 이것이 실제로 구현된 "어떤 모델이든, 어떤 하네스든 (any model, any harness)"의 모습입니다.
Claude Code는 로스팅을 하는 내내 두 번째 터미널에서 제 옆을 지켰습니다. Claude Code는 로스터에 직접 손을 대지는 않았습니다. 대신 로스터 주변의 모든 것을 준비하고 검증했으며, 제가 Warp에 전달한 프롬프트(prompts)를 저에게 제공했고, 나중에는 그날의 결과물을 커밋된 증거(committed evidence)로 변환하고 열려 있던 에픽(epic)을 종료했습니다. 루프(loop) 안에 있었던 유일한 인간은 저뿐이었습니다. 개발 과정에서 저는 엔지니어링 리드(engineering lead)로 일했습니다. 로스팅 과정에서는 제가 인간 하네스(human harness)로서 일했습니다.
이 포스트는 그 자체로 독립적인 글이기도 하지만, 더 긴 시리즈의 결론이기도 합니다. 이 글은 제가 아직 쓰지 않았던 부분들, 즉 MCP 서버 재구축, 왜 하나의 서버가 두 개의 서버를 대체했는지, 그리고 에이전트(agent)를 통해 로스팅을 진행하는 것이 실제로 어떤 모습인지에 대해 다룹니다.
디텍터(detector) 자체는 제가 별도로 기록한 5부작 빌드 과정에서 나왔으며, 그 시작은 Part 1: Architecture였습니다. 그 작업을 통해 모델은 만들어졌지만, 이를 사용할 수 있는 프로덕션(production) 방식은 만들어지지 않았습니다. 지난 11월부터 제 주방에서 이를 사용했던 것은 프로토타입(prototype)이었습니다. 두 개의 MCP 서버, Auth0 레이어, SSE 전송(transport), 그리고 이들을 오케스트레이션(orchestrating)하는 n8n 에이전트로 구성되었습니다. 그것은 작동했고, 지금도 작동합니다. 또한 그 과정에서 자체적인 설계 문제점들도 명확해졌습니다.
왜 하나의 MCP 서버가 두 개를 대체했는가
프로토타입은 익숙한 서비스 경계에 따라 책임을 나누었습니다. 로스터 제어를 위한 하나의 MCP 서버, 1차 크랙(first-crack) 감지를 위한 또 다른 MCP 서버, 그리고 그 위에서 작동하는 오케스트레이터(orchestrator)가 있었습니다.
문제는 로스팅은 하나의 타임라인(timeline)인데, 제가 그 진실(truth)을 여러 프로세스에 나누어 놓았다는 점입니다.
가열(charge) 이벤트는 제어 서버에 있었습니다. 1차 크랙 이벤트는 감지 서버에 있었습니다. 로스팅에서 가장 중요한 수치인 개발 시간(development time, 1차 크랙 이후 경과된 초 단위 시간)을 계산하려면, 오케스트레이션 에이전트가 두 서버 모두에서 상태(state)를 가져오고, 시계(clocks)를 조정하며, 상태를 읽을 때마다 프롬프트 공간(prompt-space)에서 결과를 계산해야 했습니다. 모든 상태 변화에는 동기화 비용(synchronization cost)이 발생했습니다:
- 두 개의 시계. 감지 타임스탬프(detection timestamps)와 제어 타임스탬프(control timestamps)가 서로 다른 프로세스에서 생성되었기 때문에, "T+9:01에 1차 크랙(first crack) 발생"과 같은 정보를 상관관계로 연결하려면 프로세스 간 시간 합의(cross-process time agreement)에 의존해야 했습니다.
- 두 개의 장애 도메인(failure domains). 로스팅 도중에 감지 서버(detection-server)가 재시작되면, 원두는 계속 로스팅되는 동안 타임라인은 미아가 되어버렸습니다.
- 상태 머신(state machine)으로서의 에이전트. LLM은 로스팅 전체가 존재하는 유일한 장소였으나, 이는 권위 있는 상태(authoritative state)를 보관하기에는 부적절한 장소였습니다.
- 목적 없는 인프라. Auth0와 SSE는 네트워크로 연결된 프로세스들이 안전하게 통신하기 위해 존재합니다. 하지만 여기의 모든 것은 로스터 옆에 있는 단 한 대의 기계에서 실행되었습니다.
재구축 버전은 이를 반전시켰습니다. coffee-roaster-mcp는 드라이버 제어, 텔레메트리 샘플링(telemetry sampling), 자동 T0 감지, 1차 크랙 오디오 런타임, 파생 지표(상승률(rate of rise), 디벨롭먼트 타임(development time), DTR)), 그리고 로그 내보내기까지 로스팅 세션 전체를 소유하는 하나의 로컬 stdio MCP 서버입니다. 하나의 프로세스, 하나의 단조 시계(monotonic clock), 하나의 추가 전용(append-only) 이벤트 타임라인을 가집니다. 그 위의 에이전트는 결정을 내릴 뿐이며, 결코 데이터베이스가 되지 않습니다.
이 차이는 실시간 로스팅 로그에서 드러납니다. 1차 크랙이 확인되면, 이벤트를 기록한 동일한 프로세스가 세션 단계를 development로 변경하고, 디벨롭먼트 타임 시계를 시작하며, 자체 오디오 캡처를 종료합니다. 세 가지 부수 효과(side effects)가 발생하지만, 동기화는 필요 없으며, 단 하나의 타임라인 행(row)에 기록됩니다.
Codex가 이를 구현한 방법
해당 ML 저장소(repository)는 Warp/Oz의 프로젝트였습니다. MCP 서버 재구축을 위해 저는 Part 1에서 사용했던 것과 동일한 스펙 기반(spec-driven) 방식을 다른 에이전트인 GPT-5.5 기반의 Codex를 사용하여 실행했습니다.
수치로 나타내면 다음과 같습니다: 8개의 에픽(epics)을 78개의 GitHub 이슈(issues)로 분해하였고, 각 스토리당 하나의 PR(Pull Request)을 전달했습니다. 저장소는 87개의 커밋에 걸쳐 78개의 머지된(merged) PR을 보유하고 있으며, 약 90%의 커버리지(coverage)를 가진 394개의 테스트와 PyPI 및 MCP 레지스트리(Registry)로 배포된 4개의 릴리스(releases)를 기록하고 있습니다. 또한 docs/session-summaries/에는 작업 세션당 하나씩, 실제 작업을 수행한 에이전트가 직접 작성한 65개의 세션 요약(session summaries)이 포함되어 있습니다.
상호작용 규칙(rules of engagement)은 리포지토리 루트의 AGENTS.md 파일에 정의되어 있습니다. 에이전트 중 두 명이 대부분의 작업을 수행했습니다:
- 로스터 하드웨어 제어는 보수적으로 유지할 것. 열(Heat), 팬(Fan), 투입(Drop), 냉각(Cooling) 및 비상 정지(Emergency stop) 동작은 명시적인 테스트나 수동 검증 노트를 필요로 함.
- 기본 로스터 드라이버는
mock임. 기본 1차 크랙(first-crack) 모드는disabled임.
mock-우선 제약 조건이 모든 것을 형성했습니다. 78개의 모든 PR(Pull Request)은 CI(지속적 통합)에서 하드웨어 없이 실행되었습니다. 실제 하드웨어는 의도를 명시적으로 선언해야 하는 CLI를 통해서만 접근할 수 있었습니다:
coffee-roaster-mcp hottop-validate \
--config coffee-roaster-mcp.yaml \
--i-understand-this-controls-hardware \
...
되돌릴 수 없는 단계인 원두 투입(bean drop)과 비상 정지(emergency stop)는 선택적 참여(opt-in) 플래그이며, 명령어를 실행하면 모든 단계를 기록한 JSON 증거 파일(evidence file)이 작성됩니다. 에이전트든 피곤한 사람이든 실수로 240 °C의 드럼을 회전시키는 일은 발생할 수 없습니다.
세션 요약(session summaries)은 변경된 사항 그 이상을 기록합니다. 운영 조건 또한 기록합니다. 다음은 첫 번째 하드웨어 검증 세션의 내용입니다:
컨텍스트 윈도우(Context window):
29% 남음 (186K 사용 / 258K)... 이것은 고컨텍스트(high-context) 하드웨어 검증 사례였습니다. 왜냐하면 E3-S4부터 E3-S8까지의 누적된 Epic 3 드라이버 결정 사항, 이전 Hottop 검토 수정 사항, 그리고 실제 운영자의 관찰 내용에 의존했기 때문입니다.
이러한 문서 65개는 나중에 이 리포지토리를 맡게 될 어떤 에이전트에게도 리포지토리의 이력을 읽기 쉽게 만들어 줍니다. 실제로 여러 에이전트가 그렇게 했습니다.
로스팅 데이: Warp가 제어권을 잡다
마지막 에픽 스토리인 E7-S6는 최종 사용자가 하는 방식 그대로, 즉 MCP 레지스트리(MCP Registry) 경로를 통해 실제 MCP 클라이언트에 배포된 패키지를 설치하고, 실제 마이크를 사용하여 실제 하드웨어에서 전체 감독 로스팅(supervised roast)을 수행하는 과정이 필요했습니다.
클라이언트는 Gemini 3.5 Flash를 실행하는 Warp였습니다. 설치에는 배포된 아티팩트(artifact)가 사용되었습니다:
{
"RoastPilot": {
"command": "uvx",
...
개발용 체크아웃(dev checkout)이나 수정 가능 설치(editable install) 방식은 사용하지 않았습니다. 레지스트리 항목 io.github.syamaner/coffee-roaster-mcp에서 누구나 받을 수 있는 것과 동일한 PyPI 패키지를 사용했습니다.
첫 번째 로스팅 중에 촬영된 장비 사진입니다. 화면에 보이는 Warp 세션은 아래에 인용된 트랜스크립트(transcript)입니다.
저의 역할은 완전히 뒤바뀌었습니다. 개발 중에는 엔지니어링 리드(engineering lead)로서 에이전트들을 지시했습니다. 하지만 로스팅 중에는 제가 '인간 하네스(human harness)' 역할을 수행했는데, 이는 정확한 표현입니다. 에이전트는 타이머나 스스로의 주도성에 따라 결코 행동하지 않았습니다. 모든 상태 읽기(state read)를 포함한 모든 MCP 호출은 제가 요청했기 때문에 발생했습니다. 저는 기계 앞에 서서 Warp에 운영자 프롬프트(operator prompt)를 붙여넣고
우리는 두 번의 로스팅을 진행했습니다.
로스팅 1은 실시간 경로(live path)를 입증했습니다: 수동 차징 마킹(manual charge marking), 기본 디텍터 프로필(default detector profile)을 사용하였으며, 0.9 임계값(threshold) 대비 0.9066의 신뢰도(confidence)로 +09:01에 퍼스트 크랙(first crack) 오디오가 감지되었습니다. 198 °C, 15.0% DTR에서 배출되었습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기