---
title: "agent-shell + OpenCode 커스텀 가이드: ACP 프로토콜 통합"
url: "https://notes.junghanacs.com/notes/20251121T131412"
author: "Junghan Kim"
date: "2025-11-21T13:14:00+09:00"
lastmod: "2025-11-21T13:14:00+09:00"
tags: ["emacs"]
description: "agent-shell과 OpenCode를 ACP 프로토콜로 직접 통합하는 방법을 정리한 노트이다."
---

> [!abstract] 이 노트에 대하여
> 
> Emacs의 agent-shell에서 OpenCode를 ACP로 직접 연결해 LSP와 서브에이전트, 다중 모델 기능을 최대한 끌어내는 구성을 설명한다. 커스텀 통합 가이드 성격의 실무 노트다.

## 관련노트

-   [§OPENCODE: #오픈코드 TUI ACP LSP](https://notes.junghanacs.com/notes/20251024T085736)

## 히스토리

-   [2025-11-21 Fri 13:20] 에이전트에게 부탁

## agent-shell + OpenCode 커스텀 가이드

### 개요

Emacs의 agent-shell에서 OpenCode를 ACP(Agent Client Protocol) 프로토콜로 직접 연결하여 사용하는 환경입니다. 기존 Zed의 claude-code-acp wrapper와 달리, OpenCode의 모든 기능을 활용할 수 있습니다.

### 🎯 프로젝트 목표

agent-shell을 커스터마이징하여 OpenCode의 핵심 강점을 최대한 활용:

-   LSP 기능 통합
-   서브에이전트 시스템 활용
-   다중 모델 지원
-   MCP 서버 생태계 연동
-   권한 관리 최적화

### 📊 현재 환경 구조

#### 연결 구조

\`\`\` Emacs (agent-shell) └─ ACP Protocol (direct) └─ OpenCode Agent ├─ Model: Claude 3.7 Sonnet (기본) ├─ 서브에이전트: PM, code-refactor, code-reviewer, security-forensics └─ MCP Servers: GitHub, Google Workspace, Jira, Playwright 등 \`\`\`

#### Zed 방식과의 차이

| 항목   | Zed (claude-code-acp) | Emacs (agent-shell + OpenCode)    |
|------|-----------------------|-----------------------------------|
| 연결 방식 | Wrapper → Claude API  | ACP → OpenCode → 다중 모델        |
| 모델 선택 | Claude만              | Anthropic, OpenRouter, DeepSeek 등 |
| 서브에이전트 | ❌                    | ✅ PM, refactor, reviewer 등      |
| MCP 서버 | 제한적                | 전체 접근                         |
| 권한 처리 | Zed UI                | 세션 레벨 공유                    |
| LSP 통합 | ❌                    | ✅ (개발 예정)                    |

### 🔧 OpenCode 아키텍처

#### 1. ACP 에이전트 (acp/agent.ts)

##### 핵심 메서드

\`\`\`typescript // 세션 초기화 async initialize(params: InitializeRequest): Promise&lt;InitializeResponse&gt;

// 모델 변경 async setSessionModel(params: SetSessionModelRequest)

// 모드(에이전트) 변경 async setSessionMode(params: SetSessionModeRequest)

// 프롬프트 처리 async prompt(params: PromptRequest) \`\`\`

##### 이벤트 구독 (setupEventSubscriptions)

\`\`\`typescript this.config.sdk.event.subscribe({ query: { directory } }).then(async (events) =&gt; { for await (const event of events.stream) { switch (event.type) { case "permission.updated": _/ 권한 요청 case "message.part.updated": /_ 메시지 부분 업데이트 (텍스트, 추론, 도구 호출) } } }) \`\`\`

#### 2. 권한 시스템 (permission/index.ts)

##### 세션 레벨 권한 관리

\`\`\`typescript export async function ask(input: { type: string title: string pattern?: string | string[] sessionID: string messageID: string metadata: Record&lt;string, any&gt; }) { // 1. 이미 승인된 권한 확인 if (covered(keys, approvedForSession)) return

// 2. Plugin 트리거 (설정 파일 기반) switch (await Plugin.trigger("permission.ask", info)) { case "deny": throw new RejectedError() case "allow": return }

// 3. 사용자 응답 대기 return new Promise&lt;void&gt;((resolve, reject) =&gt; { pending[sessionID][info.id] = { info, resolve, reject } Bus.publish(Event.Updated, info) }) } \`\`\`

##### 응답 처리

\`\`\`typescript export function respond(input: { sessionID: string permissionID: string response: "once" | "always" | "reject" }) { if (response `=` "always") { approved[sessionID][pattern] = true // 세션 전체에 적용 } } \`\`\`

**핵심**: ACP 모드에서 \`always\`를 선택하면, 서브에이전트를 포함한 모든 도구 호출이 세션 동안 자동 승인됨!

#### 3. 서브에이전트 시스템 (agent/agent.ts)

##### 내장 에이전트

-   \`general\`: 범용 에이전트 (복잡한 검색, 다단계 작업)
-   \`build\`: 기본 빌드/개발 에이전트
-   \`plan\`: 계획 전용 (읽기 전용 bash, edit 불가)

##### 커스텀 에이전트 설정 (opencode.jsonc)

\`\`\`jsonc { "agent": { "PM": { "description": "PM 에이전트 역할", "mode": "subagent", "model": "openrouter/openai/gpt-5.1-chat", "prompt": "PM 에이전트 프롬프트...", "tools": { "write": true, "edit": false } }, "code-refactor": { "mode": "subagent", "model": "openrouter/openai/gpt-5.1-codex", "prompt": "You are a code refactoring expert...", "tools": { "write": true, "edit": true, "bash": true } } } } \`\`\`

##### 권한 설정

\`\`\`typescript const defaultPermission: Agent.Info["permission"] = { edit: "allow", bash: { "\*": "allow" }, webfetch: "allow", doom_loop: "ask", external_directory: "ask" }

// plan 모드는 읽기 전용 const planPermission = { edit: "deny", bash: { "ls\*": "allow", "git status\*": "allow", "find **": "allow", "**": "ask" } } \`\`\`

#### 4. 프로바이더 시스템 (provider/provider.ts)

##### 현재 사용 가능한 프로바이더

-   \`anthropic\`: Claude 시리즈
-   \`openrouter\`: 다양한 모델 프록시
-   \`deepseek\`: DeepSeek 모델
-   \`opencode\`: OpenCode 자체 모델

##### 모델 ID 형식

\`\`\` {providerID}/{modelID}

예시:

-   anthropic/claude-3-7-sonnet-20250219
-   openrouter/openai/gpt-5.1-chat
-   deepseek/deepseek-coder

\`\`\`

##### 커스텀 로더 예시

\`\`\`typescript const CUSTOM_LOADERS: Record&lt;string, CustomLoader&gt; = { async anthropic() { return { autoload: false, options: { headers: { "anthropic-beta": "claude-code-20250219,interleaved-thinking-2025-05-14" } } } } } \`\`\`

### 🎨 커스터마이징 전략

#### 1. LSP 통합 계획

##### 목표

-   Emacs LSP 서버와 OpenCode 연동
-   코드 네비게이션, 자동완성, 타입 정보를 AI에게 제공
-   에이전트가 LSP 정보를 활용하여 더 정확한 코드 수정

##### 구현 방향

1.  **MCP 서버로 LSP 래퍼 개발** \`\`\`typescript // mcp-lsp-server { "name": "lsp", "tools": [ "lsp_find_definition", "lsp_find_references", "lsp_get_hover", "lsp_get_completion" ] } \`\`\`

2.  **agent-shell에서 LSP 컨텍스트 전달** \`\`\`elisp (defun agent-shell-send-with-lsp-context (prompt) (let ((lsp-info (get-current-lsp-context))) (agent-shell-send-prompt (format "%s\n\nLSP Context:\n%s" prompt lsp-info)))) \`\`\`

3.  **OpenCode 에이전트에 LSP 도구 추가** \`\`\`jsonc { "agent": { "build": { "tools": { "lsp_find_definition": true, "lsp_find_references": true } } } } \`\`\`

#### 2. 서브에이전트 최적화

##### 작업별 모델 할당

\`\`\`jsonc { "agent": { "code-refactor": { "model": "deepseek/deepseek-coder", _/ 코드 리팩토링에 특화 "temperature": 0.2 }, "PM": { "model": "openrouter/openai/gpt-5.1", /_ 기획/관리 "temperature": 0.7 }, "code-reviewer": { "model": "anthropic/claude-sonnet-4-5", // 코드 리뷰 "temperature": 0 } } } \`\`\`

##### Task 도구 활용

\`\`\`typescript // 메인 에이전트에서 서브에이전트 호출 await task({ subagent_type: "code-refactor", description: "Refactor authentication module", prompt: "리팩토링할 파일: src/auth.ts\n목표: 스레드 안전성 개선" }) \`\`\`

#### 3. 권한 프리셋

##### 개발 모드 (빠른 반복)

\`\`\`jsonc { "permission": { "write": "allow", "edit": "allow", "bash": "allow", "doom_loop": "allow" } } \`\`\`

##### 프로덕션 모드 (안전 우선)

\`\`\`jsonc { "permission": { "write": "ask", "edit": "ask", "bash": { "git **": "allow", "ls \*": "allow", "**": "ask" }, "doom_loop": "deny" } } \`\`\`

##### 읽기 전용 모드 (분석/리뷰)

\`\`\`jsonc { "agent": { "analyzer": { "mode": "primary", "permission": { "write": "deny", "edit": "deny", "bash": { "git **": "allow", "find \*": "allow", "grep \*": "allow", "**": "deny" } } } } } \`\`\`

#### 4. MCP 서버 최적화

##### 선별적 활성화

\`\`\`jsonc { "mcp": { // 항상 활성화 "git": { "enabled": true }, "github": { "enabled": true },

_/ 프로젝트별 활성화 "playwright-extension": { "enabled": false }, /_ 웹 프로젝트만 "nixos": { "enabled": true }, // NixOS 환경

_/ 작업별 활성화 "browserbase": { "enabled": false }, /_ 필요시만 "n8n-mcp": { "enabled": false } } } \`\`\`

##### agent-shell 통합

\`\`\`elisp (defun agent-shell-enable-mcp-server (server-name) "특정 MCP 서버를 동적으로 활성화" (agent-shell-send-command (format "opencode mcp add %s" server-name)))

;; 예시: 브라우저 자동화 작업 시작 (agent-shell-enable-mcp-server "playwright-extension") \`\`\`

### 🔄 워크플로우 예시

#### 1. 코드 리팩토링 워크플로우

\`\`\`

1.  [Emacs] 파일 선택 및 컨텍스트 수집
    -   LSP: 타입 정보, 참조 위치
    -   Git: 변경 이력

2.  [agent-shell] code-refactor 에이전트 호출 (agent-shell-send-with-context "이 모듈의 스레드 안전성을 개선해주세요" :lsp-context t :git-context t)

3.  [OpenCode] code-refactor 에이전트 실행
    -   Model: deepseek/deepseek-coder
    -   Tools: read, edit, bash (git), lsp_find_references
    -   Permission: edit=allow (세션에서 always 선택 시)

4.  [OpenCode] 리팩토링 수행
    -   LSP로 모든 참조 찾기
    -   변경 사항 적용
    -   Git diff 확인

5.  [agent-shell] code-reviewer 에이전트로 검증 (agent-shell-switch-agent "code-reviewer") "방금 리팩토링한 코드를 리뷰해주세요"

6.  [Emacs] 결과 확인 및 적용

\`\`\`

#### 2. PM 워크플로우

\`\`\`

1.  [agent-shell] PM 에이전트 활성화 (agent-shell-switch-agent "PM")

2.  [OpenCode] PM 에이전트 실행
    -   Model: openrouter/openai/gpt-5.1
    -   Tools: write=true (메모리 파일 생성)
    -   MCP: google-workspace-work (타임로그)

3.  메모리 스캔 및 정리 "~/claude-memory를 스캔하고 중복 문서를 통합해주세요"

4.  Google Chat에 타임로그 "현재 작업 상태를 구글챗에 남겨주세요"

5.  [Emacs] 정리된 메모리 확인 (dired "~/claude-memory")

\`\`\`

#### 3. 복잡한 버그 수정 워크플로우

\`\`\`

1.  [Emacs] 버그 리포트와 관련 파일 수집

2.  [agent-shell] general 에이전트로 분석 "이 버그의 원인을 찾아주세요"
    -   코드베이스 전체 검색
    -   관련 파일 식별

3.  [agent-shell] code-reviewer로 검증 (agent-shell-switch-agent "code-reviewer") "방금 찾은 원인이 맞는지 확인해주세요"

4.  [agent-shell] build 에이전트로 수정 (agent-shell-switch-agent "build") "확인된 원인을 수정하고 테스트를 실행해주세요"

5.  [OpenCode] 테스트 실행 및 검증
    -   bash: npm test
    -   실패 시 재시도

6.  [Emacs] Git 커밋 (magit-commit)

\`\`\`

### 📁 설정 파일 구조

#### OpenCode 설정 (~/.config/opencode/ 또는 프로젝트/.opencode/)

\`\`\` .opencode/ ├── opencode.jsonc # 메인 설정 │ ├── model # 기본 모델 │ ├── agent # 커스텀 에이전트 │ ├── permission # 권한 설정 │ ├── mcp # MCP 서버 설정 │ └── command # 커스텀 명령어 │ ├── AGENTS.md # 에이전트 프롬프트 └── package.json # MCP 의존성 \`\`\`

#### 프로젝트별 설정 예시

\`\`\`jsonc // .opencode/opencode.jsonc { "instructions": ["AGENTS.md", "PROJECT_CONTEXT.md"], "model": "anthropic/claude-sonnet-4-5",

"agent": { "build": { "permission": { "edit": "ask", "bash": { "npm **": "allow", "git \*": "allow", "**": "ask" } } },

"test": { "mode": "subagent", "model": "deepseek/deepseek-coder", "prompt": "You are a test engineer. Write comprehensive tests.", "tools": { "write": true, "edit": true, "bash": true }, "permission": { "bash": { "npm test\*": "allow", "npm run test\*": "allow", "\*": "ask" } } } },

"command": { "test": { "template": "Run tests and fix any failures", "agent": "test", "model": "deepseek/deepseek-coder" } },

"mcp": { "git": { "enabled": true }, "github": { "enabled": true }, "playwright-extension": { "enabled": false } } } \`\`\`

### 🛠️ agent-shell 커스터마이징

#### Elisp 통합 함수

\`\`\`elisp ;; ~/.emacs.d/init.el 또는 agent-shell 설정

(defun agent-shell-opencode-switch-agent (agent-name) "OpenCode 에이전트를 변경합니다." (interactive (list (completing-read "Agent: " '("build" "plan" "PM" "code-refactor" "code-reviewer" "security-forensics")))) (agent-shell-send-command (format "/mode %s" agent-name)))

(defun agent-shell-opencode-switch-model (model-id) "OpenCode 모델을 변경합니다." (interactive (list (completing-read "Model: " '("anthropic/claude-sonnet-4-5" "anthropic/claude-haiku-4-5" "openrouter/openai/gpt-5.1-chat" "deepseek/deepseek-coder")))) ;; ACP 프로토콜로 모델 변경 요청 (agent-shell-send-acp-request 'setSessionModel \`((modelId . ,model-id))))

(defun agent-shell-opencode-with-lsp-context () "현재 LSP 컨텍스트와 함께 프롬프트를 전송합니다." (interactive) (let\* ((symbol (thing-at-point 'symbol)) (definition (lsp-find-definition)) (references (lsp-find-references)) (hover (lsp-hover)) (context (format "Symbol: %s\nDefinition: %s\nReferences: %d\nHover: %s" symbol definition (length references) hover)) (prompt (read-string "Prompt: "))) (agent-shell-send-prompt (format "%s\n\n&lt;lsp-context&gt;\n%s\n&lt;/lsp-context&gt;" prompt context))))

(defun agent-shell-opencode-enable-mcp (server-name) "MCP 서버를 동적으로 활성화합니다." (interactive (list (completing-read "MCP Server: " '("playwright-extension" "browserbase" "n8n-mcp" "supabase")))) (agent-shell-send-command (format "opencode mcp add %s" server-name)))

;; 키바인딩 (with-eval-after-load 'agent-shell (define-key agent-shell-mode-map (kbd "C-c a a") 'agent-shell-opencode-switch-agent) (define-key agent-shell-mode-map (kbd "C-c a m") 'agent-shell-opencode-switch-model) (define-key agent-shell-mode-map (kbd "C-c a l") 'agent-shell-opencode-with-lsp-context) (define-key agent-shell-mode-map (kbd "C-c a e") 'agent-shell-opencode-enable-mcp)) \`\`\`

#### ACP 프로토콜 직접 제어

\`\`\`elisp (defun agent-shell-send-acp-request (method params) "ACP 프로토콜 요청을 직접 전송합니다." (let ((request (json-encode \`((jsonrpc . "2.0") (id . ,(number-to-string (random 10000))) (method . ,method) (params . ,params))))) (agent-shell-send-raw request)))

;; 예시: 세션 모드 변경 (agent-shell-send-acp-request 'setSessionMode '((sessionId . "current-session-id") (modeId . "code-refactor")))

;; 예시: 모델 변경 (agent-shell-send-acp-request 'setSessionModel '((sessionId . "current-session-id") (modelId . "deepseek/deepseek-coder"))) \`\`\`

### 🧪 테스트 시나리오

#### 1. 권한 시스템 테스트

\`\`\`

1.  새 세션 시작
2.  파일 쓰기 시도 → "once" 선택
3.  다시 파일 쓰기 → 다시 물어봄 (once 효과)
4.  "always" 선택
5.  서브에이전트로 전환 (code-refactor)
6.  파일 쓰기 → 물어보지 않음 (세션 레벨 권한 공유)

\`\`\`

#### 2. 모델 전환 테스트

\`\`\`

1.  기본 모델 확인: Claude 3.7 Sonnet
2.  code-refactor 에이전트로 전환
3.  자동으로 deepseek/deepseek-coder로 전환 확인
4.  수동으로 모델 변경: anthropic/claude-haiku-4-5
5.  작업 완료 후 기본 모델로 복귀

\`\`\`

#### 3. MCP 서버 동적 로딩 테스트

\`\`\`

1.  기본 MCP 서버만 활성화 (git, github)
2.  브라우저 작업 필요 시:
    -   playwright-extension 활성화
    -   작업 수행
    -   비활성화 (메모리 절약)
3.  대규모 스크래핑 필요 시:
    -   browserbase 활성화
    -   병렬 작업 실행

\`\`\`

### 📚 참고 자료

#### OpenCode 소스 코드

-   전체 코드: ~/repos/3rd/opencode/
-   ACP 에이전트: packages/opencode/src/acp/agent.ts
-   권한 시스템: packages/opencode/src/permission/index.ts
-   에이전트 시스템: packages/opencode/src/agent/agent.ts
-   프로바이더: packages/opencode/src/provider/provider.ts

#### 설정 파일

-   OpenCode 설정: ~/sync/emacs/claude-config/opencode/opencode.jsonc
-   인증 정보: ~/.local/share/opencode/auth.json
-   현재 디바이스: ~/.current-device

#### 관련 문서

-   [브라우저 자동화 도구 비교](https://notes.junghanacs.com/notes/20251121T122829)
-   OpenCode 공식 문서: <https://opencode.ai/docs>
-   ACP 프로토콜: <https://github.com/agentclientprotocol>

### 🎬 다음 단계

#### 단계 1: agent-shell 기본 통합

-   [ ] OpenCode ACP 연결 안정화
-   [ ] 에이전트 전환 UI 개선
-   [ ] 권한 프리셋 설정

#### 단계 2: LSP 통합

-   [ ] MCP LSP 서버 개발
-   [ ] Emacs LSP 컨텍스트 추출
-   [ ] OpenCode 에이전트에 LSP 도구 추가

#### 단계 3: 고급 기능

-   [ ] 다중 모델 프로필 (작업별 자동 전환)
-   [ ] MCP 서버 동적 로딩
-   [ ] 세션 이력 관리 및 복원

#### 단계 4: 워크플로우 최적화

-   [ ] 코드 리뷰 자동화
-   [ ] PM 에이전트 메모리 관리 통합
-   [ ] Git 워크플로우 연동

### 💡 핵심 포인트

1.  **ACP 직접 연결의 장점**
    -   Zed wrapper 없이 OpenCode 전체 기능 활용
    -   다중 모델 선택 가능
    -   서브에이전트 시스템 완전 지원

2.  **세션 레벨 권한 관리**
    -   \`always\` 선택 시 서브에이전트 포함 전체 적용
    -   세션별로 독립적인 권한 상태 유지
    -   설정 파일로 기본 권한 프리셋 가능

3.  **서브에이전트의 힘**
    -   작업별로 최적화된 모델 사용
    -   권한을 세밀하게 제어
    -   도구 접근 제한 가능 (plan 모드: 읽기 전용)

4.  **LSP 통합 잠재력**
    -   코드 컨텍스트를 AI에게 정확하게 전달
    -   타입 안전한 리팩토링
    -   네비게이션 기반 코드 분석

5.  **MCP 생태계**
    -   필요한 서버만 선별적 활성화
    -   메모리 및 성능 최적화
    -   프로젝트별 커스터마이징

### 메타데이터

-   작성일: 2025-11-21 금요일 13:14
-   작성자: AI Assistant (OpenCode via ACP)
-   컨텍스트: agent-shell + OpenCode 커스터마이징
-   디바이스: LAPTOP
-   목적: 향후 커스터마이징 작업 시 빠른 문맥 전환
