From 424514dd67a0d7dbafd9a5f739f9aee0d74eb356 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=84=EB=8F=84=ED=98=84?= <181519138+cosmosjeon@users.noreply.github.com> Date: Tue, 11 Aug 2026 02:51:53 +0900 Subject: [PATCH 1/2] docs(learning): scaffold senpi study guide --- learning/README.md | 70 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 learning/README.md diff --git a/learning/README.md b/learning/README.md new file mode 100644 index 000000000..57305b89a --- /dev/null +++ b/learning/README.md @@ -0,0 +1,70 @@ +# Senpi 소스 코드 학습 가이드 + +이 폴더는 Senpi를 처음부터 직접 읽고 이해하려는 개발자를 위한 한국어 학습 자료다. 기능 사용법을 나열하기보다, 사용자 입력 하나가 모델 호출과 도구 실행을 거쳐 화면과 세션에 기록되기까지의 흐름을 중심으로 설명한다. + +## 대상 독자 + +- TypeScript 코드를 읽을 수 있지만 AI 에이전트 런타임은 처음인 개발자 +- Senpi의 구조를 이해한 뒤 작은 기능이나 확장을 직접 만들어 보고 싶은 개발자 +- 개별 파일보다 전체 실행 흐름을 먼저 이해하고 싶은 개발자 + +## 학습 목표 + +이 가이드를 끝까지 읽으면 다음 질문에 답할 수 있어야 한다. + +1. `senpi`를 실행했을 때 어떤 객체들이 어떤 순서로 만들어지는가? +2. 사용자 메시지가 어떻게 LLM 요청으로 변환되는가? +3. 모델이 요청한 도구는 누가 검증하고 실행하며 결과는 어디에 저장되는가? +4. `Agent`, `AgentSession`, `ExtensionRunner`, `InteractiveMode`는 각각 무엇을 책임지는가? +5. 대화가 길어지거나 명령이 백그라운드에서 실행될 때 Senpi는 어떻게 작업을 이어 가는가? +6. 기능을 수정할 때 어느 패키지와 테스트부터 살펴봐야 하는가? + +## 목차 + +1. [전체 구조와 실행 흐름](01-architecture-and-flow.md) +2. [LLM 통신과 Agent Loop](02-llm-and-agent-loop.md) +3. [Coding Agent, 세션과 Tool](03-coding-agent-session-tools.md) +4. [Extension과 Senpi 주요 기능](04-extensions-and-features.md) +5. [컨텍스트 관리와 장시간 실행](05-context-and-long-running-work.md) +6. [TUI, 실행 모드와 품질 관리](06-ui-modes-and-quality.md) +7. [용어집](glossary.md) + +## 각 장을 읽는 방법 + +각 장은 같은 순서로 구성한다. + +1. **먼저 한 문장으로**: 이 장의 핵심을 짧게 잡는다. +2. **쉽게 이해하기**: 낯선 개념을 익숙한 비유와 작은 예제로 설명한다. +3. **코드로 자세히 보기**: 실제 패키지, 객체, 함수와 호출 순서를 연결한다. +4. **소스 읽기 경로**: 처음부터 거대한 파일 전체를 읽지 않도록 권장 순서를 제시한다. +5. **확인 문제와 실습**: 이해한 내용을 직접 코드에서 확인한다. + +처음 읽을 때는 코드의 모든 분기를 이해하려 하지 않아도 된다. 첫 번째 회독에서는 “누가 누구를 호출하는가”만 잡고, 두 번째 회독부터 오류 처리와 확장 이벤트를 살펴보는 편이 효율적이다. + +## 전체 학습 경로 + +```text +전체 실행 흐름 + ↓ +LLM 메시지와 Agent Loop + ↓ +AgentSession과 기본 Tool + ↓ +Extension과 Builtin 기능 + ↓ +Compaction·Terminal·Codemode + ↓ +TUI·원격 모드·테스트·유지보수 +``` + +## 코드와 문서가 다를 때 + +Senpi는 빠르게 변하는 장기 포크이므로 설명 문서와 실제 등록 코드 사이에 시차가 생길 수 있다. 판단할 때는 다음 순서를 권장한다. + +1. 실제 등록 배열과 호출 코드 +2. 해당 디렉터리에서 가장 가까운 `changes.md` +3. 관련 테스트 +4. 해당 디렉터리의 `AGENTS.md` +5. 최상위 README와 일반 설명 문서 + +이 가이드는 큰 구조를 이해하기 위한 출발점이다. 세부 동작을 수정하려면 반드시 해당 코드와 테스트를 다시 확인해야 한다. From 82dc1e528081e375df74b7b12f59a234c2827974 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A0=84=EB=8F=84=ED=98=84?= <181519138+cosmosjeon@users.noreply.github.com> Date: Tue, 11 Aug 2026 02:58:33 +0900 Subject: [PATCH 2/2] docs(learning): complete senpi source curriculum --- learning/01-architecture-and-flow.md | 262 ++++++++++++++++ learning/02-llm-and-agent-loop.md | 287 +++++++++++++++++ learning/03-coding-agent-session-tools.md | 278 +++++++++++++++++ learning/04-extensions-and-features.md | 304 +++++++++++++++++++ learning/05-context-and-long-running-work.md | 285 +++++++++++++++++ learning/06-ui-modes-and-quality.md | 285 +++++++++++++++++ learning/glossary.md | 216 +++++++++++++ 7 files changed, 1917 insertions(+) create mode 100644 learning/01-architecture-and-flow.md create mode 100644 learning/02-llm-and-agent-loop.md create mode 100644 learning/03-coding-agent-session-tools.md create mode 100644 learning/04-extensions-and-features.md create mode 100644 learning/05-context-and-long-running-work.md create mode 100644 learning/06-ui-modes-and-quality.md create mode 100644 learning/glossary.md diff --git a/learning/01-architecture-and-flow.md b/learning/01-architecture-and-flow.md new file mode 100644 index 000000000..8d839488b --- /dev/null +++ b/learning/01-architecture-and-flow.md @@ -0,0 +1,262 @@ +# 1장. Senpi 전체 구조와 실행 흐름 + +## 먼저 한 문장으로 + +Senpi는 **LLM 통신**, **에이전트 반복 실행**, **코딩 세션 관리**, **터미널 UI**를 서로 다른 패키지로 나누고, `coding-agent`가 이들을 하나의 프로그램으로 조립한 프로젝트다. + +## 이 장에서 답할 질문 + +- Senpi는 왜 여러 패키지로 나뉘어 있을까? +- 사용자가 입력한 한 문장은 어떤 경로로 모델까지 전달될까? +- 모델이 `read`나 `bash`를 요청하면 누가 실행할까? +- `Agent`와 `AgentSession`은 무엇이 다를까? +- 처음 코드를 읽을 때 어느 파일부터 시작해야 할까? + +## 1. 쉽게 이해하기: Senpi는 작은 조직이다 + +Senpi를 하나의 개발 조직이라고 생각해 보자. + +- `packages/ai`는 여러 외부 업체와 대화하는 **통역팀**이다. +- `packages/agent`는 일을 한 단계씩 진행하는 **실무 담당자**다. +- `packages/coding-agent`는 파일, 세션, 확장 기능을 조율하는 **프로젝트 매니저**다. +- `packages/tui`는 현재 상황을 사용자에게 보여 주는 **상황판**이다. +- `packages/pty`는 오래 실행되는 명령을 관리하는 **터미널 운영팀**이다. +- `packages/protocol`, `client`, `server`는 다른 프로세스와 연결하는 **통신팀**이다. + +각 팀이 분리되어 있기 때문에 `packages/agent`는 터미널 UI 없이도 사용할 수 있고, `packages/ai`는 코딩 에이전트가 아닌 다른 프로그램에서도 사용할 수 있다. + +## 2. 핵심 패키지 지도 + +### `packages/ai`: 모델과 통신하는 계층 + +이 패키지는 OpenAI, Anthropic, Google처럼 서로 다른 API를 공통 인터페이스로 감싼다. + +주요 책임은 다음과 같다. + +- 모델과 provider 정보 표현 +- user/assistant/tool 메시지 타입 정의 +- streaming 응답을 공통 이벤트로 변환 +- provider별 요청 형식과 인증 처리 +- thinking, tool call, usage, stop reason 정규화 +- timeout, rate limit, context overflow 같은 오류 분류 + +이 계층 위에서는 “Anthropic 응답인가 OpenAI 응답인가”보다 “assistant가 텍스트를 보냈는가, tool call을 보냈는가”가 중요해진다. + +### `packages/agent`: LLM과 Tool을 반복 실행하는 계층 + +`Agent`는 현재 대화, 모델, tool 목록과 실행 상태를 가진다. `agentLoop()`는 assistant 응답에 tool call이 있으면 도구를 실행하고 그 결과를 다시 모델에게 전달한다. + +이 패키지는 기본적으로 다음 질문에 답한다. + +> 지금까지의 메시지와 사용할 수 있는 도구가 주어졌을 때, 모델을 호출하고 작업이 끝날 때까지 어떤 순서로 반복할 것인가? + +파일 읽기나 Git 같은 코딩 도메인 지식은 거의 여기에 속하지 않는다. + +### `packages/coding-agent`: Senpi 애플리케이션 본체 + +사용자가 실제로 실행하는 `senpi` CLI가 들어 있다. + +- CLI 인자 해석 +- 인증과 모델 선택 +- 설정 파일 로딩 +- 세션 생성·저장·복구 +- `read`, `bash`, `edit`, `write` 같은 기본 도구 +- extension 로딩과 이벤트 전달 +- 동적 시스템 프롬프트 +- compaction +- interactive, print, RPC, app-server 모드 + +소스 공부에서 가장 많은 시간을 쓰게 될 패키지다. + +### `packages/tui`: 터미널 화면 계층 + +대화, editor, tool 진행 상태, footer와 overlay를 그린다. 매번 화면 전체를 새로 출력하지 않고 이전 프레임과 새 프레임의 차이만 반영하는 differential rendering을 사용한다. + +### 보조 패키지 + +| 패키지 | 역할 | +|---|---| +| `packages/pty` | 오래 살아 있는 shell/PTY session과 screen buffer | +| `packages/senpi-codemode` | persistent eval kernel과 `eval` tool | +| `packages/protocol` | 원격 세션용 CBOR 메시지 계약 | +| `packages/client` | protocol을 사용하는 원격 client | +| `packages/server` | 여러 session을 제공하는 server runtime | +| `packages/storage/sqlite-node` | SQLite 기반 session storage | +| `packages/evals` | 실제 `AgentSession`을 이용한 행동 평가 | + +## 3. 패키지 의존 방향 + +의존성은 대체로 아래에서 위로 흐른다. + +```text +packages/ai + ↑ +packages/agent + ↑ +packages/coding-agent ───→ packages/tui + │ + ├───────────────→ packages/pty + ├───────────────→ packages/protocol / client + └───────────────→ packages/senpi-codemode +``` + +여기서 중요한 규칙은 아래 계층이 위 계층을 몰라야 한다는 것이다. 예를 들어 `packages/ai`가 `InteractiveMode`를 import한다면 계층이 뒤집힌다. 반대로 `coding-agent`가 `pi-ai`의 모델과 메시지 타입을 사용하는 것은 자연스럽다. + +## 4. `senpi` 실행부터 화면 표시까지 + +### 4.1 CLI 부트스트랩 + +첫 진입점은 [`packages/coding-agent/src/cli.ts`](../packages/coding-agent/src/cli.ts)다. + +이 파일은 가능한 한 가벼운 작업만 한다. + +1. 프로세스 이름과 환경을 초기화한다. +2. `--version`처럼 즉시 끝낼 수 있는 명령을 처리한다. +3. 설치 상태에 문제가 있으면 self-update bootstrap을 확인한다. +4. 실제 CLI 로직이 있는 `cli-main.ts`를 별도 프로세스로 실행한다. + +`cli-main.ts`는 HTTP dispatcher와 inspector 관련 초기화를 한 뒤 `main()`을 호출한다. + +### 4.2 `main()`이 실행 환경을 조립한다 + +[`packages/coding-agent/src/main.ts`](../packages/coding-agent/src/main.ts)의 `main()`은 큰 조정 함수다. + +대략 다음 순서로 일한다. + +```text +CLI 인자 파싱 +→ 실행 모드 결정 +→ migration과 설정 로딩 +→ 프로젝트 신뢰 여부 확인 +→ 인증·모델 runtime 준비 +→ 세션 선택 또는 생성 +→ extension과 resource 준비 +→ AgentSession 생성 +→ Interactive / Print / RPC 실행 +``` + +`main()`에 기능 정책을 직접 추가하기보다, 필요한 서비스를 만들고 적절한 실행 모드로 넘기는 coordinator로 보는 것이 좋다. + +### 4.3 세션 서비스를 만든다 + +`createAgentSessionServices()`는 설정, 모델, resource loader처럼 세션 생성에 필요한 재료를 준비한다. `createAgentSessionFromServices()`는 그 재료로 실제 세션을 만든다. + +이렇게 둘로 나누면 CLI뿐 아니라 RPC나 app-server도 같은 조립 로직을 재사용할 수 있다. + +### 4.4 `Agent`와 `AgentSession`을 만든다 + +[`packages/coding-agent/src/core/sdk.ts`](../packages/coding-agent/src/core/sdk.ts)의 `createAgentSession()`은 다음을 연결한다. + +- `ModelRuntime` +- `SettingsManager` +- `SessionManager` +- `ResourceLoader` +- 기본 Tool과 extension Tool +- `Agent` +- `AgentSession` + +`Agent`와 `AgentSession`을 혼동하기 쉽다. + +| 객체 | 쉬운 표현 | 주요 책임 | +|---|---|---| +| `Agent` | 모델과 도구를 돌리는 엔진 | 메시지 상태, streaming, tool loop, abort | +| `AgentSession` | 코딩 작업의 감독자 | 저장, extension, prompt, compaction, 모델 변경, UI event | + +`Agent`만으로도 모델과 tool을 반복 실행할 수 있지만, 프로젝트 규칙을 읽고 세션을 저장하며 compaction하는 것은 `AgentSession`의 책임이다. + +### 4.5 실행 모드가 사용자와 연결한다 + +`main()`은 환경에 따라 하나를 선택한다. + +- 터미널에서 실행하면 `InteractiveMode` +- `--print` 또는 pipe 환경이면 print mode +- `--mode json`이면 JSON event stream +- `--mode rpc`이면 JSONL RPC +- `app-server` 명령이면 app-server + +모드는 표현과 입출력 방식을 책임지고, 실제 agent 실행은 같은 session runtime을 공유한다. + +## 5. 사용자 요청 한 번의 전체 흐름 + +사용자가 다음과 같이 입력했다고 해 보자. + +```text +package.json을 읽고 프로젝트 이름을 알려줘. +``` + +실행 흐름은 다음과 같다. + +1. `InteractiveMode`가 editor 입력을 받는다. +2. 입력을 `AgentSession.prompt()`로 전달한다. +3. `AgentSession`이 extension hook과 동적 system prompt를 준비한다. +4. `Agent`가 user message를 대화 context에 추가한다. +5. `agentLoop()`가 `pi-ai` streaming 함수를 호출한다. +6. provider adapter가 공통 context를 실제 API payload로 바꾼다. +7. 모델이 `read({ path: "package.json" })` tool call을 보낸다. +8. `agentLoop()`가 tool call을 찾아 실행한다. +9. coding-agent의 read tool이 파일 내용을 반환한다. +10. tool result가 대화 context와 session log에 기록된다. +11. `agentLoop()`가 tool result를 포함해 모델을 다시 호출한다. +12. 모델이 최종 텍스트 답변을 보낸다. +13. `InteractiveMode`가 streaming 결과를 TUI component로 표시한다. +14. assistant message가 session에 저장된다. + +핵심은 **모델이 직접 파일을 읽는 것이 아니라, 읽어 달라는 구조화된 요청을 만들고 Senpi가 실행한다**는 점이다. + +## 6. 핵심 객체 관계 + +```text +main + └─ AgentSessionRuntime + ├─ AgentSession + │ ├─ Agent + │ ├─ SessionManager + │ ├─ ExtensionRunner + │ ├─ ResourceLoader + │ └─ ModelRuntime + └─ Mode + ├─ InteractiveMode → TUI + ├─ PrintMode + ├─ RpcMode + └─ AppServer +``` + +이 그림을 기억하면 파일을 읽다가 길을 잃었을 때 현재 코드가 어느 책임에 속하는지 다시 판단할 수 있다. + +## 7. 첫 번째 소스 읽기 경로 + +처음부터 파일 전체를 외우려 하지 말고 다음 순서로 함수 경계만 따라간다. + +1. [`packages/coding-agent/src/cli.ts`](../packages/coding-agent/src/cli.ts) +2. [`packages/coding-agent/src/cli-main.ts`](../packages/coding-agent/src/cli-main.ts) +3. [`packages/coding-agent/src/main.ts`](../packages/coding-agent/src/main.ts)의 `main()` +4. [`packages/coding-agent/src/core/sdk.ts`](../packages/coding-agent/src/core/sdk.ts)의 `createAgentSession()` +5. [`packages/coding-agent/src/core/agent-session.ts`](../packages/coding-agent/src/core/agent-session.ts)의 생성자와 `prompt()` +6. [`packages/agent/src/agent.ts`](../packages/agent/src/agent.ts)의 `prompt()` +7. [`packages/agent/src/agent-loop.ts`](../packages/agent/src/agent-loop.ts)의 `agentLoop()`와 `runLoop()` + +첫 회독에서는 오류 복구 분기를 건너뛰고 정상 경로만 표시해도 충분하다. + +## 8. 확인 문제 + +1. `packages/ai`가 파일을 직접 수정하지 않는 이유는 무엇인가? +2. `Agent`와 `AgentSession` 중 세션 저장을 책임지는 쪽은 어디인가? +3. Interactive와 RPC가 같은 Agent 실행 구조를 공유할 수 있는 이유는 무엇인가? +4. 모델이 `bash` tool call을 만들었을 때 실제 명령을 실행하는 주체는 누구인가? +5. 새로운 provider를 추가할 때 TUI 코드부터 수정하면 안 되는 이유는 무엇인가? + +## 9. 작은 실습 + +다음 함수에 편집기 bookmark를 걸고, 호출 관계만 화살표로 적어 본다. + +- `main()` +- `createAgentSessionServices()` +- `createAgentSessionFromServices()` +- `createAgentSession()` +- `AgentSession.prompt()` +- `Agent.prompt()` +- `agentLoop()` + +목표는 세부 구현이 아니라 “CLI가 Agent Loop까지 어떻게 도달하는가”를 자기 말로 설명하는 것이다. + +[다음 장: LLM 통신과 Agent Loop](02-llm-and-agent-loop.md) diff --git a/learning/02-llm-and-agent-loop.md b/learning/02-llm-and-agent-loop.md new file mode 100644 index 000000000..e82800191 --- /dev/null +++ b/learning/02-llm-and-agent-loop.md @@ -0,0 +1,287 @@ +# 2장. LLM 통신과 Agent Loop + +## 먼저 한 문장으로 + +`packages/ai`가 서로 다른 모델 API를 같은 언어로 번역하고, `packages/agent`가 그 공통 언어를 이용해 **모델 호출 → Tool 실행 → 결과 전달**을 반복한다. + +## 이 장에서 답할 질문 + +- Provider와 API adapter는 무엇이 다른가? +- Streaming 중에는 어떤 이벤트가 흐르는가? +- Agent Loop는 언제 도구를 실행하고 언제 종료하는가? +- 여러 Tool Call은 어떻게 처리되는가? +- 잘못되거나 멈춘 모델 응답을 어떻게 복구하는가? + +## 1. 쉽게 이해하기: 통역사와 작업 진행자 + +식당 주문에 비유해 보자. + +- 사용자는 원하는 일을 말한다. +- Agent는 지금까지의 주문과 사용할 수 있는 작업 목록을 정리한다. +- AI 계층은 그 내용을 각 식당이 이해하는 양식으로 번역한다. +- 모델은 답변하거나 “재료 창고를 확인해 달라”는 작업 요청을 보낸다. +- Agent Loop는 요청을 실행하고 결과를 모델에게 다시 알려 준다. + +여기서 AI 계층은 **번역과 통신**, Agent Loop는 **일의 진행 순서**를 책임진다. + +## 2. 공통 메시지 모델 + +Provider마다 실제 JSON 형식은 다르지만 Senpi 내부에서는 공통 메시지를 사용한다. + +### User message + +사용자의 텍스트나 이미지 입력이다. + +### Assistant message + +모델의 출력이다. 단순 문자열 하나가 아니라 여러 content block을 가질 수 있다. + +- 일반 text +- thinking/reasoning +- tool call +- provider 전용 content + +### Tool result message + +Tool 실행 결과다. 어떤 tool call에 대한 결과인지 식별자가 함께 들어간다. + +이 구조 덕분에 Agent Loop는 provider별 wire JSON을 직접 알 필요가 없다. 주요 타입은 [`packages/ai/src/types.ts`](../packages/ai/src/types.ts)에서 시작해 볼 수 있다. + +## 3. Model, Provider, API adapter + +세 용어를 분리해야 한다. + +### Model + +실제로 선택하는 모델 하나를 표현한다. + +- provider ID +- model ID +- 사용할 API 종류 +- context window +- max tokens +- 입력 modality +- reasoning 지원 여부 +- 비용과 호환성 metadata + +### Provider + +모델을 제공하고 인증·endpoint 설정을 공급하는 주체다. 같은 OpenAI-compatible API를 쓰더라도 OpenRouter와 로컬 서버는 서로 다른 provider가 될 수 있다. + +### API adapter + +공통 Senpi context를 특정 wire protocol로 변환하는 구현이다. + +예를 들어 여러 provider가 `openai-completions` adapter를 공유할 수 있다. 반대로 같은 OpenAI 계열이라도 Chat Completions와 Responses는 서로 다른 adapter를 사용한다. + +```text +Model + ├─ provider: 어디에 요청할지 + └─ api: 어떤 요청 형식으로 말할지 +``` + +Provider 정의는 [`packages/ai/src/providers`](../packages/ai/src/providers), API adapter는 [`packages/ai/src/api`](../packages/ai/src/api)에 모여 있다. + +## 4. Streaming 이벤트 + +LLM 응답은 완성된 문장 하나가 한 번에 도착하지 않는다. 작은 이벤트가 계속 도착한다. + +개념적인 흐름은 다음과 같다. + +```text +start +→ text_start +→ text_delta +→ text_delta +→ text_end +→ done +``` + +Thinking과 Tool Call도 비슷한 start/delta/end 수명주기를 가진다. Tool argument가 JSON이라면 streaming 중에는 아직 닫히지 않은 부분 JSON일 수 있다. + +AI adapter의 책임은 provider 원본 event를 공통 event로 바꾸고, 최종적으로 완전한 `AssistantMessage`를 만드는 것이다. UI는 delta event를 받아 즉시 화면을 갱신할 수 있고, Agent Loop는 최종 message에서 tool call을 찾는다. + +## 5. `Agent`가 보관하는 상태 + +[`packages/agent/src/agent.ts`](../packages/agent/src/agent.ts)의 `Agent`는 대략 다음 상태를 가진다. + +- system prompt +- message history +- 현재 model +- reasoning/thinking level +- 등록된 tool +- 현재 streaming 여부 +- 현재 assistant message +- 오류 상태 +- steering/follow-up queue + +`Agent.prompt()`는 새 사용자 입력을 받고 loop를 시작한다. `Agent.continue()`는 새 사용자 입력 없이 현재 대화에서 실행을 이어 간다. + +## 6. Agent Loop의 정상 경로 + +핵심 구현은 [`packages/agent/src/agent-loop.ts`](../packages/agent/src/agent-loop.ts)의 `runLoop()`다. + +### 6.1 사용자 메시지를 context에 추가한다 + +새 요청이라면 user message를 event로 내보내고 현재 context에 넣는다. + +### 6.2 모델을 호출한다 + +`streamAssistantResponse()`가 system prompt, messages, tools, reasoning 설정을 stream function에 전달한다. + +### 6.3 Assistant message를 완성한다 + +Streaming event가 올 때마다 다음 일이 함께 진행된다. + +- message 내용 누적 +- `message_update` event 발생 +- usage와 stop reason 수집 +- abort와 timeout 감시 + +### 6.4 Tool Call을 찾는다 + +최종 assistant content에서 `toolCall` block을 골라낸다. + +Tool call이 없다면 보통 현재 turn은 종료된다. Tool call이 있다면 각 call에 맞는 tool을 찾아 실행한다. + +### 6.5 Tool Result를 context에 넣는다 + +결과는 `ToolResultMessage`로 바뀌어 message history에 추가된다. 다음 provider 호출에서 모델은 자신이 요청한 작업의 결과를 볼 수 있다. + +### 6.6 다시 모델을 호출한다 + +Tool을 실행했으므로 loop가 한 번 더 돈다. 모델이 추가 tool을 요청하면 반복하고, 최종 텍스트만 반환하면 끝난다. + +```text +User + ↓ +Assistant(tool call) + ↓ +Tool result + ↓ +Assistant(tool call 또는 final text) +``` + +## 7. 순차 실행과 병렬 실행 + +한 assistant message에 여러 tool call이 포함될 수 있다. + +### 순차 실행 + +앞의 결과가 뒤 작업에 영향을 주거나 실행 모드가 순차로 지정된 경우 하나씩 실행한다. + +### 병렬 실행 + +서로 독립적인 여러 읽기나 검색은 동시에 실행할 수 있다. Senpi의 `executeToolCallsParallel()`은 실행을 동시에 진행하면서도 최종 tool result의 순서는 원래 tool call 순서와 맞도록 관리한다. + +병렬 실행에서 구분할 것이 두 가지다. + +- **실제 완료 시점**: 빠른 tool이 먼저 끝날 수 있다. +- **대화에 기록되는 순서**: 모델이 보낸 tool call 순서를 보존해야 한다. + +순서를 보존하지 않으면 provider의 tool-call/result pairing 규칙을 깨거나 재현하기 어려운 transcript가 생길 수 있다. + +## 8. Steering과 Follow-up + +Agent가 실행되는 동안 사용자가 새 메시지를 입력할 수 있다. + +### Steering message + +현재 작업 방향을 바꾸는 메시지다. 다음 provider 호출 전에 context로 들어갈 수 있다. + +예: + +```text +그 파일은 건드리지 말고 테스트만 확인해 줘. +``` + +### Follow-up message + +현재 작업이 자연스럽게 끝난 뒤 이어서 수행할 메시지다. + +Agent Loop는 종료하려는 시점에 두 queue를 확인한다. Compaction이나 abort 도중 queue를 꺼냈다가 실행이 실패하면 메시지를 잃지 않도록 복원해야 한다. + +## 9. Timeout과 Abort + +Provider 호출이 영원히 기다리게 두면 전체 session이 멈춘다. Senpi는 몇 가지 시간을 구분한다. + +- 요청을 보냈는데 첫 event가 오지 않는 stream-start timeout +- event가 오기 시작했지만 다음 event가 오래 오지 않는 idle timeout +- Tool 자체의 timeout +- 사용자가 명시적으로 중단하는 abort + +AbortSignal은 provider와 tool에 전달된다. 중요한 점은 “중단을 요청했다”와 “하위 process가 실제로 사라졌다”가 항상 같지는 않다는 것이다. Agent Loop는 사용자가 더 기다리지 않도록 자신의 wait를 해제하면서도 terminal 계층이 남은 process를 정리할 수 있게 해야 한다. + +## 10. 이상한 모델 응답 복구 + +실제 모델은 항상 완벽한 메시지를 만들지 않는다. + +### 빈 assistant 응답 + +텍스트도 tool call도 없는 응답은 그대로 끝내면 사용자는 아무 결과를 받지 못한다. 모델과 상황에 따라 제한된 복구 요청을 시도한다. + +### 잘린 Tool Call + +stop reason이 `length`인데 tool argument가 도중에 잘렸다면 실행하면 위험하다. Senpi는 해당 call을 실패 결과로 바꾸고 모델이 다시 판단하게 한다. + +### Text Tool Call + +Native function calling을 지원하지 않는 모델은 XML이나 특수 문법으로 tool call을 텍스트에 출력할 수 있다. [`packages/ai/src/tool-call-middleware`](../packages/ai/src/tool-call-middleware)가 이를 공통 tool call event로 복구한다. + +### Tool pair 불일치 + +Compaction이나 provider 변환 뒤 tool call과 result 쌍이 깨질 수 있다. Provider 요청 직전 sanitizer와 compaction repair 단계가 orphan pair를 정리한다. + +## 11. Event를 기준으로 읽기 + +Agent Loop를 이해할 때 함수만 따라가기 어렵다면 event 순서로 읽는다. + +```text +agent_start +└─ turn_start + ├─ message_start + ├─ message_update ... + ├─ message_end + ├─ tool_execution_start ... + ├─ tool_execution_end ... + └─ turn_end +agent_end +``` + +Tool이 여러 번 이어지면 `turn_start`부터 `turn_end`까지가 반복된다. Interactive UI, session 저장, extension은 이 event들을 관찰해 각자의 일을 한다. + +## 12. 소스 읽기 경로 + +1. [`packages/ai/src/types.ts`](../packages/ai/src/types.ts): message와 stream event 타입 +2. [`packages/ai/src/model.ts`](../packages/ai/src/model.ts): model 표현 +3. [`packages/ai/src/api-registry.ts`](../packages/ai/src/api-registry.ts): adapter 등록과 조회 +4. [`packages/ai/src/api/anthropic-messages.ts`](../packages/ai/src/api/anthropic-messages.ts) 또는 [`openai-responses.ts`](../packages/ai/src/api/openai-responses.ts): 실제 adapter 하나 +5. [`packages/agent/src/types.ts`](../packages/agent/src/types.ts): Agent event와 tool 타입 +6. [`packages/agent/src/agent.ts`](../packages/agent/src/agent.ts): public state와 prompt 진입점 +7. [`packages/agent/src/agent-loop.ts`](../packages/agent/src/agent-loop.ts): `runLoop()`, `streamAssistantResponse()`, `executeToolCalls*()` + +API adapter는 처음에 하나만 선택해서 읽는 것이 좋다. 모든 provider를 동시에 보면 공통 구조보다 예외 처리만 눈에 들어온다. + +## 13. 확인 문제 + +1. Provider와 API adapter를 별도로 표현하는 이유는 무엇인가? +2. Streaming event와 최종 `AssistantMessage`는 어떤 관계인가? +3. Tool result를 user message가 아니라 별도 message로 기록하는 이유는 무엇인가? +4. 병렬 tool 실행에서도 결과 순서를 보존해야 하는 이유는 무엇인가? +5. stream-start timeout과 idle timeout은 어떻게 다른가? +6. Steering과 follow-up 중 현재 작업 방향을 바꾸는 것은 어느 쪽인가? + +## 14. 추적 실습 + +`agent-loop.ts`에서 아래 흐름에 서로 다른 색의 표시를 해 본다. + +1. Provider 호출 경로 +2. Assistant message 누적 경로 +3. Tool call 실행 경로 +4. Tool result 삽입 경로 +5. 종료 판단 경로 +6. Steering/follow-up queue 경로 + +그다음 “Tool이 없는 정상 답변”과 “Tool을 한 번 사용하는 답변”을 각각 종이에 event 순서로 적는다. + +[이전 장: 전체 구조와 실행 흐름](01-architecture-and-flow.md) · [다음 장: Coding Agent, 세션과 Tool](03-coding-agent-session-tools.md) diff --git a/learning/03-coding-agent-session-tools.md b/learning/03-coding-agent-session-tools.md new file mode 100644 index 000000000..19ba8f6c1 --- /dev/null +++ b/learning/03-coding-agent-session-tools.md @@ -0,0 +1,278 @@ +# 3장. Coding Agent, 세션과 Tool + +## 먼저 한 문장으로 + +`AgentSession`은 범용 `Agent`에 프로젝트 설정, 세션 저장, 코딩 Tool, Extension과 Compaction을 연결해 실제 코딩 에이전트로 만드는 중앙 조정자다. + +## 이 장에서 답할 질문 + +- CLI 옵션은 어떻게 실제 session 설정으로 변환될까? +- `AgentSession`은 왜 필요한가? +- 대화는 어떤 형식으로 저장되고 다시 복구될까? +- `read`, `bash`, `edit`, `write`는 어떤 공통 절차로 실행될까? +- Interactive와 RPC는 어떻게 같은 session을 사용할까? + +## 1. 쉽게 이해하기: 엔진과 작업 노트 + +`Agent`를 자동차 엔진이라고 하면 `AgentSession`은 운전석과 계기판, 내비게이션, 운행 기록을 합친 부분이다. + +엔진은 동력을 만들지만 다음은 알지 못한다. + +- 어느 프로젝트에서 일하는가? +- 이전 작업 기록을 어디에 저장하는가? +- 어떤 `AGENTS.md`를 따라야 하는가? +- 어떤 extension이 tool call을 허용하거나 막는가? +- context가 넘치기 전에 언제 요약해야 하는가? + +이런 애플리케이션 수준의 책임이 `AgentSession`에 모인다. + +## 2. CLI 설정이 Session으로 들어오는 과정 + +### 2.1 인자 파싱 + +[`packages/coding-agent/src/cli/args.ts`](../packages/coding-agent/src/cli/args.ts)는 문자열 배열을 구조화된 `Args`로 바꾼다. + +대표적으로 다음 항목을 해석한다. + +- model과 provider 선택 +- thinking level +- 새 session, continue, resume, fork +- tool allowlist와 denylist +- extension 경로 +- print/JSON/RPC 모드 +- 초기 prompt와 첨부 파일 + +인자 파서는 실행하지 않고 해석과 진단만 담당하는 것이 중요하다. + +### 2.2 실행 모드 선택 + +`main.ts`는 명시적인 mode 옵션뿐 아니라 stdin/stdout이 TTY인지도 확인한다. + +```text +터미널 입출력 + 별도 옵션 없음 → interactive +pipe 입력 또는 --print → print +--mode json → JSON event stream +--mode rpc → RPC +app-server 명령 → app-server +``` + +### 2.3 설정 우선순위 합성 + +Senpi에는 기본값, 전역 설정, 프로젝트 설정, CLI 옵션이 함께 존재한다. `SettingsManager`와 model 관련 resolver가 이 값들을 합쳐 현재 session에서 사용할 값을 결정한다. + +일반적인 원칙은 더 구체적이고 명시적인 입력이 우선한다는 것이다. 단, model fallback이나 session 복구처럼 저장된 상태와 현재 설정을 함께 고려해야 하는 기능에는 별도 정책이 있다. + +## 3. `createAgentSession()` 조립 과정 + +[`packages/coding-agent/src/core/sdk.ts`](../packages/coding-agent/src/core/sdk.ts)는 외부 프로그램도 사용할 수 있는 조립 진입점이다. + +주요 단계는 다음과 같다. + +1. 작업 디렉터리와 agent directory를 정한다. +2. `SettingsManager`와 `SessionManager`를 준비한다. +3. `ModelRuntime`을 통해 사용할 모델과 인증을 결정한다. +4. `ResourceLoader`가 prompt, context file, skill, extension을 찾는다. +5. 기본 coding tool을 만든다. +6. extension이 등록한 tool과 합친다. +7. 활성 tool allowlist/denylist를 적용한다. +8. 공통 `streamSimple`을 이용하도록 `Agent`를 만든다. +9. 모든 서비스를 묶어 `AgentSession`을 만든다. + +이 함수는 dependency injection 지점이기도 하다. 테스트나 외부 SDK 사용자는 기본 manager 대신 준비한 구현을 넘길 수 있다. + +## 4. `AgentSession`의 주요 책임 + +[`packages/coding-agent/src/core/agent-session.ts`](../packages/coding-agent/src/core/agent-session.ts)는 크기가 크므로 책임별로 나눠 읽어야 한다. + +### Prompt lifecycle + +- 현재 tool과 resource를 반영한 system prompt 구성 +- 새 사용자 prompt 전처리 +- extension의 `before_agent_start` 실행 +- Agent 실행 시작 + +### Event bridge + +Agent가 내보낸 message/tool/turn event를 session event로 변환한다. Extension과 UI는 이 경계를 통해 실행 상태를 관찰한다. + +### Persistence + +완료된 user/assistant/tool/custom entry를 `SessionManager`에 기록한다. Streaming 중인 불완전한 상태와 확정된 저장 상태를 구분해야 한다. + +### Model과 Tool 상태 + +- 모델 변경 +- thinking level 변경 +- 활성 tool 변경 +- 모델 변경 후 system prompt 재생성 +- session에 저장할 상태와 임시 상태 구분 + +### Compaction + +- context 사용량 확인 +- compaction hook 실행 +- summary 적용 +- overflow 후 재시도 +- queue와 상태 복구 + +### Extension binding + +Extension API가 실제 session 기능을 호출할 수 있도록 core 구현을 연결한다. Extension은 `AgentSession`의 private 상태를 직접 만지지 않고 공개된 event와 API를 사용한다. + +## 5. Session 저장 모델 + +세션은 단순한 `messages.json`이 아니다. 대화가 분기될 수 있는 append-only entry 흐름으로 생각하는 것이 좋다. + +대표적인 entry는 다음과 같다. + +- session header +- user message +- assistant message +- tool result를 포함한 agent message +- model/thinking 변경 +- custom extension entry +- compaction summary +- branch summary + +구체적인 저장 형식은 [`packages/coding-agent/docs/session-format.md`](../packages/coding-agent/docs/session-format.md)와 [`session-manager.ts`](../packages/coding-agent/src/core/session-manager.ts)를 함께 본다. + +### 왜 append-only에 가까운가? + +과거 상태를 직접 덮어쓰면 다음이 어려워진다. + +- 어느 시점에서 어떤 모델을 사용했는지 확인 +- 과거 지점에서 branch 생성 +- extension 상태 복구 +- compaction 전후의 경계 추적 + +Entry를 시간 순서대로 쌓으면 현재 leaf까지의 경로를 재생해 session state를 복구할 수 있다. + +## 6. 새 Session, Continue, Resume, Fork + +### 새 Session + +새 ID와 header를 만들고 현재 cwd를 기준으로 시작한다. + +### Continue + +현재 프로젝트의 최근 session을 찾아 마지막 상태에서 계속한다. + +### Resume + +사용자가 선택하거나 지정한 기존 session 파일을 연다. 저장된 cwd가 사라졌다면 사용자 확인이나 오류 처리가 필요하다. + +### Fork와 Branch + +기존 대화의 특정 지점을 부모로 삼아 새로운 작업 경로를 만든다. 원래 대화를 삭제하지 않고 다른 결정을 시험할 수 있다. + +쉽게 말하면 Git commit graph와 비슷하지만, 대상이 코드가 아니라 대화 entry라는 차이가 있다. + +## 7. 기본 Coding Tool + +Tool factory는 [`packages/coding-agent/src/core/tools`](../packages/coding-agent/src/core/tools)에 있다. + +### `read` + +- 경로를 cwd 기준으로 해석한다. +- 파일 또는 이미지 여부를 판단한다. +- 필요한 범위만 읽을 수 있다. +- 큰 결과는 model context를 보호하도록 제한한다. + +### `grep`, `find`, `ls` + +프로젝트를 탐색하는 read-only tool이다. 결과 크기와 출력 형식을 제한해 모델이 다루기 쉬운 텍스트를 만든다. + +### `write` + +새 내용을 파일에 기록한다. 결과에는 단순 성공 문자열뿐 아니라 UI와 extension이 활용할 수 있는 변경 detail이 포함될 수 있다. + +### `edit` + +기존 내용에서 정확한 부분을 찾아 교체한다. 일치하지 않거나 여러 곳이 애매하게 일치하면 안전하게 실패해야 한다. + +### `bash` + +명령을 child process로 실행하고 stdout/stderr, exit code, timeout과 abort를 관리한다. Persistent terminal builtin이 활성화된 경우 더 긴 실행은 PTY 경로로 이어질 수 있다. + +### `apply_patch` + +GPT/Responses 계열에서는 builtin extension이 `edit`와 `write` 대신 Codex식 freeform patch tool을 노출할 수 있다. + +## 8. Tool 실행 파이프라인 + +모델이 tool call을 만들었다고 즉시 구현 함수가 호출되는 것은 아니다. + +```text +모델 Tool Call +→ 등록된 Tool 찾기 +→ 입력 schema 검증 +→ prepareArguments 등 전처리 +→ Extension tool_call hook +→ Permission 검사 +→ Tool execute +→ 진행 update 전달 +→ Extension tool_result hook +→ Agent용 ToolResultMessage 생성 +→ Session 저장 및 UI 표시 +``` + +이 파이프라인을 거치기 때문에 permission system, hooks, custom renderer가 기본 tool과 extension tool에 공통으로 적용될 수 있다. + +## 9. 파일 변경의 동시성 + +여러 tool call이 병렬로 실행될 때 두 edit이 같은 파일을 동시에 바꾸면 문제가 생긴다. Coding tool 계층에는 file mutation queue가 있어 충돌 가능한 변경을 직렬화한다. + +여기서 Agent Loop의 병렬성과 파일 변경 안전성을 구분해야 한다. + +- 서로 다른 read는 병렬 실행 가능 +- tool call 전체는 병렬로 시작할 수 있음 +- 같은 파일의 mutation은 queue에서 순서를 보장 + +## 10. 실행 모드와 Session의 관계 + +### Interactive + +TUI, dialog, editor, shortcut을 모두 지원한다. + +### Print/JSON + +한 번의 작업을 비대화형으로 실행한다. 사용자에게 확인을 요청할 수 없는 기능은 명시된 fallback 정책을 사용해야 한다. + +### RPC/App Server + +다른 프로세스가 session command를 보내고 event를 수신한다. UI 요청도 protocol을 통해 외부 host로 전달하거나 지원하지 않는 것으로 처리한다. + +핵심 AgentSession은 공유하지만 `ExtensionUIContext`의 구현이 mode마다 다르다. + +## 11. 소스 읽기 경로 + +1. [`packages/coding-agent/src/cli/args.ts`](../packages/coding-agent/src/cli/args.ts) +2. [`packages/coding-agent/src/main.ts`](../packages/coding-agent/src/main.ts)의 mode 선택과 session 생성 부분 +3. [`packages/coding-agent/src/core/sdk.ts`](../packages/coding-agent/src/core/sdk.ts) +4. [`packages/coding-agent/src/core/agent-session.ts`](../packages/coding-agent/src/core/agent-session.ts)의 생성자, `prompt()`, Agent event handler +5. [`packages/coding-agent/src/core/session-manager.ts`](../packages/coding-agent/src/core/session-manager.ts) +6. [`packages/coding-agent/src/core/tools/index.ts`](../packages/coding-agent/src/core/tools/index.ts) +7. `read.ts`, `bash.ts`, `edit.ts`, `write.ts` 중 하나씩 + +`agent-session.ts`는 처음부터 끝까지 읽기보다 “prompt”, “model”, “compact”, “event” 중 한 책임을 정해서 읽는다. + +## 12. 확인 문제 + +1. `AgentSession`이 없고 `Agent`만 있다면 어떤 기능이 빠질까? +2. Session을 단순한 message 배열이 아니라 entry graph로 관리하는 이유는 무엇인가? +3. Tool schema 검증과 permission 검사는 각각 무엇을 막는가? +4. 병렬 tool 실행과 file mutation queue가 동시에 필요한 이유는 무엇인가? +5. Interactive와 RPC에서 UI 기능이 다르지만 같은 AgentSession을 쓸 수 있는 이유는 무엇인가? + +## 13. 추적 실습 + +`read` tool 하나를 골라 다음 네 관점을 따로 추적한다. + +1. Tool이 만들어지는 위치 +2. Agent에 등록되는 위치 +3. 모델 호출에 schema가 포함되는 위치 +4. 실행 결과가 session과 UI에 전달되는 위치 + +그다음 session 파일 하나를 열어 user message, assistant tool call, tool result, 최종 assistant message가 어떤 순서로 나타나는지 확인한다. 실제 credential이나 민감한 경로가 포함된 session은 공유하지 않는다. + +[이전 장: LLM 통신과 Agent Loop](02-llm-and-agent-loop.md) · [다음 장: Extension과 Senpi 주요 기능](04-extensions-and-features.md) diff --git a/learning/04-extensions-and-features.md b/learning/04-extensions-and-features.md new file mode 100644 index 000000000..108bb7d18 --- /dev/null +++ b/learning/04-extensions-and-features.md @@ -0,0 +1,304 @@ +# 4장. Extension과 Senpi 주요 기능 + +## 먼저 한 문장으로 + +Senpi는 핵심 실행 흐름을 유지한 채 Tool, Command, Prompt, Permission, Compaction 정책을 교체할 수 있도록 Extension을 주요 설계 경계로 사용한다. + +## 이 장에서 답할 질문 + +- Extension은 언제, 어떤 순서로 로드될까? +- Extension은 Agent 실행에 어떻게 개입할까? +- Builtin extension과 사용자가 설치한 extension은 무엇이 다를까? +- 동적 system prompt는 어떻게 구성될까? +- Senpi의 수많은 builtin을 어떤 기준으로 분류하면 좋을까? + +## 1. 쉽게 이해하기: 건물의 표준 연결 포트 + +코어를 건물 골조라고 생각해 보자. 새 기능을 추가할 때마다 벽을 뜯으면 건물이 불안정해진다. 대신 전기, 수도, 네트워크 연결 포트를 정해 두면 장비를 꽂아 기능을 추가할 수 있다. + +Extension event와 `ExtensionAPI`가 이 연결 포트다. + +- 새 Tool을 등록한다. +- `/command`를 등록한다. +- Tool 실행 전에 허가를 확인한다. +- 모델에게 보낼 context를 보강한다. +- footer와 widget을 표시한다. +- session에 custom state를 저장한다. + +Extension으로 표현할 수 없는 저수준 변경만 core에 남기는 것이 Senpi의 extension-first 원칙이다. + +## 2. Extension의 기본 모양 + +Extension은 `ExtensionAPI`를 받는 factory다. + +```ts +export default function example(pi: ExtensionAPI) { + pi.registerCommand("hello", { + description: "인사하기", + handler: async (_args, ctx) => { + ctx.ui.notify("안녕하세요", "info"); + }, + }); + + pi.on("tool_call", async (event) => { + if (event.toolName === "dangerous-tool") { + return { block: true, reason: "이 도구는 허용되지 않았습니다." }; + } + }); +} +``` + +Factory는 등록만 하고, 장시간 process나 timer는 `session_start` 이후에 시작하는 것이 원칙이다. 종료할 자원은 `session_shutdown`에서 정리한다. + +## 3. Extension 로딩 순서 + +[`packages/coding-agent/src/core/resource-loader.ts`](../packages/coding-agent/src/core/resource-loader.ts)가 여러 출처의 resource를 모은다. + +개념적인 순서는 다음과 같다. + +1. 소스에 포함된 builtin factory +2. 번들된 codemode 같은 extension +3. SDK가 직접 전달한 inline extension +4. 전역·프로젝트·설정·CLI 경로의 extension + +Builtin의 배열 순서는 동작에 영향을 줄 수 있다. 앞선 extension이 Tool이나 provider payload를 바꾸고, 뒤 extension이 그 결과를 관찰할 수 있기 때문이다. + +실제 builtin 목록은 [`packages/coding-agent/src/core/extensions/builtin/index.ts`](../packages/coding-agent/src/core/extensions/builtin/index.ts)가 기준이다. 최상위 README의 요약 목록보다 이 배열을 먼저 신뢰한다. + +## 4. `ExtensionRunner` + +[`runner.ts`](../packages/coding-agent/src/core/extensions/runner.ts)는 등록된 handler와 기능을 보관하고 session event를 전달한다. + +주요 책임은 다음과 같다. + +- event별 handler 등록 +- handler 실행 순서 유지 +- handler 반환값 수집과 병합 +- Tool, Command, Provider registry 유지 +- extension source 추적 +- 종료 handler 실행 +- core 구현과 Extension API 연결 + +Extension factory가 처음 로드될 때는 아직 실제 session 객체가 완전히 연결되지 않았을 수 있다. `bindCore()`가 나중에 model 변경, tool 실행, session metadata 변경 같은 privileged 동작을 실제 구현에 연결한다. + +## 5. `ExtensionAPI`와 `ExtensionContext` + +두 객체의 역할을 구분한다. + +### `ExtensionAPI` + +Factory에서 기능을 **등록**하는 표면이다. + +- `registerTool` +- `registerCommand` +- `registerShortcut` +- `registerFlag` +- `registerProvider` +- `on(event, handler)` +- message renderer 등록 + +### `ExtensionContext` + +각 event handler가 현재 session 상태를 **읽거나 제한적으로 조작**할 때 사용한다. + +- 현재 cwd와 model +- session manager +- 현재 system prompt +- UI dialog, status, widget +- compaction 설정 +- 현재 활성 tool +- extension event bus + +Context가 event마다 전달되는 이유는 오래된 전역 상태를 보지 않고 그 시점의 session 상태를 사용하게 하기 위해서다. + +## 6. 중요한 Lifecycle Event + +모든 event를 외우기보다 실행 단계에 따라 묶는다. + +### Resource와 Session 준비 + +- `resources_discover` +- `session_start` +- `session_before_reload` +- `session_shutdown` + +### Agent 실행 + +- `before_agent_start` +- `context` +- `before_provider_request` +- `agent_end` + +### 메시지와 Tool + +- message 관련 event +- `tool_call` +- tool execution 관련 event +- `tool_result` + +### 상태 변경 + +- `model_select` +- `system_prompt_change` +- compaction 관련 event + +일부 handler는 단순 알림이 아니라 값을 반환해 실행을 변경한다. 예를 들어 tool call을 block하거나 context를 바꾸고 compaction을 취소할 수 있다. + +## 7. 동적 System Prompt + +Senpi는 고정된 문자열 대신 [`dynamic-prompt/build.ts`](../packages/coding-agent/src/core/dynamic-prompt/build.ts)의 builder를 사용한다. + +기본 조립 순서는 다음과 같다. + +1. identity +2. intent gate +3. parallel tool 지침 +4. exploration 규율 +5. verification 규율 +6. 현재 활성 tool 설명 +7. 정책과 금지 사항 +8. 응답 style +9. 모델별 tuning +10. 프로젝트 context file과 skill +11. workstation, 날짜와 cwd + +### 왜 동적인가? + +활성 Tool, 프로젝트 규칙, 모델 family가 session마다 다르기 때문이다. 존재하지 않는 Tool을 system prompt에서 사용하라고 가르치거나, Claude에 맞춘 규칙을 GPT에 그대로 주면 품질이 떨어질 수 있다. + +### Prompt preset + +`prompt-preset` builtin은 모델 ID와 metadata를 보고 적절한 tuning 또는 full core prompt를 선택한다. 공통 builder를 재사용하면서 모델별 행동 차이를 보정한다. + +## 8. Builtin을 기능별로 분류하기 + +등록 개수가 많으므로 배열 순서보다 목적별로 보는 편이 쉽다. + +### 안전과 실행 통제 + +- `permission-system` +- `bash-timeout` +- `loop-guard` +- `tool-pair-guard` +- `ttsr` + +### 모델과 Provider 적응 + +- `prompt-preset` +- `gpt-apply-patch` +- `anthropic-bash` +- `anthropic-web-search` +- `openai-web-search` +- `service-tier` +- `model-fallback` +- `recommended-models` +- `claude-sdk-oauth` + +### 작업 지속성 + +- `todowrite` +- `goal` +- `terminal` +- `compaction` +- `cache-keepalive` + +### Context와 외부 지식 + +- `rules` +- `nested-agents-md` +- `websearch` +- `webfetch` +- `MCP` +- `history-search` +- `look-at` +- `video-in` + +### 사용자 경험과 운영 + +- `help` +- `config-reload` +- `hooks` +- `redraws` +- `btw` + +## 9. 대표 Builtin의 동작 + +### Permission System + +Tool call을 permission name과 pattern으로 바꾸고 allow/ask/deny 규칙을 적용한다. Interactive mode에서는 사용자에게 물을 수 있지만, headless mode에서는 설정된 fallback을 사용해야 한다. + +Permission은 OS sandbox가 아니다. 허용된 Tool은 여전히 Senpi process가 가진 시스템 권한으로 실행된다. + +### Todo와 Goal + +Todo는 현재 작업을 구조화해 보여 주는 목록이다. Goal은 여러 turn과 session 재개를 넘어 “아직 끝나지 않은 목표”를 유지하고 agent를 다시 진행시키는 상위 상태다. + +```text +Goal: 사용자에게 제공할 최종 결과 +└─ Todo: 목표를 달성하기 위한 현재 작업 단계 +``` + +### Rules와 Nested AGENTS.md + +프로젝트 루트 규칙뿐 아니라 Agent가 더 깊은 디렉터리의 파일을 읽을 때 그 경로에 적용되는 가까운 규칙을 context에 삽입한다. 더 구체적인 디렉터리 규칙이 우선한다. + +### Model Fallback + +재시도 가능한 provider 장애가 발생하면 설정된 fallback chain의 다음 모델로 session을 전환할 수 있다. 단순히 API를 다시 호출하는 것이 아니라 thinking level과 system prompt, 사용자 표시 상태도 함께 갱신해야 한다. + +### MCP + +외부 MCP server의 Tool, resource와 prompt를 session에 연결한다. 모든 Tool을 처음부터 모델에게 노출하지 않고 검색을 통해 필요한 Tool만 활성화하는 context 절약 기능도 포함한다. + +## 10. Builtin과 외부 Extension + +둘 다 같은 event와 registration API를 사용하려고 하지만 차이가 있다. + +| 구분 | Builtin | 외부 Extension | +|---|---|---| +| 배포 | Senpi 소스와 함께 | 사용자·프로젝트·package에서 로드 | +| 기본 상태 | 대체로 기본 활성 | 명시적 설치 또는 경로 필요 | +| 설정 | `disabledBuiltinExtensions`로 끌 수 있음 | 설정과 파일 제거로 관리 | +| 결합도 | 일부 Senpi 내부 기능과 밀접 | 공개 Extension API에 의존 | + +좋은 설계는 builtin도 가능한 한 외부 extension과 같은 경계를 사용하게 만든다. + +## 11. 소스 읽기 경로 + +1. [`packages/coding-agent/src/core/extensions/AGENTS.md`](../packages/coding-agent/src/core/extensions/AGENTS.md) +2. [`types.ts`](../packages/coding-agent/src/core/extensions/types.ts)의 `ExtensionAPI`, `ExtensionContext`, event 타입 +3. [`runner.ts`](../packages/coding-agent/src/core/extensions/runner.ts) +4. [`loader.ts`](../packages/coding-agent/src/core/extensions/loader.ts) +5. [`resource-loader.ts`](../packages/coding-agent/src/core/resource-loader.ts)의 extension loading 부분 +6. [`builtin/index.ts`](../packages/coding-agent/src/core/extensions/builtin/index.ts) +7. 작은 builtin 하나를 선택해 `index.ts`부터 읽기 +8. [`dynamic-prompt/build.ts`](../packages/coding-agent/src/core/dynamic-prompt/build.ts) + +첫 builtin으로는 파일 수가 적고 목적이 분명한 `help`, `bash-timeout`, `history-search` 중 하나가 좋다. Compaction이나 MCP부터 시작하지 않는다. + +## 12. 확인 문제 + +1. Extension factory에서 장시간 timer를 바로 시작하면 왜 문제가 될 수 있는가? +2. `ExtensionAPI`와 event의 `ExtensionContext`는 무엇이 다른가? +3. Builtin 배열 순서가 동작에 영향을 줄 수 있는 이유는 무엇인가? +4. Permission system이 sandbox를 대신하지 못하는 이유는 무엇인가? +5. 동적 system prompt가 현재 활성 Tool 목록을 알아야 하는 이유는 무엇인가? +6. Goal과 Todo는 어떻게 다른가? + +## 13. 작은 Extension 읽기 실습 + +Builtin 하나를 골라 다음 표를 채운다. + +| 질문 | 찾은 내용 | +|---|---| +| Factory는 어디에서 export되는가? | | +| 어떤 event를 구독하는가? | | +| Tool이나 Command를 등록하는가? | | +| 상태는 어디에 저장하는가? | | +| UI를 사용하는가? | | +| session shutdown에서 정리할 자원이 있는가? | | +| 어떤 테스트가 동작을 고정하는가? | | + +그다음 이 기능을 core 코드에 직접 넣었을 때 어떤 결합이 생길지 생각해 본다. + +[이전 장: Coding Agent, 세션과 Tool](03-coding-agent-session-tools.md) · [다음 장: 컨텍스트 관리와 장시간 실행](05-context-and-long-running-work.md) diff --git a/learning/05-context-and-long-running-work.md b/learning/05-context-and-long-running-work.md new file mode 100644 index 000000000..b10321d0e --- /dev/null +++ b/learning/05-context-and-long-running-work.md @@ -0,0 +1,285 @@ +# 5장. 컨텍스트 관리와 장시간 실행 + +## 먼저 한 문장으로 + +Senpi는 제한된 모델 context 안에서 중요한 작업 상태를 보존하고, 오래 실행되는 명령과 평가를 background resource로 분리한 뒤 완료 시 Agent를 다시 깨우는 구조를 갖는다. + +## 이 장에서 답할 질문 + +- 대화가 길어지면 왜 단순히 오래된 메시지를 삭제하지 않을까? +- Compaction은 언제 준비되고 언제 적용될까? +- Todo, 작업 의도와 읽었던 파일은 요약 뒤 어떻게 보존될까? +- Background process가 끝났다는 사실을 Agent는 어떻게 알까? +- Persistent terminal과 Codemode는 무엇이 다를까? + +## 1. 쉽게 이해하기: 작은 책상과 별도의 작업실 + +모델의 context window는 책상 크기와 비슷하다. 처음에는 모든 자료를 펼칠 수 있지만, 대화와 파일 출력이 쌓이면 더 놓을 공간이 없다. + +해결책은 두 가지다. + +1. 오래된 자료를 요약해 작은 노트로 바꾼다. 이것이 compaction이다. +2. 오래 걸리는 작업은 책상 앞에서 계속 기다리지 않고 별도의 작업실에 맡긴다. 이것이 persistent terminal과 detached eval이다. + +요약할 때는 단순히 종이 양만 줄이면 안 된다. 현재 목표, 아직 끝나지 않은 Todo, 중요한 파일과 결정은 남겨야 한다. + +## 2. Context Window + +Provider 요청에는 다음이 함께 들어간다. + +- system prompt +- user/assistant 대화 +- Tool call과 Tool result +- Tool schema +- 이미지와 provider metadata + +모델마다 context 한도가 있고, 출력에 사용할 token 공간도 남겨야 한다. 입력이 한도에 너무 가까우면 provider가 context overflow 오류를 반환하거나 답변에 필요한 여유가 줄어든다. + +특히 `read`와 `bash` 결과는 짧은 시간에 context를 크게 늘릴 수 있다. + +## 3. Compaction의 기본 아이디어 + +Compaction은 과거 대화 일부를 summary로 바꾸고 최근 메시지를 유지하는 작업이다. + +```text +오래된 대화 A + B + C + 최근 대화 D + E + ↓ +요약 S(A,B,C) + 최근 대화 D + E +``` + +좋은 summary는 문장 수만 줄이는 것이 아니라 다음 작업에 필요한 상태를 전달해야 한다. + +- 사용자의 원래 목적 +- 이미 확인한 사실 +- 변경한 파일 +- 실패한 접근과 이유 +- 아직 남은 작업 +- 지켜야 할 제약 + +## 4. Senpi Compaction Pipeline + +Core의 기본 compaction 구현은 [`packages/coding-agent/src/core/compaction`](../packages/coding-agent/src/core/compaction)에 있고, 정책이 풍부한 동작은 [`builtin/compaction`](../packages/coding-agent/src/core/extensions/builtin/compaction)에 있다. + +### 4.1 사전 판단 + +현재 token 사용량과 모델 context window를 비교해 아직 여유가 있는지, 미리 준비해야 하는지, 즉시 줄여야 하는지 판단한다. + +### 4.2 Speculative Compaction + +한도에 가까워지기 전에 다음 turn과 병렬로 summary를 준비할 수 있다. 실제로 필요해졌을 때 기다리는 시간을 줄이는 목적이다. + +준비 중 원본 session이 바뀌면 오래된 summary를 그대로 적용하면 안 된다. Compaction job이 시작될 때의 revision과 적용 시점의 revision을 비교해야 한다. + +### 4.3 Deterministic Reduction + +항상 LLM summary부터 요청하지 않는다. 먼저 규칙만으로 줄일 수 있는 부분을 처리한다. + +- 오래된 큰 Tool result 축소 +- 연속된 Tool output 정리 +- 오래된 답변의 세부 내용 축소 +- 끊어진 Tool Call/Result pair 수리 + +이 단계는 빠르고 결과가 예측 가능하다. + +### 4.4 Blocking Compaction + +Provider 호출 전에 이미 한계에 도달했거나 context overflow가 발생했다면 현재 실행을 막고 compaction을 완료한다. 성공하면 줄어든 context로 provider 요청을 제한된 횟수만큼 재시도한다. + +### 4.5 Idle Compaction + +Turn이 끝나 사용자가 생각하거나 다음 입력을 준비하는 동안 summary를 미리 warm할 수 있다. 다음 prompt의 critical path에서 summary 생성을 제거하려는 최적화다. + +## 5. 상태를 잃지 않는 장치 + +### Checkpoint + +Compaction 경계에서 model, thinking level과 주요 session 상태를 snapshot으로 남긴다. + +### Todo Bridge + +열린 Todo를 summarization 입력에 포함해 “무엇을 끝냈고 무엇이 남았는가”가 summary에 보존되도록 한다. + +### Task Intent + +세부 대화가 줄어들어도 사용자의 원래 목적과 핵심 제약이 사라지지 않도록 별도 anchor를 유지한다. + +### Restoration Tracker + +Summary만으로는 실제 파일 내용이나 skill 지침이 충분하지 않을 수 있다. Compaction 전 중요하게 사용하던 file/skill context를 첫 post-compaction turn에서 다시 읽도록 추적한다. + +### Tool Pair Repair + +Provider는 tool call 바로 뒤에 대응하는 result가 있기를 요구할 수 있다. 메시지를 자르는 과정에서 한쪽만 남으면 placeholder나 정리 규칙으로 pair를 복구한다. + +## 6. Compaction 실패 방어 + +Compaction도 모델 호출이므로 실패할 수 있다. + +- summary가 비어 있음 +- 결과가 원본보다 충분히 작지 않음 +- provider timeout +- 연속 context overflow +- speculative 결과가 현재 session보다 오래됨 +- compaction 후 assistant 품질 저하 + +Senpi는 retry budget, circuit breaker, stale revision 검사, degradation monitor와 절대 실행 횟수 제한을 사용한다. 자동 복구가 계속 반복되어 실제 작업을 방해하지 않게 하는 것이 목적이다. + +## 7. 장시간 작업의 문제 + +모델이 `npm test`처럼 오래 걸리는 명령을 요청했다고 해 보자. Tool call 하나가 20분 동안 반환되지 않으면 다음 문제가 생긴다. + +- 사용자가 다른 요청을 전달하기 어렵다. +- provider prompt cache가 만료될 수 있다. +- 실행 중인지 멈췄는지 알기 어렵다. +- 앱이 재시작되면 process와 연결을 잃을 수 있다. + +그래서 Senpi는 foreground wait와 실제 작업 수명을 분리한다. + +## 8. Persistent Terminal + +[`packages/pty`](../packages/pty)는 pseudo-terminal session을 관리한다. Terminal builtin은 이를 Agent가 사용할 Tool로 연결한다. + +### 일반 Bash와 PTY + +일반 child process pipe는 한 번 실행하고 출력을 모아 반환하는 데 적합하다. PTY는 shell과 계속 상호작용하거나 process를 background에서 유지하는 데 적합하다. + +### 주요 개념 + +- Terminal session ID +- screen/output buffer +- foreground wait +- background detach +- 입력 전송 +- output peek +- monitor +- 종료 상태 + +### Foreground에서 Background로 + +짧게 끝날 것으로 예상되는 명령은 잠시 foreground에서 기다린다. 일정 시간 안에 끝나지 않거나 명령 형태가 긴 wait를 암시하면 session을 유지한 채 control을 Agent에게 돌려준다. + +Agent는 다른 작업을 하다가 `bash_output` 등으로 상태를 확인할 수 있다. + +## 9. Monitor와 Wake Source + +Background process를 매 turn마다 polling하라고 모델에게 시키면 token과 tool call을 낭비한다. Monitor는 특정 상태 변화를 구독한다. + +예: + +- process 종료 +- 특정 output pattern 등장 +- Anthropic service 상태 회복 +- detached eval 완료 + +Wake source는 “현재 session을 나중에 다시 깨울 책임이 있는 작업”을 나타낸다. 활성 wake source가 있으면 Goal builtin은 성급하게 동일한 continuation을 반복하지 않고 완료 알림을 기다릴 수 있다. + +```text +Agent turn 종료 +→ background 작업은 계속 실행 +→ monitor가 완료 감지 +→ hidden notification/continuation 등록 +→ Agent가 결과를 가지고 다시 진행 +``` + +## 10. Steering, Follow-up과 Continuation + +세 개념은 비슷해 보여도 출처와 목적이 다르다. + +| 개념 | 누가 만드는가 | 목적 | +|---|---|---| +| Steering | 주로 사용자 | 현재 진행 방향 변경 | +| Follow-up | 사용자 또는 시스템 | 현재 turn 뒤에 새 작업 수행 | +| Continuation | Goal/monitor 등의 정책 | 끝나지 않은 기존 작업 재개 | + +Compaction, abort와 model fallback 도중에도 이 queue가 유실되지 않아야 한다. 그래서 queue drain과 restore가 transaction처럼 다뤄진다. + +## 11. Codemode + +[`packages/senpi-codemode`](../packages/senpi-codemode)는 source-only extension으로 persistent eval kernel을 제공한다. + +### 일반 Tool과의 차이 + +일반 Tool call은 하나의 정해진 작업을 수행한다. Codemode의 eval cell은 작은 프로그램 안에서 계산과 여러 bridge 동작을 조합할 수 있다. + +### Persistent Kernel + +각 eval마다 완전히 새 process를 만드는 대신 kernel 상태를 유지할 수 있다. 앞 cell에서 만든 값이나 helper를 다음 cell에서 활용할 수 있다. + +### Detached Cell + +오래 걸리는 eval은 caller를 계속 막지 않고 detach할 수 있다. + +- 실행 중 상태 유지 +- cell ID로 조회 +- 결과 peek +- 명시적 stop +- 완료 시 wake-source 상태 갱신 + +Terminal은 shell/process 중심이고 Codemode는 code cell과 kernel 중심이라는 차이가 있다. + +## 12. Prompt Cache와 긴 Wait + +Provider에 따라 prompt prefix cache의 수명이 다르다. 너무 긴 foreground wait 뒤에 같은 대화로 모델을 다시 호출하면 cache가 사라져 비용과 latency가 증가할 수 있다. + +Senpi는 provider별 cache retention 정보를 이용해 foreground window와 keep-alive 정책을 조정한다. 그렇다고 cache 때문에 process를 중단하는 것은 아니다. 사용자의 wait와 background process 수명을 분리한다. + +## 13. 소스 읽기 경로 + +### Compaction + +1. [`packages/coding-agent/docs/compaction.md`](../packages/coding-agent/docs/compaction.md) +2. [`core/compaction/compaction.ts`](../packages/coding-agent/src/core/compaction/compaction.ts) +3. [`builtin/compaction/AGENTS.md`](../packages/coding-agent/src/core/extensions/builtin/compaction/AGENTS.md) +4. `policy.ts`, `speculative.ts`, `context-reduction.ts` +5. `checkpoint-state.ts`, `todo-bridge.ts`, `restoration-tracker.ts` + +### Terminal과 Codemode + +1. [`packages/pty/README.md`](../packages/pty/README.md) +2. [`builtin/terminal`](../packages/coding-agent/src/core/extensions/builtin/terminal) +3. [`packages/coding-agent/docs/terminal-tools.md`](../packages/coding-agent/docs/terminal-tools.md) +4. [`packages/senpi-codemode/README.md`](../packages/senpi-codemode/README.md) +5. Codemode의 `src/tool`과 detached cell manager + +## 14. 확인 문제 + +1. 오래된 메시지를 단순 삭제하는 것보다 compaction이 나은 이유는 무엇인가? +2. Speculative 결과를 적용하기 전에 revision을 확인해야 하는 이유는 무엇인가? +3. Deterministic reduction과 LLM summary는 각각 어떤 장점이 있는가? +4. Todo bridge와 restoration tracker는 서로 무엇을 보존하는가? +5. Polling 대신 monitor를 사용하는 이유는 무엇인가? +6. Persistent terminal과 detached eval cell의 중심 abstraction은 어떻게 다른가? + +## 15. 추적 실습 + +다음 두 시나리오를 각각 상태 전이 그림으로 그린다. + +### 시나리오 A: Context Overflow + +```text +provider 요청 +→ overflow 감지 +→ 현재 turn 정리 +→ blocking compaction +→ summary 적용 +→ provider 재시도 +``` + +각 단계에서 메시지 queue와 session revision이 어떻게 보호되는지 코드에서 찾는다. + +### 시나리오 B: 오래 걸리는 테스트 + +```text +bash 시작 +→ foreground window 초과 +→ PTY session detach +→ monitor 활성화 +→ Agent는 다른 작업 수행 +→ 테스트 종료 +→ wake notification +→ Agent continuation +``` + +각 화살표를 담당하는 패키지와 builtin을 적는다. + +[이전 장: Extension과 Senpi 주요 기능](04-extensions-and-features.md) · [다음 장: TUI, 실행 모드와 품질 관리](06-ui-modes-and-quality.md) diff --git a/learning/06-ui-modes-and-quality.md b/learning/06-ui-modes-and-quality.md new file mode 100644 index 000000000..0a7da86b1 --- /dev/null +++ b/learning/06-ui-modes-and-quality.md @@ -0,0 +1,285 @@ +# 6장. TUI, 실행 모드와 품질 관리 + +## 먼저 한 문장으로 + +Senpi는 하나의 `AgentSession`을 여러 입출력 모드에서 사용하고, TUI의 차등 렌더링과 계층화된 테스트·QA로 긴 실행에서도 일관된 동작을 유지한다. + +## 이 장에서 답할 질문 + +- TUI component는 어떻게 화면이 될까? +- 매 token마다 화면 전체를 다시 그리지 않는 방법은 무엇일까? +- Interactive, JSON, RPC, App Server는 무엇을 공유하고 무엇이 다를까? +- Unit test가 통과해도 실제 CLI QA가 필요한 이유는 무엇일까? +- Fork 고유 변경은 어떻게 기록하고 upstream과 함께 유지할까? + +## 1. 쉽게 이해하기: 같은 공연, 다른 중계 화면 + +AgentSession이 공연이라면 실행 모드는 공연을 전달하는 방식이다. + +- Interactive는 무대와 관객이 같은 공간에 있는 공연이다. +- Print는 끝난 결과만 전달하는 녹화본이다. +- JSON은 모든 장면을 구조화된 기록으로 내보낸다. +- RPC는 외부 감독이 명령을 보내고 event를 받는다. +- App Server는 여러 client가 연결될 수 있는 공연장 서버다. + +공연 내용은 같지만 입력, 출력과 사용자 확인 방식이 다르다. + +## 2. TUI Component 모델 + +[`packages/tui/src/tui.ts`](../packages/tui/src/tui.ts)의 component는 주어진 폭에서 화면의 줄 배열을 만든다. + +핵심 계약은 단순하다. + +- `render(width)`: 표시할 문자열 줄을 반환 +- `handleInput(data)`: 키 입력 처리 +- `invalidate()`: cache 무효화 +- 선택적으로 focus와 dispose 지원 + +Text, Markdown, Editor, Box, Stack, SelectList와 Overlay 같은 부품을 조합해 interactive 화면을 만든다. + +### 순수한 render가 중요한 이유 + +가능하면 동일한 상태와 폭에서 동일한 줄이 나와야 이전 frame과 비교할 수 있다. Render 중 외부 상태를 무작위로 변경하면 differential rendering과 테스트가 어려워진다. + +## 3. InteractiveMode와 TUI의 구분 + +[`interactive-mode.ts`](../packages/coding-agent/src/modes/interactive/interactive-mode.ts)는 애플리케이션 UI controller이고 `packages/tui`는 범용 terminal renderer다. + +### InteractiveMode + +- AgentSession event 구독 +- assistant message component 생성 +- Tool call/result renderer 선택 +- editor와 message queue 연결 +- dialog, footer와 extension widget 관리 +- model selector와 slash command 처리 + +### TUI + +- component tree render +- terminal 크기와 cursor 관리 +- 이전 frame과 새 frame 비교 +- ANSI sequence 출력 +- input dispatch +- overlay 배치 + +InteractiveMode가 “무엇을 보여 줄지” 결정하고 TUI가 “터미널에 어떻게 그릴지” 처리한다. + +## 4. Differential Rendering + +매 streaming delta마다 전체 transcript를 다시 출력하면 화면이 깜빡이고 긴 session에서 CPU 사용량이 커진다. + +차등 렌더링은 다음 과정을 사용한다. + +1. component tree로 새 화면 줄을 계산한다. +2. 이전에 그린 줄과 비교한다. +3. 변경된 범위를 찾는다. +4. terminal cursor를 필요한 위치로 이동한다. +5. 바뀐 줄만 출력한다. + +### Viewport 제한 + +대화가 매우 길어도 화면에 보이는 영역은 terminal 높이 정도다. Senpi는 가능한 경우 전체 transcript가 아니라 현재 viewport와 주변 overscan만 정규화하고 비교한다. + +### Insert-scroll fast path + +Streaming으로 아래쪽에 새 줄이 추가될 때 기존 화면 전체를 다시 그리지 않고 terminal의 scroll 동작을 활용한다. + +### Atomic frame + +Terminal이 중간 상태를 보여 주지 않도록 synchronized output 계열 sequence를 사용해 한 frame의 변경을 묶을 수 있다. Cursor, IME와 animation이 섞일 때 중요하다. + +## 5. Streaming UI + +Provider event가 올 때마다 즉시 모든 문자를 그대로 표시하면 빠른 모델에서는 읽기 어려운 burst가 생길 수 있다. Interactive mode에는 reveal pacing과 buffer가 있어 출력 속도를 조절한다. + +Tool도 상태가 변한다. + +```text +argument streaming +→ 실행 대기 +→ 실행 중 progress +→ 완료 또는 실패 +→ 최종 result renderer +``` + +Tool definition과 extension은 call/result renderer를 제공할 수 있고, interactive mode가 현재 단계에 맞는 renderer를 선택한다. + +## 6. Terminal의 까다로운 부분 + +### 문자 폭 + +문자열 길이와 terminal column 수는 다르다. 한글, CJK, emoji, 결합 문자를 grapheme과 표시 폭 기준으로 다뤄야 한다. + +### ANSI sequence + +색상과 cursor sequence는 화면 폭을 차지하지 않는다. 문자열을 자를 때 escape sequence를 중간에서 끊으면 terminal 상태가 깨질 수 있다. + +### IME + +한글 입력기의 후보 창이 올바른 위치에 나타나려면 실제 hardware cursor를 editor cursor와 맞춰야 한다. + +### tmux와 이미지 + +Kitty graphics 같은 protocol은 tmux passthrough와 pane 위치를 고려해야 한다. Terminal capability를 탐지하고 지원되지 않으면 안전한 fallback을 사용한다. + +## 7. 실행 모드 + +### Interactive Mode + +사람이 직접 사용하는 기본 모드다. 모든 UI primitive와 실시간 입력을 지원한다. + +### Print Mode + +Prompt를 실행한 뒤 최종 텍스트를 stdout에 출력한다. Shell script와 일회성 자동화에 적합하다. + +### JSON Event Stream + +사람용 화면 대신 구조화된 event를 출력한다. 외부 도구가 streaming 과정과 Tool 상태를 기계적으로 처리할 수 있다. + +### RPC Mode + +stdin/stdout JSONL을 통해 command를 받고 event를 전송한다. Session 생성, prompt, abort, 상태 조회 등을 외부 host가 제어한다. + +### App Server + +장기 실행되는 server runtime이 session을 관리한다. 연결, 재연결, backpressure와 여러 session의 수명을 고려한다. + +## 8. Protocol, Client, Server + +### Protocol + +[`packages/protocol`](../packages/protocol)은 request, response와 event의 transport-neutral schema와 CBOR framing을 정의한다. + +### Client + +[`packages/client`](../packages/client)는 byte transport 위에서 request ID, pending response와 event subscription을 관리한다. + +### Server + +[`packages/server`](../packages/server)는 protocol 요청을 session runtime 동작으로 연결한다. Unix socket 같은 transport와 독립적인 server core를 지향한다. + +세 패키지를 분리하면 protocol 타입을 browser와 Node client가 공유하면서 실제 socket 구현은 필요한 환경에만 둘 수 있다. + +## 9. 테스트 계층 + +### Unit Test + +작은 함수와 정책을 빠르게 검증한다. 예를 들어 parser, token policy, renderer helper가 대상이다. + +### Package Test + +AI adapter, Agent Loop, TUI, Coding Agent의 package 단위 동작을 검증한다. + +### Faux Provider Test + +실제 credential과 token을 사용하지 않고 정해진 streaming event와 오류를 재현한다. Tool call, retry, compaction 같은 Agent 동작을 결정적으로 테스트할 수 있다. + +### Regression Test + +실제 발견된 버그의 입력과 기대 동작을 고정한다. 문제를 고친 뒤 같은 버그가 돌아오지 않게 한다. + +### Fixture와 Golden + +복잡한 compaction context나 TUI output을 입력 fixture와 기대 결과로 관리한다. + +### Live API Test + +Provider 실제 동작이 필요한 테스트다. 기본 test에서는 실행하지 않고 명시적인 환경 변수와 credential이 있을 때만 실행한다. + +## 10. 실제 CLI QA가 필요한 이유 + +Unit test가 모두 통과해도 다음 문제는 놓칠 수 있다. + +- CLI process가 시작되지 않음 +- TTY에서 raw mode가 복구되지 않음 +- JSONL stdout에 불필요한 로그가 섞임 +- PTY가 종료 후에도 남음 +- 실제 event 순서가 외부 client와 맞지 않음 +- Extension이 source 실행 환경에서만 load 실패 + +`.agents/skills/senpi-qa`의 harness는 실제 CLI를 격리된 환경에서 실행한다. + +- CLI smoke +- RPC JSONL +- mock provider agent loop +- TUI/PTY smoke + +Runtime 변경에는 이런 end-to-end 증거가 필요하지만, 이 학습 자료처럼 Markdown만 추가하는 변경은 링크와 문서 형식 검증으로 충분하다. + +## 11. `AGENTS.md`와 `changes.md` + +### `AGENTS.md` + +특정 디렉터리를 수정할 때 따라야 할 구조, 규칙, 금지 사항과 검증 방법을 설명한다. 더 가까운 디렉터리의 지침이 더 구체적인 범위를 가진다. + +### `changes.md` + +Fork가 upstream 파일을 왜 수정했는지 기록한다. + +- 무엇을 변경했는가? +- 왜 필요한가? +- Extension으로 해결할 수 없었던 이유는 무엇인가? +- 다음 upstream merge에서 어디가 충돌할 가능성이 있는가? + +일반 사용자용 changelog와 달리 코드 유지보수자를 위한 fork ledger다. + +## 12. 변경 작업의 판단 순서 + +Senpi에 기능을 추가하려면 다음 순서로 생각한다. + +1. 기존 Extension API만으로 만들 수 있는가? +2. 필요한 작은 context/event seam만 core에 추가하면 되는가? +3. 정말 Agent Loop나 provider adapter의 저수준 변경인가? +4. 어떤 package test가 책임을 고정해야 하는가? +5. 실제 CLI QA가 필요한 runtime 변경인가? +6. Upstream 파일을 바꿨다면 어느 `changes.md`에 기록할 것인가? + +이 순서를 지키면 fork 고유 기능이 core 전체로 퍼지는 것을 줄일 수 있다. + +## 13. 소스 읽기 경로 + +### TUI + +1. [`packages/tui/README.md`](../packages/tui/README.md) +2. [`packages/tui/src/tui.ts`](../packages/tui/src/tui.ts)의 Component와 render 요청 +3. `components/text.ts`, `components/markdown.ts`, `components/editor.ts` +4. [`interactive-mode.ts`](../packages/coding-agent/src/modes/interactive/interactive-mode.ts)의 session event 처리 +5. tool progress와 streaming reveal helper + +### 원격 모드와 품질 + +1. [`packages/coding-agent/docs/rpc.md`](../packages/coding-agent/docs/rpc.md) +2. [`packages/coding-agent/src/modes/rpc`](../packages/coding-agent/src/modes/rpc) +3. [`packages/protocol/README.md`](../packages/protocol/README.md) +4. [`packages/client/README.md`](../packages/client/README.md) +5. [`packages/server/README.md`](../packages/server/README.md) +6. 루트 [`AGENTS.md`](../AGENTS.md)와 package별 `AGENTS.md` + +## 14. 확인 문제 + +1. InteractiveMode와 TUI를 분리한 이유는 무엇인가? +2. 긴 transcript에서 viewport-bounded rendering이 중요한 이유는 무엇인가? +3. JSON mode와 RPC mode는 어떻게 다른가? +4. Faux provider가 live API test보다 유리한 경우는 언제인가? +5. TypeScript 검사와 실제 CLI QA가 서로 대체할 수 없는 이유는 무엇인가? +6. Fork 변경을 일반 changelog뿐 아니라 `changes.md`에 기록하는 이유는 무엇인가? + +## 15. 최종 종합 실습 + +다음 기능을 실제로 구현하지 말고 설계 위치만 정해 본다. + +> `/count-tools` 명령을 추가해 현재 session에서 실행된 Tool 수를 footer에 표시하고, session 재시작 후에도 누적 수를 복구한다. + +아래 질문에 답한다. + +1. Extension으로 만들 수 있는가? +2. 어떤 Agent/Session event를 구독해야 하는가? +3. 상태를 memory와 session 중 어디에 저장해야 하는가? +4. Interactive가 아닌 mode에서는 어떻게 동작해야 하는가? +5. 어떤 unit test와 CLI QA가 필요한가? +6. Core 파일을 수정하지 않고 구현할 수 있는가? + +이 질문에 답할 수 있다면 Senpi의 주요 경계를 이해한 것이다. + +[이전 장: 컨텍스트 관리와 장시간 실행](05-context-and-long-running-work.md) · [용어집](glossary.md) · [처음으로](README.md) diff --git a/learning/glossary.md b/learning/glossary.md new file mode 100644 index 000000000..4f0ab4ddb --- /dev/null +++ b/learning/glossary.md @@ -0,0 +1,216 @@ +# Senpi 소스 코드 용어집 + +이 용어집은 사전식 정의보다 “Senpi 코드에서 이 단어가 무엇을 뜻하는가”에 초점을 둔다. + +## Agent + +- **쉽게 말하면:** 모델과 Tool을 반복해서 실행하는 엔진. +- **코드에서:** message state, model, thinking level, Tool과 streaming 상태를 가진다. +- **주의:** 세션 파일, 프로젝트 규칙과 TUI 전체를 책임지는 객체는 아니다. + +## Agent Loop + +- **쉽게 말하면:** 모델이 최종 답을 낼 때까지 모델 호출과 Tool 실행을 반복하는 절차. +- **코드에서:** `packages/agent/src/agent-loop.ts`의 `agentLoop()`, `runLoop()`가 중심이다. + +## AgentSession + +- **쉽게 말하면:** 범용 Agent를 실제 코딩 작업으로 운영하는 감독자. +- **코드에서:** 세션 저장, Extension, 동적 prompt, compaction, model과 Tool 변경을 조정한다. + +## API Adapter + +- **쉽게 말하면:** Senpi 공통 메시지를 특정 LLM API 형식으로 번역하는 통역사. +- **예:** Anthropic Messages, OpenAI Responses, OpenAI Completions. + +## App Server + +- **쉽게 말하면:** 외부 애플리케이션이 Senpi session을 장기적으로 실행하고 제어할 수 있는 server mode. + +## Assistant Message + +- **쉽게 말하면:** 모델이 만든 한 번의 응답. +- **코드에서:** Text, thinking, Tool Call 등 여러 content block을 가질 수 있다. + +## Abort + +- **쉽게 말하면:** 현재 실행을 더 진행하지 말라는 취소 신호. +- **코드에서:** `AbortSignal`을 provider와 Tool에 전달한다. 하위 OS process 종료와는 별도 문제가 될 수 있다. + +## Builtin Extension + +- **쉽게 말하면:** 외부 설치 없이 Senpi에 포함되어 기본 등록되는 Extension. +- **예:** permission-system, compaction, terminal, goal, MCP. + +## Compaction + +- **쉽게 말하면:** 긴 대화의 과거 부분을 요약해 context 공간을 확보하는 작업. +- **주의:** 단순 메시지 삭제가 아니라 작업 상태를 보존해야 한다. + +## Context + +- **쉽게 말하면:** 현재 모델 호출에서 모델이 볼 수 있는 정보 전체. +- **포함:** System prompt, 대화, Tool Call/Result, 이미지와 Tool schema. + +## Context Window + +- **쉽게 말하면:** 모델이 한 요청에서 처리할 수 있는 token의 최대 범위. + +## Codemode + +- **쉽게 말하면:** Persistent kernel에서 code cell을 실행하는 Senpi Extension. +- **코드에서:** `packages/senpi-codemode`에 있으며 긴 cell은 detach할 수 있다. + +## Differential Rendering + +- **쉽게 말하면:** 이전 화면과 달라진 부분만 terminal에 다시 그리는 방식. +- **목적:** 깜빡임과 CPU·출력 비용 감소. + +## Entry + +- **쉽게 말하면:** Session log에 append되는 기록 한 단위. +- **예:** Message, 모델 변경, custom state, compaction summary. + +## Extension + +- **쉽게 말하면:** Core를 직접 수정하지 않고 Tool, Command, Event handler, UI와 Provider를 추가하는 모듈. + +## ExtensionAPI + +- **쉽게 말하면:** Extension factory가 기능을 등록할 때 쓰는 API. + +## ExtensionContext + +- **쉽게 말하면:** Event handler가 현재 session 상태와 UI에 접근할 때 받는 문맥 객체. + +## ExtensionRunner + +- **쉽게 말하면:** 등록된 Extension 기능과 event handler를 보관하고 실행하는 관리자. + +## Faux Provider + +- **쉽게 말하면:** 실제 API와 credential 없이 정해진 모델 event를 재현하는 테스트 provider. + +## Follow-up Message + +- **쉽게 말하면:** 현재 Agent 작업이 끝난 다음 실행할 추가 메시지. + +## Goal + +- **쉽게 말하면:** 여러 turn이나 재시작을 지나도 유지되는 완료 목표. +- **주의:** 현재 작업 단계 목록인 Todo보다 상위 개념이다. + +## MCP + +- **풀어 쓰면:** Model Context Protocol. +- **쉽게 말하면:** 외부 server가 제공하는 Tool, Resource와 Prompt를 Agent에 연결하는 표준. + +## Model + +- **쉽게 말하면:** 사용자가 선택할 수 있는 구체적인 LLM 하나와 capability metadata. +- **포함:** Provider, API 종류, context window, reasoning, modality. + +## ModelRuntime + +- **쉽게 말하면:** 모델 목록, 인증과 실제 사용 가능 상태를 관리하는 coding-agent 계층. + +## Mode + +- **쉽게 말하면:** 같은 AgentSession을 사용자나 외부 프로그램과 연결하는 입출력 방식. +- **예:** Interactive, Print, JSON, RPC, App Server. + +## Permission + +- **쉽게 말하면:** 특정 Tool 동작을 allow, ask 또는 deny할지 정하는 논리적 정책. +- **주의:** OS sandbox나 container boundary는 아니다. + +## Prompt Preset + +- **쉽게 말하면:** 모델 family의 행동 특성에 맞춰 system prompt를 조정하는 builtin 기능. + +## Provider + +- **쉽게 말하면:** 모델 endpoint와 인증을 제공하는 서비스 또는 실행 환경. +- **주의:** Provider와 API wire format은 항상 일대일 관계가 아니다. + +## PTY + +- **풀어 쓰면:** Pseudo Terminal. +- **쉽게 말하면:** 실제 terminal처럼 shell process와 계속 상호작용할 수 있게 하는 가상 terminal. + +## ResourceLoader + +- **쉽게 말하면:** Extension, Skill, Prompt, Theme, 프로젝트 context file 등 session resource를 발견하고 로드하는 객체. + +## RPC + +- **풀어 쓰면:** Remote Procedure Call. +- **쉽게 말하면:** 외부 process가 JSONL command를 보내 Senpi session을 제어하고 event를 받는 mode. + +## Session + +- **쉽게 말하면:** 하나의 지속 가능한 대화·작업 기록. +- **포함:** 메시지뿐 아니라 모델 상태, compaction, Extension custom entry와 branch 관계. + +## SessionManager + +- **쉽게 말하면:** Session entry를 저장하고 읽으며 branch와 현재 leaf를 관리하는 객체. + +## Steering Message + +- **쉽게 말하면:** Agent가 실행 중일 때 현재 방향을 바꾸기 위해 끼워 넣는 사용자 메시지. + +## Stream / Streaming + +- **쉽게 말하면:** 완성된 답변을 기다리지 않고 생성되는 text, thinking과 Tool Call 조각을 순서대로 받는 방식. + +## System Prompt + +- **쉽게 말하면:** 모델의 역할, Tool, 규칙과 현재 작업 환경을 설명하는 상위 지침. +- **Senpi에서:** 활성 Tool, model preset, 프로젝트 규칙에 따라 동적으로 조립된다. + +## Thinking Level + +- **쉽게 말하면:** 모델이 답변 전에 사용할 reasoning 강도를 표현하는 공통 설정. +- **주의:** 실제 provider payload와 지원 단계는 모델마다 다르다. + +## Todo + +- **쉽게 말하면:** 현재 목표를 달성하기 위한 진행 단계 목록. + +## Tool + +- **쉽게 말하면:** 모델이 구조화된 입력으로 요청할 수 있는 외부 동작. +- **예:** read, bash, edit, web search, MCP Tool. + +## Tool Call + +- **쉽게 말하면:** 모델이 특정 Tool을 특정 argument로 실행해 달라고 보낸 요청. + +## Tool Result + +- **쉽게 말하면:** Tool 실행 뒤 모델에게 돌려주는 성공·실패 결과. + +## Tool Pair + +- **쉽게 말하면:** 하나의 Tool Call과 그에 대응하는 Tool Result의 쌍. +- **주의:** 일부 provider는 pair가 깨진 transcript를 거부한다. + +## TUI + +- **풀어 쓰면:** Terminal User Interface. +- **쉽게 말하면:** 일반 GUI 대신 terminal 문자와 ANSI sequence로 구성하는 사용자 인터페이스. + +## Wake Source + +- **쉽게 말하면:** Background 작업이 끝났을 때 session을 다시 진행시킬 책임이 있는 활성 작업. +- **예:** Terminal monitor, detached eval cell. + +## Wire Format + +- **쉽게 말하면:** 실제 provider나 remote transport로 전송되는 JSON 또는 binary 형식. +- **주의:** Senpi 내부 공통 타입과는 다를 수 있다. + +--- + +[처음으로](README.md) · [1장: 전체 구조와 실행 흐름](01-architecture-and-flow.md)