Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions README-ja.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Learn Claude Code -- 真の Agent のための Harness Engineering

[English](./README.md) | [中文](./README-zh.md) | [日本語](./README-ja.md)
[English](./README.md) | [中文](./README-zh.md) | [日本語](./README-ja.md) | [한국어](./README-ko.md)

## モデルこそが Agent である

Expand Down Expand Up @@ -291,7 +291,7 @@ s08 バックグラウンドタスク [6] s10 チームプロトコル
learn-claude-code/
|
|-- agents/ # Python リファレンス実装 (s01-s12 + s_full 総括)
|-- docs/{en,zh,ja}/ # メンタルモデル優先のドキュメント (3言語)
|-- docs/{en,zh,ja,ko}/ # メンタルモデル優先のドキュメント (4言語)
|-- web/ # インタラクティブ学習プラットフォーム (Next.js)
|-- skills/ # s05 の Skill ファイル
+-- .github/workflows/ci.yml # CI: 型チェック + ビルド
Expand All @@ -300,7 +300,7 @@ learn-claude-code/
## ドキュメント

メンタルモデル優先: 問題、解決策、ASCII図、最小限のコード。
[English](./docs/en/) | [中文](./docs/zh/) | [日本語](./docs/ja/)
[English](./docs/en/) | [中文](./docs/zh/) | [日本語](./docs/ja/) | [한국어](./docs/ko/)

| セッション | トピック | モットー |
|-----------|---------|---------|
Expand Down
380 changes: 380 additions & 0 deletions README-ko.md

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions README-zh.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Learn Claude Code -- 真正的 Agent Harness 工程

[English](./README.md) | [中文](./README-zh.md) | [日本語](./README-ja.md)
[English](./README.md) | [中文](./README-zh.md) | [日本語](./README-ja.md) | [한국어](./README-ko.md)

## 模型就是 Agent

Expand Down Expand Up @@ -291,7 +291,7 @@ s08 Background Tasks [6] s10 Team Protocols [12]
learn-claude-code/
|
|-- agents/ # Python 参考实现 (s01-s12 + s_full 总纲)
|-- docs/{en,zh,ja}/ # 心智模型优先的文档 (3 种语言)
|-- docs/{en,zh,ja,ko}/ # 心智模型优先的文档 (4 种语言)
|-- web/ # 交互式学习平台 (Next.js)
|-- skills/ # s05 的 Skill 文件
+-- .github/workflows/ci.yml # CI: 类型检查 + 构建
Expand All @@ -300,7 +300,7 @@ learn-claude-code/
## 文档

心智模型优先: 问题、方案、ASCII 图、最小化代码。
[English](./docs/en/) | [中文](./docs/zh/) | [日本語](./docs/ja/)
[English](./docs/en/) | [中文](./docs/zh/) | [日本語](./docs/ja/) | [한국어](./docs/ko/)

| 课程 | 主题 | 格言 |
|------|------|------|
Expand Down
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[English](./README.md) | [中文](./README-zh.md) | [日本語](./README-ja.md)
[English](./README.md) | [中文](./README-zh.md) | [日本語](./README-ja.md) | [한국어](./README-ko.md)
# Learn Claude Code -- Harness Engineering for Real Agents

## The Model IS the Agent
Expand Down Expand Up @@ -290,7 +290,7 @@ s08 Background Tasks [6] s10 Team Protocols [12]
learn-claude-code/
|
|-- agents/ # Python reference implementations (s01-s12 + s_full capstone)
|-- docs/{en,zh,ja}/ # Mental-model-first documentation (3 languages)
|-- docs/{en,zh,ja,ko}/ # Mental-model-first documentation (4 languages)
|-- web/ # Interactive learning platform (Next.js)
|-- skills/ # Skill files for s05
+-- .github/workflows/ci.yml # CI: typecheck + build
Expand All @@ -299,7 +299,7 @@ learn-claude-code/
## Documentation

Mental-model-first: problem, solution, ASCII diagram, minimal code.
Available in [English](./docs/en/) | [中文](./docs/zh/) | [日本語](./docs/ja/).
Available in [English](./docs/en/) | [中文](./docs/zh/) | [日本語](./docs/ja/) | [한국어](./docs/ko/).

| Session | Topic | Motto |
|---------|-------|-------|
Expand Down
116 changes: 116 additions & 0 deletions docs/ko/s01-the-agent-loop.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
# s01: The Agent Loop

`[ s01 ] s02 > s03 > s04 > s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12`

> *"One loop & Bash is all you need"* -- 도구 하나 + 루프 하나 = 에이전트.
>
> **하네스(Harness) 계층**: 루프 -- 모델이 현실 세계와 만나는 첫 번째 접점.

## 문제

언어 모델은 코드에 대해 추론할 수 있지만, 현실 세계에 손을 뻗을 수는 없다. 파일을 읽지도, 테스트를 실행하지도, 에러를 확인하지도 못한다. 루프가 없으면 도구를 호출할 때마다 사용자가 직접 결과를 복사해서 붙여넣어야 한다. 사용자 자신이 루프가 되는 셈이다.

## 해결 방법

```
+--------+ +-------+ +---------+
| User | ---> | LLM | ---> | Tool |
| prompt | | | | execute |
+--------+ +---+---+ +----+----+
^ |
| tool_result |
+----------------+
(loop until stop_reason != "tool_use")
```

하나의 종료 조건이 전체 흐름을 제어한다. 모델이 도구 호출을 멈출 때까지 루프는 계속 돈다.

## 동작 방식

1. 사용자의 프롬프트가 첫 번째 메시지가 된다.

```python
messages.append({"role": "user", "content": query})
```

2. 메시지와 도구 정의를 LLM에 전송한다.

```python
response = client.messages.create(
model=MODEL, system=SYSTEM, messages=messages,
tools=TOOLS, max_tokens=8000,
)
```

3. 어시스턴트 응답을 추가하고 `stop_reason`을 확인한다. 모델이 도구를 호출하지 않았다면 종료.

```python
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason != "tool_use":
return
```

4. 각 도구 호출을 실행하고 결과를 수집하여 user 메시지로 추가한다. 2단계로 돌아간다.

```python
results = []
for block in response.content:
if block.type == "tool_use":
output = run_bash(block.input["command"])
results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": output,
})
messages.append({"role": "user", "content": results})
```

하나의 함수로 조립하면:

```python
def agent_loop(query):
messages = [{"role": "user", "content": query}]
while True:
response = client.messages.create(
model=MODEL, system=SYSTEM, messages=messages,
tools=TOOLS, max_tokens=8000,
)
messages.append({"role": "assistant", "content": response.content})

if response.stop_reason != "tool_use":
return

results = []
for block in response.content:
if block.type == "tool_use":
output = run_bash(block.input["command"])
results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": output,
})
messages.append({"role": "user", "content": results})
```

이것이 30줄도 안 되는 에이전트의 전부다. 이 과정의 나머지는 모두 이 루프 위에 쌓아 올리는 것이며, 루프 자체는 변하지 않는다.

## 변경 사항

| Component | Before | After |
|---------------|------------|--------------------------------|
| Agent loop | (none) | `while True` + stop_reason |
| Tools | (none) | `bash` (one tool) |
| Messages | (none) | Accumulating list |
| Control flow | (none) | `stop_reason != "tool_use"` |

## 직접 실행해 보기

```sh
cd learn-claude-code
python agents/s01_agent_loop.py
```

1. `Create a file called hello.py that prints "Hello, World!"`
2. `List all Python files in this directory`
3. `What is the current git branch?`
4. `Create a directory called test_output and write 3 files in it`
99 changes: 99 additions & 0 deletions docs/ko/s02-tool-use.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
# s02: Tool Use

`s01 > [ s02 ] s03 > s04 > s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12`

> *"Adding a tool means adding one handler"* -- 루프는 그대로, 새 도구는 디스패치 맵에 등록하기만 하면 된다.
>
> **하네스(Harness) 계층**: 도구 디스패치 -- 모델이 닿을 수 있는 범위를 넓힌다.

## 문제

`bash` 하나만으로는 에이전트가 모든 것을 셸에 의존하게 된다. `cat`은 예측 불가능하게 출력을 잘라내고, `sed`는 특수 문자에서 실패하며, 모든 bash 호출은 제약 없는 보안 표면이 된다. `read_file`이나 `write_file` 같은 전용 도구를 쓰면 도구 수준에서 경로 샌드박싱을 강제할 수 있다.

핵심 포인트: 도구를 추가해도 루프를 바꿀 필요가 없다.

## 해결 방법

```
+--------+ +-------+ +------------------+
| User | ---> | LLM | ---> | Tool Dispatch |
| prompt | | | | { |
+--------+ +---+---+ | bash: run_bash |
^ | read: run_read |
| | write: run_wr |
+-----------+ edit: run_edit |
tool_result | } |
+------------------+

The dispatch map is a dict: {tool_name: handler_function}.
One lookup replaces any if/elif chain.
```

## 동작 방식

1. 각 도구에 핸들러 함수를 정의한다. 경로 샌드박싱으로 워크스페이스 밖으로의 탈출을 방지한다.

```python
def safe_path(p: str) -> Path:
path = (WORKDIR / p).resolve()
if not path.is_relative_to(WORKDIR):
raise ValueError(f"Path escapes workspace: {p}")
return path

def run_read(path: str, limit: int = None) -> str:
text = safe_path(path).read_text()
lines = text.splitlines()
if limit and limit < len(lines):
lines = lines[:limit]
return "\n".join(lines)[:50000]
```

2. 디스패치 맵이 도구 이름과 핸들러를 연결한다.

```python
TOOL_HANDLERS = {
"bash": lambda **kw: run_bash(kw["command"]),
"read_file": lambda **kw: run_read(kw["path"], kw.get("limit")),
"write_file": lambda **kw: run_write(kw["path"], kw["content"]),
"edit_file": lambda **kw: run_edit(kw["path"], kw["old_text"],
kw["new_text"]),
}
```

3. 루프 안에서 이름으로 핸들러를 조회한다. 루프 본체는 s01에서 달라진 것이 없다.

```python
for block in response.content:
if block.type == "tool_use":
handler = TOOL_HANDLERS.get(block.name)
output = handler(**block.input) if handler \
else f"Unknown tool: {block.name}"
results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": output,
})
```

도구 추가 = 핸들러 추가 + 스키마 항목 추가. 루프는 절대 바뀌지 않는다.

## s01에서의 변경 사항

| Component | Before (s01) | After (s02) |
|----------------|--------------------|----------------------------|
| Tools | 1 (bash only) | 4 (bash, read, write, edit)|
| Dispatch | Hardcoded bash call | `TOOL_HANDLERS` dict |
| Path safety | None | `safe_path()` sandbox |
| Agent loop | Unchanged | Unchanged |

## 직접 실행해 보기

```sh
cd learn-claude-code
python agents/s02_tool_use.py
```

1. `Read the file requirements.txt`
2. `Create a file called greet.py with a greet(name) function`
3. `Edit greet.py to add a docstring to the function`
4. `Read greet.py to verify the edit worked`
96 changes: 96 additions & 0 deletions docs/ko/s03-todo-write.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
# s03: TodoWrite

`s01 > s02 > [ s03 ] s04 > s05 > s06 | s07 > s08 > s09 > s10 > s11 > s12`

> *"An agent without a plan drifts"* -- 먼저 단계를 나열하고, 그다음 실행한다.
>
> **하네스(Harness) 계층**: 계획 수립 -- 경로를 정해주지 않으면서도 모델이 궤도를 이탈하지 않게 한다.

## 문제

여러 단계로 이루어진 작업에서 모델은 길을 잃는다. 같은 작업을 반복하거나, 단계를 건너뛰거나, 엉뚱한 곳으로 빠진다. 대화가 길어질수록 더 심해지는데, 도구 결과가 컨텍스트를 채우면서 시스템 프롬프트의 영향력이 희미해지기 때문이다. 10단계짜리 리팩토링에서 1~3단계를 끝낸 뒤, 4~10단계를 잊어버리고 즉흥적으로 행동하기 시작한다.

## 해결 방법

```
+--------+ +-------+ +---------+
| User | ---> | LLM | ---> | Tools |
| prompt | | | | + todo |
+--------+ +---+---+ +----+----+
^ |
| tool_result |
+----------------+
|
+-----------+-----------+
| TodoManager state |
| [ ] task A |
| [>] task B <- doing |
| [x] task C |
+-----------------------+
|
if rounds_since_todo >= 3:
inject <reminder> into tool_result
```

## 동작 방식

1. TodoManager가 상태를 가진 항목 목록을 관리한다. `in_progress` 상태는 한 번에 하나만 허용된다.

```python
class TodoManager:
def update(self, items: list) -> str:
validated, in_progress_count = [], 0
for item in items:
status = item.get("status", "pending")
if status == "in_progress":
in_progress_count += 1
validated.append({"id": item["id"], "text": item["text"],
"status": status})
if in_progress_count > 1:
raise ValueError("Only one task can be in_progress")
self.items = validated
return self.render()
```

2. `todo` 도구는 다른 도구와 마찬가지로 디스패치 맵에 추가된다.

```python
TOOL_HANDLERS = {
# ...base tools...
"todo": lambda **kw: TODO.update(kw["items"]),
}
```

3. nag 리마인더가 모델이 3라운드 이상 `todo`를 호출하지 않을 경우 알림을 주입한다.

```python
if rounds_since_todo >= 3 and messages:
last = messages[-1]
if last["role"] == "user" and isinstance(last.get("content"), list):
last["content"].insert(0, {
"type": "text",
"text": "<reminder>Update your todos.</reminder>",
})
```

"한 번에 in_progress는 하나만" 제약이 순차적 집중을 강제하고, nag 리마인더가 책임감을 부여한다.

## s02에서의 변경 사항

| Component | Before (s02) | After (s03) |
|----------------|------------------|----------------------------|
| Tools | 4 | 5 (+todo) |
| Planning | None | TodoManager with statuses |
| Nag injection | None | `<reminder>` after 3 rounds|
| Agent loop | Simple dispatch | + rounds_since_todo counter|

## 직접 실행해 보기

```sh
cd learn-claude-code
python agents/s03_todo_write.py
```

1. `Refactor the file hello.py: add type hints, docstrings, and a main guard`
2. `Create a Python package with __init__.py, utils.py, and tests/test_utils.py`
3. `Review all Python files and fix any style issues`
Loading