스폰서LobeHubLobeHub자세히 알아보기
dshfind

2과: 동기가 되는 예시: VSCode 플러그인과 AI 에이전트

한 문장 요약: 플러그인 시스템은 일반적으로 "설치는 쉽지만 제거는 어렵습니다" — VSCode에서 플러그인을 비활성화하려면 전체 프로세스를 재시작해야 하고, 다른 플러그인에 의존하려 합어도 타입 보장이 없습니다. 반면 미래의 AI 에이전트는 바로 런타임에 끊임없이 스스로를 수정하므로, "설치할 수 있고, 제거할 수 있고, 서로를 파괴하지 않는" 능력이 가장 절실합니다. 이것이 바로 Cordis 논문이 해결하려는 문제입니다.

1. 시간적 제약: 설치는 쉽지만, 제거하려면 전체 프로세스를 재시작해야 한다

먼저 사고 실험을 하나 합시다. 여러분의 스마트폰은 언제 어디서나 새로운 앱을 설치할 수 있습니다. 그런데 "실행 중인 어떤 앱 하나만 시스템에서 떼어내어" 완전히 멈추고 메모리에서 나가게 하려 할 때, 시스템이 이렇게 말한다면 어떻겠습니까? 죄송하지만 스마트폰 전체를 재시작해야 하며, 다른 앱들도 함께 재시작됩니다. 황당하지 않습니까?

VSCode의 플러그인 시스템이 바로 이 황당한 현실입니다. 논문은 이것을 "플러그인 시스템"의 대표 사례로 사용합니다:

  • VSCode는 모든 확장을 같은 "확장 호스트"(extension host) 공유 프로세스 안에서 실행합니다;
  • 확장은 동적으로 설치할 수 있습니다 — 런타임에 설치하는 것은 문제없습니다;
  • 하지만 호스트에는 "런타임에 개별 확장의 코드를 언로드하는" 메커니즘이 없습니다;
  • 따라서 어떤 확장의 activate 함수 실행이 완료된 후에는, 그것을 비활성화하거나 언인스톨하려면 호스트 프로세스 전체를 재시작할 수밖에 없으며, 로드된 모든 확장이 영향을 받습니다.

모든 확장이 이렇게 번거로울까요? 아닙니다. 핵심은 코드를 포함하는지 여부입니다:

확장 유형대표적인 예단독으로 제거 가능한가
순수 선언형(코드 없음)테마, 키 바인딩, 코드 스니펫✅ 가능, 자유롭게 제거
실행 가능한 코드 포함언어 서비스, 디버거, 자동 완성❌ 불가능, 제거하려면 호스트 전체를 재시작해야 함

데이터가 얼마나 심각할까요? 설치 수 상위 100개 확장 중 87개가 실행 가능한 코드를 포함합니다. 즉, 일반적으로 많이 쓰는 확장 대부분은 "제거"조차 제대로 할 수 없습니다.

deactivate 훅: "임종 콜백"일 뿐, 언로드가 아니다

VSCode가 deactivate 훅을 제공하지 않느냐고요? "언로드 콜백"처럼 들리지 않나요? 이름에 속지 마세요:

  • 이것은 호스트 프로세스가 이미 종료를 시작한 후에 호출되는 "우아한 종료 콜백"일 뿐입니다 — 유언장을 쓰는 것과 같지, 실시간으로 제거하는 것이 아닙니다;
  • 더 나쁜 것은, 이것이 이펙트의 해제(deactivate에 작성)와 이펙트의 생성(activate에 작성)을 두 곳으로 분리하여 관심사의 지역성(locality of concern)을 위반하고, "완전한 정리"를 검증하기 어렵게 만든다는 점입니다.

💡 "관심사의 지역성"이란 무엇일까요? 책 대출과 반납의 예를 들어 봅시다: activate는 "책을 빌릴 때 등록하는 것", deactivate는 "책을 반납할 때 등록하는 것"입니다. 대출 등록과 반납 등록이 완전히 독립된 두 시스템에 나뉘어 있다면, "빌려간 책이 모두 반납되었는지" 검증하기 어렵습니다. deactivate가 "생성"과 "파괴"를 분리하기 때문에, 정리가 완전한지는 추측 게임이 되어버립니다.

2. 공간적 제약: 플러그인끼리 "거의 서로 의존하지 않는다"

시간 축에서는 제거가 제대로 안 됩니다. 그렇다면 공간 축에서는 어떨까요? 플러그인과 플러그인이 잘 "협력"할 수 있을까요? 논문의 답은 이렇습니다: VSCode는 "확장이 확장에 의존하는" 것에 대한 안전하고 구조화된 방법을 거의 제공하지 않습니다.

VSCode는 확장 간 의존 관계를 선언하는 extensionDependencies라는 필드를 분명 제공합니다. 하지만 실제 사용 현황을 보세요:

설치 수 상위 100개 확장 중, extensionDependencies를 통해 내장이 아닌 확장에 대한 의존을 선언하는 것은 단 7개에 불과합니다.

왜 이렇게 드물까요? 논문은 두 가지 이유를 듭니다:

  1. 확장 API의 형태가 확장을 "각자도생"하게 만든다: 이 API는 명령, 뷰, 언어 기능 등 고정된 표면적 확장 지점만을 노출합니다. 확장은 확장 지점을 통해 호스트에 "기능을 제공"할 뿐 서로 의존하지 않습니다 — 그래서 확장 간 의존이 거의 발생하지 않습니다.
  2. 확장 간 상호작용에 구조화된 계약이 없다: VSCode는 vscode.extensions.getExtension(...).exports를 통해 어떤 확장의 기능을 다른 확장에 노출하지만, 반환값에 타입이 없습니다(기본값은 any) — 의존하는 쪽은 "타입 검사를 거친 인터페이스"에 전혀 의존할 수 없습니다.

🏢 비유를 들어 보겠습니다: 아파트 입주민(확장)은 관리사무소(호스트)하고만 거래합니다. 옆집에서 무언가를 빌리고 싶다고요? 관리사무소를 통해 쪽지를 전달(exports)할 수밖에 없고, 게다가 쪽지 내용의 형식은 아묵도 검사하지 않습니다(타입 검사 없음). 이웃의 숙제를 베끼고 싶어도, 이웃이 주는 것은 앞뒤 맥락 없는 쪽지뿐이라 형식이 맞기를 기도할 수밖에 없습니다.

요약: VSCode는 확장이 호스트가 제공하는 고정된 확장 지점 집합을 사용하도록 유도하지만, "확장끼리 서로 의존하는" 안전하고 구조화된 방법은 제공하지 않습니다.

⚠️ 참고로, 이 두 가지 제약은 VSCode만의 문제가 아닙니다 — 다양한 플러그인 시스템에서 정도의 차이만 있을 뿐 나타납니다. 그러니 이것은 특정 제품의 사소한 결함이 아니라 "플러그인 아키텍처" 전체의 고질병이며, 논문이 수술하려는 바로 그 지점입니다.

3. 왜 자기 진화하는 AI 에이전트에게는 더욱 필요한가?

어떤 사람은 이렇게 말할 것입니다: VSCode 플러그인 문제쯤은 참으면 되는 것 아닌가? 참을 수 없습니다. AI 에이전트 시대는 이 고통을 수없이 증폭시키기 때문입니다.

현대의 AI 에이전트는 런타임 에이전트 프레임워크에 의존합니다: 각종 도구 스위트와 실행 환경을 조합하고, 권한과 샌드박스를 관리하고, 세션 상태와 영속성을 유지하고, 컨텍스트 관리와 메모리를 제공하고, 서브 에이전트와 멀티 에이전트 워크플로를 오케스트레이션하고, 사용자 및 자동화 시스템에 인터페이스를 제공합니다. 그리고 미래의 에이전트 프레임워크는 요청을 계속 처리하는 동시에 자기 컴포넌트에 대한 수정을 생성하고 배포할 수도 있습니다 — 이것이 "자기 진화"입니다. 모델이 합성한 재사용 가능한 도구는 "컴포넌트 수준 자기 수정"의 범위가 더 좁은 선구적 형태입니다.

핵심은 이것입니다: 이러한 자기 수정은 매번 그 자체로 하나의 동적 컴포지션 인스턴스입니다 — 새로운 컴포넌트를 설치하거나, 오래된 컴포넌트를 제거하는 것입니다. 그리고 이러한 수정은 지속적으로 발생하며 인간의 감독이 제한적이거나 심지어 전혀 없기 때문에, 동적 컴포저빌리티는 "불가결"합니다. 이것이 없다면 어떻게 될까요?

부족한 것결과
시간적 컴포저빌리티자기 수정마다 전체 프로세스의 재시작이 강제되고, 프로세스에 로컬로 축적된 모든 상태가 버려집니다. 이러한 빈도로 발생하면 누적된 다운타임이 상당하며, 실행 중인 작업은 계속해서 중단됩니다. 더 나쁘게는: 결함 있는 자기 수정이 "복구에 필요한 프로세스" 자체를 무효화할 수 있습니다
공간적 컴포저빌리티모든 모듈이 스스로 의존하는 모듈의 변화를 감지하고, 그 모듈이 나타나거나 사라지거나 정체성이 바뀔 때 적응해야 하는데, 임시방편의 수단에만 의지할 수밖에 없습니다. 더 나쁘게는: 순진한 코드 교체 전략은 의존하는 쪽을 소리소문 없이 파괴하거나, 리로드할 때만 드러나는 순환 의존을 도입할 수 있습니다

⚠️ 이 두 가지 "더 나쁘게는"에 주목하세요: 시간 차원에서는 "시스템을 복구하는 데 쓰이는 프로세스"조차 망가질 수 있습니다 — 마지막 구명줄마저 불태우는 셈입니다. 공간 차원에서는 파괴가 "소리소문 없이" 일어나며, 리로드할 때야 문제가 드러나지만 그때는 이미 늦습니다. 오랫동안 무인으로 운영되는 에이전트에게 이 두 가지 "더 나쁘게는"는 모두 재앙입니다.

4. 거친 입도의 우회책: 쓸 만은 하지만 대가가 크다

이렇게 물을 수도 있습니다: 이렇게 명백한 문제를 왜 아묵도 해결하지 않았을까? 논문이 낸 답은 정직합니다: 운영체제와 컨테이너 오케스트레이터가 이미 "거친 입도"의 대안을 제공해 와서, 모두 그것으로 오래도록 그럭저럭 버텨왔기 때문입니다:

  • 프로세스를 종료하고 재시작전체 주소 공간의 입도에서 시간적 컴포저빌리티를 실현할 수 있습니다;
  • 컨테이너 오케스트레이션전체 서비스의 입도에서 공간적 컴포저빌리티를 실현할 수 있습니다.

실제로 대부분의 소프트웨어는 이 두 가지 수단으로 마지노선을 지킵니다: 모듈의 동작이 이상하면 프로세스를 재시작하고, 서비스 의존 관계는 오케스트레이터에 맡기는 것입니다.

그러나 이 우회책은 대가가 큽니다:

  • 시간 차원: 재시작할 때마다 프로세스에 로컬로 축적된 모든 상태 — 캐시, 연결, 부분적으로 완료된 계산 — 가 전부 초기화됩니다. 이러한 상태를 재구축하는 데는 수 초에서 수 분이 걸립니다. 그동안 가용성을 유지하고 싶다고요? 중복된 레플리카를 돌릴 수밖에 없고, 리소스 오버헤드로 "개별 컴포넌트를 복구할 수 없는" 문제를 메우게 됩니다.
  • 공간 차원: 컨테이너 수준 오케스트레이션은 같은 주소 공간을 공유하는 컴포넌트 간의 의존을 표현할 수 없습니다. 본래 로컬 함수 호출 한 번이면 끝날 상호작용이 네트워크 오버헤드를 감당해야 합니다.
传统做法换 1 个插件 → 重启整个宿主插件A插件B插件C插件D全部一起停 ❌Cordis 做法换 1 个插件 → 只拆它自己插件A插件B插件C插件D只停 1 个,其他继续 ✅

粒度不匹配:想换一个组件,传统做法却要推倒整个进程

이것이 입도 불일치입니다: 재시작과 오케스트레이션은 프로세스와 컨테이너의 경계에서 작동하지만, 현대 시스템은 점점 더 세밀한 수준에서 조합됩니다. 우리에게 필요한 것은 컴포넌트 자체와 같은 수준에서 이펙트와 의존을 관리할 수 있는 컴포지셔널 추상화 — 컴포넌트 수준의 "설치, 제거, 서로를 파괴하지 않음"입니다. 이것이 바로 논문이 이어서 세워 나갈 형식화의 기초입니다.

핵심 포인트 복습

이번 레슨은 정볼량이 적지 않지만, 다음 다섯 문장만 기억하면 충분합니다:

  1. 시간적 제약: VSCode의 모든 확장은 하나의 "확장 호스트" 프로세스를 공유하며, 런타임에 개별 확장을 언로드하는 메커니즘이 없습니다. 설치 수 상위 100개 확장 중 87개가 코드를 포함하며, 제거하려면 호스트 전체를 재시작해야 합니다.
  2. deactivate는 언로드가 아니다: 이것은 호스트 프로세스 종료 시의 "임종 콜백"일 뿐입니다. 해제와 생성을 두 곳으로 분리하여 관심사의 지역성을 위반하므로, 완전한 정리를 검증하기 어렵습니다.
  3. 공간적 제약: 확장은 호스트에 기능을 제공할 뿐 거의 서로 의존하지 않습니다(상위 100개 중 의존을 선언하는 것은 7개뿐). exports로 기능을 노출핼도 반환값은 any이며, 타입화된 계약이 없습니다.
  4. 자기 진화하는 에이전트: 자기 수정은 매번 하나의 동적 컴포지션입니다. 시간적 컴포저빌리티가 없으면 빈번한 재시작으로 상태를 잃고 복구 프로세스조차 망가질 수 있습니다. 공간적 컴포저빌리티가 없으면 모듈이 각자 임시방편으로 처리하고 파괴는 소리소문 없이 일어납니다.
  5. 우회책의 입도 불일치: 프로세스 재시작과 컨테이너 오케스트레이션은 프로세스/컨테이너 경계에서 작동하며, 상태 손실, 중복 레플리카, 네트워크 오버헤드라는 대가가 따르고, 컴포넌트 자체의 수준에서 이펙트와 의존을 관리할 수 없습니다.

🚀 다음 레슨 예고: 동기는 분명해졌습니다 — 우리에게 필요한 것은 "컴포넌트 수준의 설치와 제거"입니다. 3과에서는 논문의 세 가지 핵심 기여(되돌릴 수 있는 이펙트, 리액티브 사이드 이펙트, 컴포넌트 생명주기 모델)을 살펴고, 전편을 관통하는 타입 판정 표기법을 알아보겠습니다.

셀프 테스트 · 동기가 되는 예시

답을 모두 선택한 후 「답안 제출」을 클릭하면 정답 여부와 해설을 확인할 수 있습니다.

1. 왜 VSCode에서 "코드를 포함하는 확장"을 비활성화하거나 언인스톨하려면 확장 호스트 프로세스 전체를 재시작해야 합니까?
2. VSCode의 deactivate 훅에 대해 올바른 설명은 무엇입니까?
3. 왜 VSCode 확장끼리는 거의 서로 의존하지 않습니까? (상위 100개 중 내장이 아닌 확장에 대한 의존을 선언하는 것은 7개뿐)
4. 왜 "프로세스 재시작, 컨테이너 오케스트레이션" 같은 거친 입도의 우회책은 자기 진화하는 에이전트 프레임워크의 요구를 충족하지 못합니까?