research

Goose — 패러다임과 약점

Goose — 패러다임과 약점

Rust 진영의 가장 큰 오픈소스 agent. 그들이 무슨 가정 위에 서 있는가, 어디서 균열이 보이는가, 그 균열을 깨려는 흐름들은 무엇인가. 코드 디테일이 아니라 패러다임과 인사이트.


Goose가 서있는 가정들

Goose의 모든 결정은 다음 가정 위에 있다. 이 가정을 알면 그들의 trait 두께·계층 구조·MCP 일원화가 왜 그런 모양인지 바로 보인다.

가정표현
모델은 서비스(provider)다LLM 호출은 외부 vendor를 부르는 행위. 추상화 한 겹이 있어야 함
Tool은 표준이어야 한다MCP를 처음부터 유일한 tool 정의로 채택. 자체 정의 없음
Agent는 single-loop에서 시작한다turn 단위 while-loop. plan/multi-agent는 그 위에 얹는
Stream-first가 옳다토큰 단위 UX가 기본. complete는 stream의 collect
Provider는 호출 추상화다session·history·permission은 호출의 부속 정보
통합 product는 trait가 두꺼워질 수밖에 없다embedding·oauth·session naming까지 한 trait에 모음

여기서 마지막 가정이 가장 중요하다. Goose는 통합 product를 만들고 있고, 그래서 trait가 시간이 지나며 두꺼워진다. v0.1은 그 가정을 안 따른다.


추상적 작동 구조

핵심: 모든 것이 Provider 호출과 Tool dispatch 두 동작으로 환원된다. 위 layer에 있는 모든 것(session·permission·oauth)은 그 호출에 끼어드는 부속 정보로 모델링된다.


이 패러다임의 강점

  • MCP 일원화로 plugin ecosystem 무료 — 70+ extension을 자체 코드 없이
  • Provider 추상화로 vendor 자유 — 15+ provider, 사용자 구독 모델까지 (ACP)
  • Cargo workspace 분리로 깔끔한 의존 방향 — core ← cli/server/extensions
  • Rust 단일 binary, native 배포 — Electron + Rust 조합으로 desktop도 커버
  • 재단 거버넌스 — block에서 AAIF로 이전. 단일 회사 의존 제거

약점 — 어디서 균열이 보이는가

1. trait이 시간이 지나며 두꺼워진다

Provider trait가 호출 추상화로 시작했지만, 통합 product의 책임이 누적되며 oauth·embedding·session naming·mode·permission까지 한 trait에 모인다. 결과:

  • 새 provider 추가 비용이 점점 커짐 (default impl로 가려지지만 본질 미해결)
  • mock 만들기가 힘들고, 부분 차용이 어려움
  • 어떤 메서드가 진짜 핵심인지 흐려짐

이 균열이 통합 product의 운명인가, 추상화 설계 실패인가는 열린 질문.

2. single-loop 가정이 후행 패턴을 끼워넣게 만든다

agent loop가 직선적이라 plan/reflect/multi-agent는 위에 얹는 형태로만 들어온다. 결과:

  • ReAct 변형은 자연스럽지만, plan-then-act, tree-of-thought, graph-based agent는 부자연스러움
  • multi-agent를 단일 loop의 sub-routine으로 표현해야 함 → 책임 경계가 흐려짐

3. Provider라는 개념의 균열

Goose는 Claude Code/Gemini CLI 같은 자기 context를 가진 wrapper까지 Provider로 다루기 위해 manages_own_context 같은 escape hatch를 추가했다. 이건 Provider 추상화의 근본 균열을 보여주는 신호:

  • LLM API 호출과 wrapper CLI 호출은 같은 개념인가? — 아니다
  • 하지만 Provider trait로 묶으려면 escape hatch가 필요해진다
  • 균열이 보이지만 paradigm을 바꾸진 않음

4. observability를 위한 인자 침투

session_id 같은 orthogonal한 정보가 모든 호출에 인자로 들어옴. 이건 trait 시그니처가 호출의 본질이 아니라 runtime 환경까지 책임지게 만드는 패턴.

  • 좋은 점: stateless 위반 없이 telemetry 가능
  • 나쁜 점: trait가 자라면 인자가 더 늘어날 수밖에 없음 (request_id, trace_id, user_id, ...)

5. MCP 일원화의 비용

모든 tool을 MCP로 통일한 결과:

  • in-process 가벼운 tool도 MCP protocol overhead를 거침
  • protocol stability에 발이 묶임 (MCP가 변하면 따라가야 함)
  • fast path가 없음 — agent가 1ms로 끝낼 수 있는 tool도 MCP 직렬화 거침

6. statelessness vs UX 책임의 충돌

Provider trait에 generate_session_name 같은 UX 헬퍼가 들어와 있음. provider가 stateless 호출 추상화여야 한다면 이건 layering violation. 통합 product를 빨리 만들려는 압력의 흔적.


이 약점들을 깨려는 흐름들

A. trait 두께 → composable trait 흐름

  • 학술/오픈소스 진영에서 Provider 추상화를 여러 trait의 조합으로 분리하려는 흐름
  • Embedder, OAuthProvider, ModelCatalog, CompletionProvider를 따로 두고 implementor가 골라 구현
  • 이건 v0.1이 직접 채택할 방향

B. single-loop 가정 → state-machine / graph 기반 agent

  • LangGraph (TypeScript/Python) — agent를 노드와 엣지로 표현
  • DSPy / BAML — agent action을 선언적으로 분리
  • 이 흐름은 "loop는 한 종류의 patten일 뿐, 일반적 agent는 graph"라고 주장
  • v0.1이 agent loop를 SDK에 안 넣은 것은 이 흐름과 정렬

C. Provider 균열 → ACP / agent-as-MCP-server

  • Agent Client Protocol(ACP) — agent와 client를 protocol로 분리. provider 추상화 아래 한 층 더 둠
  • agent-as-MCP-server — agent 자체를 tool로 노출. provider/tool 구분이 흐려짐
  • Goose도 이미 ACP/MCP server 양쪽 채택했지만, Provider라는 개념 자체의 재정의는 아직 안 됨

D. observability 침투 → context propagation 표준

  • OpenTelemetry context propagation을 함수 인자가 아니라 task-local storage로 받는 패턴
  • Tokio의 task_local, Rust의 thread_local 활용
  • v0.1이 채택하면 trait 시그니처에서 session_id를 뺄 수 있음

E. MCP overhead → fast path / capability layer

  • 가벼운 tool은 in-process trait로 호출하고, 외부 tool만 MCP를 거치는 2-tier 흐름
  • 아직 표준은 없지만, Claude Code의 hooks(zero-cost lifecycle)와 skills(low-cost) 차등이 같은 통찰
  • v0.1이 trait 설계에서 미리 고려할 만함

F. UX 헬퍼 침투 → presentation layer 분리

  • session naming 같은 건 LLM 호출의 한 종류일 뿐 → 별도 utility로 빼는 것이 옳음
  • 이건 trait 설계 원칙 문제이지 새 패러다임은 아님

v0.1이 흡수할 인사이트

(코드가 아니라 철학 단위로)

  1. 호출 추상화와 product 책임을 섞지 않는다 — 이게 두꺼운 trait의 근본 원인
  2. single-loop를 SDK에 박지 않는다 — 다른 패턴 실험을 위한 substrate가 v0.1의 가치
  3. observability는 인자가 아니라 context propagation으로 — 가능하면 task-local로
  4. Tool을 MCP에 일원화는 하되, fast path는 열어둔다 — 지금 결정할 필요 없지만 trait 설계가 그걸 막지는 말 것
  5. provider라는 개념은 모델 호출 추상화에 한정 — wrapper CLI는 다른 추상화 (escape hatch는 v0.1에서 거부)

v0.1이 다르게 갈 가설

  • trait 분할 — Provider는 호출만. embedding·oauth·permission·session naming은 모두 별도 trait 또는 utility
  • agent loop 외부화 — v0.2에서 별도 crate. v0.1 사용자는 loop 없이도 SDK 사용 가능
  • session_id 같은 인자 최소화 — observability는 옵션 또는 task-local
  • wrapper CLI escape hatch 거부 — provider 의미를 좁게 유지

이 가설들은 통합 product가 아닌 라이브러리를 지향하기 때문에 가능. Goose는 product, v0.1은 library. 둘은 다른 게 옳다.


관련


Sources