mirror of
https://github.com/shareAI-lab/learn-claude-code.git
synced 2026-05-03 00:30:12 +00:00
better doc
This commit is contained in:
parent
aea8844bac
commit
665831c774
46 changed files with 1217 additions and 3505 deletions
|
|
@ -1,21 +1,19 @@
|
|||
# s08: Background Tasks
|
||||
|
||||
> BackgroundManagerがコマンドを別スレッドで実行し、各LLM呼び出しの前に通知キューをドレインすることで、エージェントは長時間実行操作でブロックされなくなる。
|
||||
`s01 > s02 > s03 > s04 > s05 > s06 | s07 > [ s08 ] s09 > s10 > s11 > s12`
|
||||
|
||||
> *"Fire and forget"* -- ノンブロッキングスレッド + 通知キュー。
|
||||
|
||||
## 問題
|
||||
|
||||
一部のコマンドは数分かかる: `npm install`、`pytest`、`docker build`。ブロッキングのagent loopでは、モデルはサブプロセスの終了を待って待機する。他のことは何もできない。ユーザーが「依存関係をインストールして、その間にconfigファイルを作成して」と言った場合、エージェントはまずインストールを行い、その後configを作成する -- 並列ではなく逐次的に。
|
||||
|
||||
エージェントには並行性が必要だ。agent loop自体の完全なマルチスレッディングではなく、長いコマンドを発射して実行中に作業を続ける能力だ。コマンドが終了したら、その結果は自然に会話に現れるべきだ。
|
||||
|
||||
解決策は、BackgroundManagerがコマンドをデーモンスレッドで実行し、結果を通知キューに収集すること。各LLM呼び出しの前にキューがドレインされ、結果がメッセージに注入される。
|
||||
一部のコマンドは数分かかる: `npm install`、`pytest`、`docker build`。ブロッキングループでは、モデルはサブプロセスの完了を待って座っている。ユーザーが「依存関係をインストールして、その間にconfigファイルを作って」と言っても、エージェントは並列ではなく逐次的に処理する。
|
||||
|
||||
## 解決策
|
||||
|
||||
```
|
||||
Main thread Background thread
|
||||
+-----------------+ +-----------------+
|
||||
| agent loop | | task executes |
|
||||
| agent loop | | subprocess runs |
|
||||
| ... | | ... |
|
||||
| [LLM call] <---+------- | enqueue(result) |
|
||||
| ^drain queue | +-----------------+
|
||||
|
|
@ -27,15 +25,12 @@ Agent --[spawn A]--[spawn B]--[other work]----
|
|||
v v
|
||||
[A runs] [B runs] (parallel)
|
||||
| |
|
||||
+-- notification queue --+
|
||||
|
|
||||
[results injected before
|
||||
next LLM call]
|
||||
+-- results injected before next LLM call --+
|
||||
```
|
||||
|
||||
## 仕組み
|
||||
|
||||
1. BackgroundManagerがタスクを追跡し、スレッドセーフな通知キューを維持する。
|
||||
1. BackgroundManagerがスレッドセーフな通知キューでタスクを追跡する。
|
||||
|
||||
```python
|
||||
class BackgroundManager:
|
||||
|
|
@ -45,109 +40,51 @@ class BackgroundManager:
|
|||
self._lock = threading.Lock()
|
||||
```
|
||||
|
||||
2. `run()`がデーモンスレッドを開始し、task_idを即座に返す。
|
||||
2. `run()`がデーモンスレッドを開始し、即座にリターンする。
|
||||
|
||||
```python
|
||||
def run(self, command: str) -> str:
|
||||
task_id = str(uuid.uuid4())[:8]
|
||||
self.tasks[task_id] = {
|
||||
"status": "running",
|
||||
"result": None,
|
||||
"command": command,
|
||||
}
|
||||
self.tasks[task_id] = {"status": "running", "command": command}
|
||||
thread = threading.Thread(
|
||||
target=self._execute,
|
||||
args=(task_id, command),
|
||||
daemon=True,
|
||||
)
|
||||
target=self._execute, args=(task_id, command), daemon=True)
|
||||
thread.start()
|
||||
return f"Background task {task_id} started"
|
||||
```
|
||||
|
||||
3. スレッドのターゲットである`_execute`がサブプロセスを実行し、結果を通知キューにプッシュする。
|
||||
3. サブプロセス完了時に、結果を通知キューへ。
|
||||
|
||||
```python
|
||||
def _execute(self, task_id: str, command: str):
|
||||
def _execute(self, task_id, command):
|
||||
try:
|
||||
r = subprocess.run(command, shell=True, cwd=WORKDIR,
|
||||
capture_output=True, text=True, timeout=300)
|
||||
output = (r.stdout + r.stderr).strip()[:50000]
|
||||
status = "completed"
|
||||
except subprocess.TimeoutExpired:
|
||||
output = "Error: Timeout (300s)"
|
||||
status = "timeout"
|
||||
self.tasks[task_id]["status"] = status
|
||||
self.tasks[task_id]["result"] = output
|
||||
with self._lock:
|
||||
self._notification_queue.append({
|
||||
"task_id": task_id,
|
||||
"status": status,
|
||||
"result": output[:500],
|
||||
})
|
||||
"task_id": task_id, "result": output[:500]})
|
||||
```
|
||||
|
||||
4. `drain_notifications()`が保留中の結果を返してクリアする。
|
||||
|
||||
```python
|
||||
def drain_notifications(self) -> list:
|
||||
with self._lock:
|
||||
notifs = list(self._notification_queue)
|
||||
self._notification_queue.clear()
|
||||
return notifs
|
||||
```
|
||||
|
||||
5. agent loopが各LLM呼び出しの前に通知をドレインする。
|
||||
4. エージェントループが各LLM呼び出しの前に通知をドレインする。
|
||||
|
||||
```python
|
||||
def agent_loop(messages: list):
|
||||
while True:
|
||||
notifs = BG.drain_notifications()
|
||||
if notifs and messages:
|
||||
if notifs:
|
||||
notif_text = "\n".join(
|
||||
f"[bg:{n['task_id']}] {n['status']}: "
|
||||
f"{n['result']}" for n in notifs
|
||||
)
|
||||
f"[bg:{n['task_id']}] {n['result']}" for n in notifs)
|
||||
messages.append({"role": "user",
|
||||
"content": f"<background-results>"
|
||||
f"\n{notif_text}\n"
|
||||
"content": f"<background-results>\n{notif_text}\n"
|
||||
f"</background-results>"})
|
||||
messages.append({"role": "assistant",
|
||||
"content": "Noted background results."})
|
||||
response = client.messages.create(...)
|
||||
```
|
||||
|
||||
## 主要コード
|
||||
|
||||
BackgroundManager(`agents/s08_background_tasks.py` 49-107行目):
|
||||
|
||||
```python
|
||||
class BackgroundManager:
|
||||
def __init__(self):
|
||||
self.tasks = {}
|
||||
self._notification_queue = []
|
||||
self._lock = threading.Lock()
|
||||
|
||||
def run(self, command: str) -> str:
|
||||
task_id = str(uuid.uuid4())[:8]
|
||||
self.tasks[task_id] = {"status": "running",
|
||||
"result": None,
|
||||
"command": command}
|
||||
thread = threading.Thread(
|
||||
target=self._execute,
|
||||
args=(task_id, command), daemon=True)
|
||||
thread.start()
|
||||
return f"Background task {task_id} started"
|
||||
|
||||
def _execute(self, task_id, command):
|
||||
# run subprocess, push to queue
|
||||
...
|
||||
|
||||
def drain_notifications(self) -> list:
|
||||
with self._lock:
|
||||
notifs = list(self._notification_queue)
|
||||
self._notification_queue.clear()
|
||||
return notifs
|
||||
```
|
||||
ループはシングルスレッドのまま。サブプロセスI/Oだけが並列化される。
|
||||
|
||||
## s07からの変更点
|
||||
|
||||
|
|
@ -158,10 +95,6 @@ class BackgroundManager:
|
|||
| Notification | None | Queue drained per loop |
|
||||
| Concurrency | None | Daemon threads |
|
||||
|
||||
## 設計原理
|
||||
|
||||
エージェントループは本質的にシングルスレッドだ(一度に1つのLLM呼び出し)。バックグラウンドスレッドはI/Oバウンドな作業(テスト、ビルド、インストール)に対してこの制約を打破する。通知キューパターン(「次のLLM呼び出し前にドレイン」)により、結果はモデルの推論を途中で中断するのではなく、会話の自然な区切りで到着する。これは最小限の並行性モデルだ: エージェントループはシングルスレッドで決定論的なまま、I/Oバウンドなサブプロセス実行のみが並列化される。
|
||||
|
||||
## 試してみる
|
||||
|
||||
```sh
|
||||
|
|
@ -169,8 +102,6 @@ cd learn-claude-code
|
|||
python agents/s08_background_tasks.py
|
||||
```
|
||||
|
||||
試せるプロンプト例:
|
||||
|
||||
1. `Run "sleep 5 && echo done" in the background, then create a file while it runs`
|
||||
2. `Start 3 background tasks: "sleep 2", "sleep 4", "sleep 6". Check their status.`
|
||||
3. `Run pytest in the background and keep working on other things`
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue