Files
analysis_claude_code/s13_background_tasks/README.ja.md

9.5 KiB
Raw Blame History

s13: Background Tasks — 遅い操作はバックグラウンドへ

English · 中文 · 日本語

s01 → ... → s11 → s12 → s13s14 → s15 → ... → s18 → s19

"遅い操作はバックグラウンドへ、agent は処理を継続" — バックグラウンドスレッドでコマンドを実行、完了時に通知を注入。

Harness 層: バックグラウンド — 非同期実行、メインループをブロックしない。


課題

洗濯機を使ったことがあるか衣類を入れ、スタートを押し、他のことをする——料理、メッセージ返信、論文読み。30 分後に洗濯機が「ピッピッ」と知らせる完了。30 分間立って待つ人はいない。

Agent の bash ツールも同じ。pip install torch は 10 分、npm run build は 3 分かかる。これらのコマンドが実行中、Agent は bash の戻りを待ち、その時間を他のタスクの処理に使えない。

ファイル読み込みはミリ秒、待たない。git status は 1 秒以内に戻る、待たない。しかし npm install分単位。Agent は 10 分間何もせず待ち、LLM 呼び出しはトークン課金、アイドル時間は無駄。


ソリューション

Background Tasks Overview

この章では、時間のかかる操作をバックグラウンドスレッドに送り、Agent はループを続行する。タスクが完了すると、結果が通知として会話に注入される。

同期 vs バックグラウンド:

同期 (s12) バックグラウンド (s13)
遅い操作 Agent が待機 バックグラウンドスレッドで実行
Agent アイドル はい いいえ、処理を継続
結果 即時返却 次ターンで通知を注入
判断基準 run_in_background パラメータ(モデル明示的リクエスト)、ヒューリスティックフォールバック

仕組み

should_run_background: 明示的リクエスト優先、ヒューリスティックフォールバック

モデルは bash ツールの run_in_background パラメータで明示的にバックグラウンド実行をリクエストする。指定がない場合は、キーワードヒューリスティックで判断する:

def is_slow_operation(tool_name: str, tool_input: dict) -> bool:
    """Fallback heuristic: commands likely to take > 30s."""
    if tool_name != "bash":
        return False
    cmd = tool_input.get("command", "").lower()
    slow_keywords = ["install", "build", "test", "deploy", "compile",
                     "docker build", "pip install", "npm install",
                     "cargo build", "pytest", "make"]
    return any(kw in cmd for kw in slow_keywords)

def should_run_background(tool_name: str, tool_input: dict) -> bool:
    """Model explicit request takes priority; fallback to heuristic."""
    if tool_input.get("run_in_background"):
        return True
    return is_slow_operation(tool_name, tool_input)

start_background_task: バックグラウンド実行とライフサイクル

ツール呼び出しをワーカー関数にラップし、daemon スレッドにディスパッチ。各バックグラウンドタスクは一意 ID を持ち、background_tasks 辞書で状態を追跡:

_bg_counter = 0
background_tasks: dict[str, dict] = {}   # bg_id → {tool_use_id, command, status}
background_results: dict[str, str] = {}   # bg_id → output
background_lock = threading.Lock()

def start_background_task(block) -> str:
    """Run tool in a daemon thread. Returns background task ID."""
    global _bg_counter
    _bg_counter += 1
    bg_id = f"bg_{_bg_counter:04d}"

    def worker():
        result = execute_tool(block)
        with background_lock:
            background_tasks[bg_id]["status"] = "completed"
            background_results[bg_id] = result

    with background_lock:
        background_tasks[bg_id] = {
            "tool_use_id": block.id,
            "command": block.input.get("command", ""),
            "status": "running",
        }
    thread = threading.Thread(target=worker, daemon=True)
    thread.start()
    return bg_id

start_background_task()bg_id を返す。daemon=True により、Agent プロセスの終了時にスレッドも終了する。

collect_background_results: 通知収集

バックグラウンドタスク完了時、結果を収集して <task_notification> メッセージとしてフォーマット:

def collect_background_results() -> list[str]:
    """Collect completed results as task_notification messages."""
    with background_lock:
        ready_ids = [bid for bid, task in background_tasks.items()
                     if task["status"] == "completed"]
    notifications = []
    for bg_id in ready_ids:
        with background_lock:
            task = background_tasks.pop(bg_id)
            output = background_results.pop(bg_id, "")
        notifications.append(
            f"<task_notification>\n"
            f"  <task_id>{bg_id}</task_id>\n"
            f"  <status>completed</status>\n"
            f"  <command>{task['command']}</command>\n"
            f"  <summary>{output[:200]}</summary>\n"
            f"</task_notification>")
    return notifications

通知は元の tool_use_id を再利用しない。元のツール呼び出しはプレースホルダー tool_result で応答済み。バックグラウンド完了は独立したイベントで、task_notification 形式で注入する。これは Messages API のツールペアリングに従う1 つの tool_use に対して正確に 1 つの tool_result

ループ統合

agent_loop でツール実行は 2 つのパスに分かれる。通知と結果は 1 つの user メッセージに統合:

results = []
for block in response.content:
    if block.type != "tool_use":
        continue
    if should_run_background(block.name, block.input):
        bg_id = start_background_task(block)
        results.append({"type": "tool_result",
            "tool_use_id": block.id,
            "content": f"[Background task {bg_id} started] "
                       f"Result will be available when complete."})
    else:
        output = execute_tool(block)
        results.append({"type": "tool_result",
            "tool_use_id": block.id, "content": output})

# 通知とツール結果を 1 つの user メッセージに統合
user_content = []
bg_notifications = collect_background_results()
if bg_notifications:
    for notif in bg_notifications:
        user_content.append({"type": "text", "text": notif})
user_content.extend(results)
messages.append({"role": "user", "content": user_content})

遅い操作は bg_id 付きプレースホルダー tool_result を返し、LLM はコマンドがまだ実行中だと知り、先に他のことをできる。バックグラウンド完了時、通知は独立した text block として現在のターンの tool_result と一緒に 1 つの user メッセージを構成する。

組み合わせて実行

Turn 1:
  LLM → bash "npm install" (run_in_background=true)
  → start_background_task → bg_0001
  → tool_result: "[Background task bg_0001 started]..."
  → LLM: "OK, I'll check later. Let me also read the config."

Turn 2:
  LLM → read_file "package.json" (fast, sync)
  → tool_result: file content
  → collect: bg_0001 done! inject <task_notification>
  → LLM sees: config file + install notification in one message

Agent は待たなかった。npm install がバックグラウンドで実行中に、設定ファイルを読んだ。


s12 からの変更

コンポーネント 変更前 (s12) 変更後 (s13)
実行モデル すべて同期 遅い操作はバックグラウンドスレッド + 通知注入
bash スキーマ command command + run_in_background
新規関数 should_run_background, is_slow_operation, start_background_task, collect_background_results
新規型 background_tasks: dict, background_results: dict, background_lock: Lock
通知形式 <task_notification>tool_use_id を再利用しない)
ループ動作 ツール直列実行 遅い操作は非同期、速い操作は同期、通知は毎ターン収集
ツール 8 (s12) 8変更なし、実行戦略が変更

試してみる

cd learn-claude-code
python s13_background_tasks/code.py

以下のプロンプトを試してください:

  1. Run pip list in the background and find all Python files in this directory
  2. Run npm install (use run_in_background) and while waiting, read package.json
  3. Create a task to setup the project, then run pip list in the background

観察ポイント:遅い操作はバックグラウンドにディスパッチされているか?bg_id は返されているか?バックグラウンド通知は <task_notification> 形式で注入されているか?


次の章

バックグラウンドタスクは「遅い操作がブロックしない」を解決した。しかし、定期的に何かをしたい場合は?例えば「毎朝 9 時にテストを実行」「5 分ごとにサーバーステータスを確認」。

s14 Cron Scheduler → Agent にアラームクロックを付ける。