← 실험실 해부 + 사용법 · 직접 세워봄

사람과 에이전트가 같은 방에서

Buzz는 Block(Square·Cash App)이 만든 자체 호스팅 워크스페이스입니다. 슬랙 같은 채팅으로 보이지만 속은 서명된 이벤트 로그 하나이고, AI 에이전트가 봇이 아니라 자기 키를 가진 멤버로 채널에 들어옵니다. 릴레이를 직접 세워 동작을 확인하고, 그 과정을 따라 할 수 있게 정리했습니다.

분석 대상: block/buzz v0.5.2 · 릴레이 이미지 :main Apache-2.0 · ★18.6k 작성일: 2026-07-31

1. 한 장 요약

팀 도구는 보통 채팅 + 이슈트래커 + CI 대시보드 + 검색이 각자 놀고, 그 사이를 봇과 글루 코드가 잇습니다. Buzz의 베팅은 그걸 전부 한 종류의 이벤트로 만들면 접착제가 필요 없다는 것입니다.

기반Nostr 릴레이

메시지·리액션·깃 패치·워크플로 실행·승인이 전부 서명된 이벤트 한 줄. 사람이 썼든 프로세스가 썼든 형식이 같습니다.

정체성키페어

에이전트도 자기 키와 채널 멤버십을 가집니다. 권한 플래그가 아니라 신원으로 범위를 자릅니다 — 사람 동료를 다루는 방식 그대로.

저장Postgres · Redis · S3

이벤트와 전문검색은 Postgres, 실시간 pub/sub은 Redis, 미디어·깃 오브젝트는 S3(MinIO). 감사로그는 해시 체인.

클라이언트Tauri 앱 · CLI

데스크톱 앱(Rust+React), 에이전트용 buzz-cli(JSON in / JSON out), 그리고 Goose·Codex·Claude Code를 물리는 buzz-acp 하네스.

정리하면 "채널이 곧 기록"입니다. 기능 브랜치를 열면 방이 생기고, 패치·CI 결과·리뷰·머지 결정이 같은 방에 쌓이니 왜 이 코드가 존재하는지가 한 곳에 남습니다. 6개월 뒤 "이 에러 본 적 있나?"를 에이전트가 검색해 근거 스레드째로 물어다 줄 수 있는 이유도 같습니다.

2. 모든 게 같은 이벤트라는 설계

README가 자기 정체를 이렇게 요약합니다.

"실제로는 팀 워크스페이스처럼 느껴진다. 그 아래는 취향이 있고 수상할 만큼 많은 Rust 크레이트로 된 이벤트 로그다."

block/buzz — README

이 설계가 실제로 무엇을 바꾸는지는 에이전트를 넣는 방식에서 드러납니다. 보통의 팀 도구는 봇에게 앱 토큰과 스코프 목록을 줍니다. Buzz는 그러지 않습니다.

  • 에이전트는 자기 키페어를 가집니다 — 사람 계정과 같은 종류의 신원.
  • 채널에 초대해서 넣습니다. 초대 안 된 방은 애초에 보이지 않습니다.
  • 에이전트가 한 일은 사람과 같은 감사 추적에 남습니다. 별도 봇 로그가 아닙니다.
  • 할 수 있는 일도 사람과 같은 표면입니다 — 채널 만들기, 캔버스 편집, 워크플로 실행, 허들 참여, 패치 보내기.

릴레이가 켜져 있는 상태에서 NIP-11 정보를 받아보면 auth_required: true, restricted_writes: true가 기본입니다. 아무나 쓰는 공개 릴레이가 아니라 가입된 신원만 쓰는 워크스페이스라는 뜻입니다.

✅ 지금 되는 것🚧 붙이는 중
릴레이, 채널·스레드·DM, 캔버스, 미디어, 검색, 감사로그 / 데스크톱 앱 / buzz-cli + ACP 하네스 / YAML 워크플로(메시지·리액션·일정·웹훅 트리거) / 깃 이벤트(NIP-34) · 깃 호스팅 모바일 클라이언트(iOS·Android) / 워크플로 승인 게이트 / 허들 생명주기 이벤트 / 푸시 알림

3. 사용법 ① 릴레이 세우기 실측 3분 8초

먼저 알아야 할 것 하나. 데스크톱 앱만 깔면 아무것도 안 됩니다. 앱은 기본으로 ws://localhost:3000을 찾고, 거기에 릴레이가 떠 있어야 화면이 채워집니다. 그래서 순서는 릴레이가 먼저입니다.

준비물: Docker(맥이면 Colima 또는 Docker Desktop) 하나. Rust·Node 빌드는 필요 없습니다 — deploy/compose 번들이 미리 구운 이미지(ghcr.io/block/buzz:main)를 씁니다.

  1. 클론하고 배포 번들로 들어갑니다
    터미널
    git clone https://github.com/block/buzz.git
    cd buzz/deploy/compose
    cp .env.example .env
  2. 비밀값을 채웁니다 (여기서 대부분 막힙니다)

    .envCHANGE_ME가 하나라도 남아 있으면 run.sh가 시작을 거부합니다. 의도된 안전장치입니다 — 재시작마다 값이 바뀌면 안 되는 것들이라서요. 아래처럼 한 번에 채웁니다.

    비밀값 7종 생성 + 로컬용 주소로 교체
    python3 - <<'EOF'
    import os, binascii
    h = lambda n: binascii.hexlify(os.urandom(n)).decode()
    s = open('.env').read()
    
    # 값이 저마다 달라야 하는 비밀값 — 줄 단위로 통째 교체
    for key, val in [
        ('RELAY_OWNER_PUBKEY',       h(32)),  # 릴레이 소유자 — 앱에서 만든 키로 나중에 교체
        ('BUZZ_RELAY_PRIVATE_KEY',   h(32)),  # 릴레이 자신의 키 (재시작해도 유지돼야 함)
        ('BUZZ_GIT_HOOK_HMAC_SECRET', h(32)),
        ('POSTGRES_PASSWORD',        h(16)),
        ('REDIS_PASSWORD',           h(16)),
        ('BUZZ_S3_ACCESS_KEY',       h(12)),
        ('BUZZ_S3_SECRET_KEY',       h(24)),
    ]:
        s = '\n'.join(f'{key}={val}' if l.startswith(f'{key}=') else l
                      for l in s.splitlines())
    
    # 로컬에서 돌릴 거라 도메인·프로토콜을 localhost로
    for old, new in [
        ('BUZZ_DOMAIN=buzz.example.com', 'BUZZ_DOMAIN=localhost'),
        ('RELAY_URL=wss://buzz.example.com', 'RELAY_URL=ws://localhost:3000'),
        ('BUZZ_MEDIA_BASE_URL=https://buzz.example.com/media', 'BUZZ_MEDIA_BASE_URL=http://localhost:3000/media'),
        ('BUZZ_MEDIA_SERVER_DOMAIN=buzz.example.com', 'BUZZ_MEDIA_SERVER_DOMAIN=localhost'),
        ('BUZZ_CORS_ORIGINS=https://buzz.example.com', 'BUZZ_CORS_ORIGINS=http://localhost:3000'),
    ]:
        s = s.replace(old, new)
    
    open('.env', 'w').write(s)
    left = [l for l in s.splitlines() if 'CHANGE_ME' in l and not l.lstrip().startswith('#')]
    print('남은 CHANGE_ME 설정줄:', len(left))
    EOF

    0이 나오면 됩니다. 주석 줄의 CHANGE_ME 한 개는 남아도 무방합니다(스크립트는 KEY= 형태만 검사).

  3. 띄웁니다

    이미지 4종(릴레이 356MB · Postgres 415MB · MinIO 228MB+112MB · Redis 59MB, 합 약 1.17GB)을 받고 마이그레이션까지 돕니다. 저희 실측은 3분 8초였습니다.

    터미널
    ./run.sh start
  4. 진짜 떴는지 확인합니다

    세 가지가 통과하면 정상입니다 — 헬스체크 200, 웹소켓 업그레이드 101, NIP-11 릴레이 정보 응답.

    검증 3종
    curl -s -o /dev/null -w "health %{http_code}\n" http://localhost:3000/health
    
    curl -s -o /dev/null -w "ws %{http_code}\n" \
      -H "Connection: Upgrade" -H "Upgrade: websocket" \
      -H "Sec-WebSocket-Version: 13" -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
      http://localhost:3000
    
    curl -s -H "Accept: application/nostr+json" http://localhost:3000 | python3 -m json.tool | head -20

    저희 결과: health 200 / ws 101 / NIP-11에 auth_required: true·restricted_writes: true·릴레이 pubkey 포함.

4. 사용법 ② 데스크톱 앱 붙이기

릴레이가 떴으면 이제 앱입니다. 최신 릴리스에서 받습니다 — macOS .dmg, Linux .AppImage/.deb, Windows .exe.

확인한 것결과
Apple Silicon DMG 크기88.3MB (앱 번들 풀면 182MB)
코드 서명Developer ID Application: Block, Inc. (EYF346PHUG)
공증(Gatekeeper)spctl accepted · Notarized Developer ID — "확인되지 않은 개발자" 경고 없음
번들 안 바이너리buzz, buzz-desktop, buzz-acp, buzz-agent, buzz-dev-mcp, git-credential-nostr (전부 arm64)

이게 중요한 발견입니다. 앱 하나에 에이전트 하네스(buzz-acp)와 MCP 도구 서버(buzz-dev-mcp)가 같이 들어 있습니다. 즉 Rust 워크스페이스를 빌드하지 않아도 에이전트를 붙일 수 있습니다.

다른 릴레이를 보게 하려면 (기본은 ws://localhost:3000)
BUZZ_RELAY_URL=ws://localhost:3000 open -a Buzz
# 또는 앱 안에서 릴레이 전환

앱을 처음 열면 키페어를 만듭니다. 그 pubkey를 릴레이 .envRELAY_OWNER_PUBKEY에 넣고 재시작하면 그 계정이 워크스페이스 소유자가 됩니다(위 2단계에서 임시값으로 채워뒀다면 여기서 교체).

5. 사용법 ③ 에이전트 들여보내기

Buzz를 쓰는 진짜 이유입니다. 에이전트에게 앱 토큰을 주는 게 아니라 키를 주고 채널에 초대합니다.

  1. 에이전트용 키를 만들고 환경에 둡니다
    터미널
    export BUZZ_PRIVATE_KEY=$(openssl rand -hex 32)
    export BUZZ_RELAY_URL=ws://localhost:3000

    사람 계정과 분리된 신원이라, 이 키가 한 일은 이 키 이름으로 감사로그에 남습니다.

  2. buzz-cli로 붙입니다 — JSON in / JSON out

    LLM 도구 호출에 맞춰 설계된 CLI입니다. 앱 번들 안의 buzz 바이너리를 그대로 쓰거나, 소스에서 cargo build -p buzz-cli로 뽑습니다.

    앱 번들 안 바이너리를 PATH에 걸기 (macOS)
    ln -sf /Applications/Buzz.app/Contents/MacOS/buzz ~/.local/bin/buzz
    buzz --help
  3. 기존 코딩 에이전트를 물립니다 (ACP 하네스)

    Goose·Codex·Claude Code를 Buzz 채널에 참여시키는 다리가 buzz-acp입니다. 앱 번들에 들어 있으니 별도 빌드가 필요 없습니다.

    ACP 하네스 실행
    /Applications/Buzz.app/Contents/MacOS/buzz-acp --help
  4. 워크플로로 자동화합니다

    YAML 워크플로가 메시지 / 리액션 / 일정 / 웹훅 트리거로 돕니다. README의 예시가 이 도구의 성격을 잘 보여줍니다 — 태그가 붙으면 워크플로가 뜨고, 에이전트가 머지된 PR을 읽어 릴리스 노트를 초안으로 올리고, 사람이 👍를 누르면 배포됩니다. 그 👍도 서명된 이벤트라 승인 기록으로 남습니다.

주의. 승인 게이트(workflow approval gates)는 README가 스스로 "인프라는 있고 글루가 마르는 중"이라고 적어둔 상태입니다. 사람 승인이 강제되는 파이프라인을 지금 이 위에 얹는 건 이릅니다.

6. 사용법 ④ 끄기·지우기·백업

일상 운영
cd buzz/deploy/compose

./run.sh start      # 시작 (헬스체크까지 기다림)
./run.sh stop       # 컨테이너 내리기 (데이터 볼륨은 남음)
./run.sh restart    # 릴레이만 재생성
docker compose --env-file .env -f compose.yml ps    # 상태 보기
docker compose --env-file .env -f compose.yml logs -f relay   # 로그
완전히 지우기 (데이터까지)
./run.sh stop
docker volume rm buzz-postgres-data buzz-redis-data buzz-minio-data buzz-git-data

백업해야 할 것은 저장소 문서가 명시해 뒀습니다 — .env(특히 BUZZ_RELAY_PRIVATE_KEY· DB/Redis/S3 비밀값·깃 훅 HMAC), 소유자 개인키, Postgres 덤프, MinIO 버킷, 깃 데이터 볼륨. 릴레이 키가 바뀌면 릴레이의 신원이 바뀝니다 — 재시작마다 새로 만들면 안 됩니다.

공개 VPS에 올릴 거라면 TLS 오버레이가 준비돼 있습니다. BUZZ_COMPOSE_TLS=true ./run.sh start로 Caddy가 붙어 Let's Encrypt 인증서를 자동 발급합니다. 다만 그때는 BUZZ_DOMAIN·RELAY_URL을 실제 도메인으로 되돌려야 합니다.

7. 실측 결과와 비판

항목실측
릴레이 기동(이미지 pull 포함)3분 8초 · 컨테이너 4개 전부 healthy
이미지 총량1.17GB (relay 356MB · postgres 415MB · minio 228+112MB · redis 59MB)
헬스체크 / 웹소켓200 / 101
릴레이 기본 보안auth_required: true, restricted_writes: true
macOS 앱v0.5.2 · 공증 완료 · 번들에 ACP·MCP 바이너리 동봉
저장소 활동릴리스 v0.5.2가 이틀 전, 커밋은 오늘까지 — 매우 활발

좋았던 것

  • 자체 호스팅이 진짜로 3분입니다. Rust 빌드 없이 미리 구운 이미지로 끝나고, --wait로 헬스까지 확인하고 반환합니다.
  • 안전장치가 기본값에 들어 있습니다. CHANGE_ME가 남으면 시작 자체를 거부하고, 릴레이는 처음부터 인증 필수입니다.
  • 앱이 공증돼 있습니다. 이 카테고리의 오픈소스 데스크톱 앱이 자주 빠뜨리는 부분인데, Gatekeeper를 우회하라고 시키지 않습니다.
  • 에이전트 하네스가 앱에 동봉돼 있어 "빌드부터 하세요"를 요구하지 않습니다.

걸리는 것

  • 앱만 깔면 빈 화면입니다. 릴레이가 전제인데 이게 다운로드 페이지에서 잘 안 보입니다. 처음 쓰는 사람이 여기서 대부분 튕깁니다.
  • 비밀값 7개를 손으로 채워야 합니다. 저장소도 "부트스트랩 스크립트가 결국 이걸 대체해야 한다"고 적어뒀습니다 — 아직 없습니다.
  • 열린 이슈 1,193개. 5개월 된 프로젝트치고 많고, 그만큼 표면적이 넓습니다.
  • 미완 영역이 핵심 근처에 있습니다. 모바일·푸시·워크플로 승인 게이트가 전부 "붙이는 중"입니다. 승인 흐름을 기대하고 도입하면 실망합니다.
  • Nostr를 알아야 디버깅이 됩니다. 잘 돌 때는 몰라도 돼도, 뭔가 틀어지면 NIP-01·NIP-42·NIP-34가 튀어나옵니다.

결론. 팀에 도입할지 판단하려면 먼저 노트북에서 개인 릴레이로 굴려보는 게 맞습니다. 3분이면 서고, 지우는 것도 볼륨 4개 삭제면 끝입니다. "채널이 곧 기록"이라는 감각이 자기 팀에 맞는지 — 그걸 확인하는 데 드는 비용이 이 정도면 싸다고 봅니다.

이 글은 2026-07-31에 block/buzz를 클론해 deploy/compose 번들로 릴레이를 실제로 기동하고, macOS DMG(v0.5.2)를 내려받아 서명·공증·번들 구성을 확인한 기록입니다. 시간·용량은 Apple Silicon + Colima 환경의 1회 측정치이며 네트워크 상태에 따라 달라집니다. Buzz는 cmore.dev와 무관한 외부 오픈소스이고, 이 페이지는 공식 문서가 아닙니다.