---
title: オーケストレーションモードを構築する
url: https://platform.claude.com/docs/ja/build-with-claude/mid-conversation-effort-example
description: マルチエージェントのファンアウトに対する継続的な同意を付与し、会話途中のシステムメッセージでオン・オフを切り替えるセッションレベルのモードを構築します。
---

「orchestration mode」（オーケストレーションモード）はセッションレベルのスイッチです。オンのとき、モデルはすべての実質的なリクエストに最大限の徹底性を注ぎ、タスクを自ら偵察したうえで、デフォルトで並列のサブエージェントに作業をファンアウトします。オフのとき、同じオーケストレーションツールはリクエストごとのオプトインに戻ります。

このモードはAPIパラメータではありません。すべてドキュメント化された部品から構築されています。

1. **エフォートレベル：** リクエストは `xhigh` などのドキュメント化された[Effort](https://platform.claude.com/docs/ja/build-with-claude/effort)（エフォート）値で実行されます。そのページに記載されているレベルより上の隠れたレベルは存在しません。この例では各リクエストのトップレベルでエフォートを設定しており、ベータヘッダーは不要です。
2. **モードリマインダー：** [会話途中のシステムメッセージ](https://platform.claude.com/docs/ja/build-with-claude/mid-conversation-system-messages)（mid-conversation system message）がモデルにモードが有効であることを伝え、数ターンごとに1行のリフレッシャーを送り、モードがオフになったときには終了通知を送ります。トップレベルの `system` フィールドは決して変更されないため、キャッシュされたプレフィックスはそのまま保たれます。
3. **ツール説明における継続的な同意：** オーケストレーションツールの説明には、モードがオンの間、モデルはすべての実質的なタスクについて事前に確認することなくワークフローを作成して実行すべきであると記載されています。

<Note>
  この例では会話途中のシステムメッセージを使用します。これをサポートするモデルとプラットフォームについては、[会話途中のシステムメッセージ](https://platform.claude.com/docs/ja/build-with-claude/mid-conversation-system-messages)を参照してください。ファンアウト自体がトークン使用量を増大させます。1つのリクエストが多数のサブエージェント会話を生成する可能性があるため、このモードはコストに見合う作業のために取っておいてください。
</Note>

## ループをセットアップする

この例は単一のファイルです。定数はエフォートレベル、ファンアウトの形状、およびモードリフレッシャーを再送信する頻度を制御します。`MAX_CONCURRENT` は同時に実行されるサブエージェントの数を制限します（PHP版は逐次実行であり、これを無視します）。`MAX_TOTAL_SUBTASKS` は1回のWorkflow呼び出しでモデルがキューに入れられる数を制限します。この2つを分けることで、モデルは大きなバックログをすべて一度に起動することなく計画できます。`DOC_TEST_MODE` のチェックは、その環境変数が設定されている場合にループを1ターンに制限するため、自動化されたドキュメントハーネスは完全なオーケストレーションを実行せずに、ファイルがコンパイルされ素早く終了することを検証できます。自分で例を実行する場合は未設定のままにしてください。

<CodeGroup exclude="shell:cURL, shell:CLI">
  ```python Python
  import atexit
  import concurrent.futures
  import hashlib
  import json
  import os
  import shutil
  import subprocess
  import sys
  import tempfile
  import threading

  import anthropic

  client = anthropic.Anthropic()

  MODEL = "claude-opus-5-5"
  EFFORT = "xhigh"

  SYSTEM_PROMPT = (
      "You are a helpful general-purpose agent. Answer the user's request directly."
  )

  REQUEST_TIMEOUT_SECONDS = 600
  BASH_TIMEOUT_SECONDS = 60
  TOOL_RESULT_MAX_CHARS = 8000
  MAX_CONCURRENT = 10
  DOC_TEST_MODE = bool(os.environ.get("DOC_TEST_MODE"))
  MAX_TOTAL_SUBTASKS = 2 if DOC_TEST_MODE else 200
  MAX_SUBAGENT_TURNS = 1 if DOC_TEST_MODE else 15
  MAX_MAIN_TURNS = 1 if DOC_TEST_MODE else 30
  TURNS_BETWEEN_REFRESHERS = 10
  JOURNAL_PATH = os.environ.get("ORCH_JOURNAL") or "orchestration_journal.json"
  ```

  ```typescript TypeScript
  import { exec } from "node:child_process";
  import { createHash } from "node:crypto";
  import { mkdtempDisposable, readFile, rename, writeFile } from "node:fs/promises";
  import { tmpdir } from "node:os";
  import { join } from "node:path";

  import Anthropic from "@anthropic-ai/sdk";

  const client = new Anthropic();

  const MODEL = "claude-opus-5-5";
  const EFFORT = "xhigh";

  const SYSTEM_PROMPT =
    "You are a helpful general-purpose agent. Answer the user's request directly.";

  const REQUEST_TIMEOUT_SECONDS = 600;
  const BASH_TIMEOUT_SECONDS = 60;
  const TOOL_RESULT_MAX_CHARS = 8000;
  const MAX_CONCURRENT = 10;
  const DOC_TEST_MODE = Boolean(process.env.DOC_TEST_MODE);
  const MAX_TOTAL_SUBTASKS = DOC_TEST_MODE ? 2 : 200;
  const MAX_SUBAGENT_TURNS = DOC_TEST_MODE ? 1 : 15;
  const MAX_MAIN_TURNS = DOC_TEST_MODE ? 1 : 30;
  const TURNS_BETWEEN_REFRESHERS = 10;
  const JOURNAL_PATH = process.env.ORCH_JOURNAL || "orchestration_journal.json";
  ```

  ```csharp C#
  using System.Diagnostics;
  using System.Security.Cryptography;
  using System.Text;
  using System.Text.Json;
  using Anthropic;
  using Anthropic.Models.Messages;

  AnthropicClient client = new();

  const Model model = Model.ClaudeOpus5_5;
  var effort = Effort.Xhigh;

  const string systemPrompt = "You are a helpful general-purpose agent. Answer the user's request directly.";

  const int requestTimeoutSeconds = 600;
  const int bashTimeoutSeconds = 60;
  const int toolResultMaxChars = 8000;
  const int maxConcurrent = 10;
  var docTestMode = Environment.GetEnvironmentVariable("DOC_TEST_MODE") is { Length: > 0 };
  var maxTotalSubtasks = docTestMode ? 2 : 200;
  var maxSubagentTurns = docTestMode ? 1 : 15;
  var maxMainTurns = docTestMode ? 1 : 30;
  const int turnsBetweenRefreshers = 10;
  var journalPath = Environment.GetEnvironmentVariable("ORCH_JOURNAL") is { Length: > 0 } configuredPath
      ? configuredPath
      : "orchestration_journal.json";
  ```

  ```go Go
  import (
  	"bytes"
  	"cmp"
  	"context"
  	"crypto/sha256"
  	"encoding/hex"
  	"encoding/json"
  	"errors"
  	"fmt"
  	"log"
  	"os"
  	"os/exec"
  	"path/filepath"
  	"strings"
  	"sync"
  	"time"

  	"github.com/anthropics/anthropic-sdk-go"
  )

  var client = anthropic.NewClient()

  const (
  	modelID = anthropic.ModelClaudeOpus5_5
  	effort  = anthropic.OutputConfigEffortXhigh

  	systemPrompt = "You are a helpful general-purpose agent. Answer the user's request directly."

  	requestTimeoutSeconds  = 600
  	bashTimeoutSeconds     = 60
  	toolResultMaxChars     = 8000
  	maxConcurrent          = 10
  	turnsBetweenRefreshers = 10
  )

  var (
  	docTestMode      = os.Getenv("DOC_TEST_MODE") != ""
  	maxTotalSubtasks = ifTest(2, 200)
  	maxSubagentTurns = ifTest(1, 15)
  	maxMainTurns     = ifTest(1, 30)
  	journalPath      = cmp.Or(os.Getenv("ORCH_JOURNAL"), "orchestration_journal.json")
  )

  func ifTest(test, normal int) int {
  	if docTestMode {
  		return test
  	}
  	return normal
  }
  ```

  ```java Java
  // コンパクトソースファイルでは java.base（java.util、java.nio.file、java.util.concurrent など）が
  // 暗黙的にインポートされるため、インポートが必要なのは SDK と Jackson のみです。
  import com.anthropic.client.AnthropicClient;
  import com.anthropic.client.okhttp.AnthropicOkHttpClient;
  import com.anthropic.core.JsonArray;
  import com.anthropic.core.JsonBoolean;
  import com.anthropic.core.JsonObject;
  import com.anthropic.core.JsonString;
  import com.anthropic.core.JsonValue;
  import com.anthropic.core.RequestOptions;
  import com.anthropic.helpers.MessageAccumulator;
  import com.anthropic.models.messages.ContentBlockParam;
  import com.anthropic.models.messages.Message;
  import com.anthropic.models.messages.MessageCreateParams;
  import com.anthropic.models.messages.MessageParam;
  import com.anthropic.models.messages.Model;
  import com.anthropic.models.messages.OutputConfig;
  import com.anthropic.models.messages.StopReason;
  import com.anthropic.models.messages.TextBlock;
  import com.anthropic.models.messages.Tool;
  import com.anthropic.models.messages.ToolBash20250124;
  import com.anthropic.models.messages.ToolResultBlockParam;
  import com.anthropic.models.messages.ToolUseBlock;
  import com.fasterxml.jackson.core.JsonProcessingException;
  import com.fasterxml.jackson.core.type.TypeReference;
  import com.fasterxml.jackson.databind.JsonNode;
  import com.fasterxml.jackson.databind.ObjectMapper;

  AnthropicClient client = AnthropicOkHttpClient.fromEnv();

  static final Model MODEL = Model.CLAUDE_OPUS_5_5;
  static final boolean DOC_TEST_MODE =
          !Objects.requireNonNullElse(System.getenv("DOC_TEST_MODE"), "").isEmpty();
  static final OutputConfig.Effort EFFORT = OutputConfig.Effort.XHIGH;

  static final String SYSTEM_PROMPT =
          "You are a helpful general-purpose agent. Answer the user's request directly.";

  static final int REQUEST_TIMEOUT_SECONDS = 600;
  static final RequestOptions REQUEST_OPTIONS =
          RequestOptions.builder().timeout(Duration.ofSeconds(REQUEST_TIMEOUT_SECONDS)).build();
  static final int BASH_TIMEOUT_SECONDS = 60;
  static final int TOOL_RESULT_MAX_CHARS = 8000;
  static final int MAX_CONCURRENT = 10;
  static final int MAX_TOTAL_SUBTASKS = DOC_TEST_MODE ? 2 : 200;
  static final int MAX_SUBAGENT_TURNS = DOC_TEST_MODE ? 1 : 15;
  static final int MAX_MAIN_TURNS = DOC_TEST_MODE ? 1 : 30;
  static final int TURNS_BETWEEN_REFRESHERS = 10;
  static final Path JOURNAL_PATH = Path.of(Optional.ofNullable(System.getenv("ORCH_JOURNAL"))
          .filter(path -> !path.isEmpty())
          .orElse("orchestration_journal.json"));
  ```

  ```php PHP
  use Anthropic\Client;
  use Anthropic\Messages\ToolUseBlock;

  $client = new Client();

  const MODEL = 'claude-opus-5-5';
  define('DOC_TEST_MODE', (string) getenv('DOC_TEST_MODE') !== '');
  const EFFORT = 'xhigh';

  const SYSTEM_PROMPT = 'You are a helpful general-purpose agent. Answer the user\'s request directly.';

  const REQUEST_TIMEOUT_SECONDS = 600;
  const BASH_TIMEOUT_SECONDS = 60;
  const TOOL_RESULT_MAX_CHARS = 8000;
  const MAX_CONCURRENT = 10;
  define('MAX_TOTAL_SUBTASKS', DOC_TEST_MODE ? 2 : 200);
  define('MAX_SUBAGENT_TURNS', DOC_TEST_MODE ? 1 : 15);
  define('MAX_MAIN_TURNS', DOC_TEST_MODE ? 1 : 30);
  const TURNS_BETWEEN_REFRESHERS = 10;
  define('JOURNAL_PATH', getenv('ORCH_JOURNAL') ?: 'orchestration_journal.json');
  ```

  ```ruby Ruby
  require "anthropic"
  require "digest"
  require "fileutils"
  require "json"
  require "open3"
  require "tmpdir"

  CLIENT = Anthropic::Client.new

  MODEL = "claude-opus-5-5"
  EFFORT = :xhigh

  SYSTEM_PROMPT = "You are a helpful general-purpose agent. Answer the user's request directly."

  REQUEST_TIMEOUT_SECONDS = 600
  BASH_TIMEOUT_SECONDS = 60
  TOOL_RESULT_MAX_CHARS = 8000
  MAX_CONCURRENT = 10
  DOC_TEST_MODE = !ENV["DOC_TEST_MODE"].to_s.empty?
  MAX_TOTAL_SUBTASKS = DOC_TEST_MODE ? 2 : 200
  MAX_SUBAGENT_TURNS = DOC_TEST_MODE ? 1 : 15
  MAX_MAIN_TURNS = DOC_TEST_MODE ? 1 : 30
  TURNS_BETWEEN_REFRESHERS = 10
  JOURNAL_PATH = ENV["ORCH_JOURNAL"].to_s.empty? ? "orchestration_journal.json" : ENV["ORCH_JOURNAL"]
  ```
</CodeGroup>

## モードリマインダーを定義する

リマインダーは意図的に短くしています。モードを切り替え、重量級の指示が置かれているツール説明を指し示します。全文はモードがオンになったときに1回送信され、リフレッシャーは数回のユーザーターンの後にのみ再送信され、終了通知はモードがオフになったときに1回送信されます。

<CodeGroup exclude="shell:cURL, shell:CLI">
  ```python Python
  MODE_ENTER = (
      "Orchestration mode is on: optimize for the most exhaustive, correct answer rather than "
      "the fastest one. Use the Workflow tool on every substantive task, sized to the problem's "
      "natural decomposition rather than the maximum the tool allows. See the Workflow tool's "
      "description for standing consent, granularity guidance, and quality patterns. Work solo "
      "only on conversational or trivial turns."
  )
  MODE_REFRESH = "Orchestration mode is still on. Use the Workflow tool; see its standing consent section."
  MODE_EXIT = (
      "Orchestration mode is off. The Workflow tool's standard opt-in rule applies again."
  )
  ```

  ```typescript TypeScript
  const MODE_ENTER =
    "Orchestration mode is on: optimize for the most exhaustive, correct answer rather than " +
    "the fastest one. Use the Workflow tool on every substantive task, sized to the problem's " +
    "natural decomposition rather than the maximum the tool allows. See the Workflow tool's " +
    "description for standing consent, granularity guidance, and quality patterns. Work solo " +
    "only on conversational or trivial turns.";
  const MODE_REFRESH =
    "Orchestration mode is still on. Use the Workflow tool; see its standing consent section.";
  const MODE_EXIT =
    "Orchestration mode is off. The Workflow tool's standard opt-in rule applies again.";
  ```

  ```csharp C#
  const string modeEnter =
      "Orchestration mode is on: optimize for the most exhaustive, correct answer rather than "
      + "the fastest one. Use the Workflow tool on every substantive task, sized to the problem's "
      + "natural decomposition rather than the maximum the tool allows. See the Workflow tool's "
      + "description for standing consent, granularity guidance, and quality patterns. Work solo "
      + "only on conversational or trivial turns.";
  const string modeRefresh =
      "Orchestration mode is still on. Use the Workflow tool; see its standing consent section.";
  const string modeExit =
      "Orchestration mode is off. The Workflow tool's standard opt-in rule applies again.";
  ```

  ```go Go
  const (
  	modeEnter = "Orchestration mode is on: optimize for the most exhaustive, correct answer rather than " +
  		"the fastest one. Use the Workflow tool on every substantive task, sized to the problem's " +
  		"natural decomposition rather than the maximum the tool allows. See the Workflow tool's " +
  		"description for standing consent, granularity guidance, and quality patterns. Work solo " +
  		"only on conversational or trivial turns."
  	modeRefresh = "Orchestration mode is still on. Use the Workflow tool; see its standing consent section."
  	modeExit    = "Orchestration mode is off. The Workflow tool's standard opt-in rule applies again."
  )
  ```

  ```java Java
  static final String MODE_ENTER = """
          Orchestration mode is on: optimize for the most exhaustive, correct answer rather than \
          the fastest one. Use the Workflow tool on every substantive task, sized to the problem's \
          natural decomposition rather than the maximum the tool allows. See the Workflow tool's \
          description for standing consent, granularity guidance, and quality patterns. Work solo \
          only on conversational or trivial turns.""";
  static final String MODE_REFRESH =
          "Orchestration mode is still on. Use the Workflow tool; see its standing consent section.";
  static final String MODE_EXIT =
          "Orchestration mode is off. The Workflow tool's standard opt-in rule applies again.";
  ```

  ```php PHP
  const MODE_ENTER =
      'Orchestration mode is on: optimize for the most exhaustive, correct answer rather than '
      . 'the fastest one. Use the Workflow tool on every substantive task, sized to the problem\'s '
      . 'natural decomposition rather than the maximum the tool allows. See the Workflow tool\'s '
      . 'description for standing consent, granularity guidance, and quality patterns. Work solo '
      . 'only on conversational or trivial turns.';
  const MODE_REFRESH =
      'Orchestration mode is still on. Use the Workflow tool; see its standing consent section.';
  const MODE_EXIT =
      'Orchestration mode is off. The Workflow tool\'s standard opt-in rule applies again.';
  ```

  ```ruby Ruby
  MODE_ENTER =
    "Orchestration mode is on: optimize for the most exhaustive, correct answer rather than " \
    "the fastest one. Use the Workflow tool on every substantive task, sized to the problem's " \
    "natural decomposition rather than the maximum the tool allows. See the Workflow tool's " \
    "description for standing consent, granularity guidance, and quality patterns. Work solo " \
    "only on conversational or trivial turns."
  MODE_REFRESH =
    "Orchestration mode is still on. Use the Workflow tool; see its standing consent section."
  MODE_EXIT =
    "Orchestration mode is off. The Workflow tool's standard opt-in rule applies again."
  ```
</CodeGroup>

## ツール説明で継続的な同意を付与する

Workflowツールが実際の行動契約を担います。オプトインのルール、モードがオンの間に適用される継続的な同意、ファンアウトの規模を決めるための粒度のガイダンス、そしてモデルが利用できる品質パターン（検証ウェーブ、完全性の批評者、多段階のシーケンス）です。サブエージェントには `report_findings` ツールも与えられるため、結果は散文ではなく構造化されたJSONとして返されます。また、bashツールはAnthropic定義の `bash_20250124` ツールをローカルで実行するものです。

<CodeGroup exclude="shell:cURL, shell:CLI">
  ```python Python
  WORKFLOW_TOOL = {
      "name": "Workflow",
      "description": (
          "Orchestrate a multiagent workflow: split a large task into independent subtasks "
          "and run them as parallel agents, then collect their results.\n\n"
          "Opt-in: only use this tool when the user explicitly asks for a workflow, or when a "
          "system message confirms that orchestration mode is on.\n\n"
          "Quality patterns: adversarial verification (a second wave of agents checks the first "
          "wave's findings against the source), a completeness critic (one agent hunts for what "
          "the others missed), and multiphase sequencing (understand, design, implement, and "
          "review as separate workflow calls, reading results between phases). A useful default "
          "is hybrid: scout inline first to discover the work-list, then fan out over it.\n\n"
          "Granularity: scope each subtask to a distinct concern, component, or question rather "
          "than per line or per file section. Scale the count to what the user asked for: a "
          "focused review of a module of a few hundred lines rarely needs more than about ten "
          "subtasks; a broad audit of a large codebase can justify more.\n\n"
          "Standing consent: while a system message confirms orchestration mode is on, that "
          "opt-in is standing. Author and run a workflow for every substantive task by default, "
          "and lean toward verifying findings adversarially. Work solo only on conversational "
          "turns or trivial mechanical edits. When a system message says the mode is off, "
          "revert to the opt-in rule above."
      ),
      "input_schema": {
          "type": "object",
          "properties": {
              "subtasks": {
                  "type": "array",
                  "items": {"type": "string"},
                  "description": "Independent subtask prompts to run as parallel agents",
              }
          },
          "required": ["subtasks"],
      },
  }

  BASH_TOOL = {"type": "bash_20250124", "name": "bash"}

  REPORT_TOOL = {
      "name": "report_findings",
      "description": (
          "Report the final findings for your subtask. Call this exactly once, when you are "
          "done investigating; it ends your task."
      ),
      "input_schema": {
          "type": "object",
          "properties": {
              "summary": {
                  "type": "string",
                  "description": "Two or three sentences of synthesis",
              },
              "findings": {
                  "type": "array",
                  "items": {
                      "type": "object",
                      "properties": {
                          "claim": {
                              "type": "string",
                              "description": "The finding, one sentence",
                          },
                          "evidence": {
                              "type": "string",
                              "description": "How it was verified (file, line, or command output)",
                          },
                          "severity": {
                              "type": "string",
                              "enum": ["high", "medium", "low", "info"],
                          },
                      },
                      "required": ["claim", "evidence", "severity"],
                  },
              },
          },
          "required": ["summary", "findings"],
      },
  }
  ```

  ```typescript TypeScript
  const WORKFLOW_TOOL: Anthropic.Tool = {
    name: "Workflow",
    description: [
      "Orchestrate a multiagent workflow: split a large task into independent subtasks " +
        "and run them as parallel agents, then collect their results.",
      "Opt-in: only use this tool when the user explicitly asks for a workflow, or when a " +
        "system message confirms that orchestration mode is on.",
      "Quality patterns: adversarial verification (a second wave of agents checks the first " +
        "wave's findings against the source), a completeness critic (one agent hunts for what " +
        "the others missed), and multiphase sequencing (understand, design, implement, and " +
        "review as separate workflow calls, reading results between phases). A useful default " +
        "is hybrid: scout inline first to discover the work-list, then fan out over it.",
      "Granularity: scope each subtask to a distinct concern, component, or question rather " +
        "than per line or per file section. Scale the count to what the user asked for: a " +
        "focused review of a module of a few hundred lines rarely needs more than about ten " +
        "subtasks; a broad audit of a large codebase can justify more.",
      "Standing consent: while a system message confirms orchestration mode is on, that " +
        "opt-in is standing. Author and run a workflow for every substantive task by default, " +
        "and lean toward verifying findings adversarially. Work solo only on conversational " +
        "turns or trivial mechanical edits. When a system message says the mode is off, " +
        "revert to the opt-in rule above."
    ].join("\n\n"),
    input_schema: {
      type: "object",
      properties: {
        subtasks: {
          type: "array",
          items: { type: "string" },
          description: "Independent subtask prompts to run as parallel agents"
        }
      },
      required: ["subtasks"]
    }
  };

  const BASH_TOOL: Anthropic.ToolBash20250124 = { type: "bash_20250124", name: "bash" };

  const REPORT_TOOL: Anthropic.Tool = {
    name: "report_findings",
    description:
      "Report the final findings for your subtask. Call this exactly once, when you are " +
      "done investigating; it ends your task.",
    input_schema: {
      type: "object",
      properties: {
        summary: { type: "string", description: "Two or three sentences of synthesis" },
        findings: {
          type: "array",
          items: {
            type: "object",
            properties: {
              claim: { type: "string", description: "The finding, one sentence" },
              evidence: {
                type: "string",
                description: "How it was verified (file, line, or command output)"
              },
              severity: { type: "string", enum: ["high", "medium", "low", "info"] }
            },
            required: ["claim", "evidence", "severity"]
          }
        }
      },
      required: ["summary", "findings"]
    }
  };
  ```

  ```csharp C#
  Tool workflowTool = new()
  {
      Name = "Workflow",
      Description = """
          Orchestrate a multiagent workflow: split a large task into independent subtasks and run them as parallel agents, then collect their results.

          Opt-in: only use this tool when the user explicitly asks for a workflow, or when a system message confirms that orchestration mode is on.

          Quality patterns: adversarial verification (a second wave of agents checks the first wave's findings against the source), a completeness critic (one agent hunts for what the others missed), and multiphase sequencing (understand, design, implement, and review as separate workflow calls, reading results between phases). A useful default is hybrid: scout inline first to discover the work-list, then fan out over it.

          Granularity: scope each subtask to a distinct concern, component, or question rather than per line or per file section. Scale the count to what the user asked for: a focused review of a module of a few hundred lines rarely needs more than about ten subtasks; a broad audit of a large codebase can justify more.

          Standing consent: while a system message confirms orchestration mode is on, that opt-in is standing. Author and run a workflow for every substantive task by default, and lean toward verifying findings adversarially. Work solo only on conversational turns or trivial mechanical edits. When a system message says the mode is off, revert to the opt-in rule above.
          """,
      InputSchema = new()
      {
          Properties = new Dictionary<string, JsonElement>
          {
              ["subtasks"] = JsonSerializer.SerializeToElement(new
              {
                  type = "array",
                  items = new { type = "string" },
                  description = "Independent subtask prompts to run as parallel agents",
              }),
          },
          Required = ["subtasks"],
      },
  };

  ToolBash20250124 bashTool = new();

  Tool reportTool = new()
  {
      Name = "report_findings",
      Description =
          "Report the final findings for your subtask. Call this exactly once, when you are "
          + "done investigating; it ends your task.",
      InputSchema = new()
      {
          Properties = new Dictionary<string, JsonElement>
          {
              ["summary"] = JsonSerializer.SerializeToElement(new
              {
                  type = "string",
                  description = "Two or three sentences of synthesis",
              }),
              ["findings"] = JsonSerializer.SerializeToElement(new
              {
                  type = "array",
                  items = new
                  {
                      type = "object",
                      properties = new
                      {
                          claim = new { type = "string", description = "The finding, one sentence" },
                          evidence = new
                          {
                              type = "string",
                              description = "How it was verified (file, line, or command output)",
                          },
                          severity = new { type = "string", @enum = new[] { "high", "medium", "low", "info" } },
                      },
                      required = new[] { "claim", "evidence", "severity" },
                  },
              }),
          },
          Required = ["summary", "findings"],
      },
  };
  ```

  ```go Go
  var workflowTool = anthropic.ToolUnionParam{
  	OfTool: &anthropic.ToolParam{
  		Name: "Workflow",
  		Description: anthropic.String("Orchestrate a multiagent workflow: split a large task into independent subtasks " +
  			"and run them as parallel agents, then collect their results.\n\n" +
  			"Opt-in: only use this tool when the user explicitly asks for a workflow, or when a " +
  			"system message confirms that orchestration mode is on.\n\n" +
  			"Quality patterns: adversarial verification (a second wave of agents checks the first " +
  			"wave's findings against the source), a completeness critic (one agent hunts for what " +
  			"the others missed), and multiphase sequencing (understand, design, implement, and " +
  			"review as separate workflow calls, reading results between phases). A useful default " +
  			"is hybrid: scout inline first to discover the work-list, then fan out over it.\n\n" +
  			"Granularity: scope each subtask to a distinct concern, component, or question rather " +
  			"than per line or per file section. Scale the count to what the user asked for: a " +
  			"focused review of a module of a few hundred lines rarely needs more than about ten " +
  			"subtasks; a broad audit of a large codebase can justify more.\n\n" +
  			"Standing consent: while a system message confirms orchestration mode is on, that " +
  			"opt-in is standing. Author and run a workflow for every substantive task by default, " +
  			"and lean toward verifying findings adversarially. Work solo only on conversational " +
  			"turns or trivial mechanical edits. When a system message says the mode is off, " +
  			"revert to the opt-in rule above."),
  		InputSchema: anthropic.ToolInputSchemaParam{
  			Properties: map[string]any{
  				"subtasks": map[string]any{
  					"type":        "array",
  					"items":       map[string]any{"type": "string"},
  					"description": "Independent subtask prompts to run as parallel agents",
  				},
  			},
  			Required: []string{"subtasks"},
  		},
  	},
  }

  var bashTool = anthropic.ToolUnionParam{
  	OfBashTool20250124: &anthropic.ToolBash20250124Param{},
  }

  var reportTool = anthropic.ToolUnionParam{
  	OfTool: &anthropic.ToolParam{
  		Name: "report_findings",
  		Description: anthropic.String("Report the final findings for your subtask. Call this exactly once, when you are " +
  			"done investigating; it ends your task."),
  		InputSchema: anthropic.ToolInputSchemaParam{
  			Properties: map[string]any{
  				"summary": map[string]any{"type": "string", "description": "Two or three sentences of synthesis"},
  				"findings": map[string]any{
  					"type": "array",
  					"items": map[string]any{
  						"type": "object",
  						"properties": map[string]any{
  							"claim": map[string]any{"type": "string", "description": "The finding, one sentence"},
  							"evidence": map[string]any{
  								"type":        "string",
  								"description": "How it was verified (file, line, or command output)",
  							},
  							"severity": map[string]any{"type": "string", "enum": []string{"high", "medium", "low", "info"}},
  						},
  						"required": []string{"claim", "evidence", "severity"},
  					},
  				},
  			},
  			Required: []string{"summary", "findings"},
  		},
  	},
  }
  ```

  ```java Java
  static final Tool WORKFLOW_TOOL = Tool.builder()
          .name("Workflow")
          .description("""
                  Orchestrate a multiagent workflow: split a large task into independent subtasks \
                  and run them as parallel agents, then collect their results.

                  Opt-in: only use this tool when the user explicitly asks for a workflow, or when a \
                  system message confirms that orchestration mode is on.

                  Quality patterns: adversarial verification (a second wave of agents checks the first \
                  wave's findings against the source), a completeness critic (one agent hunts for what \
                  the others missed), and multiphase sequencing (understand, design, implement, and \
                  review as separate workflow calls, reading results between phases). A useful default \
                  is hybrid: scout inline first to discover the work-list, then fan out over it.

                  Granularity: scope each subtask to a distinct concern, component, or question rather \
                  than per line or per file section. Scale the count to what the user asked for: a \
                  focused review of a module of a few hundred lines rarely needs more than about ten \
                  subtasks; a broad audit of a large codebase can justify more.

                  Standing consent: while a system message confirms orchestration mode is on, that \
                  opt-in is standing. Author and run a workflow for every substantive task by default, \
                  and lean toward verifying findings adversarially. Work solo only on conversational \
                  turns or trivial mechanical edits. When a system message says the mode is off, \
                  revert to the opt-in rule above.""")
          .inputSchema(Tool.InputSchema.builder()
                  .properties(Tool.InputSchema.Properties.builder()
                          .putAdditionalProperty("subtasks", JsonValue.from(Map.of(
                                  "type", "array",
                                  "items", Map.of("type", "string"),
                                  "description", "Independent subtask prompts to run as parallel agents")))
                          .build())
                  .putAdditionalProperty("required", JsonValue.from(List.of("subtasks")))
                  .build())
          .build();

  static final ToolBash20250124 BASH_TOOL = ToolBash20250124.builder().build();

  static final Tool REPORT_TOOL = Tool.builder()
          .name("report_findings")
          .description("""
                  Report the final findings for your subtask. Call this exactly once, when you are \
                  done investigating; it ends your task.""")
          .inputSchema(Tool.InputSchema.builder()
                  .properties(Tool.InputSchema.Properties.builder()
                          .putAdditionalProperty("summary", JsonValue.from(Map.of(
                                  "type", "string",
                                  "description", "Two or three sentences of synthesis")))
                          .putAdditionalProperty("findings", JsonValue.from(Map.of(
                                  "type", "array",
                                  "items", Map.of(
                                          "type", "object",
                                          "properties", Map.of(
                                                  "claim", Map.of(
                                                          "type", "string",
                                                          "description", "The finding, one sentence"),
                                                  "evidence", Map.of(
                                                          "type", "string",
                                                          "description", "How it was verified (file, line, or command output)"),
                                                  "severity", Map.of(
                                                          "type", "string",
                                                          "enum", List.of("high", "medium", "low", "info"))),
                                          "required", List.of("claim", "evidence", "severity")))))
                          .build())
                  .putAdditionalProperty("required", JsonValue.from(List.of("summary", "findings")))
                  .build())
          .build();
  ```

  ```php PHP
  const WORKFLOW_TOOL = [
      'name' => 'Workflow',
      'description' =>
          'Orchestrate a multiagent workflow: split a large task into independent subtasks '
          . "and run them as parallel agents, then collect their results.\n\n"
          . 'Opt-in: only use this tool when the user explicitly asks for a workflow, or when a '
          . "system message confirms that orchestration mode is on.\n\n"
          . 'Quality patterns: adversarial verification (a second wave of agents checks the first '
          . 'wave\'s findings against the source), a completeness critic (one agent hunts for what '
          . 'the others missed), and multiphase sequencing (understand, design, implement, and '
          . 'review as separate workflow calls, reading results between phases). A useful default '
          . "is hybrid: scout inline first to discover the work-list, then fan out over it.\n\n"
          . 'Granularity: scope each subtask to a distinct concern, component, or question rather '
          . 'than per line or per file section. Scale the count to what the user asked for: a '
          . 'focused review of a module of a few hundred lines rarely needs more than about ten '
          . "subtasks; a broad audit of a large codebase can justify more.\n\n"
          . 'Standing consent: while a system message confirms orchestration mode is on, that '
          . 'opt-in is standing. Author and run a workflow for every substantive task by default, '
          . 'and lean toward verifying findings adversarially. Work solo only on conversational '
          . 'turns or trivial mechanical edits. When a system message says the mode is off, '
          . 'revert to the opt-in rule above.',
      'input_schema' => [
          'type' => 'object',
          'properties' => [
              'subtasks' => [
                  'type' => 'array',
                  'items' => ['type' => 'string'],
                  'description' => 'Independent subtask prompts to run as parallel agents',
              ],
          ],
          'required' => ['subtasks'],
      ],
  ];

  const BASH_TOOL = ['type' => 'bash_20250124', 'name' => 'bash'];

  const REPORT_TOOL = [
      'name' => 'report_findings',
      'description' =>
          'Report the final findings for your subtask. Call this exactly once, when you are '
          . 'done investigating; it ends your task.',
      'input_schema' => [
          'type' => 'object',
          'properties' => [
              'summary' => ['type' => 'string', 'description' => 'Two or three sentences of synthesis'],
              'findings' => [
                  'type' => 'array',
                  'items' => [
                      'type' => 'object',
                      'properties' => [
                          'claim' => ['type' => 'string', 'description' => 'The finding, one sentence'],
                          'evidence' => [
                              'type' => 'string',
                              'description' => 'How it was verified (file, line, or command output)',
                          ],
                          'severity' => ['type' => 'string', 'enum' => ['high', 'medium', 'low', 'info']],
                      ],
                      'required' => ['claim', 'evidence', 'severity'],
                  ],
              ],
          ],
          'required' => ['summary', 'findings'],
      ],
  ];
  ```

  ```ruby Ruby
  WORKFLOW_TOOL = {
    name: "Workflow",
    description:
      "Orchestrate a multiagent workflow: split a large task into independent subtasks " \
      "and run them as parallel agents, then collect their results.\n\n" \
      "Opt-in: only use this tool when the user explicitly asks for a workflow, or when a " \
      "system message confirms that orchestration mode is on.\n\n" \
      "Quality patterns: adversarial verification (a second wave of agents checks the first " \
      "wave's findings against the source), a completeness critic (one agent hunts for what " \
      "the others missed), and multiphase sequencing (understand, design, implement, and " \
      "review as separate workflow calls, reading results between phases). A useful default " \
      "is hybrid: scout inline first to discover the work-list, then fan out over it.\n\n" \
      "Granularity: scope each subtask to a distinct concern, component, or question rather " \
      "than per line or per file section. Scale the count to what the user asked for: a " \
      "focused review of a module of a few hundred lines rarely needs more than about ten " \
      "subtasks; a broad audit of a large codebase can justify more.\n\n" \
      "Standing consent: while a system message confirms orchestration mode is on, that " \
      "opt-in is standing. Author and run a workflow for every substantive task by default, " \
      "and lean toward verifying findings adversarially. Work solo only on conversational " \
      "turns or trivial mechanical edits. When a system message says the mode is off, " \
      "revert to the opt-in rule above.",
    input_schema: {
      type: "object",
      properties: {
        subtasks: {
          type: "array",
          items: {type: "string"},
          description: "Independent subtask prompts to run as parallel agents"
        }
      },
      required: ["subtasks"]
    }
  }.freeze

  BASH_TOOL = {type: "bash_20250124", name: "bash"}.freeze

  REPORT_TOOL = {
    name: "report_findings",
    description:
      "Report the final findings for your subtask. Call this exactly once, when you are " \
      "done investigating; it ends your task.",
    input_schema: {
      type: "object",
      properties: {
        summary: {type: "string", description: "Two or three sentences of synthesis"},
        findings: {
          type: "array",
          items: {
            type: "object",
            properties: {
              claim: {type: "string", description: "The finding, one sentence"},
              evidence: {
                type: "string",
                description: "How it was verified (file, line, or command output)"
              },
              severity: {type: "string", enum: ["high", "medium", "low", "info"]}
            },
            required: ["claim", "evidence", "severity"]
          }
        }
      },
      required: ["summary", "findings"]
    }
  }.freeze
  ```
</CodeGroup>

## bashツールをローカルで実行する

bashハンドラーは要求されたコマンドをタイムアウト付きで実行し、stdoutとstderrを結合してキャプチャし、暴走したコマンドが「context window」（コンテキストウィンドウ）を溢れさせないように結果を切り詰めます。コマンドは例を起動したディレクトリで実行されるため、プロジェクトを対象にするにはそこで起動する必要があります。`DOC_TEST_MODE` が設定されている場合、ハーネスは代わりに、終了時に削除される小さな使い捨てのフィクスチャディレクトリをbashに与えます。ここにサンドボックスはありません。コマンドは例を起動したプロセスの権限で実行されます。わかりやすさのため、この例では `bash_20250124` の契約が記述する永続的なセッションを維持するのではなく、各呼び出しを新しいサブシェルで実行します。本番のエージェントでは、作業ディレクトリ、環境、および `restart` アクションがドキュメントどおりに動作するよう、長寿命のシェルでツールを支えるべきです。

<CodeGroup exclude="shell:cURL, shell:CLI">
  ```python Python
  # 例が起動された場所でbashを実行します。DOC_TEST_MODEではドキュメントのハーネスが
  # 代わりに使い捨てのフィクスチャディレクトリを指すようにし、終了時に削除されます。
  if DOC_TEST_MODE:
      WORK_DIR = tempfile.mkdtemp(prefix="orchestration-")
      atexit.register(shutil.rmtree, WORK_DIR, ignore_errors=True)
      with open(os.path.join(WORK_DIR, "sample.py"), "w") as fixture:
          fixture.write(
              "def fib(n):\n"
              "    return n if n < 2 else fib(n - 1) + fib(n - 2)\n\n"
              "print(fib(10))\n"
          )
  else:
      WORK_DIR = os.getcwd()


  def run_bash(command: str) -> tuple[str, bool]:
      """Run a shell command and return (output, is_error). No sandbox: example code only."""
      print(f"[bash] {command}", file=sys.stderr)
      try:
          proc = subprocess.run(
              ["bash", "-c", command],
              cwd=WORK_DIR,
              capture_output=True,
              text=True,
              errors="replace",
              timeout=BASH_TIMEOUT_SECONDS,
          )
      except subprocess.TimeoutExpired:
          return f"command timed out after {BASH_TIMEOUT_SECONDS}s", True
      output = (proc.stdout + proc.stderr).strip() or "(no output)"
      if len(output) > TOOL_RESULT_MAX_CHARS:
          output = (
              output[:TOOL_RESULT_MAX_CHARS]
              + f"\n(truncated at {TOOL_RESULT_MAX_CHARS} chars)"
          )
      if proc.returncode != 0:
          output = f"(exit code {proc.returncode})\n{output}"
      return output, proc.returncode != 0


  def handle_bash_block(block) -> tuple[str, bool]:
      if block.input.get("restart") is True:
          return "Shell restarted.", False
      command = block.input.get("command")
      if not isinstance(command, str) or not command:
          return "bash error: no command was provided.", True
      return run_bash(command)
  ```

  ```typescript TypeScript
  type ToolOutcome = { output: string; isError: boolean };

  // サンプルを起動した場所でbashを実行します。DOC_TEST_MODEではドキュメントのテストハーネスが
  // 代わりに使い捨てのフィクスチャディレクトリを指定し、プログラム終了時に削除されます。
  await using fixtureDir = DOC_TEST_MODE
    ? await mkdtempDisposable(join(tmpdir(), "orchestration-"))
    : null;
  const WORK_DIR = fixtureDir?.path ?? process.cwd();
  if (fixtureDir) {
    await writeFile(
      join(WORK_DIR, "sample.py"),
      `def fib(n):
      return n if n < 2 else fib(n - 1) + fib(n - 2)

  print(fib(10))
  `
    );
  }

  // シェルコマンドを実行して出力を返します。サンドボックスなし:サンプルコード専用です。
  function runBash(command: string): Promise<ToolOutcome> {
    console.error(`[bash] ${command}`);
    const options = {
      shell: "/bin/bash",
      cwd: WORK_DIR,
      timeout: BASH_TIMEOUT_SECONDS * 1000,
      maxBuffer: 16 * 1024 * 1024
    };
    return new Promise((resolve) => {
      exec(command, options, (error, stdout, stderr) => {
        if (error?.killed) {
          resolve({ output: `command timed out after ${BASH_TIMEOUT_SECONDS}s`, isError: true });
          return;
        }
        const exitCode = error === null ? 0 : typeof error.code === "number" ? error.code : 1;
        let output = (stdout + stderr).trim() || "(no output)";
        const codePoints = [...output];
        if (codePoints.length > TOOL_RESULT_MAX_CHARS) {
          const kept = codePoints.slice(0, TOOL_RESULT_MAX_CHARS).join("");
          output = `${kept}\n(truncated at ${TOOL_RESULT_MAX_CHARS} chars)`;
        }
        if (exitCode !== 0) {
          output = `(exit code ${exitCode})\n${output}`;
        }
        resolve({ output, isError: exitCode !== 0 });
      });
    });
  }

  async function handleBashBlock(block: Anthropic.ToolUseBlock): Promise<ToolOutcome> {
    const { command, restart } = block.input as { command?: string; restart?: boolean };
    if (restart === true) {
      return { output: "Shell restarted.", isError: false };
    }
    if (!command) {
      return { output: "bash error: no command was provided.", isError: true };
    }
    return runBash(command);
  }
  ```

  ```csharp C#
  // 例が起動された場所で bash を実行します。DOC_TEST_MODE ではドキュメントハーネスが
  // 代わりに使い捨てのフィクスチャディレクトリを指定し、終了時に削除されます。
  var workDir = Environment.CurrentDirectory;
  if (docTestMode)
  {
      workDir = Directory.CreateTempSubdirectory("orchestration-").FullName;
      await File.WriteAllTextAsync(Path.Combine(workDir, "sample.py"), """
          def fib(n):
              return n if n < 2 else fib(n - 1) + fib(n - 2)

          print(fib(10))

          """);
      var fixtureDir = workDir;
      AppDomain.CurrentDomain.ProcessExit += (_, _) =>
      {
          try { Directory.Delete(fixtureDir, recursive: true); }
          catch { /* Best-effort cleanup; the OS tmp sweeper handles leftovers. */ }
      };
  }

  // シェルコマンドを実行し、出力とエラーフラグを返します。サンドボックスなし：例示コード専用。
  async Task<(string Output, bool IsError)> RunBash(string command)
  {
      Console.Error.WriteLine($"[bash] {command}");
      using var process = Process.Start(new ProcessStartInfo("bash")
      {
          ArgumentList = { "-c", command },
          WorkingDirectory = workDir,
          RedirectStandardOutput = true,
          RedirectStandardError = true,
      });
      if (process is null)
      {
          return ("bash error: the shell process failed to start.", true);
      }
      var stdoutTask = process.StandardOutput.ReadToEndAsync();
      var stderrTask = process.StandardError.ReadToEndAsync();
      using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(bashTimeoutSeconds));
      try
      {
          await process.WaitForExitAsync(timeout.Token);
      }
      catch (OperationCanceledException)
      {
          process.Kill(entireProcessTree: true);
          // プロセスが破棄される前にリーダータスクを完了させます。
          try
          {
              await Task.WhenAll(stdoutTask, stderrTask);
          }
          catch
          {
              // タイムアウト時は出力が破棄されるため、リーダーの失敗も無視されます。
          }
          return ($"command timed out after {bashTimeoutSeconds}s", true);
      }
      var output = (await stdoutTask + await stderrTask).Trim();
      if (output.Length == 0)
      {
          output = "(no output)";
      }
      if (output.Length > toolResultMaxChars)
      {
          output = output[..toolResultMaxChars] + $"\n(truncated at {toolResultMaxChars} chars)";
      }
      if (process.ExitCode != 0)
      {
          output = $"(exit code {process.ExitCode})\n{output}";
      }
      return (output, process.ExitCode != 0);
  }

  // モデルが要求した1つの bash ツール呼び出しを実行します。
  async Task<(string Output, bool IsError)> HandleBashBlock(ToolUseBlock block)
  {
      if (block.Input.GetValueOrDefault("restart").ValueKind is JsonValueKind.True)
      {
          return ("Shell restarted.", false);
      }
      return block.Input.GetValueOrDefault("command") is { ValueKind: JsonValueKind.String } command
          && command.GetString() is { Length: > 0 } commandText
          ? await RunBash(commandText)
          : ("bash error: no command was provided.", true);
  }
  ```

  ```go Go
  // 例を起動した場所でbashを実行します。DOC_TEST_MODEではドキュメントハーネスが
  // 代わりに使い捨てのフィクスチャディレクトリを指し、終了時に削除されます。
  var workDir = func() string {
  	if !docTestMode {
  		dir, err := os.Getwd()
  		if err != nil {
  			log.Fatal(err)
  		}
  		return dir
  	}
  	dir, err := os.MkdirTemp("", "orchestration-")
  	if err != nil {
  		log.Fatal(err)
  	}
  	const fixture = `def fib(n):
      return n if n < 2 else fib(n - 1) + fib(n - 2)

  print(fib(10))
  `
  	if err := os.WriteFile(filepath.Join(dir, "sample.py"), []byte(fixture), 0o644); err != nil {
  		log.Fatal(err)
  	}
  	return dir
  }()

  // runBashはシェルコマンドを実行し、出力とエラーフラグを返します。
  // サンドボックスなし：サンプルコード専用です。
  func runBash(ctx context.Context, command string) (string, bool) {
  	fmt.Fprintf(os.Stderr, "[bash] %s\n", command)
  	ctx, cancel := context.WithTimeout(ctx, bashTimeoutSeconds*time.Second)
  	defer cancel()
  	cmd := exec.CommandContext(ctx, "bash", "-c", command)
  	cmd.Dir = workDir
  	combined, err := cmd.CombinedOutput()
  	if errors.Is(ctx.Err(), context.DeadlineExceeded) {
  		return fmt.Sprintf("command timed out after %ds", bashTimeoutSeconds), true
  	}
  	output := strings.TrimSpace(string(combined))
  	if output == "" {
  		output = "(no output)"
  	}
  	if runes := []rune(output); len(runes) > toolResultMaxChars {
  		output = string(runes[:toolResultMaxChars]) + fmt.Sprintf("\n(truncated at %d chars)", toolResultMaxChars)
  	}
  	if err == nil {
  		return output, false
  	}
  	if exitErr, ok := errors.AsType[*exec.ExitError](err); ok {
  		return fmt.Sprintf("(exit code %d)\n%s", exitErr.ExitCode(), output), true
  	}
  	return fmt.Sprintf("(%s)\n%s", err, output), true
  }

  // handleBashBlockはモデルが要求した1つのbashツール呼び出しを実行します。
  func handleBashBlock(ctx context.Context, block anthropic.ToolUseBlock) (string, bool) {
  	var input struct {
  		Command string `json:"command"`
  		Restart bool   `json:"restart"`
  	}
  	if err := json.Unmarshal(block.Input, &input); err != nil {
  		return fmt.Sprintf("bash error: could not parse input: %s", err), true
  	}
  	if input.Restart {
  		return "Shell restarted.", false
  	}
  	if input.Command == "" {
  		return "bash error: no command was provided.", true
  	}
  	return runBash(ctx, input.Command)
  }
  ```

  ```java Java
  record ToolOutput(String output, boolean isError) {}

  // 例を起動した場所でbashを実行します。DOC_TEST_MODEではドキュメントハーネスが
  // 代わりに使い捨てのフィクスチャディレクトリを指定し、終了時に削除されます。
  static final Path WORK_DIR = createWorkDir();

  static Path createWorkDir() {
      if (!DOC_TEST_MODE) {
          return Path.of(System.getProperty("user.dir"));
      }
      try {
          var dir = Files.createTempDirectory("orchestration-");
          Files.writeString(dir.resolve("sample.py"), """
                  def fib(n):
                      return n if n < 2 else fib(n - 1) + fib(n - 2)

                  print(fib(10))
                  """);
          Runtime.getRuntime().addShutdownHook(new Thread(() -> {
              // ベストエフォートのクリーンアップです。残ったものは OS の tmp 掃除機能が処理します。
              try (var paths = Files.walk(dir)) {
                  paths.sorted(Comparator.reverseOrder()).forEach(path -> {
                      try { Files.deleteIfExists(path); } catch (IOException _) {}
                  });
              } catch (IOException _) {}
          }));
          return dir;
      } catch (IOException error) {
          throw new UncheckedIOException(error);
      }
  }

  // シェルコマンドを実行し、出力とエラーフラグを返します。サンドボックスなし：サンプルコード専用。
  ToolOutput runBash(String command) throws InterruptedException {
      System.err.println("[bash] " + command);
      Process process;
      try {
          process = new ProcessBuilder("bash", "-c", command)
                  .directory(WORK_DIR.toFile())
                  .redirectErrorStream(true)
                  .start();
      } catch (IOException error) {
          return new ToolOutput("(" + error + ")", true);
      }
      // パイプが詰まって下のタイムアウト待機が停止しないよう、別スレッドでstdoutを排出します。
      CompletableFuture<String> outputReader = CompletableFuture.supplyAsync(() -> {
          try (var stdout = process.getInputStream()) {
              return new String(stdout.readAllBytes(), StandardCharsets.UTF_8);
          } catch (IOException _) {
              return "";
          }
      });
      if (!process.waitFor(BASH_TIMEOUT_SECONDS, TimeUnit.SECONDS)) {
          process.destroyForcibly();
          outputReader.cancel(true);
          return new ToolOutput("command timed out after " + BASH_TIMEOUT_SECONDS + "s", true);
      }
      String output = outputReader.join().strip();
      if (output.isEmpty()) {
          output = "(no output)";
      } else if (output.length() > TOOL_RESULT_MAX_CHARS) {
          output = output.substring(0, TOOL_RESULT_MAX_CHARS)
                  + "\n(truncated at " + TOOL_RESULT_MAX_CHARS + " chars)";
      }
      int exitCode = process.exitValue();
      return exitCode == 0
              ? new ToolOutput(output, false)
              : new ToolOutput("(exit code " + exitCode + ")\n" + output, true);
  }

  // ツール呼び出しの入力フィールド。モデルがオブジェクト以外を送った場合は空のマップ。
  Map<String, JsonValue> toolInput(ToolUseBlock toolUse) {
      return toolUse._input() instanceof JsonObject object ? object.values() : Map.of();
  }

  // モデルが要求した1つのbashツール呼び出しを実行します。
  ToolOutput handleBashBlock(ToolUseBlock toolUse) throws InterruptedException {
      Map<String, JsonValue> input = toolInput(toolUse);
      if (input.get("restart") instanceof JsonBoolean restart && restart.value()) {
          return new ToolOutput("Shell restarted.", false);
      }
      String command = input.get("command") instanceof JsonString text ? text.value() : "";
      if (command.isEmpty()) {
          return new ToolOutput("bash error: no command was provided.", true);
      }
      return runBash(command);
  }
  ```

  ```php PHP
  // 例を起動した場所で bash を実行します。DOC_TEST_MODE ではドキュメントのテストハーネスが
  // 代わりに使い捨てのフィクスチャディレクトリを参照させ、終了時に削除します。
  if (DOC_TEST_MODE) {
      $workDir = sys_get_temp_dir() . '/orchestration-' . bin2hex(random_bytes(8));
      if (!mkdir($workDir, 0700)) {
          throw new \RuntimeException("could not create working directory {$workDir}");
      }
      file_put_contents(
          $workDir . '/sample.py',
          "def fib(n):\n"
          . "    return n if n < 2 else fib(n - 1) + fib(n - 2)\n\n"
          . "print(fib(10))\n",
      );
      register_shutdown_function(function () use ($workDir): void {
          foreach (glob($workDir . '/*') ?: [] as $entry) {
              @unlink($entry);
          }
          @rmdir($workDir);
      });
  } else {
      $workDir = getcwd() ?: '.';
  }
  define('WORK_DIR', $workDir);

  /**
   * Run a shell command and return [output, isError]. The coreutils timeout command
   * enforces the time limit. No sandbox: example code only.
   */
  function runBash(string $command): array
  {
      fwrite(STDERR, "[bash] {$command}\n");
      // GNU coreutils の 'timeout' が必要です。macOS では brew install coreutils を実行するか、gtimeout に置き換えてください。
      exec(
          'cd ' . escapeshellarg(WORK_DIR) . ' && timeout ' . BASH_TIMEOUT_SECONDS
              . ' bash -c ' . escapeshellarg($command) . ' 2>&1',
          $outputLines,
          $exitCode,
      );
      if ($exitCode === 124) {
          return ['command timed out after ' . BASH_TIMEOUT_SECONDS . 's', true];
      }
      $output = trim(implode("\n", $outputLines));
      if ($output === '') {
          $output = '(no output)';
      }
      if (mb_strlen($output) > TOOL_RESULT_MAX_CHARS) {
          $output = mb_substr($output, 0, TOOL_RESULT_MAX_CHARS)
              . "\n(truncated at " . TOOL_RESULT_MAX_CHARS . ' chars)';
      }
      if ($exitCode !== 0) {
          $output = "(exit code {$exitCode})\n{$output}";
      }
      return [$output, $exitCode !== 0];
  }

  /** Execute one bash tool call requested by the model. */
  function handleBashBlock(ToolUseBlock $block): array
  {
      if (($block->input['restart'] ?? null) === true) {
          return ['Shell restarted.', false];
      }
      $command = $block->input['command'] ?? '';
      if (!is_string($command) || $command === '') {
          return ['bash error: no command was provided.', true];
      }
      return runBash($command);
  }
  ```

  ```ruby Ruby
  # この例を起動した場所でbashを実行します。DOC_TEST_MODEではドキュメントのテストハーネスが
  # 代わりに使い捨てのフィクスチャディレクトリを参照させ、終了時に削除します。
  WORK_DIR =
    if DOC_TEST_MODE
      Dir.mktmpdir("orchestration-").tap do |dir|
        File.write(File.join(dir, "sample.py"), <<~PYTHON)
          def fib(n):
              return n if n < 2 else fib(n - 1) + fib(n - 2)

          print(fib(10))
        PYTHON
        at_exit { FileUtils.remove_entry(dir, true) }
      end
    else
      Dir.pwd
    end

  # ツール入力はHashとして、またはストリーミングアキュムレータからの生のJSON文字列として
  # 届きます。どちらの形でも文字列キーのHashに正規化します。
  def parse_tool_input(raw)
    return raw.transform_keys(&:to_s) if raw.is_a?(Hash)
    parsed = JSON.parse(raw.to_s) rescue nil
    parsed.is_a?(Hash) ? parsed : {}
  end

  # シェルコマンドを実行して [output, is_error] を返します。サンドボックスなし:サンプルコード専用です。
  def run_bash(command)
    warn "[bash] #{command}"
    begin
      stdin, stdout_and_stderr, wait_thr = Open3.popen2e("bash", "-c", command, pgroup: true, chdir: WORK_DIR)
      stdin.close
      reader = Thread.new { stdout_and_stderr.read.scrub }
      # モノトニッククロックによる期限で制限時間を強制し、タイムアウトしたコマンドが
      # バックグラウンドで実行され続けるのではなく終了されるようにします。
      deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + BASH_TIMEOUT_SECONDS
      until wait_thr.join(0.1)
        next if Process.clock_gettime(Process::CLOCK_MONOTONIC) < deadline

        begin
          Process.kill("-TERM", wait_thr.pid)
        rescue Errno::ESRCH
        end
        unless wait_thr.join(2)
          begin
            Process.kill("-KILL", wait_thr.pid)
          rescue Errno::ESRCH
          end
        end
        wait_thr.join(5)
        reader.join(1) || reader.kill
        stdout_and_stderr.close rescue nil
        return ["command timed out after #{BASH_TIMEOUT_SECONDS}s", true]
      end
      status = wait_thr.value
      output = reader.value.strip
      stdout_and_stderr.close
      output = "(no output)" if output.empty?
      if output.length > TOOL_RESULT_MAX_CHARS
        output = "#{output[0, TOOL_RESULT_MAX_CHARS]}\n(truncated at #{TOOL_RESULT_MAX_CHARS} chars)"
      end
      output = "(exit code #{status.exitstatus})\n#{output}" unless status.success?
      [output, !status.success?]
    rescue Errno::ENOENT => e
      return ["bash error: #{e.message}", true]
    end
  end

  # モデルが要求した1件のbashツール呼び出しを実行します。
  def handle_bash_block(block)
    input = parse_tool_input(block.input)
    return ["Shell restarted.", false] if input["restart"] == true

    command = input["command"]
    return ["bash error: no command was provided.", true] unless command.is_a?(String) && !command.empty?

    run_bash(command)
  end

  # レスポンスのcontentをリクエスト形式のパラメータに変換します。ストリーミングアキュムレータは
  # tool_useの入力を生のJSON文字列として返し、レスポンス専用のフィールドも含むため、
  # 各ブロックをリクエストスキーマに整形してから送り返します。
  def assistant_content_param(content)
    content.map do |block|
      case block
      when Anthropic::Models::ToolUseBlock
        input = parse_tool_input(block.input)
        {type: "tool_use", id: block.id, name: block.name, input: input}
      when Anthropic::Models::TextBlock
        {type: "text", text: block.text}
      when Anthropic::Models::ThinkingBlock
        {type: "thinking", thinking: block.thinking, signature: block.signature}
      when Anthropic::Models::RedactedThinkingBlock then {type: "redacted_thinking", data: block.data}
      else
        block.to_h
      end
    end
  end
  ```
</CodeGroup>

## 1つのサブエージェントを実行する

各ワークフローのサブタスクは、bashツールを備えた独自の小さなエージェントループとなり、メインループと同じエフォートで実行されます。リクエストごとのタイムアウトが各API呼び出しを制限するため、接続が切れても実行全体が停止するのではなく、1つのサブエージェントが劣化するだけで済みます。

<CodeGroup exclude="shell:cURL, shell:CLI">
  ```python Python
  def run_subagent(model: str, prompt: str) -> str:
      """One subagent: a small nested agent loop with the bash tool plus report_findings.
      Subagents inherit the main loop's effort level."""
      subagent_system = (
          "You are one agent in a larger parallel fan-out, assigned a single subtask. "
          "Investigate it directly, using bash to check facts rather than guessing, and finish "
          "by calling report_findings exactly once. Return findings, not narration."
      )
      messages = [{"role": "user", "content": prompt}]
      for _ in range(MAX_SUBAGENT_TURNS):
          with client.messages.stream(
              model=model,
              max_tokens=64000,
              system=subagent_system,
              output_config={"effort": EFFORT},
              tools=[BASH_TOOL, REPORT_TOOL],
              messages=messages,
              timeout=REQUEST_TIMEOUT_SECONDS,
          ) as stream:
              response = stream.get_final_message()
          messages.append({"role": "assistant", "content": response.content})
          if response.stop_reason == "pause_turn":
              continue
          if response.stop_reason != "tool_use":
              text = "".join(
                  block.text for block in response.content if block.type == "text"
              )
              if response.stop_reason == "max_tokens":
                  text += "\n\n(warning: subagent response was truncated at max_tokens)"
              return text
          tool_results = []
          report = None
          for block in response.content:
              if block.type != "tool_use":
                  continue
              match block.name:
                  case "report_findings":
                      report = json.dumps(block.input, indent=2)
                      output, is_error = "Findings recorded.", False
                  case "bash":
                      output, is_error = handle_bash_block(block)
                  case _:
                      output, is_error = f"unknown tool: {block.name}", True
              tool_results.append(
                  {
                      "type": "tool_result",
                      "tool_use_id": block.id,
                      "content": output,
                      "is_error": is_error,
                  }
              )
          if report is not None:
              return report
          messages.append({"role": "user", "content": tool_results})
      return "(subagent hit the turn limit before finishing)"
  ```

  ```typescript TypeScript
  // 1つのサブエージェント:bashツールとreport_findingsを備えた小さな入れ子のエージェントループです。
  // サブエージェントはメインループのeffortレベルを継承します。
  async function runSubagent(model: string, prompt: string): Promise<string> {
    const subagentSystem =
      "You are one agent in a larger parallel fan-out, assigned a single subtask. " +
      "Investigate it directly, using bash to check facts rather than guessing, and finish " +
      "by calling report_findings exactly once. Return findings, not narration.";
    const messages: Anthropic.MessageParam[] = [{ role: "user", content: prompt }];
    for (let turn = 0; turn < MAX_SUBAGENT_TURNS; turn++) {
      const response = await client.messages
        .stream(
          {
            model,
            max_tokens: 64000,
            system: subagentSystem,
            output_config: { effort: EFFORT },
            tools: [BASH_TOOL, REPORT_TOOL],
            messages
          },
          { signal: AbortSignal.timeout(REQUEST_TIMEOUT_SECONDS * 1000) }
        )
        .finalMessage();
      messages.push({ role: "assistant", content: response.content });
      if (response.stop_reason === "pause_turn") {
        continue;
      }
      if (response.stop_reason !== "tool_use") {
        let text = response.content
          .flatMap((block) => (block.type === "text" ? [block.text] : []))
          .join("");
        if (response.stop_reason === "max_tokens") {
          text += "\n\n(warning: subagent response was truncated at max_tokens)";
        }
        return text;
      }
      const toolResults: Anthropic.ToolResultBlockParam[] = [];
      let report: string | null = null;
      for (const block of response.content) {
        if (block.type !== "tool_use") {
          continue;
        }
        let outcome: ToolOutcome;
        switch (block.name) {
          case "report_findings":
            report = JSON.stringify(block.input, null, 2);
            outcome = { output: "Findings recorded.", isError: false };
            break;
          case "bash":
            outcome = await handleBashBlock(block);
            break;
          default:
            outcome = { output: `unknown tool: ${block.name}`, isError: true };
        }
        toolResults.push({
          type: "tool_result",
          tool_use_id: block.id,
          content: outcome.output,
          is_error: outcome.isError
        });
      }
      if (report !== null) {
        return report;
      }
      messages.push({ role: "user", content: toolResults });
    }
    return "(subagent hit the turn limit before finishing)";
  }
  ```

  ```csharp C#
  // 1つのサブエージェント：bash ツールと report_findings を持つ小さな入れ子エージェントループ。
  // サブエージェントはメインループのエフォートレベルを継承します。
  JsonSerializerOptions indented = new() { WriteIndented = true };

  async Task<string> RunSubagent(string prompt)
  {
      const string subagentSystem =
          "You are one agent in a larger parallel fan-out, assigned a single subtask. "
          + "Investigate it directly, using bash to check facts rather than guessing, and finish "
          + "by calling report_findings exactly once. Return findings, not narration.";
      List<MessageParam> messages = [new() { Role = Role.User, Content = prompt }];
      for (var turn = 0; turn < maxSubagentTurns; turn++)
      {
          using var deadline = new CancellationTokenSource(TimeSpan.FromSeconds(requestTimeoutSeconds));
          var response = await client.Messages.CreateStreaming(new MessageCreateParams
          {
              Model = model,
              MaxTokens = 64000,
              System = subagentSystem,
              OutputConfig = new OutputConfig { Effort = effort },
              Tools = [bashTool, reportTool],
              Messages = messages,
          }, cancellationToken: deadline.Token).Aggregate();
          messages.Add(new()
          {
              Role = Role.Assistant,
              Content = response.Content.Select(block => new ContentBlockParam(block.Json)).ToList(),
          });
          if (response.StopReason == StopReason.PauseTurn)
          {
              continue;
          }
          if (response.StopReason != StopReason.ToolUse)
          {
              var text = string.Concat(
                  response.Content.Select(block => block.TryPickText(out var textBlock) ? textBlock.Text : ""));
              if (response.StopReason == StopReason.MaxTokens)
              {
                  text += "\n\n(warning: subagent response was truncated at max_tokens)";
              }
              return text;
          }
          List<ContentBlockParam> toolResults = [];
          string? report = null;
          foreach (var block in response.Content)
          {
              if (!block.TryPickToolUse(out var toolUse))
              {
                  continue;
              }
              if (toolUse.Name == "report_findings")
              {
                  report = JsonSerializer.Serialize(toolUse.Input, indented);
              }
              var (output, isError) = toolUse.Name switch
              {
                  "report_findings" => ("Findings recorded.", false),
                  "bash" => await HandleBashBlock(toolUse),
                  _ => ($"unknown tool: {toolUse.Name}", true),
              };
              toolResults.Add(new ToolResultBlockParam(toolUse.ID) { Content = output, IsError = isError });
          }
          if (report is not null)
          {
              return report;
          }
          messages.Add(new() { Role = Role.User, Content = toolResults });
      }
      return "(subagent hit the turn limit before finishing)";
  }
  ```

  ```go Go
  // streamMessage はリクエストを 1 つ送信し、ストリーミングされたイベントを蓄積して
  // 最終メッセージを組み立てます。
  func streamMessage(ctx context.Context, params anthropic.MessageNewParams) (anthropic.Message, error) {
  	ctx, cancel := context.WithTimeout(ctx, requestTimeoutSeconds*time.Second)
  	defer cancel()
  	stream := client.Messages.NewStreaming(ctx, params)
  	defer stream.Close()
  	var message anthropic.Message
  	for stream.Next() {
  		if err := message.Accumulate(stream.Current()); err != nil {
  			return message, err
  		}
  	}
  	return message, stream.Err()
  }

  // textOf はレスポンスのテキストブロックを連結します。
  func textOf(message anthropic.Message) string {
  	var text strings.Builder
  	for _, block := range message.Content {
  		if textBlock, ok := block.AsAny().(anthropic.TextBlock); ok {
  			text.WriteString(textBlock.Text)
  		}
  	}
  	return text.String()
  }

  // runSubagentは1つのサブエージェントを実行します：bashツールとreport_findingsを持つ
  // 小さな入れ子のエージェントループです。サブエージェントはメインループのエフォートレベルを継承します。
  func runSubagent(ctx context.Context, model string, prompt string) (string, error) {
  	const subagentSystem = "You are one agent in a larger parallel fan-out, assigned a single subtask. " +
  		"Investigate it directly, using bash to check facts rather than guessing, and finish " +
  		"by calling report_findings exactly once. Return findings, not narration."
  	messages := []anthropic.MessageParam{anthropic.NewUserMessage(anthropic.NewTextBlock(prompt))}
  	for range maxSubagentTurns {
  		response, err := streamMessage(ctx, anthropic.MessageNewParams{
  			Model:        model,
  			MaxTokens:    64000,
  			System:       []anthropic.TextBlockParam{{Text: subagentSystem}},
  			OutputConfig: anthropic.OutputConfigParam{Effort: effort},
  			Tools:        []anthropic.ToolUnionParam{bashTool, reportTool},
  			Messages:     messages,
  		})
  		if err != nil {
  			return "", err
  		}
  		messages = append(messages, response.ToParam())

  		switch response.StopReason {
  		case anthropic.StopReasonPauseTurn:
  			continue
  		case anthropic.StopReasonMaxTokens:
  			return textOf(response) + "\n\n(warning: subagent response was truncated at max_tokens)", nil
  		case anthropic.StopReasonToolUse:
  		default:
  			return textOf(response), nil
  		}

  		var toolResults []anthropic.ContentBlockParamUnion
  		var report string
  		var reportRecorded bool
  		for _, block := range response.Content {
  			toolUse, ok := block.AsAny().(anthropic.ToolUseBlock)
  			if !ok {
  				continue
  			}
  			var output string
  			var isError bool
  			switch toolUse.Name {
  			case "report_findings":
  				report = string(toolUse.Input)
  				var pretty bytes.Buffer
  				if err := json.Indent(&pretty, toolUse.Input, "", "  "); err == nil {
  					report = pretty.String()
  				}
  				reportRecorded = true
  				output = "Findings recorded."
  			case "bash":
  				output, isError = handleBashBlock(ctx, toolUse)
  			default:
  				output, isError = "unknown tool: "+toolUse.Name, true
  			}
  			toolResults = append(toolResults, anthropic.NewToolResultBlock(toolUse.ID, output, isError))
  		}
  		if reportRecorded {
  			return report, nil
  		}
  		messages = append(messages, anthropic.NewUserMessage(toolResults...))
  	}
  	return "(subagent hit the turn limit before finishing)", nil
  }
  ```

  ```java Java
  static final String SUBAGENT_SYSTEM = """
          You are one agent in a larger parallel fan-out, assigned a single subtask. \
          Investigate it directly, using bash to check facts rather than guessing, and finish \
          by calling report_findings exactly once. Return findings, not narration.""";

  // 1つのサブエージェント：bashツールとreport_findingsを持つ小さなネストされたエージェントループ。
  // サブエージェントはメインループのエフォートレベルを継承します。
  String runSubagent(Model model, String prompt) throws InterruptedException {
      List<MessageParam> messages = new ArrayList<>();
      messages.add(MessageParam.builder().role(MessageParam.Role.USER).content(prompt).build());
      for (int turn = 0; turn < MAX_SUBAGENT_TURNS; turn++) {
          MessageCreateParams params = MessageCreateParams.builder()
                  .model(model)
                  .maxTokens(64000L)
                  .system(SUBAGENT_SYSTEM)
                  .outputConfig(OutputConfig.builder().effort(EFFORT).build())
                  .addTool(BASH_TOOL)
                  .addTool(REPORT_TOOL)
                  .messages(messages)
                  .build();
          MessageAccumulator accumulator = MessageAccumulator.create();
          try (var stream = client.messages().createStreaming(params, REQUEST_OPTIONS)) {
              stream.stream().forEach(accumulator::accumulate);
          }
          Message response = accumulator.message();
          messages.add(response.toParam());
          StopReason stopReason = response.stopReason().orElse(null);
          if (StopReason.PAUSE_TURN.equals(stopReason)) {
              continue;
          }
          if (!StopReason.TOOL_USE.equals(stopReason)) {
              String text = response.content().stream()
                      .flatMap(block -> block.text().stream())
                      .map(TextBlock::text)
                      .collect(Collectors.joining());
              if (StopReason.MAX_TOKENS.equals(stopReason)) {
                  text += "\n\n(warning: subagent response was truncated at max_tokens)";
              }
              return text;
          }
          List<ToolUseBlock> toolUses = response.content().stream()
                  .flatMap(block -> block.toolUse().stream())
                  .toList();
          List<ContentBlockParam> toolResults = new ArrayList<>();
          String report = null;
          for (ToolUseBlock toolUse : toolUses) {
              ToolOutput result = switch (toolUse.name()) {
                  case "report_findings" -> {
                      report = toolUse._input().convert(JsonNode.class).toPrettyString();
                      yield new ToolOutput("Findings recorded.", false);
                  }
                  case "bash" -> handleBashBlock(toolUse);
                  default -> new ToolOutput("unknown tool: " + toolUse.name(), true);
              };
              toolResults.add(ContentBlockParam.ofToolResult(ToolResultBlockParam.builder()
                      .toolUseId(toolUse.id())
                      .content(result.output())
                      .isError(result.isError())
                      .build()));
          }
          if (report != null) {
              return report;
          }
          messages.add(MessageParam.builder()
                  .role(MessageParam.Role.USER)
                  .contentOfBlockParams(toolResults)
                  .build());
      }
      return "(subagent hit the turn limit before finishing)";
  }
  ```

  ```php PHP
  /**
   * Consume a message stream and assemble the final assistant turn from its events:
   * the full content-block list plus the stop reason, equivalent to what a
   * non-streaming create call returns.
   */
  function drainMessageStream(iterable $events): array
  {
      $stringValue = fn ($value) => $value instanceof \BackedEnum ? $value->value : $value;
      $blocks = [];
      $jsonBuffers = [];
      $stopReason = null;
      foreach ($events as $event) {
          $type = $stringValue($event->type);
          if ($type === 'content_block_start') {
              $blocks[$event->index] = $event->contentBlock;
              $jsonBuffers[$event->index] = '';
          } elseif ($type === 'content_block_delta') {
              $block = $blocks[$event->index];
              $delta = $event->delta;
              $deltaType = $stringValue($delta->type);
              if ($deltaType === 'text_delta') {
                  $blocks[$event->index] = $block->withText($block->text . $delta->text);
              } elseif ($deltaType === 'input_json_delta') {
                  $jsonBuffers[$event->index] .= $delta->partialJSON;
              } elseif ($deltaType === 'thinking_delta') {
                  $blocks[$event->index] = $block->withThinking($block->thinking . $delta->thinking);
              } elseif ($deltaType === 'signature_delta') {
                  $blocks[$event->index] = $block->withSignature($delta->signature);
              }
          } elseif ($type === 'message_delta') {
              $stopReason = $stringValue($event->delta->stopReason);
          }
      }
      foreach ($jsonBuffers as $index => $buffer) {
          if ($buffer !== '' && $blocks[$index] instanceof \Anthropic\Messages\ToolUseBlock) {
              $decoded = json_decode($buffer, true);
              $blocks[$index] = $blocks[$index]->withInput(is_array($decoded) ? $decoded : []);
          }
      }
      return [array_values($blocks), $stopReason];
  }

  /**
   * One subagent: a small nested agent loop with the bash tool plus report_findings.
   * Subagents inherit the main loop's effort level.
   */
  function runSubagent(Client $client, string $model, string $prompt): string
  {
      $subagentSystem =
          'You are one agent in a larger parallel fan-out, assigned a single subtask. '
          . 'Investigate it directly, using bash to check facts rather than guessing, and finish '
          . 'by calling report_findings exactly once. Return findings, not narration.';
      $messages = [['role' => 'user', 'content' => $prompt]];
      for ($turn = 0; $turn < MAX_SUBAGENT_TURNS; $turn++) {
          $stream = $client->messages->createStream(
              model: $model,
              maxTokens: 64000,
              system: $subagentSystem,
              outputConfig: ['effort' => EFFORT],
              tools: [BASH_TOOL, REPORT_TOOL],
              messages: $messages,
              requestOptions: ['timeout' => REQUEST_TIMEOUT_SECONDS],
          );
          [$content, $stopReason] = drainMessageStream($stream);
          $messages[] = ['role' => 'assistant', 'content' => $content];
          if ($stopReason === 'pause_turn') {
              continue;
          }
          if ($stopReason !== 'tool_use') {
              $text = '';
              foreach ($content as $block) {
                  if ($block instanceof \Anthropic\Messages\TextBlock) {
                      $text .= $block->text;
                  }
              }
              if ($stopReason === 'max_tokens') {
                  $text .= "\n\n(warning: subagent response was truncated at max_tokens)";
              }
              return $text;
          }
          $report = null;
          $toolResults = [];
          foreach ($content as $block) {
              if (!$block instanceof \Anthropic\Messages\ToolUseBlock) {
                  continue;
              }
              if ($block->name === 'report_findings') {
                  $report = json_encode($block->input, JSON_PRETTY_PRINT);
                  $output = 'Findings recorded.';
                  $isError = false;
              } elseif ($block->name === 'bash') {
                  [$output, $isError] = handleBashBlock($block);
              } else {
                  $output = "unknown tool: {$block->name}";
                  $isError = true;
              }
              $toolResults[] = [
                  'type' => 'tool_result',
                  'tool_use_id' => $block->id,
                  'content' => $output,
                  'is_error' => $isError,
              ];
          }
          if ($report !== null) {
              return $report;
          }
          $messages[] = ['role' => 'user', 'content' => $toolResults];
      }
      return '(subagent hit the turn limit before finishing)';
  }
  ```

  ```ruby Ruby
  # 1つのサブエージェント:bashツールとreport_findingsを持つ小さな入れ子のエージェントループです。
  # サブエージェントはメインループのeffortレベルを継承します。
  def run_subagent(model, prompt)
    subagent_system =
      "You are one agent in a larger parallel fan-out, assigned a single subtask. " \
      "Investigate it directly, using bash to check facts rather than guessing, and finish " \
      "by calling report_findings exactly once. Return findings, not narration."
    messages = [{role: "user", content: prompt}]
    MAX_SUBAGENT_TURNS.times do
      stream = CLIENT.messages.stream(
        model: model,
        max_tokens: 64_000,
        system_: subagent_system,
        output_config: {effort: EFFORT},
        tools: [BASH_TOOL, REPORT_TOOL],
        messages: messages,
        request_options: {timeout: REQUEST_TIMEOUT_SECONDS}
      )
      response = stream.accumulated_message
      messages << {role: "assistant", content: assistant_content_param(response.content)}
      next if response.stop_reason == :pause_turn

      unless response.stop_reason == :tool_use
        text = response.content.select { |block| block.type == :text }.map(&:text).join
        text += "\n\n(warning: subagent response was truncated at max_tokens)" if response.stop_reason == :max_tokens
        return text
      end

      report = nil
      tool_results = []
      response.content.each do |block|
        next unless block.type == :tool_use

        input = parse_tool_input(block.input)
        case block.name
        when "report_findings"
          report = JSON.pretty_generate(input)
          output, is_error = "Findings recorded.", false
        when "bash"
          output, is_error = handle_bash_block(block)
        else
          output, is_error = "unknown tool: #{block.name}", true
        end
        tool_results << {
          type: "tool_result",
          tool_use_id: block.id,
          content: output,
          is_error: is_error
        }
      end
      return report unless report.nil?

      messages << {role: "user", content: tool_results}
    end
    "(subagent hit the turn limit before finishing)"
  end
  ```
</CodeGroup>

## 再実行時に再開できるよう結果をジャーナルに記録する

数十のサブエージェントを生成するファンアウトは、最初からやり直すにはコストがかかります。小さなコンテンツアドレス方式のジャーナルがこれを冪等にします。サブエージェントをディスパッチする前に、そのプロンプトのSHA-256をローカルのJSONファイルで検索し、記録された結果が存在すればそれを返します。実行を中断して再実行すると、完了しなかったサブタスクだけが再計算されます。ジャーナルは実行をまたいで重複を排除しますが、単一のファンアウトウェーブ内では排除しません。最初からやり直すにはジャーナルファイルを削除してください。

<CodeGroup exclude="shell:cURL, shell:CLI">
  ```python Python
  _journal_lock = threading.Lock()


  def _load_journal() -> dict:
      try:
          with open(JOURNAL_PATH) as file:
              return json.load(file) or {}
      except (OSError, json.JSONDecodeError):
          return {}


  def journaled(prompt: str, compute) -> str:
      """Return a cached result for this exact prompt, or compute and persist it. This
      makes the fan-out resumable: interrupt the run, rerun it, and only the subtasks
      that never finished are recomputed. Delete the journal file to start fresh."""
      key = hashlib.sha256(prompt.encode()).hexdigest()
      cached = _load_journal().get(key)
      if cached is not None:
          print(f"[journal] cache hit for {key[:12]}", file=sys.stderr)
          return cached
      result = compute()
      try:
          with _journal_lock:  # fan-out writes from many threads
              journal = _load_journal()
              journal[key] = result
              temp = f"{JOURNAL_PATH}.tmp"
              with open(temp, "w") as file:
                  json.dump(journal, file)
              os.replace(temp, JOURNAL_PATH)  # atomic on POSIX and Windows
      # ジャーナルへの書き込みが失敗しても、計算結果を失ってはなりません
      except OSError as error:
          print(f"[journal] write failed: {error}", file=sys.stderr)
      return result
  ```

  ```typescript TypeScript
  let journalWriteChain = Promise.resolve();

  async function loadJournal(): Promise<Record<string, string>> {
    try {
      return JSON.parse(await readFile(JOURNAL_PATH, "utf8")) ?? {};
    } catch (error) {
      if ((error as NodeJS.ErrnoException).code !== "ENOENT") {
        console.error(`[journal] discarding unreadable journal: ${error}`);
      }
      return {};
    }
  }

  // このプロンプトに対するキャッシュ済みの結果を返すか、計算して永続化します。これにより
  // ファンアウトが再開可能になります:実行を中断して再実行すると、完了しなかった
  // サブタスクだけが再計算されます。最初からやり直すにはジャーナルファイルを削除してください。
  async function journaled(prompt: string, compute: () => Promise<string>): Promise<string> {
    const key = createHash("sha256").update(prompt).digest("hex");
    const cached = (await loadJournal())[key];
    if (cached !== undefined) {
      console.error(`[journal] cache hit for ${key.slice(0, 12)}`);
      return cached;
    }
    const result = await compute();
    // 並行するサブエージェントが互いのエントリを上書きしないよう、書き込みを連鎖させます。
    // 1つの書き込み失敗が後続に波及しないよう、チェーンは常に解決済みに保たれます。
    await (journalWriteChain = journalWriteChain
      .then(async () => {
        const journal = await loadJournal();
        journal[key] = result;
        const temp = `${JOURNAL_PATH}.tmp`;
        await writeFile(temp, JSON.stringify(journal));
        await rename(temp, JOURNAL_PATH);
      })
      .catch((error) => console.error(`[journal] write failed: ${error}`)));
    return result;
  }
  ```

  ```csharp C#
  SemaphoreSlim journalLock = new(1, 1);

  async Task<Dictionary<string, string>> LoadJournal()
  {
      try
      {
          return JsonSerializer.Deserialize<Dictionary<string, string>>(await File.ReadAllTextAsync(journalPath)) ?? [];
      }
      catch (Exception error) when (error is IOException or UnauthorizedAccessException or JsonException)
      {
          return [];
      }
  }

  // このプロンプトに対するキャッシュ済み結果を返すか、計算して永続化します。これにより
  // ファンアウトが再開可能になります：実行を中断して再実行すると、未完了のサブタスク
  // のみが再計算されます。最初からやり直すにはジャーナルファイルを削除してください。
  async Task<string> Journaled(string prompt, Func<Task<string>> compute)
  {
      var key = Convert.ToHexStringLower(SHA256.HashData(Encoding.UTF8.GetBytes(prompt)));
      if ((await LoadJournal()).TryGetValue(key, out var cached))
      {
          Console.Error.WriteLine($"[journal] cache hit for {key[..12]}");
          return cached;
      }
      var result = await compute();
      await journalLock.WaitAsync(); // fan-out writes from many tasks
      try
      {
          var journal = await LoadJournal();
          journal[key] = result;
          var temp = journalPath + ".tmp";
          await File.WriteAllTextAsync(temp, JsonSerializer.Serialize(journal));
          File.Move(temp, journalPath, overwrite: true);
      }
      catch (Exception error) when (error is IOException or UnauthorizedAccessException or NotSupportedException)
      {
          // ジャーナルはベストエフォートです。計算済みの結果は決して破棄しません。
          Console.Error.WriteLine($"[journal] write failed: {error.Message}");
      }
      finally
      {
          journalLock.Release();
      }
      return result;
  }
  ```

  ```go Go
  var journalMutex sync.Mutex

  func loadJournal() map[string]string {
  	data, err := os.ReadFile(journalPath)
  	if err != nil {
  		return map[string]string{}
  	}
  	var journal map[string]string
  	if err := json.Unmarshal(data, &journal); err != nil || journal == nil {
  		return map[string]string{}
  	}
  	return journal
  }

  // journaledはこのプロンプトに対するキャッシュ済み結果を返すか、計算して永続化します。
  // これによりファンアウトが再開可能になります：実行を中断して再実行すると、
  // 未完了のサブタスクのみが再計算されます。最初からやり直すにはジャーナルファイルを削除してください。
  func journaled(prompt string, compute func() (string, error)) (string, error) {
  	sum := sha256.Sum256([]byte(prompt))
  	key := hex.EncodeToString(sum[:])
  	if cached, ok := loadJournal()[key]; ok {
  		fmt.Fprintf(os.Stderr, "[journal] cache hit for %s\n", key[:12])
  		return cached, nil
  	}
  	result, err := compute()
  	if err != nil {
  		return "", err
  	}
  	journalMutex.Lock() // fan-out writes from many goroutines
  	defer journalMutex.Unlock()
  	journal := loadJournal()
  	journal[key] = result
  	data, _ := json.Marshal(journal)
  	temp := journalPath + ".tmp"
  	if err := os.WriteFile(temp, data, 0o644); err != nil {
  		fmt.Fprintf(os.Stderr, "[journal] write failed: %s\n", err)
  	} else if err := os.Rename(temp, journalPath); err != nil {
  		fmt.Fprintf(os.Stderr, "[journal] write failed: %s\n", err)
  		_ = os.Remove(temp)
  	}
  	return result, nil
  }
  ```

  ```java Java
  static final ObjectMapper JOURNAL_MAPPER = new ObjectMapper();
  static final ReentrantLock JOURNAL_LOCK = new ReentrantLock();

  Map<String, String> loadJournal() {
      try {
          return Objects.requireNonNullElseGet(
                  JOURNAL_MAPPER.readValue(Files.readString(JOURNAL_PATH), new TypeReference<HashMap<String, String>>() {}),
                  HashMap::new);
      } catch (IOException _) {
          return new HashMap<>();
      }
  }

  // このプロンプトと完全一致するキャッシュ結果を返すか、計算して永続化します。これにより
  // ファンアウトが再開可能になります：実行を中断して再実行しても、完了しなかったサブタスクのみが
  // 再計算されます。最初からやり直すにはジャーナルファイルを削除してください。
  String journaled(String prompt, Callable<String> compute) throws Exception {
      var digest = MessageDigest.getInstance("SHA-256").digest(prompt.getBytes(StandardCharsets.UTF_8));
      String key = HexFormat.of().formatHex(digest);
      String cached = loadJournal().get(key);
      if (cached != null) {
          System.err.println("[journal] cache hit for " + key.substring(0, 12));
          return cached;
      }
      String result = compute.call();
      JOURNAL_LOCK.lock(); // fan-out writes from many threads
      try {
          Map<String, String> journal = loadJournal();
          journal.put(key, result);
          Path temp = JOURNAL_PATH.resolveSibling(JOURNAL_PATH.getFileName() + ".tmp");
          Files.writeString(temp, JOURNAL_MAPPER.writeValueAsString(journal));
          Files.move(temp, JOURNAL_PATH, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
      } catch (IOException error) {
          // ジャーナルはベストエフォートです。計算済みの結果は決して破棄しません。
          System.err.println("[journal] write failed: " + error);
      } finally {
          JOURNAL_LOCK.unlock();
      }
      return result;
  }
  ```

  ```php PHP
  function loadJournal(): array
  {
      $raw = @file_get_contents(JOURNAL_PATH);
      if ($raw === false) {
          return [];
      }
      $decoded = json_decode($raw, true);
      return is_array($decoded) ? $decoded : [];
  }

  /**
   * Return a cached result for this exact prompt, or compute and persist it. This
   * makes the fan-out resumable: interrupt the run, rerun it, and only the subtasks
   * that never finished are recomputed. Delete the journal file to start fresh.
   */
  function journaled(string $prompt, callable $compute): string
  {
      $key = hash('sha256', $prompt);
      $journal = loadJournal();
      if (array_key_exists($key, $journal)) {
          fwrite(STDERR, '[journal] cache hit for ' . substr($key, 0, 12) . "\n");
          return $journal[$key];
      }
      $result = $compute();
      $journal = loadJournal();
      $journal[$key] = $result;
      $temp = JOURNAL_PATH . '.tmp';
      $encoded = json_encode($journal, JSON_INVALID_UTF8_SUBSTITUTE);
      if ($encoded === false || @file_put_contents($temp, $encoded) === false || !@rename($temp, JOURNAL_PATH)) {
          fwrite(STDERR, '[journal] write failed: ' . (error_get_last()['message'] ?? json_last_error_msg()) . "\n");
          @unlink($temp);
      }
      return $result;
  }
  ```

  ```ruby Ruby
  JOURNAL_LOCK = Mutex.new

  def load_journal
    JSON.parse(File.read(JOURNAL_PATH)) || {}
  rescue SystemCallError, JSON::ParserError
    {}
  end

  # このプロンプトに対するキャッシュ済みの結果を返すか、計算して永続化します。
  # これによりファンアウトが再開可能になります。実行を中断して再実行すると、
  # 未完了のサブタスクだけが再計算されます。最初からやり直すにはジャーナルファイルを削除します。
  def journaled(prompt)
    key = Digest::SHA256.hexdigest(prompt)
    cached = load_journal[key]
    unless cached.nil?
      warn "[journal] cache hit for #{key[0, 12]}"
      return cached
    end
    result = yield
    begin
      JOURNAL_LOCK.synchronize do # fan-out writes from many threads
        journal = load_journal
        journal[key] = result
        temp = "#{JOURNAL_PATH}.tmp"
        File.write(temp, JSON.generate(journal))
        File.rename(temp, JOURNAL_PATH)
      end
    rescue SystemCallError => error # the journal is best-effort; never discard a computed result
      warn "[journal] write failed: #{error}"
    end
    result
  end
  ```
</CodeGroup>

## ファンアウトし、その後検証する

ファンアウトは最大 `MAX_TOTAL_SUBTASKS` 個のプロンプトを受け付け、同時実行数を最大 `MAX_CONCURRENT` に抑えながら（PHP版では逐次）ジャーナルを通して実行し、障害を隔離することで、1つの壊れたサブエージェントが実行を終了させるのではなくエラー文字列に劣化するだけで済むようにします。最初のウェーブが終了すると、2番目のウェーブが同じサブエージェントの経路を再利用して各結果の反証を試みます。すべての検証者はソースから主張を再導出し、不確かな場合はデフォルトで反証済みとします。元の結果とその判定の両方がオーケストレーターに返されるため、オーケストレーターはそれらを合わせて評価できます。

<CodeGroup exclude="shell:cURL, shell:CLI">
  ```python Python
  def normalize_subtasks(raw) -> list[str]:
      """Accept the subtasks input in whatever shape the model emits: an array, the array
      JSON-encoded as a single string, or a newline-separated list."""
      if isinstance(raw, str):
          try:
              raw = json.loads(raw)
          except json.JSONDecodeError:
              raw = raw.splitlines() if "\n" in raw else [raw]
      if not isinstance(raw, list):
          return []
      return [task.strip() for task in raw if isinstance(task, str) and task.strip()]


  def verify_prompt_for(subtask: str, result: str) -> str:
      return (
          "Adversarially verify the subagent result below: try to REFUTE it. Re-derive the "
          "claims yourself with bash rather than trusting the result, and look for evidence "
          "that contradicts them. Default to refuted if uncertain. Call report_findings with "
          "summary 'refuted: <why>' or 'confirmed: <why>', citing the file:line or command "
          "output that decided it.\n\n"
          f"""Subtask: {subtask}

  Result to verify:
  {result}"""
      )


  def run_workflow(model: str, raw_subtasks) -> tuple[str, bool]:
      """Run subtasks as parallel subagents, then run a second verification wave over
      the results, and return both. MAX_TOTAL_SUBTASKS bounds how many the model can
      queue; MAX_CONCURRENT bounds how many run at once."""
      all_subtasks = normalize_subtasks(raw_subtasks)
      subtasks = all_subtasks[:MAX_TOTAL_SUBTASKS]
      dropped = len(all_subtasks) - len(subtasks)
      if not subtasks:
          return "Workflow error: no usable subtasks were provided.", True
      print(f"[workflow] fanning out {len(subtasks)} agents", file=sys.stderr)

      def run_one(prompt: str) -> str:
          try:
              return journaled(prompt, lambda: run_subagent(model, prompt))
          # 1 つのサブエージェントが失敗しても、実行全体を終了させてはなりません
          except Exception as error:
              return f"(subagent failed: {type(error).__name__}: {error})"

      with concurrent.futures.ThreadPoolExecutor(max_workers=MAX_CONCURRENT) as pool:
          results = list(pool.map(run_one, subtasks))
          print(f"[workflow] verifying {len(results)} results", file=sys.stderr)
          verify_prompts = [
              verify_prompt_for(task, result) for task, result in zip(subtasks, results)
          ]
          verdicts = list(pool.map(run_one, verify_prompts))

      joined = "\n\n".join(
          f"""[agent {index + 1}: {task}]
  {result}

  [verify {index + 1}]
  {verdict}"""
          for index, (task, result, verdict) in enumerate(
              zip(subtasks, results, verdicts)
          )
      )
      if dropped > 0:
          joined = (
              f"(note: {dropped} subtasks beyond MAX_TOTAL_SUBTASKS={MAX_TOTAL_SUBTASKS} were not "
              "run; rerun them in a follow-up Workflow call)\n\n" + joined
          )
      return joined, False
  ```

  ```typescript TypeScript
  // モデルが出力するどの形式のsubtasks入力も受け付けます:配列、配列を
  // 単一の文字列としてJSONエンコードしたもの、または改行区切りのリストです。
  function normalizeSubtasks(raw: unknown): string[] {
    let value = raw;
    if (typeof raw === "string") {
      try {
        value = JSON.parse(raw);
      } catch {
        value = raw.includes("\n") ? raw.split("\n") : [raw];
      }
    }
    if (!Array.isArray(value)) {
      return [];
    }
    return value
      .filter((task) => typeof task === "string")
      .map((task) => task.trim())
      .filter((task) => task.length > 0);
  }

  function verifyPromptFor(subtask: string, result: string): string {
    const instructions =
      "Adversarially verify the subagent result below: try to REFUTE it. Re-derive the " +
      "claims yourself with bash rather than trusting the result, and look for evidence " +
      "that contradicts them. Default to refuted if uncertain. Call report_findings with " +
      "summary 'refuted: <why>' or 'confirmed: <why>', citing the file:line or command " +
      "output that decided it.";
    return `${instructions}

  Subtask: ${subtask}

  Result to verify:
  ${result}`;
  }

  // 並行数制限付きのマップ:同時に実行されるタスクは最大`limit`個です。
  async function mapWithLimit<In, Out>(
    items: readonly In[],
    limit: number,
    task: (item: In) => Promise<Out>
  ): Promise<Out[]> {
    const results = new Array<Out>(items.length);
    let cursor = 0;
    const workers = Array.from({ length: Math.min(limit, items.length) }, async () => {
      while (cursor < items.length) {
        const index = cursor++;
        results[index] = await task(items[index]);
      }
    });
    await Promise.all(workers);
    return results;
  }

  // サブタスクを並列サブエージェントとして実行し、その結果に対して2回目の検証ウェーブを
  // 実行して、両方を返します。MAX_TOTAL_SUBTASKSはモデルがキューに入れられる数を、
  // MAX_CONCURRENTは同時に実行される数を制限します。
  async function runWorkflow(model: string, rawSubtasks: unknown): Promise<ToolOutcome> {
    const allSubtasks = normalizeSubtasks(rawSubtasks);
    const subtasks = allSubtasks.slice(0, MAX_TOTAL_SUBTASKS);
    const dropped = allSubtasks.length - subtasks.length;
    if (subtasks.length === 0) {
      return { output: "Workflow error: no usable subtasks were provided.", isError: true };
    }
    console.error(`[workflow] fanning out ${subtasks.length} agents`);

    const runOne = async (prompt: string): Promise<string> => {
      try {
        return await journaled(prompt, () => runSubagent(model, prompt));
      } catch (error) {
        // 分離境界:1つのサブエージェントの失敗で実行全体が終了しないようにします。
        const reason =
          error instanceof Error ? `${error.name}: ${error.message}` : String(error);
        return `(subagent failed: ${reason})`;
      }
    };

    const results = await mapWithLimit(subtasks, MAX_CONCURRENT, runOne);
    console.error(`[workflow] verifying ${results.length} results`);
    const verifyPrompts = subtasks.map((task, index) => verifyPromptFor(task, results[index]));
    const verdicts = await mapWithLimit(verifyPrompts, MAX_CONCURRENT, runOne);

    const report = subtasks
      .map(
        (task, index) => `[agent ${index + 1}: ${task}]
  ${results[index]}

  [verify ${index + 1}]
  ${verdicts[index]}`
      )
      .join("\n\n");
    const droppedNote =
      dropped > 0
        ? `(note: ${dropped} subtasks beyond MAX_TOTAL_SUBTASKS=${MAX_TOTAL_SUBTASKS} were ` +
          "not run; rerun them in a follow-up Workflow call)\n\n"
        : "";
    return { output: droppedNote + report, isError: false };
  }
  ```

  ```csharp C#
  // モデルが出力するどの形式のサブタスク入力も受け付けます：配列、単一文字列として
  // JSONエンコードされた配列、または改行区切りのリスト。
  List<string> NormalizeSubtasks(JsonElement raw)
  {
      IEnumerable<string?> tasks = raw.ValueKind switch
      {
          JsonValueKind.Array => raw.EnumerateArray()
              .Where(item => item.ValueKind == JsonValueKind.String)
              .Select(item => item.GetString()),
          JsonValueKind.String => ParseSubtaskString(raw.GetString()!),
          _ => [],
      };
      return [.. tasks.OfType<string>().Select(task => task.Trim()).Where(task => task.Length > 0)];
  }

  IEnumerable<string?> ParseSubtaskString(string single)
  {
      try
      {
          return JsonSerializer.Deserialize<List<string?>>(single) ?? [];
      }
      catch (JsonException)
      {
          return single.Split('\n');
      }
  }

  string VerifyPromptFor(string subtask, string result) => $"""
      Adversarially verify the subagent result below: try to REFUTE it. Re-derive the claims yourself with bash rather than trusting the result, and look for evidence that contradicts them. Default to refuted if uncertain. Call report_findings with summary 'refuted: <why>' or 'confirmed: <why>', citing the file:line or command output that decided it.

      Subtask: {subtask}

      Result to verify:
      {result}
      """;

  // サブタスクを並列サブエージェントとして実行し、その結果に対して2回目の検証ウェーブを
  // 実行して両方を返します。maxTotalSubtasks はモデルがキューに入れられる数を、
  // maxConcurrent は同時実行数を制限します。
  async Task<(string Output, bool IsError)> RunWorkflow(JsonElement rawSubtasks)
  {
      var allSubtasks = NormalizeSubtasks(rawSubtasks);
      List<string> subtasks = [.. allSubtasks.Take(maxTotalSubtasks)];
      var dropped = allSubtasks.Count - subtasks.Count;
      if (subtasks.Count == 0)
      {
          return ("Workflow error: no usable subtasks were provided.", true);
      }
      Console.Error.WriteLine($"[workflow] fanning out {subtasks.Count} agents");

      using SemaphoreSlim gate = new(maxConcurrent);
      async Task<string> RunOne(string prompt)
      {
          await gate.WaitAsync();
          try
          {
              return await Journaled(prompt, () => RunSubagent(prompt));
          }
          catch (Exception error)
          {
              // 分離境界：1つの不良サブエージェントが実行全体を終了させるべきではありません。
              return $"(subagent failed: {error.GetType().Name}: {error.Message})";
          }
          finally
          {
              gate.Release();
          }
      }

      var results = await Task.WhenAll(subtasks.Select(RunOne));
      Console.Error.WriteLine($"[workflow] verifying {results.Length} results");
      var verdicts = await Task.WhenAll(subtasks.Zip(results, VerifyPromptFor).Select(RunOne));

      var joined = string.Join("\n\n", subtasks.Select((task, index) => $"""
          [agent {index + 1}: {task}]
          {results[index]}

          [verify {index + 1}]
          {verdicts[index]}
          """));
      if (dropped > 0)
      {
          joined = $"""
              (note: {dropped} subtasks beyond maxTotalSubtasks={maxTotalSubtasks} were not run; rerun them in a follow-up Workflow call)

              {joined}
              """;
      }
      return (joined, false);
  }
  ```

  ```go Go
  // normalizeSubtasksはモデルが出力するどの形式のサブタスク入力も受け付けます：
  // 配列、単一文字列としてJSONエンコードされた配列、または改行区切りリスト。
  func normalizeSubtasks(raw json.RawMessage) []string {
  	var tasks []string
  	if err := json.Unmarshal(raw, &tasks); err != nil {
  		var single string
  		if err := json.Unmarshal(raw, &single); err != nil {
  			return nil
  		}
  		if err := json.Unmarshal([]byte(single), &tasks); err != nil {
  			tasks = strings.Split(single, "\n")
  		}
  	}
  	cleaned := make([]string, 0, len(tasks))
  	for _, task := range tasks {
  		if trimmed := strings.TrimSpace(task); trimmed != "" {
  			cleaned = append(cleaned, trimmed)
  		}
  	}
  	return cleaned
  }

  func verifyPromptFor(subtask, result string) string {
  	return "Adversarially verify the subagent result below: try to REFUTE it. Re-derive the " +
  		"claims yourself with bash rather than trusting the result, and look for evidence " +
  		"that contradicts them. Default to refuted if uncertain. Call report_findings with " +
  		"summary 'refuted: <why>' or 'confirmed: <why>', citing the file:line or command " +
  		"output that decided it.\n\n" +
  		fmt.Sprintf(`Subtask: %s

  Result to verify:
  %s`, subtask, result)
  }

  // mapWithLimitは最大limit個のゴルーチンを同時実行してitemsに対しtaskを実行します。
  func mapWithLimit(items []string, limit int, task func(string) string) []string {
  	results := make([]string, len(items))
  	semaphore := make(chan struct{}, limit)
  	var waitGroup sync.WaitGroup
  	for index, item := range items {
  		semaphore <- struct{}{}
  		waitGroup.Go(func() {
  			defer func() { <-semaphore }()
  			results[index] = task(item)
  		})
  	}
  	waitGroup.Wait()
  	return results
  }

  // runWorkflowはサブタスクを並列サブエージェントとして実行し、その結果に対して2回目の
  // 検証ウェーブを実行して両方を返します。maxTotalSubtasksはモデルがキューに入れられる数を、
  // maxConcurrentは同時実行数を制限します。
  func runWorkflow(ctx context.Context, model string, rawSubtasks json.RawMessage) (string, bool) {
  	allSubtasks := normalizeSubtasks(rawSubtasks)
  	subtasks := allSubtasks[:min(len(allSubtasks), maxTotalSubtasks)]
  	dropped := len(allSubtasks) - len(subtasks)
  	if len(subtasks) == 0 {
  		return "Workflow error: no usable subtasks were provided.", true
  	}
  	fmt.Fprintf(os.Stderr, "[workflow] fanning out %d agents\n", len(subtasks))

  	runOne := func(prompt string) string {
  		report, err := journaled(prompt, func() (string, error) { return runSubagent(ctx, model, prompt) })
  		if err != nil {
  			// 分離境界：1つの不良サブエージェントが実行全体を終了させないようにします。
  			return fmt.Sprintf("(subagent failed: %s)", err)
  		}
  		return report
  	}

  	results := mapWithLimit(subtasks, maxConcurrent, runOne)
  	fmt.Fprintf(os.Stderr, "[workflow] verifying %d results\n", len(results))
  	verifyPrompts := make([]string, len(subtasks))
  	for index, task := range subtasks {
  		verifyPrompts[index] = verifyPromptFor(task, results[index])
  	}
  	verdicts := mapWithLimit(verifyPrompts, maxConcurrent, runOne)

  	sections := make([]string, len(subtasks))
  	for index, task := range subtasks {
  		sections[index] = fmt.Sprintf(`[agent %d: %s]
  %s

  [verify %d]
  %s`,
  			index+1, task, results[index], index+1, verdicts[index])
  	}
  	joined := strings.Join(sections, "\n\n")
  	if dropped > 0 {
  		joined = fmt.Sprintf("(note: %d subtasks beyond maxTotalSubtasks=%d were not run; "+
  			"rerun them in a follow-up Workflow call)\n\n", dropped, maxTotalSubtasks) + joined
  	}
  	return joined, false
  }
  ```

  ```java Java
  // モデルが出力するどの形式のsubtasks入力も受け付けます：配列、単一文字列として
  // JSONエンコードされた配列、または改行区切りのリスト。
  List<String> normalizeSubtasks(JsonValue rawSubtasks) {
      List<String> tasks = switch (rawSubtasks) {
          case JsonArray array -> array.values().stream()
                  .map(item -> item instanceof JsonString text ? text.value() : item.toString())
                  .toList();
          case JsonString string -> parseSubtaskString(string.value());
          default -> List.of();
      };
      return tasks.stream()
              .filter(Objects::nonNull)
              .map(String::strip)
              .filter(task -> !task.isEmpty())
              .toList();
  }

  List<String> parseSubtaskString(String encoded) {
      try {
          String[] parsed = new ObjectMapper().readValue(encoded, String[].class);
          return parsed == null ? List.of() : Arrays.asList(parsed);
      } catch (JsonProcessingException _) {
          return encoded.lines().toList();
      }
  }

  String verifyPromptFor(String subtask, String result) {
      return """
              Adversarially verify the subagent result below: try to REFUTE it. Re-derive the \
              claims yourself with bash rather than trusting the result, and look for evidence \
              that contradicts them. Default to refuted if uncertain. Call report_findings with \
              summary 'refuted: <why>' or 'confirmed: <why>', citing the file:line or command \
              output that decided it.

              Subtask: %s

              Result to verify:
              %s""".formatted(subtask, result);
  }

  // invokeAll はすべてのジョブを待つため、ここで読み取る時点で各 future は完了しています。
  List<String> runAll(ExecutorService pool, List<String> prompts, Model model) throws InterruptedException {
      List<Callable<String>> jobs = prompts.stream()
              .<Callable<String>>map(prompt -> () -> journaled(prompt, () -> runSubagent(model, prompt)))
              .toList();
      return pool.invokeAll(jobs).stream()
              .map(future -> switch (future.state()) {
                  case SUCCESS -> future.resultNow();
                  // 分離境界：1 つのサブエージェントの失敗で実行全体を終了させません。
                  case FAILED -> "(subagent failed: " + future.exceptionNow() + ")";
                  case CANCELLED, RUNNING -> "(subagent failed: cancelled)";
              })
              .toList();
  }

  // サブタスクを並列サブエージェントとして実行し、次にその結果に対して2回目の検証ウェーブを
  // 実行し、両方を返します。MAX_TOTAL_SUBTASKSはモデルがキューに入れられる数の上限、
  // MAX_CONCURRENTは同時実行数の上限です。
  ToolOutput runWorkflow(Model model, JsonValue rawSubtasks) throws InterruptedException {
      List<String> allSubtasks = normalizeSubtasks(rawSubtasks);
      List<String> subtasks = allSubtasks.stream().limit(MAX_TOTAL_SUBTASKS).toList();
      int dropped = allSubtasks.size() - subtasks.size();
      if (subtasks.isEmpty()) {
          return new ToolOutput("Workflow error: no usable subtasks were provided.", true);
      }
      System.err.println("[workflow] fanning out " + subtasks.size() + " agents");

      List<String> results;
      List<String> verdicts;
      try (ExecutorService pool = Executors.newFixedThreadPool(MAX_CONCURRENT, Thread.ofVirtual().factory())) {
          results = runAll(pool, subtasks, model);
          System.err.println("[workflow] verifying " + results.size() + " results");
          List<String> verifyPrompts = IntStream.range(0, subtasks.size())
                  .mapToObj(index -> verifyPromptFor(subtasks.get(index), results.get(index)))
                  .toList();
          verdicts = runAll(pool, verifyPrompts, model);
      }
      String joined = IntStream.range(0, subtasks.size())
              .mapToObj(index -> """
                      [agent %d: %s]
                      %s

                      [verify %d]
                      %s""".formatted(index + 1, subtasks.get(index), results.get(index), index + 1, verdicts.get(index)))
              .collect(Collectors.joining("\n\n"));
      if (dropped > 0) {
          joined = """
                  (note: %d subtasks beyond MAX_TOTAL_SUBTASKS=%d were not run; rerun them in a follow-up Workflow call)

                  %s""".formatted(dropped, MAX_TOTAL_SUBTASKS, joined);
      }
      return new ToolOutput(joined, false);
  }
  ```

  ```php PHP
  /**
   * Accept the subtasks input in whatever shape the model emits: an array, the array
   * JSON-encoded as a single string, or a newline-separated list.
   */
  function normalizeSubtasks(mixed $raw): array
  {
      if (is_string($raw)) {
          try {
              $raw = json_decode($raw, true, flags: JSON_THROW_ON_ERROR);
          } catch (\JsonException) {
              $raw = str_contains($raw, "\n") ? explode("\n", $raw) : [$raw];
          }
      }
      if (!is_array($raw)) {
          return [];
      }
      $tasks = array_map('trim', array_filter($raw, 'is_string'));
      return array_values(array_filter($tasks, fn ($task) => $task !== ''));
  }

  function verifyPromptFor(string $subtask, string $result): string
  {
      return 'Adversarially verify the subagent result below: try to REFUTE it. Re-derive the '
          . 'claims yourself with bash rather than trusting the result, and look for evidence '
          . 'that contradicts them. Default to refuted if uncertain. Call report_findings with '
          . "summary 'refuted: <why>' or 'confirmed: <why>', citing the file:line or command "
          . "output that decided it.\n\n"
          . <<<PROMPT
              Subtask: {$subtask}

              Result to verify:
              {$result}
              PROMPT;
  }

  /**
   * Run subtasks through the journal, then run a second verification wave over the
   * results, and return both. PHP's standard runtime has no lightweight thread pool,
   * so both waves run sequentially here (MAX_CONCURRENT is unused); the SDK examples
   * in other languages fan them out in parallel.
   */
  function runWorkflow(Client $client, string $model, mixed $rawSubtasks): array
  {
      $allSubtasks = normalizeSubtasks($rawSubtasks);
      $subtasks = array_slice($allSubtasks, 0, MAX_TOTAL_SUBTASKS);
      $dropped = count($allSubtasks) - count($subtasks);
      if ($subtasks === []) {
          return ['Workflow error: no usable subtasks were provided.', true];
      }
      fwrite(STDERR, '[workflow] running ' . count($subtasks) . " agents\n");

      $runOne = function (string $prompt) use ($client, $model): string {
          try {
              return journaled($prompt, fn () => runSubagent($client, $model, $prompt));
          } catch (\Throwable $error) {
              // 分離境界:1つのサブエージェントの失敗で実行全体が終了しないようにします。
              return '(subagent failed: ' . $error::class . ': ' . $error->getMessage() . ')';
          }
      };

      $results = array_map($runOne, $subtasks);
      fwrite(STDERR, '[workflow] verifying ' . count($results) . " results\n");
      $verifyPrompts = array_map(verifyPromptFor(...), $subtasks, $results);
      $verdicts = array_map($runOne, $verifyPrompts);

      $sections = [];
      foreach ($subtasks as $index => $task) {
          $sections[] = '[agent ' . ($index + 1) . ": {$task}]\n{$results[$index]}"
              . "\n\n[verify " . ($index + 1) . "]\n{$verdicts[$index]}";
      }
      $joined = implode("\n\n", $sections);
      if ($dropped > 0) {
          $joined = '(note: ' . $dropped . ' subtasks beyond MAX_TOTAL_SUBTASKS=' . MAX_TOTAL_SUBTASKS
              . " were not run; rerun them in a follow-up Workflow call)\n\n" . $joined;
      }
      return [$joined, false];
  }
  ```

  ```ruby Ruby
  # モデルが出力するどの形式のsubtasks入力も受け付けます:配列、配列全体を
  # 単一の文字列としてJSONエンコードしたもの、または改行区切りのリストです。
  def normalize_subtasks(raw)
    if raw.is_a?(String)
      begin
        raw = JSON.parse(raw)
      rescue JSON::ParserError
        raw = raw.include?("\n") ? raw.split("\n") : [raw]
      end
    end
    return [] unless raw.is_a?(Array)
    raw.select { |task| task.is_a?(String) }.map(&:strip).reject(&:empty?)
  end

  def verify_prompt_for(subtask, result)
    "Adversarially verify the subagent result below: try to REFUTE it. Re-derive the " \
      "claims yourself with bash rather than trusting the result, and look for evidence " \
      "that contradicts them. Default to refuted if uncertain. Call report_findings with " \
      "summary 'refuted: <why>' or 'confirmed: <why>', citing the file:line or command " \
      "output that decided it.\n\n" +
      <<~PROMPT.chomp
        Subtask: #{subtask}

        Result to verify:
        #{result}
      PROMPT
  end

  # 並行数制限付きのmap:同時に実行されるスレッドは最大 `limit` 個です。
  def map_with_limit(items, limit)
    results = Array.new(items.length)
    queue = Queue.new
    items.each_with_index { |item, index| queue << [index, item] }
    workers = Array.new([limit, items.length].min) do
      Thread.new do
        until queue.empty?
          index, item = queue.pop(true) rescue break
          results[index] = yield item
        end
      end
    end
    workers.each(&:join)
    results
  end

  # サブタスクを並列サブエージェントとして実行し、その結果に対して2回目の検証ウェーブを
  # 実行して、両方を返します。MAX_TOTAL_SUBTASKSはモデルがキューに入れられる数を、
  # MAX_CONCURRENTは同時に実行される数を制限します。
  def run_workflow(model, raw_subtasks)
    all_subtasks = normalize_subtasks(raw_subtasks)
    subtasks = all_subtasks.first(MAX_TOTAL_SUBTASKS)
    dropped = all_subtasks.length - subtasks.length
    return ["Workflow error: no usable subtasks were provided.", true] if subtasks.empty?

    warn "[workflow] fanning out #{subtasks.length} agents"
    run_one = lambda do |prompt|
      journaled(prompt) { run_subagent(model, prompt) }
    rescue => error # isolation boundary: one bad subagent should not end the run
      "(subagent failed: #{error.class}: #{error.message})"
    end

    results = map_with_limit(subtasks, MAX_CONCURRENT, &run_one)
    warn "[workflow] verifying #{results.length} results"
    verify_prompts = subtasks.zip(results).map { |task, result| verify_prompt_for(task, result) }
    verdicts = map_with_limit(verify_prompts, MAX_CONCURRENT, &run_one)

    joined = subtasks.each_with_index.map do |task, index|
      <<~ENTRY.chomp
        [agent #{index + 1}: #{task}]
        #{results[index]}

        [verify #{index + 1}]
        #{verdicts[index]}
      ENTRY
    end.join("\n\n")
    if dropped > 0
      joined =
        "(note: #{dropped} subtasks beyond MAX_TOTAL_SUBTASKS=#{MAX_TOTAL_SUBTASKS} were not " \
        "run; rerun them in a follow-up Workflow call)\n\n#{joined}"
    end
    [joined, false]
  end
  ```
</CodeGroup>

## 会話途中のシステムメッセージでモードを切り替える

エージェントはまずユーザーのメッセージを追加し、次に送信すべきシステムメッセージがあればそれを追加します。終了通知、開始時のモード全文、または定期的なリフレッシャーです。システムメッセージをユーザーターンの後に配置することで、それより前にあるキャッシュ済みのすべてのバイトが手つかずのまま保たれ、システムメッセージはユーザーターンの後に続くという配置ルールも満たされます。

<CodeGroup>
  ```bash cURL
  # 1回のオーケストレーションモードターン:モードリマインダーはmessages配列内の
  # {"role": "system"}エントリとして、適用対象のユーザーターンの後に配置されます。レスポンスは
  # 最初のツール呼び出しで停止します。ツール呼び出しを実行しサブエージェントに
  # ファンアウトするエージェントループはSDKタブに示されています。Workflowの説明はここでは
  # 要約されており、SDKの例に完全なstanding-consentテキストが含まれています。
  curl --fail-with-body -sS https://api.anthropic.com/v1/messages \
    -H "x-api-key: $ANTHROPIC_API_KEY" \
    -H "anthropic-version: 2023-06-01" \
    -H "content-type: application/json" \
    -d '{
      "model": "claude-opus-5-5",
      "max_tokens": 64000,
      "system": "You are a helpful general-purpose agent. Answer the request from the user directly.",
      "output_config": {"effort": "xhigh"},
      "tools": [
        {
          "name": "Workflow",
          "description": "Orchestrate a multiagent workflow: split a large task into independent subtasks and run them as parallel agents, then collect their results. Opt-in: only use this tool when the user explicitly asks for a workflow, or when a system message confirms that orchestration mode is on. Granularity: scope each subtask to a distinct concern rather than per line or file section; a review of a small module rarely needs more than about ten. Standing consent: while a system message confirms orchestration mode is on, author and run a workflow for every substantive task by default; work solo only on conversational turns or trivial mechanical edits.",
          "input_schema": {
            "type": "object",
            "properties": {
              "subtasks": {
                "type": "array",
                "items": {"type": "string"},
                "description": "Independent subtask prompts to run as parallel agents"
              }
            },
            "required": ["subtasks"]
          }
        },
        {"type": "bash_20250124", "name": "bash"}
      ],
      "messages": [
        {
          "role": "user",
          "content": "Explore the current directory, then give a thorough review: what it does, code-quality issues, and concrete improvements."
        },
        {
          "role": "system",
          "content": "Orchestration mode is on: optimize for the most exhaustive, correct answer rather than the fastest one. Use the Workflow tool on every substantive task, sized to the natural decomposition of the problem rather than the maximum the tool allows. See the description of the Workflow tool for standing consent, granularity guidance, and quality patterns. Work solo only on conversational or trivial turns."
        }
      ]
    }'
  ```

  ```bash CLI
  # オーケストレーションモードの1ターン：モードリマインダーは、適用対象のユーザーターンの後に
  # 配置された system ロールのエントリとして messages 配列に含まれます。レスポンスは
  # 最初のツール呼び出しで停止します。ツール呼び出しを実行しサブエージェントに
  # ファンアウトするエージェントループはSDKタブに示されています。Workflowの説明はここでは要約されており、
  # 完全な standing-consent テキストはSDKの例に含まれています。
  ant messages create <<'YAML'
  model: claude-opus-5-5
  max_tokens: 64000
  system: You are a helpful general-purpose agent. Answer the user's request directly.
  output_config: {effort: xhigh}
  tools:
    - name: Workflow
      description: >-
        Orchestrate a multiagent workflow: split a large task into independent
        subtasks and run them as parallel agents, then collect their results.
        Opt-in: only use this tool when the user explicitly asks for a workflow,
        or when a system message confirms that orchestration mode is on.
        Granularity: scope each subtask to a distinct concern rather than per
        line or file section; a review of a small module rarely needs more than
        about ten. Standing consent: while a system message confirms
        orchestration mode is on, author and run a workflow for every
        substantive task by default; work solo only on conversational turns or
        trivial mechanical edits.
      input_schema:
        type: object
        properties:
          subtasks:
            type: array
            items: {type: string}
            description: Independent subtask prompts to run as parallel agents
        required: [subtasks]
    - {type: bash_20250124, name: bash}
  messages:
    - role: user
      content: >-
        Explore the current directory, then give a thorough review: what it
        does, code-quality issues, and concrete improvements.
    - role: system
      content: >-
        Orchestration mode is on: optimize for the most exhaustive, correct
        answer rather than the fastest one. Use the Workflow tool on every
        substantive task, sized to the problem's natural decomposition rather
        than the maximum the tool allows. See the Workflow tool's description
        for standing consent, granularity guidance, and quality patterns. Work
        solo only on conversational or trivial turns.
  YAML
  ```

  ```python Python
  class ModeAgent:
      """An agent loop whose orchestration mode is toggled with mid-conversation system messages."""

      def __init__(self, model: str, mode_on: bool = True):
          self.model = model
          self.mode_on = mode_on
          self.messages: list[dict] = []
          self._mode_announced = False
          self._exit_pending = False
          self._turns_since_reminder = 0

      def set_mode(self, mode_on: bool) -> None:
          """Turn the mode on or off. The notice is delivered with the next user turn."""
          if mode_on == self.mode_on:
              return
          if not mode_on:
              if self._mode_announced:
                  self._exit_pending = True
          else:
              self._exit_pending = False
          self.mode_on = mode_on

      def _due_system_messages(self) -> list[dict]:
          """System messages owed on this turn: an exit notice, the full mode text on entry,
          or a one-line refresher every TURNS_BETWEEN_REFRESHERS user turns."""
          due = []
          if self._exit_pending:
              self._exit_pending = False
              self._mode_announced = False
              due.append({"role": "system", "content": MODE_EXIT})
          if self.mode_on:
              if not self._mode_announced:
                  self._mode_announced = True
                  self._turns_since_reminder = 0
                  due.append({"role": "system", "content": MODE_ENTER})
              elif self._turns_since_reminder >= TURNS_BETWEEN_REFRESHERS:
                  self._turns_since_reminder = 0
                  due.append({"role": "system", "content": MODE_REFRESH})
          return due

      def turn(self, user_input: str) -> str:
          # 会話途中のシステムメッセージは、適用対象のユーザーターンの直後に置かれるため、
          # その前にあるキャッシュ済みプレフィックスはそのまま保たれます。
          self.messages.append({"role": "user", "content": user_input})
          self.messages.extend(self._due_system_messages())
          self._turns_since_reminder += 1

          for _ in range(MAX_MAIN_TURNS):
              with client.messages.stream(
                  model=self.model,
                  max_tokens=64000,
                  system=SYSTEM_PROMPT,  # static for the whole session
                  output_config={"effort": EFFORT},
                  tools=[WORKFLOW_TOOL, BASH_TOOL],
                  messages=self.messages,
                  timeout=REQUEST_TIMEOUT_SECONDS,
              ) as stream:
                  response = stream.get_final_message()
              self.messages.append({"role": "assistant", "content": response.content})

              if response.stop_reason == "pause_turn":
                  continue
              if response.stop_reason != "tool_use":
                  text = "".join(
                      block.text for block in response.content if block.type == "text"
                  )
                  if response.stop_reason == "max_tokens":
                      # 切り詰められたアシスタントメッセージを破棄し、後続のターンがそれを前提にしないようにします。
                      self.messages.pop()
                      text += "\n\n(warning: response was truncated at max_tokens)"
                  return text

              tool_results = []
              for block in response.content:
                  if block.type != "tool_use":
                      continue
                  match block.name:
                      case "Workflow":
                          output, is_error = run_workflow(
                              self.model, block.input.get("subtasks", [])
                          )
                      case "bash":
                          output, is_error = handle_bash_block(block)
                      case _:
                          output, is_error = f"unknown tool: {block.name}", True
                  tool_results.append(
                      {
                          "type": "tool_result",
                          "tool_use_id": block.id,
                          "content": output,
                          "is_error": is_error,
                      }
                  )
              self.messages.append({"role": "user", "content": tool_results})
          return "(hit the main loop turn limit before finishing)"
  ```

  ```typescript TypeScript
  // 会話途中のシステムメッセージでオーケストレーションモードを切り替えるエージェントループです。
  class ModeAgent {
    readonly #model: string;
    readonly #messages: Anthropic.MessageParam[] = [];
    #modeOn: boolean;
    #modeAnnounced = false;
    #exitPending = false;
    #turnsSinceReminder = 0;

    constructor(model: string, modeOn = true) {
      this.#model = model;
      this.#modeOn = modeOn;
    }

    // モードのオン/オフを切り替えます。通知は次のユーザーターンと一緒に配信されます。
    setMode(modeOn: boolean): void {
      if (modeOn === this.#modeOn) {
        return;
      }
      // 終了通知は、モードがオンであるとモデルに伝えていた場合にのみ必要です。
      this.#exitPending = !modeOn && this.#modeAnnounced;
      this.#modeOn = modeOn;
    }

    // このターンで送るべきシステムメッセージ:解除通知、開始時のモード全文、
    // またはTURNS_BETWEEN_REFRESHERSユーザーターンごとの1行のリマインダーです。
    #dueSystemMessages(): Anthropic.MessageParam[] {
      const due: Anthropic.MessageParam[] = [];
      if (this.#exitPending) {
        this.#exitPending = false;
        this.#modeAnnounced = false;
        due.push({ role: "system", content: MODE_EXIT });
      }
      if (this.#modeOn) {
        if (!this.#modeAnnounced) {
          this.#modeAnnounced = true;
          this.#turnsSinceReminder = 0;
          due.push({ role: "system", content: MODE_ENTER });
        } else if (this.#turnsSinceReminder >= TURNS_BETWEEN_REFRESHERS) {
          this.#turnsSinceReminder = 0;
          due.push({ role: "system", content: MODE_REFRESH });
        }
      }
      return due;
    }

    async turn(userInput: string): Promise<string> {
      // 会話途中のシステムメッセージは、適用対象のユーザーターンの後に置かれるため、
      // その前にあるキャッシュ済みのプレフィックスはそのまま保たれます。
      this.#messages.push({ role: "user", content: userInput }, ...this.#dueSystemMessages());
      this.#turnsSinceReminder += 1;

      for (let turn = 0; turn < MAX_MAIN_TURNS; turn++) {
        const response = await client.messages
          .stream(
            {
              model: this.#model,
              max_tokens: 64000,
              system: SYSTEM_PROMPT, // static for the whole session
              output_config: { effort: EFFORT },
              tools: [WORKFLOW_TOOL, BASH_TOOL],
              messages: this.#messages
            },
            { signal: AbortSignal.timeout(REQUEST_TIMEOUT_SECONDS * 1000) }
          )
          .finalMessage();
        this.#messages.push({ role: "assistant", content: response.content });

        if (response.stop_reason === "pause_turn") {
          continue;
        }
        if (response.stop_reason !== "tool_use") {
          let text = response.content
            .flatMap((block) => (block.type === "text" ? [block.text] : []))
            .join("");
          if (response.stop_reason === "max_tokens") {
            // 後続のターンが途中で切れたアシスタントメッセージの上に構築されないよう、それを破棄します。
            this.#messages.pop();
            text += "\n\n(warning: response was truncated at max_tokens)";
          }
          return text;
        }

        const toolResults: Anthropic.ToolResultBlockParam[] = [];
        for (const block of response.content) {
          if (block.type !== "tool_use") {
            continue;
          }
          let outcome: ToolOutcome;
          switch (block.name) {
            case "Workflow": {
              const { subtasks } = block.input as { subtasks?: unknown };
              outcome = await runWorkflow(this.#model, subtasks);
              break;
            }
            case "bash":
              outcome = await handleBashBlock(block);
              break;
            default:
              outcome = { output: `unknown tool: ${block.name}`, isError: true };
          }
          toolResults.push({
            type: "tool_result",
            tool_use_id: block.id,
            content: outcome.output,
            is_error: outcome.isError
          });
        }
        this.#messages.push({ role: "user", content: toolResults });
      }
      return "(hit the main loop turn limit before finishing)";
    }
  }
  ```

  ```csharp C#
  // 会話途中のシステムメッセージでオーケストレーションモードを切り替えるエージェントループ。
  List<MessageParam> messages = [];
  var modeOn = true;
  var modeAnnounced = false;
  var exitPending = false;
  var turnsSinceReminder = 0;

  // モードをオンまたはオフにします。通知は次のユーザーターンで配信されます。
  void SetMode(bool nextModeOn)
  {
      if (nextModeOn == modeOn)
      {
          return;
      }
      // 終了通知が必要なのは、モードがオンであるとモデルに伝えていた場合のみです。
      exitPending = !nextModeOn && modeAnnounced;
      modeOn = nextModeOn;
  }

  MessageParam SystemMessage(string content) => new() { Role = Role.System, Content = content };

  // このターンで送るべきシステムメッセージ：終了通知、開始時のモード全文、
  // または turnsBetweenRefreshers ユーザーターンごとの1行リマインダー。
  List<MessageParam> DueSystemMessages()
  {
      List<MessageParam> due = [];
      if (exitPending)
      {
          exitPending = false;
          modeAnnounced = false;
          due.Add(SystemMessage(modeExit));
      }
      if (modeOn)
      {
          if (!modeAnnounced)
          {
              modeAnnounced = true;
              turnsSinceReminder = 0;
              due.Add(SystemMessage(modeEnter));
          }
          else if (turnsSinceReminder >= turnsBetweenRefreshers)
          {
              turnsSinceReminder = 0;
              due.Add(SystemMessage(modeRefresh));
          }
      }
      return due;
  }

  // 1つのユーザーターンをループに通し、モデルが停止するまでツール呼び出しを実行します。
  async Task<string> Turn(string userInput)
  {
      // 会話途中のシステムメッセージは適用対象のユーザーターンの後に続くため、その前の
      // キャッシュ済みプレフィックスは変更されません。
      messages.Add(new() { Role = Role.User, Content = userInput });
      messages.AddRange(DueSystemMessages());
      turnsSinceReminder++;

      for (var turn = 0; turn < maxMainTurns; turn++)
      {
          using var deadline = new CancellationTokenSource(TimeSpan.FromSeconds(requestTimeoutSeconds));
          var response = await client.Messages.CreateStreaming(new MessageCreateParams
          {
              Model = model,
              MaxTokens = 64000,
              System = systemPrompt, // static for the whole session
              OutputConfig = new OutputConfig { Effort = effort },
              Tools = [workflowTool, bashTool],
              Messages = messages,
          }, cancellationToken: deadline.Token).Aggregate();
          messages.Add(new()
          {
              Role = Role.Assistant,
              Content = response.Content.Select(block => new ContentBlockParam(block.Json)).ToList(),
          });

          if (response.StopReason == StopReason.PauseTurn)
          {
              continue;
          }
          if (response.StopReason != StopReason.ToolUse)
          {
              var text = string.Concat(
                  response.Content.Select(block => block.TryPickText(out var textBlock) ? textBlock.Text : ""));
              if (response.StopReason == StopReason.MaxTokens)
              {
                  // 切り詰められたアシスタントメッセージを削除し、後続のターンがそれを前提にしないようにします。
                  messages.RemoveAt(messages.Count - 1);
                  text += "\n\n(warning: response was truncated at max_tokens)";
              }
              return text;
          }

          List<ContentBlockParam> toolResults = [];
          foreach (var block in response.Content)
          {
              if (!block.TryPickToolUse(out var toolUse))
              {
                  continue;
              }
              var (output, isError) = toolUse.Name switch
              {
                  "Workflow" => await RunWorkflow(toolUse.Input.GetValueOrDefault("subtasks")),
                  "bash" => await HandleBashBlock(toolUse),
                  _ => ($"unknown tool: {toolUse.Name}", true),
              };
              toolResults.Add(new ToolResultBlockParam(toolUse.ID) { Content = output, IsError = isError });
          }
          messages.Add(new() { Role = Role.User, Content = toolResults });
      }
      return "(hit the main loop turn limit before finishing)";
  }
  ```

  ```go Go
  // modeAgentは会話途中のシステムメッセージでオーケストレーションモードを切り替える
  // エージェントループです。
  type modeAgent struct {
  	model              string
  	modeOn             bool
  	messages           []anthropic.MessageParam
  	modeAnnounced      bool
  	exitPending        bool
  	turnsSinceReminder int
  }

  func newModeAgent(model string) *modeAgent {
  	return &modeAgent{model: model, modeOn: true}
  }

  // setModeはモードをオンまたはオフにします。通知は次のユーザーターンで配信されます。
  func (agent *modeAgent) setMode(modeOn bool) {
  	if modeOn == agent.modeOn {
  		return
  	}
  	if !modeOn {
  		if agent.modeAnnounced {
  			agent.exitPending = true
  		}
  	} else {
  		agent.exitPending = false
  	}
  	agent.modeOn = modeOn
  }

  // dueSystemMessagesはこのターンで送るべきシステムメッセージを返します：終了通知、
  // 開始時の完全なモードテキスト、またはturnsBetweenRefreshersユーザーターンごとの1行リマインダー。
  func (agent *modeAgent) dueSystemMessages() []anthropic.MessageParam {
  	systemMessage := func(content string) anthropic.MessageParam {
  		return anthropic.MessageParam{
  			Role:    anthropic.MessageParamRoleSystem,
  			Content: []anthropic.ContentBlockParamUnion{anthropic.NewTextBlock(content)},
  		}
  	}
  	var due []anthropic.MessageParam
  	if agent.exitPending {
  		agent.exitPending = false
  		agent.modeAnnounced = false
  		due = append(due, systemMessage(modeExit))
  	}
  	if agent.modeOn {
  		if !agent.modeAnnounced {
  			agent.modeAnnounced = true
  			agent.turnsSinceReminder = 0
  			due = append(due, systemMessage(modeEnter))
  		} else if agent.turnsSinceReminder >= turnsBetweenRefreshers {
  			agent.turnsSinceReminder = 0
  			due = append(due, systemMessage(modeRefresh))
  		}
  	}
  	return due
  }

  // turnは1つのユーザーターンをループに通し、モデルが停止するまでツール呼び出しを実行します。
  func (agent *modeAgent) turn(ctx context.Context, userInput string) (string, error) {
  	// 会話途中のシステムメッセージは適用対象のユーザーターンの後に続くため、
  	// その前にあるキャッシュ済みプレフィックスは変更されません。
  	agent.messages = append(agent.messages, anthropic.NewUserMessage(anthropic.NewTextBlock(userInput)))
  	agent.messages = append(agent.messages, agent.dueSystemMessages()...)
  	agent.turnsSinceReminder++

  	for range maxMainTurns {
  		response, err := streamMessage(ctx, anthropic.MessageNewParams{
  			Model:        agent.model,
  			MaxTokens:    64000,
  			System:       []anthropic.TextBlockParam{{Text: systemPrompt}}, // static for the whole session
  			OutputConfig: anthropic.OutputConfigParam{Effort: effort},
  			Tools:        []anthropic.ToolUnionParam{workflowTool, bashTool},
  			Messages:     agent.messages,
  		})
  		if err != nil {
  			return "", err
  		}
  		agent.messages = append(agent.messages, response.ToParam())

  		switch response.StopReason {
  		case anthropic.StopReasonPauseTurn:
  			continue
  		case anthropic.StopReasonMaxTokens:
  			// 後続のターンがそれを前提にしないよう、途中で切れたアシスタントメッセージを破棄します。
  			agent.messages = agent.messages[:len(agent.messages)-1]
  			return textOf(response) + "\n\n(warning: response was truncated at max_tokens)", nil
  		case anthropic.StopReasonToolUse:
  		default:
  			return textOf(response), nil
  		}

  		var toolResults []anthropic.ContentBlockParamUnion
  		for _, block := range response.Content {
  			toolUse, ok := block.AsAny().(anthropic.ToolUseBlock)
  			if !ok {
  				continue
  			}
  			var output string
  			var isError bool
  			switch toolUse.Name {
  			case "Workflow":
  				var input struct {
  					Subtasks json.RawMessage `json:"subtasks"`
  				}
  				if err := json.Unmarshal(toolUse.Input, &input); err != nil {
  					output, isError = fmt.Sprintf("Workflow error: could not parse input: %s", err), true
  				} else {
  					output, isError = runWorkflow(ctx, agent.model, input.Subtasks)
  				}
  			case "bash":
  				output, isError = handleBashBlock(ctx, toolUse)
  			default:
  				output, isError = "unknown tool: "+toolUse.Name, true
  			}
  			toolResults = append(toolResults, anthropic.NewToolResultBlock(toolUse.ID, output, isError))
  		}
  		agent.messages = append(agent.messages, anthropic.NewUserMessage(toolResults...))
  	}
  	return "(hit the main loop turn limit before finishing)", nil
  }
  ```

  ```java Java
  // 会話途中のシステムメッセージでオーケストレーションモードを切り替えるエージェントループ。
  class ModeAgent {
      private final Model model;
      private final List<MessageParam> messages = new ArrayList<>();
      private boolean modeOn;
      private boolean modeAnnounced;
      private boolean exitPending;
      private int turnsSinceReminder;

      ModeAgent(Model model) {
          this(model, true);
      }

      ModeAgent(Model model, boolean modeOn) {
          this.model = model;
          this.modeOn = modeOn;
      }

      // モードをオンまたはオフにします。通知は次のユーザーターンとともに送られます：
      // 終了通知が必要なのは、モデルにモードがオンだと伝えていた場合のみです。
      void setMode(boolean modeOn) {
          if (modeOn == this.modeOn) {
              return;
          }
          exitPending = !modeOn && modeAnnounced;
          this.modeOn = modeOn;
      }

      // このターンで送るべきシステムメッセージ：終了通知、開始時のモード全文、
      // またはTURNS_BETWEEN_REFRESHERSユーザーターンごとの1行リマインダー。
      private List<MessageParam> dueSystemMessages() {
          List<MessageParam> due = new ArrayList<>();
          if (exitPending) {
              exitPending = false;
              modeAnnounced = false;
              due.add(systemMessage(MODE_EXIT));
          }
          if (modeOn) {
              if (!modeAnnounced) {
                  modeAnnounced = true;
                  turnsSinceReminder = 0;
                  due.add(systemMessage(MODE_ENTER));
              } else if (turnsSinceReminder >= TURNS_BETWEEN_REFRESHERS) {
                  turnsSinceReminder = 0;
                  due.add(systemMessage(MODE_REFRESH));
              }
          }
          return due;
      }

      private MessageParam systemMessage(String content) {
          return MessageParam.builder()
                  .role(MessageParam.Role.SYSTEM)
                  .content(content)
                  .build();
      }

      // 1つのユーザーターンをループに通し、モデルが停止するまでツール呼び出しを実行します。
      String turn(String userInput) throws InterruptedException {
          // 会話途中のシステムメッセージは、適用対象のユーザーターンの後に続きます。これにより
          // その前にあるキャッシュ済みプレフィックスが変更されずに保たれます。
          messages.add(MessageParam.builder().role(MessageParam.Role.USER).content(userInput).build());
          messages.addAll(dueSystemMessages());
          turnsSinceReminder++;

          for (int turn = 0; turn < MAX_MAIN_TURNS; turn++) {
              MessageCreateParams params = MessageCreateParams.builder()
                      .model(model)
                      .maxTokens(64000L)
                      .system(SYSTEM_PROMPT) // static for the whole session
                      .outputConfig(OutputConfig.builder().effort(EFFORT).build())
                      .addTool(WORKFLOW_TOOL)
                      .addTool(BASH_TOOL)
                      .messages(messages)
                      .build();
              MessageAccumulator accumulator = MessageAccumulator.create();
              try (var stream = client.messages().createStreaming(params, REQUEST_OPTIONS)) {
                  stream.stream().forEach(accumulator::accumulate);
              }
              Message response = accumulator.message();
              messages.add(response.toParam());

              StopReason stopReason = response.stopReason().orElse(null);
              if (StopReason.PAUSE_TURN.equals(stopReason)) {
                  continue;
              }
              if (!StopReason.TOOL_USE.equals(stopReason)) {
                  String text = response.content().stream()
                          .flatMap(block -> block.text().stream())
                          .map(TextBlock::text)
                          .collect(Collectors.joining());
                  if (StopReason.MAX_TOKENS.equals(stopReason)) {
                      // 後続のターンがそれを前提にしないよう、途中で切れたアシスタントメッセージを破棄します。
                      messages.removeLast();
                      text += "\n\n(warning: response was truncated at max_tokens)";
                  }
                  return text;
              }

              List<ToolUseBlock> toolUses = response.content().stream()
                      .flatMap(block -> block.toolUse().stream())
                      .toList();
              List<ContentBlockParam> toolResults = new ArrayList<>();
              for (ToolUseBlock toolUse : toolUses) {
                  ToolOutput result = switch (toolUse.name()) {
                      case "Workflow" -> runWorkflow(model,
                              toolInput(toolUse).getOrDefault("subtasks", JsonValue.from(List.of())));
                      case "bash" -> handleBashBlock(toolUse);
                      default -> new ToolOutput("unknown tool: " + toolUse.name(), true);
                  };
                  toolResults.add(ContentBlockParam.ofToolResult(ToolResultBlockParam.builder()
                          .toolUseId(toolUse.id())
                          .content(result.output())
                          .isError(result.isError())
                          .build()));
              }
              messages.add(MessageParam.builder()
                      .role(MessageParam.Role.USER)
                      .contentOfBlockParams(toolResults)
                      .build());
          }
          return "(hit the main loop turn limit before finishing)";
      }
  }
  ```

  ```php PHP
  /** An agent loop whose orchestration mode is toggled with mid-conversation system messages. */
  class ModeAgent
  {
      private array $messages = [];
      private bool $modeAnnounced = false;
      private bool $exitPending = false;
      private int $turnsSinceReminder = 0;

      public function __construct(
          private readonly Client $client,
          private readonly string $model,
          private bool $modeOn = true,
      ) {
      }

      /** Turn the mode on or off. The notice is delivered with the next user turn. */
      public function setMode(bool $modeOn): void
      {
          if ($modeOn === $this->modeOn) {
              return;
          }
          if ($modeOn) {
              $this->exitPending = false;
          } elseif ($this->modeAnnounced) {
              $this->exitPending = true;
          }
          $this->modeOn = $modeOn;
      }

      public function turn(string $userInput): string
      {
          // 会話途中のシステムメッセージは、適用対象のユーザーターンの後に配置するため、
          // その前にあるキャッシュ済みプレフィックスはそのまま保たれます。
          $this->messages[] = ['role' => 'user', 'content' => $userInput];
          array_push($this->messages, ...$this->dueSystemMessages());
          $this->turnsSinceReminder++;

          for ($turn = 0; $turn < MAX_MAIN_TURNS; $turn++) {
              $stream = $this->client->messages->createStream(
                  model: $this->model,
                  maxTokens: 64000,
                  system: SYSTEM_PROMPT, // static for the whole session
                  outputConfig: ['effort' => EFFORT],
                  tools: [WORKFLOW_TOOL, BASH_TOOL],
                  messages: $this->messages,
                  requestOptions: ['timeout' => REQUEST_TIMEOUT_SECONDS],
              );
              [$content, $stopReason] = drainMessageStream($stream);
              $this->messages[] = ['role' => 'assistant', 'content' => $content];

              if ($stopReason === 'pause_turn') {
                  continue;
              }
              if ($stopReason !== 'tool_use') {
                  $text = '';
                  foreach ($content as $block) {
                      if ($block instanceof \Anthropic\Messages\TextBlock) {
                          $text .= $block->text;
                      }
                  }
                  if ($stopReason === 'max_tokens') {
                      // 途中で切れたアシスタントメッセージを破棄し、後続のターンがそれを前提にしないようにします。
                      array_pop($this->messages);
                      $text .= "\n\n(warning: response was truncated at max_tokens)";
                  }
                  return $text;
              }

              $toolResults = [];
              foreach ($content as $block) {
                  if (!$block instanceof \Anthropic\Messages\ToolUseBlock) {
                      continue;
                  }
                  if ($block->name === 'Workflow') {
                      [$output, $isError] =
                          runWorkflow($this->client, $this->model, $block->input['subtasks'] ?? []);
                  } elseif ($block->name === 'bash') {
                      [$output, $isError] = handleBashBlock($block);
                  } else {
                      $output = "unknown tool: {$block->name}";
                      $isError = true;
                  }
                  $toolResults[] = [
                      'type' => 'tool_result',
                      'tool_use_id' => $block->id,
                      'content' => $output,
                      'is_error' => $isError,
                  ];
              }
              $this->messages[] = ['role' => 'user', 'content' => $toolResults];
          }
          return '(hit the main loop turn limit before finishing)';
      }

      /**
       * System messages owed on this turn: an exit notice, the full mode text on entry,
       * or a one-line refresher every TURNS_BETWEEN_REFRESHERS user turns.
       */
      private function dueSystemMessages(): array
      {
          $due = [];
          if ($this->exitPending) {
              $this->exitPending = false;
              $this->modeAnnounced = false;
              $due[] = ['role' => 'system', 'content' => MODE_EXIT];
          }
          if ($this->modeOn) {
              if (!$this->modeAnnounced) {
                  $this->modeAnnounced = true;
                  $this->turnsSinceReminder = 0;
                  $due[] = ['role' => 'system', 'content' => MODE_ENTER];
              } elseif ($this->turnsSinceReminder >= TURNS_BETWEEN_REFRESHERS) {
                  $this->turnsSinceReminder = 0;
                  $due[] = ['role' => 'system', 'content' => MODE_REFRESH];
              }
          }
          return $due;
      }
  }
  ```

  ```ruby Ruby
  # 会話途中のシステムメッセージでオーケストレーションモードを切り替えるエージェントループです。
  class ModeAgent
    def initialize(model, mode_on: true)
      @model = model
      @mode_on = mode_on
      @messages = []
      @mode_announced = false
      @exit_pending = false
      @turns_since_reminder = 0
    end

    # モードのオン/オフを切り替えます。通知は次のユーザーターンと一緒に送られます。
    def set_mode(mode_on)
      return if mode_on == @mode_on

      if mode_on
        @exit_pending = false
      else
        @exit_pending = true if @mode_announced
      end
      @mode_on = mode_on
    end

    def turn(user_input)
      # 会話途中のシステムメッセージは、適用対象のユーザーターンの後に置かれるため、
      # その前にあるキャッシュ済みのプレフィックスはそのまま保たれます。
      @messages << {role: "user", content: user_input}
      @messages.concat(due_system_messages)
      @turns_since_reminder += 1

      MAX_MAIN_TURNS.times do
        stream = CLIENT.messages.stream(
          model: @model,
          max_tokens: 64_000,
          system_: SYSTEM_PROMPT, # static for the whole session
          output_config: {effort: EFFORT},
          tools: [WORKFLOW_TOOL, BASH_TOOL],
          messages: @messages,
          request_options: {timeout: REQUEST_TIMEOUT_SECONDS}
        )
        response = stream.accumulated_message
        @messages << {role: "assistant", content: assistant_content_param(response.content)}

        next if response.stop_reason == :pause_turn

        unless response.stop_reason == :tool_use
          text = response.content.select { |block| block.type == :text }.map(&:text).join
          if response.stop_reason == :max_tokens
            # 途中で切れたアシスタントメッセージを破棄し、以降のターンがそれを前提にしないようにします。
            @messages.pop
            text += "\n\n(warning: response was truncated at max_tokens)"
          end
          return text
        end

        tool_results = []
        response.content.each do |block|
          next unless block.type == :tool_use

          input = parse_tool_input(block.input)
          case block.name
          when "Workflow"
            output, is_error = run_workflow(@model, input["subtasks"] || [])
          when "bash"
            output, is_error = handle_bash_block(block)
          else
            output, is_error = "unknown tool: #{block.name}", true
          end
          tool_results << {
            type: "tool_result",
            tool_use_id: block.id,
            content: output,
            is_error: is_error
          }
        end
        @messages << {role: "user", content: tool_results}
      end
      "(hit the main loop turn limit before finishing)"
    end

    private

    # このターンで送るべきシステムメッセージ:解除通知、開始時のモード全文、
    # またはTURNS_BETWEEN_REFRESHERSユーザーターンごとの1行のリマインダーです。
    def due_system_messages
      due = []
      if @exit_pending
        @exit_pending = false
        @mode_announced = false
        due << {role: "system", content: MODE_EXIT}
      end
      if @mode_on
        if !@mode_announced
          @mode_announced = true
          @turns_since_reminder = 0
          due << {role: "system", content: MODE_ENTER}
        elsif @turns_since_reminder >= TURNS_BETWEEN_REFRESHERS
          @turns_since_reminder = 0
          due << {role: "system", content: MODE_REFRESH}
        end
      end
      due
    end
  end
  ```
</CodeGroup>

## 実行する

<Warning>
  この例のbashツールは、モデルが書いたコマンドをサンドボックスなしであなたのマシン上で直接実行し、ファンアウトはそれらのエージェントを複数並列で実行します。公開しても差し支えないディレクトリと環境で実行し、ローカルでの実験を超える用途に適応させる前にサンドボックス化を追加してください。
</Warning>

<CodeGroup exclude="shell:cURL, shell:CLI">
  ```python Python
  if __name__ == "__main__":
      task = (
          sys.argv[1]
          if len(sys.argv) > 1
          else "Explore the current directory, then give a thorough review: what it does, "
          "code-quality issues, and concrete improvements."
      )
      agent = ModeAgent(MODEL)
      print(agent.turn(task))
      agent.set_mode(False)
      print(agent.turn("Briefly summarize what you found above, no fan-out needed."))
  ```

  ```typescript TypeScript
  const task =
    process.argv[2] ??
    "Explore the current directory, then give a thorough review: what it does, " +
      "code-quality issues, and concrete improvements.";
  const agent = new ModeAgent(MODEL);
  console.log(await agent.turn(task));
  agent.setMode(false);
  console.log(await agent.turn("Briefly summarize what you found above, no fan-out needed."));
  ```

  ```csharp C#
  var task = args.Length > 0
      ? args[0]
      : "Explore the current directory, then give a thorough review: what it does, "
          + "code-quality issues, and concrete improvements.";
  Console.WriteLine(await Turn(task));
  SetMode(false);
  Console.WriteLine(await Turn("Briefly summarize what you found above, no fan-out needed."));
  ```

  ```go Go
  func main() {
  	if err := run(context.Background()); err != nil {
  		log.Fatal(err)
  	}
  }

  func run(ctx context.Context) error {
  	if docTestMode {
  		defer os.RemoveAll(workDir)
  	}
  	task := "Explore the current directory, then give a thorough review: what it does, " +
  		"code-quality issues, and concrete improvements."
  	if len(os.Args) > 1 {
  		task = os.Args[1]
  	}
  	agent := newModeAgent(modelID)
  	answer, err := agent.turn(ctx, task)
  	if err != nil {
  		return err
  	}
  	fmt.Println(answer)

  	agent.setMode(false)
  	summary, err := agent.turn(ctx, "Briefly summarize what you found above, no fan-out needed.")
  	if err != nil {
  		return err
  	}
  	fmt.Println(summary)
  	return nil
  }
  ```

  ```java Java
  void main(String[] args) throws InterruptedException {
      String task = args.length > 0 ? args[0] : """
              Explore the current directory, then give a thorough review: what it does, \
              code-quality issues, and concrete improvements.""";
      ModeAgent agent = new ModeAgent(MODEL);
      IO.println(agent.turn(task));
      agent.setMode(false);
      IO.println(agent.turn("Briefly summarize what you found above, no fan-out needed."));
  }
  ```

  ```php PHP
  $task = $argv[1] ??
      'Explore the current directory, then give a thorough review: what it does, '
      . 'code-quality issues, and concrete improvements.';
  $agent = new ModeAgent($client, MODEL);
  echo $agent->turn($task), PHP_EOL;
  $agent->setMode(false);
  echo $agent->turn('Briefly summarize what you found above, no fan-out needed.'), PHP_EOL;
  ```

  ```ruby Ruby
  task = ARGV[0] ||
    "Explore the current directory, then give a thorough review: what it does, " \
    "code-quality issues, and concrete improvements."
  agent = ModeAgent.new(MODEL)
  puts agent.turn(task)
  agent.set_mode(false)
  puts agent.turn("Briefly summarize what you found above, no fan-out needed.")
  ```
</CodeGroup>

エージェントに作業させたいディレクトリ、たとえばレビュー対象のリポジトリのルートから例を起動します。

```bash
python orchestration_mode.py "Review this repository for flaky tests and propose fixes."
```

モードがオンの場合、モデルはいくつかのbashコマンドで偵察し、促されることなくWorkflowツールをディスパッチし、サブエージェントのレポートを最終的な回答に統合することが期待されます。リマインダーの指示どおり、些細なリクエストや会話的なリクエストは単独で処理されます。

## 本番用ハーネスに向けて

この例は意図的に小さくしています。実際のワークロード向けのハーネスでは、通常次のものを追加します。

* **サンドボックス化されたオーケストレーションスクリプト：** サブタスク文字列のフラットなリストだけを受け付けるのではなく、モデルに短いオーケストレーションプログラム（分岐、ループ、リデュースステップ）を出力させ、隔離されたインタープリター内で実行します。
* **永続的なジャーナリング：** ローカルのJSONファイルを、プロセスの再起動に耐え、複数マシンにまたがる同時書き込みに対して安全なストアに置き換えます。
* **予算の強制：** Workflow呼び出しごとだけでなく、セッション全体で起動されたサブエージェントの総数を追跡し、ハードキャップを超えることを拒否することで、暴走した計画がクォータを使い果たせないようにします。

この例のパターン（モードリマインダー、ツール説明における継続的な同意、ジャーナリング、検証ウェーブ）はそのまま引き継がれます。より堅牢になるのは、それらを取り巻く実行基盤だけです。

## 関連項目

<CardGroup cols={2}>
  <Card title="会話途中のシステムメッセージ" icon="message" href="https://platform.claude.com/docs/ja/build-with-claude/mid-conversation-system-messages">
    モードリマインダーが使用するメカニズムと、それがプロンプトキャッシングとどのように相互作用するか。
  </Card>

  <Card title="エフォート" icon="gauge" href="https://platform.claude.com/docs/ja/build-with-claude/effort">
    APIが受け付けるエフォートレベルとその選び方。
  </Card>

  <Card title="Claudeでのツール使用" icon="wrench" href="https://platform.claude.com/docs/ja/agents-and-tools/tool-use/overview">
    ツールの定義、ツール呼び出しの処理、およびツール結果。
  </Card>

  <Card title="Bashツール" icon="terminal" href="https://platform.claude.com/docs/ja/agents-and-tools/tool-use/bash-tool">
    この例がローカルで実行するAnthropic定義のbashツール。
  </Card>
</CardGroup>
