agentproto 0.6 + 0.7 — 트래픽을 읽을 수 없는 브로커를 통해 두 머신을 페어링하기
요약
agentproto 0.6 및 0.7 버전이 출시되었습니다. 이번 업데이트는 브로커가 트래픽 내용을 읽을 수 없는 보안 페어링 메커니즘을 도입하여, 에이전트 데몬과 외부 기기 간의 안전한 연결을 지원합니다.
핵심 포인트
- 브로커는 데이터를 파싱하지 않고 전달만 하는 '멍청한 WebSocket 스플라이서' 역할 수행
- ECDH 핸드셰이크와 AEAD 채널을 통한 강력한 보안 통신 구현
- 일 단위 에포크 라우팅 토큰을 사용하여 자동 재연결 지원
- 사용자가 직접 브로커를 호스팅할 수 있도록 Dockerfile 제공
0.6.0은 2026-07-16에, 0.7.0은 2026-07-17에 출시되었습니다 · npm i -g @agentproto/cli · source · Apache-2.0
당신의 에이전트 데몬(agent daemon)은 자격 증명(credentials), 리포지토리(repo), 그리고 셸(shell)이 있는 머신에서 실행됩니다. 머지않아 당신은 노트북, 휴대폰, CI 박스 등 다른 곳에서 에이전트에 접속하고 싶어질 것입니다. 이는 중간에 릴레이(relay)가 필요함을 의미합니다. 그리고 중간에 있는 릴레이는 반드시 신뢰해야만 하는 대상입니다.
0.6 버전은 주로 그럴 필요가 없도록 만드는 것에 관한 것입니다.
여기에 두 가지 **중대한 변경 사항(breaking changes)**이 있으므로, 업그레이드하기 전에 하단의 마이그레이션 노트를 읽어주세요.
페어링(Pairing): 브로커는 의도적으로 멍청합니다
agentproto pair offer는 일회용 토큰을 생성하고 agentproto://pair?… URL을 출력합니다. 다른 머신에서는 다음과 같이 수행합니다:
# 데몬 머신에서
agentproto pair offer
# → agentproto://pair?t=<token>&rv=<url>&fp=<fingerprint>
...
이는 랑데부 브로커(rendezvous broker)를 통해 pair/v1 ECDH 핸드셰이크(handshake)를 트리거하며, 그 이후의 모든 바이트는 AEAD 채널(AEAD channel)로 래핑(wrapped)됩니다. 흥미로운 점은 브로커가 무엇을 _할 수 있는지_에 대한 부분입니다:
랑데부는 멍청한 WebSocket 스플라이서(splicer)입니다. 동일한 일회용 토큰을 가진 두 소켓을 매칭하고, 양방향으로 바이트를 있는 그대로 전달(pipes bytes verbatim)하며, 아무것도 파싱(parses)하지 않습니다.
브로커는 토큰, IP, 메시지 크기, 타이밍을 학습합니다. 하지만 콘텐츠를 읽을 수도 없고 위조할 수도 없습니다. 이것은 우리의 운영 규율에 대한 약속이 아니라, 프로토콜(protocol) 자체의 속성입니다. 이 차이는 매우 중요합니다. 왜냐하면 "우리는 신중하니 평문(plaintext)을 믿고 맡겨달라"는 주장은 우리를 포함해 그 누구도 받아들여서는 안 되는 주장이기 때문입니다.
첫 페어링 이후, 데몬은 공유된 pairRoot 비밀값에서 파생된 일 단위 에포크 라우팅 토큰(day-scoped epoch routing tokens) — HKDF(pairRoot, "rv-route"‖epoch)를 사용하여 자동으로 재연결합니다. 데몬은 현재 에포크와 이전 에포크 모두에 머무르므로(parks), 시계가 자정을 가로지르는 클라이언트도 여전히 연결을 찾을 수 있습니다. (그렇지 않았다면 하루에 한 번 발생하는 미스터리한 버그가 되었을 작은 디테일입니다.)
agentproto pair rm <fingerprint>로 권한을 취소할 수 있으며, 취소 즉시 데몬은 해당 토큰들에 머무르는 것을 중단합니다.
브로커를 원하지 않으시나요? 직접 호스팅하세요. 릴리스에는 다음과 같은 특징을 가진 Dockerfile이 포함되어 있습니다 —
non-root, RENDEZVOUS_* 환경 변수 설정, /healthz 프로브(probe). 다른 설정이 없을 때는 호스팅된 랑데부(rendezvous)를 기본값으로 제공하며, pair offer 시에는 조용히 기본값으로 넘어가는 대신 명시적으로 이를 알립니다.
serve --connect에 대한 동일한 처리
페어링(Pairing)만이 바이트(bytes)를 중개인에게 전달하는 유일한 곳은 아닙니다. agentproto serve --connect는 터널(tunnel)을 통해 데몬(daemon)을 외부로 밀어내며, 역사적으로 해당 터널 호스트는 평문(plaintext)을 노출해 왔습니다. 0.6 버전에서는 이를 방지하기 위해 선택적(opt-in) E2E 핸드셰이크(handshake)인 tunnel-e2e/v1을 추가했습니다 — :
{ "tunnel": { "e2e": true } }
이는 PKI(공개키 기반 구조) 기반이 아닌 토큰 인증(token-authenticated) 방식입니다. 터널을 인증하는 것과 동일한 공유 비밀(shared secret)이 채널의 키(key)가 되므로, 별도로 관리해야 할 인증서(certificate) 문제가 없습니다. 이는 의도적으로 선택 사항(opt-in)으로 설계되었으며, 데몬은 E2E를 광고하지 않는 호스트에 대해 평문으로 조용히 폴백(fallback)하는 것을 거부합니다. 즉, 암호화를 요청했으나 사용할 수 없는 경우, 조용한 다운그레이드가 아닌 에러를 발생시킵니다. 이것이 바로 이 플래그를 도입한 전체 이유입니다. 검증할 수 없는 보안 속성은 보안 속성이 아니기 때문입니다.
주요 변경 사항: Claude Code 스폰(spawn) 시 명시적 자격 증명 요구
@agentproto/adapter-claude-code@1.0.0 및 @agentproto/driver-agent-cli@1.0.0은 **메이저 업데이트(major bumps)**이며, 그 이유는 다음과 같습니다.
이전에는 Claude Code 세션을 스폰(spawn)할 때 데몬을 실행하는 쉘(shell)이 내보내는(export) 환경 변수를 그대로 상속받았습니다. 이는 환경 변수에 잘못 포함된 ANTHROPIC_API_KEY가 사용자가 Max 플랜을 사용 중이라고 생각하는 동안 조직의 API 크레딧을 조용히 소모할 수 있음을 의미하며, 이는 나중에 인보이스(invoice)를 보고서야 알게 될 수 있었습니다.
이제 런타임(runtime)은 스폰 시점에 두 가지 모드 중 하나를 결정하고 이를 기계적으로 적용합니다:
| mode | sets | scrubs |
|---|---|---|
subscription | CLAUDE_CODE_OAUTH_TOKEN (claude setup-token을 통해) — Max/Pro 플랜 비용 청구 | ANTHROPIC_AUTH_TOKEN, 그리고 Bedrock/Vertex 리다이렉트 토글 |
api-key | 프로바이더 스토어(provider store)의 ANTHROPIC_API_KEY | 구독 자격 증명 |
subscription 모드에서 CLAUDE_CODE_USE_BEDROCK / CLAUDE_CODE_USE_VERTEX를 스크러빙(scrubbing)하는 기능은 조용한 영웅입니다. 그렇지 않았다면 주변 환경 변수(ambient env var)가 아무런 예고 없이 네이티브 스폰(native spawn)을 Bedrock으로 리라우팅(reroute)할 수도 있었을 것입니다.
한 번만 설정하세요:
{
"defaults": {
"adapters": {
...
또는 agent_start.auth를 통해 스폰(spawn)마다 오버라이드(override)할 수 있습니다. 이제 동일한 리졸버(resolver)가 adapter-codex, adapter-claude-sdk, 그리고 adapter-hermes를 지원합니다 — 모든 어댑터(adapter)는 주변 환경 변수 추론 대신 카탈로그(catalog)에서 가져온 결제 자격 증명(billing credentials)을 사용합니다.
agentproto providers set <slug> <key>로 설정한 키는 스폰 시점에 자식 프로세스(child process)에 주입되며, 사용자의 셸(shell)에는 절대 주입되지 않습니다. 리졸버는 카탈로그에서 어댑터에 선언된 프로바이더(provider)를 조회하고, 이를 환경 변수 이름(providerEnvVar("anthropic") → ANTHROPIC_API_KEY)에 매핑한 뒤, 오직 그곳에만 설정합니다.
Worktree는 생명주기(lifecycle)를 갖게 되었습니다 — 그리고 혹독한 경험을 통해 절제를 배운 gc(garbage collection)도 생겼습니다
에이전트(agents)를 병렬로 실행하면 워크트리(worktrees)가 너무 많아집니다. 저도 확실히 그렇습니다.
agentproto worktree new # worktrees.root + 출처 마커(provenance marker) 아래에 프로비저닝(provisions)
agentproto worktree ls --status # 모든 항목 분류
agentproto worktree gc # 드라이 런(dry-run) 계획
...
ls --status는 각 워크트리(worktree)를 세 가지 독립적인 축에 따라 분류합니다: 트리 (tree) (clean/dirty), 통합 (integration) (11가지 상태 — fresh, merged, open, partial, pushed-no-pr, unpushed, local-only, gone-unexplained, diverged, detached, unknown), 그리고 활성 상태 (liveness) (idle, sessions, daemon-unreachable). 통합(integration) 축은 스쿼시 머지(squash-merge)로부터 안전합니다. 이는 origin/main에 대한 SHA 포함 여부를 확인하고 불변의 포지(forge) 판결을 캐싱(caching)하므로(머지된 PR은 머지된 상태로 유지됨), 레지스트리(registry) 없이도 오프라인 상태에서 정직함을 유지합니다. 결코 추측하지 않습니다. 오프라인 상태에서 캐시 미스(cache miss)가 발생하면 즉시 unknown으로 분류됩니다.
7월 15일, gc가 활성 워크트리를 삼켜버렸습니다
여러분이 직접 발견하기 전에 제가 먼저 말씀드리는 편이 낫겠습니다.
gc의 첫 번째 버전에는 안전해 보이는 규칙이 있었습니다: dirty 워크트리는 회수(reclaimed)되는 대신 구조(salvaged) (즉, ~/.agentproto/worktree-salvage/로 스냅샷 생성) 처리되므로, 커밋되지 않은 작업이 단순히 삭제되는 일은 없어야 했습니다. 하지만 2026-07-15, 이 규칙이 실행 중인 에이전트(agent)의 활성 워크트리를 파괴했습니다. 메커니즘은 다음과 같았습니다: 자체 커밋이 전혀 없는 브랜치가 fresh로 분류되었고, fresh는 '구조 후 삭제'를 수행할 수 있는 권한으로 취급되었으며, 에이전트의 커밋되지 않은 작업도 함께 사라졌습니다. 스냅샷은 존재했지만, 에이전트가 실제로 쓰고 있던 워크트리는 존재하지 않게 되었습니다.
버그는 구조(salvage) 단계에 있었던 것이 아닙니다. 축(axis)이 코드에서 가정하는 것보다 덜 증명하도록 방치한 것이 문제였습니다. fresh는
- 오직
merged— 항상 forge-confirmed된 상태 — 만이 지저분하고 커밋되지 않은 작업(dirty, uncommitted work)을 파괴하는 라이선스를 가집니다.fresh는 깨끗하든 아니든 절대 그렇게 하지 않습니다. merged ∧ dirty상태의 워크트리(worktree)가 최근에 작성되었다면, 최근 작성 윈도우(recent-write window) 내에 어쨌든 유지됩니다. 누군가 아마 여전히 그 안에서 작업 중일 것입니다.- Reclaim (깨끗한 트리만 대상)에는
tree=clean ∧ integration ∈ {merged, fresh} ∧ liveness ∈ {idle, daemon-unreachable}조건이 필요합니다.fresh가 여기서 괜찮은 이유는 정확히 깨끗한 트리는 잃을 것이 없기 때문입니다. gc는 작업에 손을 대기 직전에 모든 워크트리를 처음부터 다시 재분류(re-classifies)합니다. 10분 전에 출력한 계획이 지금 삭제를 유발할 수는 없습니다.
작업 손실을 방지하기 위한 강력한 보증은 결코 liveness probe가 아니었습니다. 그것은 --force 없이 지저분한 워크트리를 제거하기를 거부하는 git 자체의 특성입니다. probe는 그 위에 얹어진 최적화일 뿐입니다. 우리는 그것을 뒤집었고, 그 대가로 워크트리 하나를 잃었습니다.
worktree rm / archive는 기존의 포괄적인 --force를 명시적인 --discard-untracked / --discard-modified로 대체합니다. "Force"는 두 가지 매우 다른 종류의 파괴를 의미하는 하나의 플래그였으나, 이제는 어떤 것을 의도했는지 이름을 지정해야 합니다.
에이전트가 권한에 대해 추측하는 것을 중단했습니다
에이전트가 턴 중간에 멈춰서 권한을 요청할 때 — 셸 명령 실행 전 Claude Code의 requestPermission처럼 — 이제 데몬(daemon)은 타임아웃이 발생하거나 자동으로 승인하는 대신 이를 영구적인 수신함(durable inbox)에 보관합니다.
agentproto permissions ls # 모든 세션에 걸쳐 대기 중인 항목 유지
agentproto permissions approve <id>
agentproto permissions deny <id>
동일한 세 가지 작업이 MCP 도구로도 존재하므로, 감독 에이전트(supervising agent)가 프로그래밍 방식으로 응답할 수 있습니다. SessionDescriptor에 blockedOn: "subagent" | "command" | null이 추가되어, UI가 단순히 스피너(spinner)를 보여주는 대신 세션이 왜 멈춰 있는 것처럼 보이는지 마침내 말해줄 수 있게 되었습니다. (포괄적으로 승인하기보다 쓰기를 제한해야 하는 이유: --approve-all은 당신의 에이전트가 위험한 것을 배포하게 만드는 방식입니다.)
또한 포함된 내용
- xAI / Grok가 퍼스트 클래스 (first-class) 프리셋으로 추가되었습니다 —
grok-4.5,grok-4.3,grok-4.20,grok-build-0.1이 카탈로그(catalog)에 등록되었습니다:
agentproto providers set xai $XAI_API_KEY
agentproto run --adapter claude-sdk --mode xai --model grok-4.5 "explain this codebase"
-
경로 인식 모델 식별 (Route-aware model identity) — 이제 카탈로그는 모델의 _제품 식별자 (product identity)_와 _서빙 경로 (serving route)_를 분리합니다:
openai/gpt-4o대openai/gpt-4o@openrouter. 기존의 레거시 ID (legacy ids)도 여전히 작동합니다. 커스텀 경로는registerCustomRoute를 통해 등록되며, 설정(config)에는 환경 변수의 _이름 (name)_만 저장되고 값(value)은 저장되지 않습니다. -
결함 허용 팬아웃 (Fault-tolerant fan-out) —
MapStep/PipelineStep에서onError: "collect"를 사용할 수 있습니다. 이를 통해 모든 항목이 완료되며, 첫 번째 오류로 인해 배치(batch) 전체를 잃는 대신Promise.allSettled형태의 결과를 얻을 수 있습니다. -
agentproto policy— 완료 정책 (completion policies)이 마침내 CLI 동사(verb)로 제공됩니다:attach/status/wait/ls/cancel. 엔진은 한동안 존재해 왔지만 명령어가 없었기에, 실제로는 아무도 정책을 연결(attach)하지 못했습니다. -
agentproto acp— 저희의 어댑터 제품군뿐만 아니라, ACP를 지원하는 모든 에이전트를config.json에 등록하고 직접 구동할 수 있습니다. -
재생 기능이 포함된 SSE (SSE with replay) —
GET /sessions/:id/events/stream은 저장된 이벤트를 재생한 후 실시간 전송으로 전환합니다. 세션 중간에 재연결하더라도 놓치는 내용이 없습니다. -
agentproto.json—setup/teardown훅 (hooks), 감독 대상 서비스 (supervised services), 그리고 워크트리(worktree)당 로컬호스트 리버스 프록시 (localhost reverse proxy)를 선언할 수 있습니다. -
agentproto run --output-schema— 실행 결과(output)를 JSON 스키마 (JSON Schema)에 따라 검증하며, 규격에 맞지 않을 경우 명령을 실패 처리합니다. 에이전트의 출력을 사용하는 CI(지속적 통합) 환경에 유용합니다. -
agentproto sessions story <id>— 세션이 실제로 수행한 작업을 읽기 쉬운 서사(narrative) 형태로 보여주며, 노이즈가 많은 저수준(low-level) 이벤트들을 단계(steps)로 압축하여 보여줍니다. -
agentproto run --model/--effort— 실행 시 모델과 추론 노력(reasoning effort)을 선택할 수 있으며, 알 수 없는 플래그(flag) 입력 시 스택 트레이스 (stack trace) 대신 친절한 에러 메시지를 출력합니다. -
daemon.authToken+--auth-token— 지속적인 게이트웨이 베어러 토큰 (bearer token)으로, 데몬 (daemon)이 재시작될 때마다 처음부터 다시 인증할 필요가 없습니다. -
config.json내의terminalPresets— 터미널 설정을 한 번 지정하면 재사용할 수 있습니다. -
@agentproto/adapter-pi— earendil-works/pi를 위한 AIP-45 전용 암 (proprietary-arm) 어댑터입니다. 어댑터 제품군(adapter family)은 코어 (core)가 알지 못하는 상태에서도 계속 확장됩니다. -
가격 카탈로그 (pricing catalog)에 등록된
claude-opus-4-8,claude-sonnet-5,claude-fable-5— 이제 실행 가능 여부 (runnable)가 가격 책정 (priced)과 분리되어, 가격 데이터가 누락된 모델이라도 여전히 실행됩니다 (추측하지 않고no-pricing이라고 보고합니다). -
Apache-2.0. 모든 패키지가 MIT에서 라이선스가 변경되었습니다 — 92개의 패키지 중 MIT 라이선스는 하나도 남지 않았습니다. API 변경 사항은 없습니다.
VS Code 확장 프로그램이 더 이상 거짓말을 하지 않습니다
이 두 릴리스(release)를 관통하는 더 큰 이야기는 CLI에 있지 않습니다.
VS Code 확장 프로그램은 0.0.1 → 0.1.0 (0.6 버전에서) → 0.1.1 (0.7 버전에서)로 업데이트되었으며, 이는 "기술적으로 존재함"에서 "내가 실제로 에이전트 (agents)를 관찰하는 도구"로 넘어가는 궤적을 보여줍니다.
0.1.0 버전은 뼈대를 갖추었습니다: 가공되지 않은 이벤트 (raw events)의 나열 대신 구조화된 대화 렌더링 (structured conversation rendering), 필터/검색/그룹화 기능이 포함된 세션 트리 (sessions tree), 실행 시 워크스페이스 자동 감지 (workspace autodetect), MCP를 통한 세션 재시작, 그리고 agentproto.openTerminal을 제공합니다. 이는 PTY 및 에이전트-CLI (agent-cli) 세션을 위한 실제 의사 터미널 (Pseudoterminal) 미러로, 시뮬레이션이 아닌 실제 터미널에서 세션을 관찰할 수 있습니다.
0.1.1 버전은 상태 (state)를 잘못 표현하는 것을 멈춘 단계이며, 이것이 제가 중요하게 생각하는 부분입니다:
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기