---
title: Costruisci una modalità di orchestrazione
url: https://platform.claude.com/docs/it/build-with-claude/mid-conversation-effort-example
description: Costruisci una modalità a livello di sessione che concede un consenso permanente per il fan-out multiagente, attivata e disattivata con messaggi di sistema a metà conversazione.
---

Una "orchestration mode" (modalità di orchestrazione) è un interruttore a livello di sessione: quando è attiva, il modello applica la massima accuratezza a ogni richiesta sostanziale, esplorando autonomamente il compito e poi distribuendo per impostazione predefinita il lavoro a subagenti paralleli tramite "fan-out" (distribuzione a ventaglio). Quando è disattivata, lo stesso strumento di orchestrazione torna all'adesione esplicita per singola richiesta.

La modalità non è un parametro API. È costruita interamente a partire da elementi documentati:

1. **Un livello di effort:** le richieste vengono eseguite a un valore di [Effort](https://platform.claude.com/docs/it/build-with-claude/effort) (impegno) documentato, come `xhigh`. Non esiste alcun livello nascosto al di sopra di quelli indicati in quella pagina. Questo esempio imposta l'effort al livello superiore di ogni richiesta, il che non richiede alcun header beta.
2. **Un promemoria della modalità:** un [messaggio di sistema a metà conversazione](https://platform.claude.com/docs/it/build-with-claude/mid-conversation-system-messages) comunica al modello che la modalità è attiva, con un richiamo di una riga ogni diversi turni e un avviso di uscita quando la modalità viene disattivata. Il campo `system` di livello superiore non cambia mai, quindi il prefisso in cache rimane intatto.
3. **Consenso permanente nella descrizione dello strumento:** la descrizione dello strumento di orchestrazione stabilisce che, mentre la modalità è attiva, il modello deve creare ed eseguire un workflow per ogni compito sostanziale senza chiedere prima.

<Note>
  Questo esempio usa i messaggi di sistema a metà conversazione; per i modelli e le piattaforme che li supportano, consulta [Messaggi di sistema a metà conversazione](https://platform.claude.com/docs/it/build-with-claude/mid-conversation-system-messages). Il fan-out stesso moltiplica l'utilizzo di token, perché una singola richiesta può generare molte conversazioni di subagenti. Riserva quindi la modalità ai lavori che ne giustificano il costo.
</Note>

## Configura il ciclo

L'esempio è costituito da un unico file. Le costanti controllano il livello di effort, la struttura del fan-out e la frequenza con cui viene reinviato il richiamo della modalità. `MAX_CONCURRENT` limita il numero di subagenti in esecuzione contemporaneamente (la versione PHP è sequenziale e lo ignora). `MAX_TOTAL_SUBTASKS` limita invece il numero di subagenti che il modello può mettere in coda in una singola chiamata Workflow. Separare i due limiti consente al modello di pianificare un ampio arretrato di lavoro senza avviarlo tutto in una volta. Quando la variabile d'ambiente `DOC_TEST_MODE` è impostata, il relativo controllo limita i cicli a un singolo turno. In questo modo l'"harness" (infrastruttura di test) automatizzato della documentazione può verificare che il file venga compilato e termini rapidamente, senza eseguire l'orchestrazione completa. Lasciala non impostata quando esegui l'esempio in autonomia.

<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, ...) è importato
  // implicitamente in un file sorgente compatto, quindi servono import solo per l'SDK e 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>

## Definisci i promemoria della modalità

I promemoria sono volutamente brevi. Attivano o disattivano la modalità e rimandano alla descrizione dello strumento, dove si trovano le istruzioni più corpose. Il testo completo viene inviato una sola volta, quando la modalità si attiva. Il richiamo viene reinviato solo dopo diversi turni dell'utente, mentre l'avviso di uscita viene inviato una sola volta, quando la modalità si disattiva.

<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>

## Concedi il consenso permanente nella descrizione dello strumento

Lo strumento Workflow contiene il vero contratto comportamentale: la regola di adesione esplicita, il consenso permanente che si applica mentre la modalità è attiva, le indicazioni sulla granularità per dimensionare il fan-out e i pattern di qualità a cui il modello può ricorrere (un'ondata di verifica, un critico della completezza, una sequenza multifase). I subagenti ricevono anche uno strumento `report_findings` in modo che i loro risultati tornino come JSON strutturato anziché come prosa, e lo strumento bash è lo strumento `bash_20250124` definito da Anthropic eseguito localmente.

<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>

## Esegui lo strumento bash in locale

L'handler bash esegue il comando richiesto con un timeout, cattura stdout e stderr combinati e tronca il risultato in modo che un comando fuori controllo non possa inondare la "context window" (finestra di contesto). I comandi vengono eseguiti nella directory da cui avvii l'esempio, quindi puntarlo a un progetto significa avviarlo lì; quando `DOC_TEST_MODE` è impostata, l'harness fornisce invece a bash una piccola directory fixture usa e getta che viene rimossa all'uscita. Qui non c'è alcuna sandbox: il comando viene eseguito con i permessi del processo che ha avviato l'esempio. Per chiarezza, questo esempio esegue ogni chiamata in una nuova subshell anziché mantenere la sessione persistente descritta dal contratto `bash_20250124`; un agente di produzione dovrebbe supportare lo strumento con una shell di lunga durata in modo che la directory di lavoro, l'ambiente e l'azione `restart` si comportino come documentato.

<CodeGroup exclude="shell:cURL, shell:CLI">
  ```python Python
  # Esegui bash dove è stato avviato l'esempio. In DOC_TEST_MODE l'harness della documentazione
  # lo punta invece a una directory di fixture temporanea, rimossa all'uscita.
  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 };

  // Esegue bash nella directory da cui è stato lanciato l'esempio. In DOC_TEST_MODE l'harness della documentazione
  // lo punta invece a una directory fixture temporanea, rimossa al termine del programma.
  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))
  `
    );
  }

  // Esegue un comando shell e ne restituisce l'output. Nessuna sandbox: solo codice di esempio.
  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#
  // Esegui bash dove è stato lanciato l'esempio. In DOC_TEST_MODE l'harness della documentazione
  // lo punta invece a una directory di fixture usa e getta, rimossa all'uscita.
  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. */ }
      };
  }

  // Esegui un comando shell e restituisci l'output più un flag di errore. Nessuna sandbox: solo codice di esempio.
  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);
          // Lascia terminare i task di lettura prima che il processo venga eliminato.
          try
          {
              await Task.WhenAll(stdoutTask, stderrTask);
          }
          catch
          {
              // L'output viene scartato in caso di timeout, quindi anche gli errori dei lettori vengono ignorati.
          }
          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);
  }

  // Esegui una singola chiamata allo strumento bash richiesta dal modello.
  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
  // Esegui bash dove è stato lanciato l'esempio. In DOC_TEST_MODE l'harness della documentazione
  // lo punta invece a una directory di fixture usa e getta, rimossa all'uscita.
  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 esegue un comando shell e restituisce il suo output più un flag di errore.
  // Nessuna sandbox: solo codice di esempio.
  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 esegue una chiamata allo strumento bash richiesta dal modello.
  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) {}

  // Esegui bash dove è stato lanciato l'esempio. In DOC_TEST_MODE l'harness della documentazione
  // lo punta invece a una directory fixture usa-e-getta, rimossa all'uscita.
  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(() -> {
              // Pulizia best-effort; lo sweeper tmp del sistema operativo gestisce i residui.
              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);
      }
  }

  // Esegui un comando shell e restituisci l'output più un flag di errore. Nessuna sandbox: solo codice di esempio.
  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);
      }
      // Svuota stdout su un altro thread così una pipe piena non può bloccare l'attesa del timeout sotto.
      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);
  }

  // I campi di input di una chiamata a strumento, o una mappa vuota se il modello non ha inviato un oggetto.
  Map<String, JsonValue> toolInput(ToolUseBlock toolUse) {
      return toolUse._input() instanceof JsonObject object ? object.values() : Map.of();
  }

  // Esegui una chiamata allo strumento bash richiesta dal modello.
  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
  // Esegui bash dove è stato lanciato l'esempio. In DOC_TEST_MODE l'harness della documentazione
  // lo punta invece a una directory fixture temporanea, rimossa all'uscita.
  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");
      // Richiede 'timeout' di GNU coreutils. Su macOS: brew install coreutils, oppure sostituisci con 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
  # Esegue bash nella directory da cui è stato lanciato l'esempio. In DOC_TEST_MODE l'harness della documentazione
  # lo punta invece a una directory fixture temporanea, rimossa all'uscita.
  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

  # L'input dello strumento arriva come Hash o come stringa JSON grezza dall'accumulatore di
  # streaming; normalizza entrambe le forme in un Hash con chiavi stringa.
  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

  # Esegue un comando shell e restituisce [output, is_error]. Nessuna sandbox: solo codice di esempio.
  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 }
      # Applica il limite di tempo con una scadenza a orologio monotono, così un comando scaduto viene
      # terminato invece di restare in esecuzione in background.
      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

  # Esegue una chiamata allo strumento bash richiesta dal modello.
  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

  # Converte il contenuto della risposta in parametri nel formato della richiesta. L'accumulatore di streaming
  # restituisce l'input tool_use come stringa JSON grezza e include campi presenti solo nella risposta,
  # quindi riadatta ogni blocco allo schema della richiesta prima di rinviarlo.
  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>

## Esegui un subagente

Ogni sottocompito del workflow diventa un piccolo ciclo agente a sé stante, dotato dello strumento bash ed eseguito con lo stesso effort del ciclo principale. Un timeout per richiesta limita la durata di ogni chiamata API: in questo modo una connessione interrotta compromette un solo subagente, invece di bloccare l'intera esecuzione.

<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
  // Un subagente: un piccolo loop di agente annidato con lo strumento bash più report_findings.
  // I subagenti ereditano il livello di effort del loop principale.
  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#
  // Un sottoagente: un piccolo ciclo agente annidato con lo strumento bash più report_findings.
  // I sottoagenti ereditano il livello di sforzo del ciclo principale.
  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 invia una richiesta e accumula gli eventi in streaming nel
  // messaggio finale.
  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 unisce i blocchi di testo di una risposta.
  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 esegue un subagente: un piccolo loop di agente annidato con lo strumento bash più
  // report_findings. I subagenti ereditano il livello di effort del loop principale.
  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.""";

  // Un subagente: un piccolo loop agente annidato con lo strumento bash più report_findings.
  // I subagenti ereditano il livello di sforzo del loop principale.
  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
  # Un subagente: un piccolo ciclo agentico annidato con lo strumento bash più report_findings.
  # I subagenti ereditano il livello di effort del ciclo principale.
  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>

## Registra i risultati in un journal per riprendere le riesecuzioni

Un fan-out che genera decine di subagenti è costoso da riavviare da zero. Un piccolo journal indirizzato per contenuto lo rende idempotente: prima di inviare un subagente, cerca lo SHA-256 del suo prompt in un file JSON locale e restituisci il risultato registrato se esiste. Interrompi l'esecuzione, rieseguila, e solo i sottocompiti che non sono mai terminati vengono ricalcolati. Il journal deduplica tra esecuzioni diverse, non all'interno di una singola ondata di fan-out; elimina il file del journal per ricominciare da capo.

<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
      # Una scrittura fallita sul journal non deve far perdere il risultato calcolato
      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 {};
    }
  }

  // Restituisce un risultato in cache per questo esatto prompt, oppure lo calcola e lo salva. Questo
  // rende il fan-out ripristinabile: interrompi l'esecuzione, rilanciala, e verranno ricalcolati solo i sottotask
  // mai completati. Elimina il file di journal per ripartire da zero.
  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();
    // Concatena le scritture così che i subagenti concorrenti non sovrascrivano le voci degli altri.
    // La catena viene mantenuta risolta (settled) così che una scrittura fallita non comprometta le successive.
    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 [];
      }
  }

  // Restituisci un risultato in cache per questo prompt esatto, oppure calcolalo e salvalo. Questo
  // rende il fan-out riprendibile: interrompi l'esecuzione, rilanciala, e solo i sottotask
  // che non sono mai terminati vengono ricalcolati. Elimina il file journal per ripartire da zero.
  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)
      {
          // Il journal è best-effort; non scartare mai un risultato calcolato.
          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 restituisce un risultato in cache per questo esatto prompt, oppure lo calcola e lo
  // persiste. Ciò rende il fan-out riprendibile: interrompi l'esecuzione, rilanciala, e solo i
  // subtask mai completati vengono ricalcolati. Elimina il file journal per ricominciare da zero.
  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<>();
      }
  }

  // Restituisci un risultato in cache per questo prompt esatto, oppure calcolalo e salvalo. Questo
  // rende il fan-out riprendibile: interrompi l'esecuzione, rilanciala, e solo i sottotask
  // mai completati vengono ricalcolati. Elimina il file journal per ricominciare da zero.
  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) {
          // Il journal è best-effort; non scartare mai un risultato calcolato.
          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

  # Restituisce un risultato in cache per questo esatto prompt, oppure lo calcola e lo salva. Questo
  # rende il fan-out ripristinabile: interrompi l'esecuzione, rilanciala, e solo i sottocompiti
  # mai completati vengono ricalcolati. Elimina il file di journal per ripartire da zero.
  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>

## Distribuisci con fan-out, poi verifica

Il fan-out accetta fino a `MAX_TOTAL_SUBTASKS` prompt, li esegue attraverso il journal con al massimo `MAX_CONCURRENT` in corso (sequenziale nel port PHP) e isola gli errori in modo che un subagente difettoso degradi a una stringa di errore invece di terminare l'esecuzione. Una volta terminata la prima ondata, una seconda ondata riutilizza lo stesso percorso dei subagenti per tentare di confutare ogni risultato: ogni verificatore rideriva le affermazioni dalla fonte, considerandole confutate per impostazione predefinita in caso di incertezza. Sia il risultato originale sia il suo verdetto vengono restituiti all'orchestratore in modo che possa valutarli insieme.

<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))
          # Un singolo subagente fallito non deve terminare l'esecuzione
          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
  // Accetta l'input dei sottotask in qualunque forma il modello lo emetta: un array, l'array
  // codificato in JSON come singola stringa, o un elenco separato da newline.
  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}`;
  }

  // Map con un limite di concorrenza: al massimo `limit` task in esecuzione contemporaneamente.
  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;
  }

  // Esegue i sottotask come subagenti paralleli, poi esegue una seconda ondata di verifica sui
  // risultati, e restituisce entrambi. MAX_TOTAL_SUBTASKS limita quanti ne può
  // accodare il modello; MAX_CONCURRENT limita quanti vengono eseguiti contemporaneamente.
  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) {
        // Confine di isolamento: un subagente difettoso non deve terminare l'esecuzione.
        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#
  // Accetta l'input dei sottotask in qualsiasi forma il modello lo emetta: un array, l'array
  // codificato in JSON come singola stringa, o un elenco separato da newline.
  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}
      """;

  // Esegui i sottotask come sottoagenti paralleli, poi esegui una seconda ondata di verifica sui
  // risultati e restituisci entrambi. maxTotalSubtasks limita quanti il modello può
  // accodare; maxConcurrent limita quanti vengono eseguiti contemporaneamente.
  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)
          {
              // Confine di isolamento: un sottoagente difettoso non deve terminare l'esecuzione.
              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 accetta l'input dei subtask in qualsiasi forma il modello lo emetta: un
  // array, l'array codificato in JSON come singola stringa, o un elenco separato da newline.
  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 esegue task sugli elementi con al massimo limit goroutine in esecuzione.
  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 esegue i subtask come subagenti paralleli, poi esegue una seconda ondata di verifica
  // sui risultati, e restituisce entrambi. maxTotalSubtasks limita quanti il modello può
  // accodare; maxConcurrent limita quanti vengono eseguiti contemporaneamente.
  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 {
  			// Confine di isolamento: un subagente difettoso non deve terminare l'esecuzione.
  			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
  // Accetta l'input dei sottotask in qualsiasi forma il modello lo emetta: un array, l'array
  // codificato in JSON come singola stringa, o una lista separata da newline.
  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 attende ogni job, quindi ogni future è completato quando viene letto qui.
  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();
                  // Confine di isolamento: un subagente difettoso non deve terminare l'esecuzione.
                  case FAILED -> "(subagent failed: " + future.exceptionNow() + ")";
                  case CANCELLED, RUNNING -> "(subagent failed: cancelled)";
              })
              .toList();
  }

  // Esegui i sottotask come subagenti paralleli, poi esegui una seconda ondata di verifica sui
  // risultati, e restituisci entrambi. MAX_TOTAL_SUBTASKS limita quanti il modello può
  // accodare; MAX_CONCURRENT limita quanti vengono eseguiti contemporaneamente.
  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) {
              // Confine di isolamento: un subagente difettoso non deve terminare l'esecuzione.
              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
  # Accetta l'input dei sottocompiti in qualunque forma il modello lo emetta: un array, l'array
  # codificato in JSON come singola stringa, o un elenco separato da newline.
  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 con un limite di concorrenza: al massimo `limit` thread in esecuzione contemporaneamente.
  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

  # Esegue i sottocompiti come subagenti paralleli, poi esegue una seconda ondata di verifica sui
  # risultati, e restituisce entrambi. MAX_TOTAL_SUBTASKS limita quanti il modello può
  # accodare; MAX_CONCURRENT limita quanti vengono eseguiti contemporaneamente.
  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>

## Attiva e disattiva la modalità con i messaggi di sistema a metà conversazione

L'agente aggiunge prima il messaggio dell'utente, poi gli eventuali messaggi di sistema previsti: l'avviso di uscita, il testo completo della modalità all'attivazione o il richiamo periodico. Posizionare il messaggio di sistema dopo il turno dell'utente lascia intatto ogni byte memorizzato nella cache che lo precede e rispetta la regola di posizionamento secondo cui un messaggio di sistema segue un turno dell'utente.

<CodeGroup>
  ```bash cURL
  # Un turno in modalità orchestrazione: il promemoria della modalità viaggia nell'array messages come
  # voce {"role": "system"} posta dopo il turno utente a cui si applica. La risposta
  # si ferma alla prima chiamata a uno strumento. Il loop dell'agente che esegue le chiamate agli strumenti e distribuisce
  # i subagenti è mostrato nelle schede SDK; la descrizione del Workflow è condensata
  # qui, gli esempi SDK contengono il testo completo del consenso permanente.
  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
  # Un turno in modalità di orchestrazione: il promemoria della modalità viaggia nell'array messages come
  # voce con ruolo system posta dopo il turno utente a cui si applica. La risposta si ferma
  # alla prima chiamata a uno strumento. Il loop dell'agente che esegue le chiamate agli strumenti e distribuisce
  # i subagenti è mostrato nelle schede SDK; la descrizione del Workflow è qui condensata,
  # gli esempi dell'SDK contengono il testo completo del consenso permanente.
  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:
          # I messaggi di sistema a metà conversazione seguono il turno utente a cui si applicano, così
          # il prefisso in cache che li precede resta intatto.
          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":
                      # Scarta il messaggio troncato dell'assistente così i turni successivi non si basano su di esso.
                      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
  // Un loop di agente la cui modalità di orchestrazione viene attivata/disattivata con messaggi di sistema a metà conversazione.
  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;
    }

    // Attiva o disattiva la modalità. L'avviso viene consegnato con il turno utente successivo.
    setMode(modeOn: boolean): void {
      if (modeOn === this.#modeOn) {
        return;
      }
      // Un avviso di uscita è dovuto solo se al modello è stato detto che la modalità era attiva.
      this.#exitPending = !modeOn && this.#modeAnnounced;
      this.#modeOn = modeOn;
    }

    // Messaggi di sistema dovuti in questo turno: un avviso di uscita, il testo completo della modalità all'ingresso,
    // o un promemoria di una riga ogni TURNS_BETWEEN_REFRESHERS turni utente.
    #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> {
      // I messaggi di sistema a metà conversazione seguono il turno utente a cui si applicano, il che mantiene
      // intatto il prefisso in cache che li precede.
      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") {
            // Scarta il messaggio assistant troncato così che i turni successivi non si basino su di esso.
            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#
  // Un ciclo agente la cui modalità di orchestrazione è commutata con messaggi di sistema a metà conversazione.
  List<MessageParam> messages = [];
  var modeOn = true;
  var modeAnnounced = false;
  var exitPending = false;
  var turnsSinceReminder = 0;

  // Attiva o disattiva la modalità. L'avviso viene consegnato con il prossimo turno utente.
  void SetMode(bool nextModeOn)
  {
      if (nextModeOn == modeOn)
      {
          return;
      }
      // Un avviso di uscita è dovuto solo se al modello è stato detto che la modalità era attiva.
      exitPending = !nextModeOn && modeAnnounced;
      modeOn = nextModeOn;
  }

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

  // Messaggi di sistema dovuti in questo turno: un avviso di uscita, il testo completo della modalità all'ingresso,
  // o un promemoria di una riga ogni turnsBetweenRefreshers turni utente.
  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;
  }

  // Invia un turno utente attraverso il ciclo, eseguendo le chiamate agli strumenti finché il modello non si ferma.
  async Task<string> Turn(string userInput)
  {
      // I messaggi di sistema a metà conversazione seguono il turno utente a cui si applicano, il che mantiene
      // intatto il prefisso in cache che li precede.
      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)
              {
                  // Scarta il messaggio troncato dell'assistente così i turni successivi non si basano su di esso.
                  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 è un loop di agente la cui modalità di orchestrazione è commutata con messaggi
  // di sistema a metà conversazione.
  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 attiva o disattiva la modalità. L'avviso viene consegnato con il turno utente successivo.
  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 restituisce i messaggi di sistema dovuti in questo turno: un avviso di uscita, il
  // testo completo della modalità all'ingresso, o un promemoria di una riga ogni turnsBetweenRefreshers turni utente.
  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 invia un turno utente attraverso il loop, eseguendo chiamate agli strumenti finché il modello non si ferma.
  func (agent *modeAgent) turn(ctx context.Context, userInput string) (string, error) {
  	// I messaggi di sistema a metà conversazione seguono il turno utente a cui si applicano, mantenendo
  	// intatto il prefisso in cache che li precede.
  	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:
  			// Scarta il messaggio assistant troncato così i turni successivi non si basano su di esso.
  			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
  // Un loop agente la cui modalità di orchestrazione è commutata con messaggi di sistema a metà conversazione.
  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;
      }

      // Attiva o disattiva la modalità. L'avviso viene consegnato con il prossimo turno utente:
      // un avviso di uscita è dovuto solo se al modello era stato detto che la modalità era attiva.
      void setMode(boolean modeOn) {
          if (modeOn == this.modeOn) {
              return;
          }
          exitPending = !modeOn && modeAnnounced;
          this.modeOn = modeOn;
      }

      // Messaggi di sistema dovuti in questo turno: un avviso di uscita, il testo completo della modalità all'ingresso,
      // o un promemoria di una riga ogni TURNS_BETWEEN_REFRESHERS turni utente.
      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();
      }

      // Invia un turno utente attraverso il loop, eseguendo chiamate agli strumenti finché il modello non si ferma.
      String turn(String userInput) throws InterruptedException {
          // I messaggi di sistema a metà conversazione seguono il turno utente a cui si applicano, il che mantiene
          // intatto il prefisso in cache che li precede.
          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)) {
                      // Scarta il messaggio assistant troncato così i turni successivi non si basano su di esso.
                      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
      {
          // I messaggi di sistema a metà conversazione seguono il turno utente a cui si applicano, il che mantiene
          // intatto il prefisso in cache che li precede.
          $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') {
                      // Scarta il messaggio dell'assistente troncato così i turni successivi non si basano su di esso.
                      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
  # Un ciclo agentico la cui modalità di orchestrazione si attiva/disattiva con messaggi di sistema a metà conversazione.
  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

    # Attiva o disattiva la modalità. L'avviso viene consegnato con il turno utente successivo.
    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)
      # I messaggi di sistema a metà conversazione seguono il turno utente a cui si applicano, il che mantiene
      # intatto il prefisso in cache che li precede.
      @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
            # Scarta il messaggio dell'assistente troncato così i turni successivi non si basano su di esso.
            @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

    # Messaggi di sistema dovuti in questo turno: un avviso di uscita, il testo completo della modalità all'ingresso,
    # o un promemoria di una riga ogni TURNS_BETWEEN_REFRESHERS turni utente.
    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>

## Eseguilo

<Warning>
  In questo esempio lo strumento bash esegue i comandi scritti dal modello direttamente sulla tua macchina, senza alcuna sandbox, e il fan-out esegue diversi di questi agenti in parallelo. Eseguilo in una directory e in un ambiente che sei disposto a esporre. Aggiungi una sandbox prima di adattarlo a qualsiasi uso che vada oltre la sperimentazione locale.
</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>

Avvia l'esempio dalla directory in cui vuoi che lavorino gli agenti, ad esempio la radice di un repository da revisionare:

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

Con la modalità attiva, aspettati che il modello esplori con alcuni comandi bash, invochi lo strumento Workflow senza che gli venga richiesto e sintetizzi i report dei subagenti in una risposta finale. Le richieste banali o conversazionali restano gestite in autonomia, come indicato dal promemoria.

## Verso un harness di produzione

Questo esempio è deliberatamente piccolo. Un harness destinato a carichi di lavoro reali aggiungerebbe tipicamente:

* **Script di orchestrazione in sandbox:** consenti al modello di emettere un breve programma di orchestrazione (ramificazioni, loop e passaggi di riduzione) ed eseguilo all'interno di un interprete isolato, anziché accettare solo un elenco piatto di stringhe di sottocompiti.
* **Journaling durevole:** sostituisci il file JSON locale con un archivio che sopravviva ai riavvii del processo e sia sicuro in presenza di scrittori concorrenti su più macchine.
* **Applicazione del budget:** tieni traccia del totale dei subagenti avviati nell'intera sessione, non solo per chiamata Workflow, e rifiuta di superare un limite rigido in modo che un piano fuori controllo non possa esaurire la tua quota.

I pattern di questo esempio (i promemoria della modalità, il consenso permanente nella descrizione dello strumento, il journaling e un'ondata di verifica) si trasferiscono invariati; solo il substrato di esecuzione che li circonda diventa più robusto.

## Correlati

<CardGroup cols={2}>
  <Card title="Messaggi di sistema a metà conversazione" icon="message" href="https://platform.claude.com/docs/it/build-with-claude/mid-conversation-system-messages">
    Il meccanismo usato dai promemoria della modalità e come interagisce con la cache dei prompt.
  </Card>

  <Card title="Effort" icon="gauge" href="https://platform.claude.com/docs/it/build-with-claude/effort">
    I livelli di effort accettati dall'API e come sceglierne uno.
  </Card>

  <Card title="Uso degli strumenti con Claude" icon="wrench" href="https://platform.claude.com/docs/it/agents-and-tools/tool-use/overview">
    Definizione degli strumenti, gestione delle chiamate agli strumenti e risultati degli strumenti.
  </Card>

  <Card title="Strumento bash" icon="terminal" href="https://platform.claude.com/docs/it/agents-and-tools/tool-use/bash-tool">
    Lo strumento bash definito da Anthropic che questo esempio esegue localmente.
  </Card>
</CardGroup>
