Claude Fable 5.1 프롬프팅

Claude Fable 5.1 프롬프팅 (Prompting Claude Fable 5.1)

이 문서는 Claude Fable 5.1과 Claude Mythos 5.1의 행동 차이와 프롬프팅 패턴을 정리한 가이드예요. effort, 진행 업데이트, 도구 호출 배치, 대화 기록, 작성 스타일, 서식, 과제 완료, 컴팩션 요약, 범위와 테스트 커버리지, 검색 트리거링, 안전장치 오탐, 파일 편집, 긴 출력, 하위 에이전트, 비전을 다룹니다. 기존 Claude Fable 5 프롬프트는 수정 없이도 잘 동작하지만, 알아두면 좋은 몇 가지 행동 차이가 있으니 천천히 살펴볼게요.

출처: 문서

본문

모델의 기능, API 변경, 가격, 가용성은 Claude Fable 5.1의 새로운 점을, Claude 모델 전체에 적용되는 기법은 프롬프트 모범 사례를 참고하세요.

기존의 Claude Fable 5 프롬프트는 변경 없이 Claude Fable 5.1에서 잘 동작해야 하지만, 알아두면 좋은 몇 가지 행동 차이가 있어요. 관찰한 것과 맞는 섹션부터 시작하세요:

Claude Fable 5.1은 안전 분류기를 실행하고 `stop_reason: "refusal"`을 반환할 수 있어요. [거부, 폴백, 과금](https://platform.claude.com/docs/en/models/fable-5-1/whats-new-fable-5-1#refusals-fallback-and-billing)과 [안전장치 오탐 줄이기](https://platform.claude.com/docs/en/build-with-claude/prompt-engineering/prompting-claude-fable-5-1#reduce-safeguard-false-positives)를 참고하세요.

모든 effort 수준 고려하기 (Consider all effort levels)

기본 effort 수준인 high에서 시작하고, 나머지 수준(low, medium, xhigh, max)를 자기 평가와 대조해 테스트하세요. effort는 Claude Fable 5.1에서 지능·지연·비용을 맞바꾸는 주요 제어 수단이에요. Claude Fable 5에서 이미 스윕을 돌렸어도 다시 돌리세요. effort 수준 이름이 모델 간 같은 양의 thinking을 의미하지는 않거든요.

Claude Fable 5.1의 Claude Fable 5 대비 능력 향상은 effort 수준 전반에 걸쳐 나타나며 더 높은 설정에서 가장 큽니다. medium에서는 결과가 더 낮은 비용으로 대략 Claude Fable 5와 비슷해요. 그러니 자기 평가에서 품질이 유지되는 곳에서는 medium이나 low로 내려가세요. low에서 Claude Fable 5.1은 종종 과제당 비용에서 Claude Opus·Claude Sonnet 모델과 경쟁하면서 점수는 더 높아요. 그러니 더 작은 모델을 높은 effort 수준으로 돌렸을 자리에 비교 대상으로 포함하세요.

두 가지 effort 특유 행동에는 별도 섹션이 있어요. low에서는 Claude Fable 5.1이 검색·검색 도구를 덜 부르고(낮은 effort에서의 검색 트리거링 참조), xhighmax에서는 긴 산출물을 쓰기 전에 더 오래 생각할 수 있어요(xhigh와 max effort에서 긴 출력에 여유 남기기 참조).

사용자 대상 진행 업데이트 요청하기 (Ask for user-facing progress updates)

Claude Fable 5.1의 기본 행동은 Claude Fable 5보다 긴 도구 호출 턴 동안 사용자 대상 업데이트를 더 적게 쓰는 것이에요. 이는 더 높은 effort와 더 긴 도구 체인에서 더 두드러져요. 사용자들은 에이전트가 수 분씩 조용히 있거나, 전체 과제가 아니라 마지막 단계만 다루는 최종 메시지를 보게 돼요.

먼저, 클라이언트가 진행 업데이트를 아예 수신하는지 확인하세요. 모델이 도구 호출 사이에 남기는 짧은 메모(방금 찾은 것, 다음에 할 것)는 progress-update thinking 블록으로 돌아오는데, 이 블록들은 기본 thinking.display"omitted"일 때 비어 있어요. display: "updates"(베타, thinking-display-updates-2026-08-18 헤더)를 설정하고 각 비어 있지 않은 thinking 블록을 상태 줄로 렌더링하거나, "summarized"를 설정해 요약된 추론과 함께 받으세요. 요청하지 않으면 모델의 업데이트는 사용자에게 도달하지 않을 수 있어요.

둘째, 내레이션을 억제하는 프롬프트 지시를 감사하세요. 일부 이전 모델은 작업 중에 업데이트를 주는 데 열심이라 "모든 발견을 최종 응답까지 보류하세요" 같은 시스템 프롬프트 줄이 생겼어요. 무언가 추가하기 전에 그런 줄을 제거하세요.

여전히 더 많은 업데이트를 원한다면, 예를 들어 페어 프로그래밍이나 다른 human-in-the-loop 작업에서, 모델이 언제 사용자 대상 텍스트를 원하는지와 각 업데이트에 무엇이 들어야 하는지 말하는 짧은 시스템 프롬프트 줄을 추가하세요:

Before you start, say in a line what you're about to do; brief updates while you work help the user follow along. Close with a short recap that stands on its own — what you found, what you did, and what's next — so a reader who only sees the last message has the full picture.

제품이 도구 출력을 접거나 숨긴다면 모델에 알려주세요. 그렇지 않으면 UI가 절대 표시하지 않는 출력을 사용자에게 "보여주려고" 명령을 실행할 수 있어요. 이 메모를 턴 범위 시스템 메시지(clear_at: "next_user_message", 베타)로 전달하세요:

Only you see that command's output — the user's terminal shows at most a few lines of it. If the user needs to read any of it, put it in your reply.

에이전트 루프에서 독립적인 도구 호출 배치하기 (Batch independent tool calls in agent loops)

Claude Fable 5.1은 보통 예상대로 병렬 도구 호출을 내요. 요청이 여러 가지를 가져오라고 하면 그 호출들을 병렬로 내죠. 예외는 코딩과 computer-use 루프인데, 거기서 다음 독립 호출은 명시적으로 요청되기보다 과제에 암시되지만(커스텀 코딩 에이전트, bash-및-에디터 하네스, computer use) 턴당 하나씩 낼 수 있어요. 이는 답 품질에 영향을 주지 않지만, 추가 턴마다 토큰, 왕복, 경과 시간 비용이 들어요. 현재 요청 끝에 한 문장 넛지로 해결할 수 있어요:

First privately list what you need next; then request every item that doesn't depend on another's result in this one response.

도구 결과를 다시 보낼 때마다 이 문장을 그 사용자 메시지 뒤에 턴 범위 시스템 메시지로 추가하세요. clear_at: "next_user_message"가 있는 role: "system" 항목이 messages에 들어가요. 나중 사용자 메시지가 생기면 API가 이전 복사본을 지우므로 모델은 가장 최신 것만 읽어요. 턴 범위 시스템 메시지는 베타이며 베타 헤더 mid-conversation-system-clear-at-2026-08-21이 필요해요. 베타가 없으면 같은 사용자 메시지의 tool_result 블록 뒤에 텍스트 블록으로 문장을 두세요.

매 턴 새 복사본을 추가하고 이전 복사본은 바이트 그대로 제자리에 두세요. 배열에는 남지만 일단 지워지면 모델이 보지 않고 입력 토큰 비용도 없어요. 그것들을 삭제하거나 다시 쓰는 것은 이전 턴에 대한 편집이에요. 그 지점부터 프롬프트 캐시를 다시 시작하고 그 뒤에 온 thinking 블록을 무효로 만들어요(대화 기록을 append-only로 유지하기 참조).

다음 루프는 이 배치를 보여줘요. 각 어시스턴트 턴은 반환된 그대로 되돌아가고, 각 사용자 턴은 도구 결과만 지니며, 새 턴 범위 넛지 복사본이 그 뒤를 따라와요.

```python Python import anthropic from anthropic.types.beta import ( BetaMessageParam, BetaToolParam, BetaToolResultBlockParam, )

client = anthropic.Anthropic()

BATCH_NUDGE = ( "First privately list what you need next; then request every item " "that doesn't depend on another's result in this one response." )

In-memory files stand in for a working directory so the sample runs anywhere.

FILES = { "pyproject.toml": """
[project] name = "demo" version = "0.1.0" description = "Demo project for the batching example" """, "README.md": """\

demo

A small demo project. Run demo --help for usage. """, } tools: list[BetaToolParam] = [ { "name": "read_file", "description": "Read a UTF-8 text file from the working directory.", "input_schema": { "type": "object", "properties": {"path": {"type": "string"}}, "required": ["path"], }, } ] messages: list[BetaMessageParam] = [ {"role": "user", "content": "Summarize pyproject.toml and README.md."} ]

while True: response = client.beta.messages.create( model="claude-fable-5-1", max_tokens=16000, betas=["mid-conversation-system-clear-at-2026-08-21"], tools=tools, messages=messages, ) # Append the assistant turn exactly as returned, thinking blocks included. messages.append({"role": "assistant", "content": response.content}) if response.stop_reason != "tool_use": break tool_results: list[BetaToolResultBlockParam] = [] for block in response.content: if block.type == "tool_use": raw_path = block.input.get("path") path = raw_path if isinstance(raw_path, str) else "" if path in FILES: tool_results.append( { "type": "tool_result", "tool_use_id": block.id, "content": FILES[path], } ) else: tool_results.append( { "type": "tool_result", "tool_use_id": block.id, "content": f"File not found: {path}", "is_error": True, } ) # Send the tool results as the user turn, then a fresh copy of the nudge as a # turn-scoped system message. Leave earlier copies in place: the API clears them, # so the model sees only the newest one. messages.append({"role": "user", "content": tool_results}) messages.append( {"role": "system", "content": BATCH_NUDGE, "clear_at": "next_user_message"} )

print(next((block.text for block in response.content if block.type == "text"), ""))


```typescript TypeScript
import Anthropic from "@anthropic-ai/sdk";

const client = new Anthropic();

const BATCH_NUDGE =
  "First privately list what you need next; then request every item " +
  "that doesn't depend on another's result in this one response.";
// In-memory files stand in for a working directory so the sample runs anywhere.
const FILES = new Map<string, string>([
  [
    "pyproject.toml",
    `[project]
name = "demo"
version = "0.1.0"
description = "Demo project for the batching example"
`,
  ],
  [
    "README.md",
    `# demo

A small demo project. Run \`demo --help\` for usage.
`,
  ],
]);
const tools: Anthropic.Beta.Messages.BetaTool[] = [
  {
    name: "read_file",
    description: "Read a UTF-8 text file from the working directory.",
    input_schema: {
      type: "object",
      properties: { path: { type: "string" } },
      required: ["path"],
    },
  },
];
const messages: Anthropic.Beta.Messages.BetaMessageParam[] = [
  { role: "user", content: "Summarize pyproject.toml and README.md." },
];

let response: Anthropic.Beta.Messages.BetaMessage;
while (true) {
  response = await client.beta.messages.create({
    model: "claude-fable-5-1",
    max_tokens: 16000,
    betas: ["mid-conversation-system-clear-at-2026-08-21"],
    tools,
    messages,
  });
  // Append the assistant turn exactly as returned, thinking blocks included.
  messages.push({ role: "assistant", content: response.content });
  if (response.stop_reason !== "tool_use") {
    break;
  }
  const toolResults: Anthropic.Beta.Messages.BetaToolResultBlockParam[] = [];
  for (const block of response.content) {
    if (block.type !== "tool_use") {
      continue;
    }
    const { input } = block;
    const path =
      typeof input === "object" &&
      input !== null &&
      "path" in input &&
      typeof input.path === "string"
        ? input.path
        : "";
    const text = FILES.get(path);
    if (text === undefined) {
      toolResults.push({
        type: "tool_result",
        tool_use_id: block.id,
        content: `File not found: ${path}`,
        is_error: true,
      });
      continue;
    }
    toolResults.push({
      type: "tool_result",
      tool_use_id: block.id,
      content: text,
    });
  }
  // Send the tool results as the user turn, then a fresh copy of the nudge as a
  // turn-scoped system message. Leave earlier copies in place: the API clears them,
  // so the model sees only the newest one.
  messages.push({ role: "user", content: toolResults });
  messages.push({
    role: "system",
    content: BATCH_NUDGE,
    clear_at: "next_user_message",
  });
}

const finalText = response.content.find((block) => block.type === "text");
console.log(finalText?.text ?? "");
using System.Text.Json;
using Anthropic;
using Anthropic.Models.Beta.Messages;

AnthropicClient client = new();

const string BatchNudge =
    "First privately list what you need next; then request every item "
    + "that doesn't depend on another's result in this one response.";

// In-memory files stand in for a working directory so the sample runs anywhere.
Dictionary<string, string> files = new()
{
    ["pyproject.toml"] = """
        [project]
        name = "demo"
        version = "0.1.0"
        description = "Demo project for the batching example"
        """,
    ["README.md"] = """
        # demo

        A small demo project. Run `demo --help` for usage.
        """,
};

List<BetaToolUnion> tools =
[
    new BetaTool
    {
        Name = "read_file",
        Description = "Read a UTF-8 text file from the working directory.",
        InputSchema = new InputSchema
        {
            Properties = new Dictionary<string, JsonElement>
            {
                ["path"] = JsonSerializer.SerializeToElement(new { type = "string" }),
            },
            Required = ["path"],
        },
    },
];

List<BetaMessageParam> messages =
[
    new() { Role = Role.User, Content = "Summarize pyproject.toml and README.md." },
];

BetaMessage response;
while (true)
{
    response = await client.Beta.Messages.Create(new MessageCreateParams
    {
        Model = "claude-fable-5-1",
        MaxTokens = 16000,
        Betas = ["mid-conversation-system-clear-at-2026-08-21"],
        Tools = tools,
        Messages = messages,
    });
    // Append the assistant turn exactly as returned, thinking blocks included.
    messages.Add(new()
    {
        Role = Role.Assistant,
        Content = response.Content.Select(block => new BetaContentBlockParam(block.Json)).ToList(),
    });
    if (response.StopReason != BetaStopReason.ToolUse)
    {
        break;
    }
    List<BetaContentBlockParam> toolResults = [];
    foreach (var block in response.Content)
    {
        if (block.TryPickToolUse(out var toolUse))
        {
            var path = toolUse.Input.TryGetValue("path", out var pathValue)
                && pathValue.ValueKind == JsonValueKind.String
                ? pathValue.GetString()!
                : "";
            if (files.TryGetValue(path, out var fileText))
            {
                toolResults.Add(new BetaToolResultBlockParam { ToolUseID = toolUse.ID, Content = fileText });
            }
            else
            {
                toolResults.Add(new BetaToolResultBlockParam
                {
                    ToolUseID = toolUse.ID,
                    Content = $"File not found: {path}",
                    IsError = true,
                });
            }
        }
    }
    // Send the tool results as the user turn, then a fresh copy of the nudge as a
    // turn-scoped system message. Leave earlier copies in place: the API clears them,
    // so the model sees only the newest one.
    messages.Add(new() { Role = Role.User, Content = toolResults });
    messages.Add(new()
    {
        Role = Role.System,
        Content = BatchNudge,
        ClearAt = ClearAt.NextUserMessage,
    });
}

foreach (var block in response.Content)
{
    if (block.TryPickText(out var text))
    {
        Console.WriteLine(text.Text);
        break;
    }
}
package main

import (
	"context"
	"encoding/json"
	"fmt"
	"log"

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

const batchNudge = "First privately list what you need next; then request every item " +
	"that doesn't depend on another's result in this one response."

// In-memory files stand in for a working directory so the sample runs anywhere.
var files = map[string]string{
	"pyproject.toml": `[project]
name = "demo"
version = "0.1.0"
description = "Demo project for the batching example"
`,
	"README.md": `# demo

A small demo project. Run "demo --help" for usage.
`,
}

func main() {
	client := anthropic.NewClient()
	ctx := context.Background()

	tools := []anthropic.BetaToolUnionParam{
		{OfTool: &anthropic.BetaToolParam{
			Name:        "read_file",
			Description: anthropic.String("Read a UTF-8 text file from the working directory."),
			InputSchema: anthropic.BetaToolInputSchemaParam{
				Properties: map[string]any{
					"path": map[string]any{"type": "string"},
				},
				Required: []string{"path"},
			},
		}},
	}
	messages := []anthropic.BetaMessageParam{
		anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Summarize pyproject.toml and README.md.")),
	}

	var response *anthropic.BetaMessage
	for {
		var err error
		response, err = client.Beta.Messages.New(ctx, anthropic.BetaMessageNewParams{
			Model:     "claude-fable-5-1",
			MaxTokens: 16000,
			Betas:     []anthropic.AnthropicBeta{"mid-conversation-system-clear-at-2026-08-21"},
			Tools:     tools,
			Messages:  messages,
		})
		if err != nil {
			log.Fatal(err)
		}
		// Append the assistant turn exactly as returned, thinking blocks included.
		messages = append(messages, response.ToParam())
		if response.StopReason != anthropic.BetaStopReasonToolUse {
			break
		}
		var toolResults []anthropic.BetaContentBlockParamUnion
		for _, block := range response.Content {
			toolUse, ok := block.AsAny().(anthropic.BetaToolUseBlock)
			if !ok {
				continue
			}
			var input struct {
				Path string `json:"path"`
			}
			// A missing or non-string path leaves input.Path empty, which takes the error-result branch.
			if err := json.Unmarshal([]byte(toolUse.JSON.Input.Raw()), &input); err != nil {
				input.Path = ""
			}
			text, found := files[input.Path]
			if !found {
				text = "File not found: " + input.Path
			}
			toolResults = append(toolResults, anthropic.NewBetaToolResultBlock(toolUse.ID, text, !found))
		}
		// Send the tool results as the user turn, then a fresh copy of the nudge as a
		// turn-scoped system message. Leave earlier copies in place: the API clears them,
		// so the model sees only the newest one.
		messages = append(messages, anthropic.NewBetaUserMessage(toolResults...))
		messages = append(messages, anthropic.BetaMessageParam{
			Role:    anthropic.BetaMessageParamRoleSystem,
			Content: []anthropic.BetaContentBlockParamUnion{anthropic.NewBetaTextBlock(batchNudge)},
			ClearAt: anthropic.BetaMessageParamClearAtNextUserMessage,
		})
	}

	for _, block := range response.Content {
		if textBlock, ok := block.AsAny().(anthropic.BetaTextBlock); ok {
			fmt.Println(textBlock.Text)
			break
		}
	}
}
import com.anthropic.client.AnthropicClient;
import com.anthropic.client.okhttp.AnthropicOkHttpClient;
import com.anthropic.core.JsonValue;
import com.anthropic.models.beta.messages.BetaContentBlockParam;
import com.anthropic.models.beta.messages.BetaMessage;
import com.anthropic.models.beta.messages.BetaMessageParam;
import com.anthropic.models.beta.messages.BetaStopReason;
import com.anthropic.models.beta.messages.BetaTool;
import com.anthropic.models.beta.messages.BetaTool.InputSchema;
import com.anthropic.models.beta.messages.BetaToolResultBlockParam;
import com.anthropic.models.beta.messages.BetaToolUseBlock;
import com.anthropic.models.beta.messages.MessageCreateParams;

static final String BATCH_NUDGE =
    "First privately list what you need next; then request every item "
        + "that doesn't depend on another's result in this one response.";

// In-memory files stand in for a working directory so the sample runs anywhere.
static final Map<String, String> FILES = Map.of(
    "pyproject.toml", """
        [project]
        name = "demo"
        version = "0.1.0"
        description = "Demo project for the batching example"
        """,
    "README.md", """
        # demo

        A small demo project. Run `demo --help` for usage.
        """);

void main() {
    AnthropicClient client = AnthropicOkHttpClient.fromEnv();

    BetaTool readFileTool = BetaTool.builder()
        .name("read_file")
        .description("Read a UTF-8 text file from the working directory.")
        .inputSchema(InputSchema.builder()
            .properties(JsonValue.from(Map.of("path", Map.of("type", "string"))))
            .required(List.of("path"))
            .build())
        .build();
    List<BetaMessageParam> messages = new ArrayList<>();
    messages.add(BetaMessageParam.builder()
        .role(BetaMessageParam.Role.USER)
        .content("Summarize pyproject.toml and README.md.")
        .build());

    BetaMessage response;
    while (true) {
        response = client.beta().messages().create(MessageCreateParams.builder()
            .model("claude-fable-5-1")
            .maxTokens(16000)
            .addBeta("mid-conversation-system-clear-at-2026-08-21")
            .addTool(readFileTool)
            .messages(messages)
            .build());
        // Append the assistant turn exactly as returned, thinking blocks included.
        messages.add(response.toParam());
        boolean requestedTools = response.stopReason()
            .map(BetaStopReason.TOOL_USE::equals)
            .orElse(false);
        if (!requestedTools) {
            break;
        }
        List<BetaToolUseBlock> toolUses = response.content().stream()
            .flatMap(block -> block.toolUse().stream())
            .toList();
        List<BetaContentBlockParam> toolResults = new ArrayList<>();
        for (BetaToolUseBlock toolUse : toolUses) {
            Map<String, JsonValue> input =
                (Map<String, JsonValue>) toolUse._input().asObject().orElseThrow();
            JsonValue pathValue = input.get("path");
            String path = pathValue != null && pathValue.asString().isPresent()
                ? pathValue.asStringOrThrow()
                : "";
            String fileText = FILES.get(path);
            BetaToolResultBlockParam.Builder result = BetaToolResultBlockParam.builder()
                .toolUseId(toolUse.id());
            if (fileText != null) {
                result.content(fileText);
            } else {
                result.content("File not found: " + path).isError(true);
            }
            toolResults.add(BetaContentBlockParam.ofToolResult(result.build()));
        }
        // Send the tool results as the user turn, then a fresh copy of the nudge as a
        // turn-scoped system message. Leave earlier copies in place: the API clears them,
        // so the model sees only the newest one.
        messages.add(BetaMessageParam.builder()
            .role(BetaMessageParam.Role.USER)
            .contentOfBetaContentBlockParams(toolResults)
            .build());
        messages.add(BetaMessageParam.builder()
            .role(BetaMessageParam.Role.SYSTEM)
            .content(BATCH_NUDGE)
            .clearAt(BetaMessageParam.ClearAt.NEXT_USER_MESSAGE)
            .build());
    }

    String finalText = response.content().stream()
        .flatMap(block -> block.text().stream())
        .map(textBlock -> textBlock.text())
        .findFirst()
        .orElse("");
    IO.println(finalText);
}
<?php

use Anthropic\Beta\Messages\BetaStopReason;
use Anthropic\Client;

$client = new Client();

const BATCH_NUDGE = 'First privately list what you need next; then request every item '
    . "that doesn't depend on another's result in this one response.";
// In-memory files stand in for a working directory so the sample runs anywhere.
const FILES = [
    'pyproject.toml' => <<<'TOML'
        [project]
        name = "demo"
        version = "0.1.0"
        description = "Demo project for the batching example"
        TOML,
    'README.md' => <<<'MD'
        # demo

        A small demo project. Run `demo --help` for usage.
        MD,
];
$tools = [
    [
        'name' => 'read_file',
        'description' => 'Read a UTF-8 text file from the working directory.',
        'input_schema' => [
            'type' => 'object',
            'properties' => ['path' => ['type' => 'string']],
            'required' => ['path'],
        ],
    ],
];
$messages = [
    ['role' => 'user', 'content' => 'Summarize pyproject.toml and README.md.'],
];

while (true) {
    $response = $client->beta->messages->create(
        model: 'claude-fable-5-1',
        maxTokens: 16000,
        betas: ['mid-conversation-system-clear-at-2026-08-21'],
        tools: $tools,
        messages: $messages,
    );
    // Append the assistant turn exactly as returned, thinking blocks included.
    $messages[] = ['role' => 'assistant', 'content' => $response->content];
    if ($response->stopReason !== BetaStopReason::TOOL_USE->value) {
        break;
    }
    $toolResults = [];
    foreach ($response->content as $block) {
        if ($block->type === 'tool_use') {
            $path = is_string($block->input['path'] ?? null) ? $block->input['path'] : '';
            if (array_key_exists($path, FILES)) {
                $toolResults[] = [
                    'type' => 'tool_result',
                    'tool_use_id' => $block->id,
                    'content' => FILES[$path],
                ];
            } else {
                $toolResults[] = [
                    'type' => 'tool_result',
                    'tool_use_id' => $block->id,
                    'content' => "File not found: {$path}",
                    'is_error' => true,
                ];
            }
        }
    }
    // Send the tool results as the user turn, then a fresh copy of the nudge as a
    // turn-scoped system message. Leave earlier copies in place: the API clears them,
    // so the model sees only the newest one.
    $messages[] = ['role' => 'user', 'content' => $toolResults];
    $messages[] = [
        'role' => 'system',
        'content' => BATCH_NUDGE,
        'clear_at' => 'next_user_message',
    ];
}

$textBlock = array_find($response->content, fn ($block) => $block->type === 'text');
echo $textBlock?->text ?? '', PHP_EOL;
require "anthropic"

client = Anthropic::Client.new

BATCH_NUDGE =
  "First privately list what you need next; then request every item " \
  "that doesn't depend on another's result in this one response."
# In-memory files stand in for a working directory so the sample runs anywhere.
FILES = {
  "pyproject.toml" => <<~TOML,
    [project]
    name = "demo"
    version = "0.1.0"
    description = "Demo project for the batching example"
  TOML
  "README.md" => <<~MD
    # demo

    A small demo project. Run `demo --help` for usage.
  MD
}
tools = [
  {
    name: "read_file",
    description: "Read a UTF-8 text file from the working directory.",
    input_schema: {
      type: "object",
      properties: {path: {type: "string"}},
      required: ["path"]
    }
  }
]
messages = [{role: "user", content: "Summarize pyproject.toml and README.md."}]

response = nil
loop do
  response = client.beta.messages.create(
    model: "claude-fable-5-1",
    max_tokens: 16000,
    betas: ["mid-conversation-system-clear-at-2026-08-21"],
    tools: tools,
    messages: messages
  )
  # Append the assistant turn exactly as returned, thinking blocks included.
  messages << {role: "assistant", content: response.content}
  break unless response.stop_reason == :tool_use

  tool_results = response.content.filter_map do |block|
    next unless block.type == :tool_use

    path = block.input[:path]
    if FILES.key?(path)
      {type: "tool_result", tool_use_id: block.id, content: FILES[path]}
    else
      {
        type: "tool_result",
        tool_use_id: block.id,
        content: "File not found: #{path}",
        is_error: true
      }
    end
  end
  # Send the tool results as the user turn, then a fresh copy of the nudge as a
  # turn-scoped system message. Leave earlier copies in place: the API clears them,
  # so the model sees only the newest one.
  messages << {role: "user", content: tool_results}
  messages << {role: "system", content: BATCH_NUDGE, clear_at: "next_user_message"}
end

puts response.content.find { it.type == :text }&.text

대화 기록을 append-only로 유지하기 (Keep the conversation history append-only)

각 어시스턴트 턴을 API가 반환한 그대로(thinking 블록 포함) 기록에 추가하고, 요청 사이에 이전 턴을 편집하지 마세요. 2026년 8월 31일 이후 생성된 새 계정에서 Claude Fable 5.1의 thinking 블록은 그것을 만든 정확한 대화에서만 유효해요. 접두사(시스템 프롬프트, 도구 목록, 또는 이전 메시지)가 바뀐 뒤 thinking 블록을 재생하는 요청은 400을 반환하거나, thinking.block_binding.prefix_mismatch_behavior: "drop_block"(베타, thinking-binding-controls-2026-08-01 헤더)을 설정하면 영항 받은 블록을 버려요. 계정에서 확인을 강제하지 않아도 이 패턴을 채택하세요. 그래야 같은 코드가 모든 계정에서 동작해요.

확인을 걸리는 기록 편집은 프롬프트 캐시를 다시 시작하는 것과 같은 것들이에요. 턴별 리마인더 주입·제거, 이전 턴을 제자리에서 요약, 세션 중간 시스템 프롬프트 변경. 턴별 리마인더는 턴 범위 시스템 메시지로 보내고, system이나 tools를 다시 쓰는 대신 대화 중간 시스템 메시지로 지시나 도구를 바꾸며, 어떤 정리도 서버 측 컴팩션이나 컨텍스트 편집에 맡기세요. 클라이언트에서 컴팩션한다면 가장 단순한 형태는 전체 기록을 요약 메시지 하나와 새 사용자 턴으로 교체하고 나머지를 재생하지 않는 거예요. 이월되는 thinking 블록이 없으니 아무것도 실패하지 않고, 모델은 컴팩션된 대화에서 새로 생각해요(클라이언트에서의 커스텀 컴팩션 참조). 이제 캐시 읽기가 더 저렴해졌으니(가격 책정 참조), 비용을 아끼려고 일찍 컴팩션하는 것이 Claude Fable 5.1에서는 더 이상 올바른 비용·지능 트레이드오프가 아닐 수 있어요. 그러니 더 늦은 컴팩션 시점을 실험해 보세요.

하네스가 이미 만드는 편집을 찾으려면 prefix_mismatch_behavior: "drop_block"로 세션을 실행하고 input_transformations를 기록하세요(통합이 영향받는지 확인하는 방법 참조). 또는 몇 번의 정상 턴에서 보내는 정확한 요청을 포착하고 연속 요청이 추가된 턴까지 바이트 단위로 동일한지 확인하세요.

글쓰기 밀도 (Writing density)

Claude Fable 5.1의 글은 관용구가 적고 설명 없는 전문 용어가 적어요. 다만 어떤 경우 산문이 Claude Fable 5보다 밀도가 높아요. 문장이 더 길고 단락 구분이 더 적죠. 반패턴인 격식체 산문( mannered prose)을 정의하는 지시가 도움이 돼요. 사용자 메시지(권장)나 시스템 프롬프트에 추가하세요:

Mannered prose substitutes metaphor and flourish for direct statement. Instead of "a parameter worth varying," the mannered writer produces "a dial worth turning." Instead of "this point still matters," they write "this point earns its keep." The phrases exist to display the writer, not to convey the idea, and readers can tell. That is why mannered prose irritates: it makes the reader work harder so the writer can perform. It is also imprecise. Metaphors drag in connotations the writer did not choose and cannot control. The fix is to say what you mean. When a literal phrase is available, use it.

짧은 버전도 보통 효과가 있어요:

Please remove all mannered prose.

채팅에서의 서식 (Formatting in chat)

이전 모델은 채팅에서 글머리 기호와 굵게를 과용했고, 많은 프롬프트가 그것을 억제하기 위해 쓰인 반서식 규칙을 지니고 있어요. Claude Fable 5.1은 반대쪽으로 기우는 편이에요. 굵게를 덜 쓰고 헤더·목록·인용부호를 잡으려는 경향이 적어요. 프롬프트에 반서식 언어가 있다면 제거하거나, 특정 서식이 언제 적절한지 말하는 규칙으로 교체하세요:

Use lists and bullet points when asked to, or when the content is multifaceted enough that they help with clarity. If the person explicitly requests minimal formatting, always format your responses without bullet points, headers, lists, or bold emphasis, as requested. In conversational, personal, or emotional exchanges, keep to plain prose.

검색한 출처 인용하기 (Quoting retrieved sources)

문서를 요약할 때 Claude Fable 5.1은 Claude Fable 5보다 출처 텍스트 구절을 인용 표시 없이 재현할 가능성이 더 높아요. 이를 다루려면 시스템 프롬프트에 올바른 응답의 완전한 예시 하나를 추가하세요. 사용자의 요청, 응답, 그리고 응답이 왜 올바른지 설명하는 문장이요.

<example>
<user>look up how the Riverton Ledger and the Coast Dispatch each covered the Harbor Bridge closure and compare their reporting</user>
<response>
[web_search: Harbor Bridge closure Riverton Ledger]
[web_search: Harbor Bridge closure Coast Dispatch]
Both outlets agree on the basics: the bridge closed on March 3 after inspectors found cracked welds, and the state expects repairs to take about eight months. Where they differ is emphasis. The Ledger treats it as a local-economy story. The Dispatch frames it as a funding failure; its editorial calls the closure "entirely foreseeable." Read together, the Ledger explains who is affected now and the Dispatch explains how it came to this — neither account alone gives the whole picture.
</response>
<rationale>CORRECT: The response is organized around where the two outlets agree and differ, not as a walk through either article. Each outlet's reporting is conveyed in one or two sentences of the assistant's own indirect speech. One short marked phrase from one source; every other claim is reworded. The response is still specific and complete.</rationale>
</example>

[web_search: ...] 줄을 자기 도구의 이름으로 바꾸세요. 그래야 모델이 그 줄을 내보낼 문자 그대로의 텍스트가 아니라 템플릿된 도구 출력으로 읽어요.

전체 과제 마무리하기 (Finish the whole task)

Claude Fable 5.1은 방법론에 대한 지침이 많지 않아도 매우 긴 과제를 실행할 수 있어요. 특히 목표가 명확할 때요. 다만 복잡한 비동기 워크로드에서는 작업이 끝나기 전에 턴을 끝내지 말라는 넛지를 주세요. 넛지 없이 모델은 때로 일을 하기보다 다음에 무엇을 할지 설명하거나("Next, I'll …"), 원래 요청이 이미 다룬 단계에 대해 허락을 구하거나("Shall I apply this?") 멈춰요. 사용자는 "continue"나 "go ahead"라고 답해야 하는데, 이는 페어 프로그래밍과 다른 human-in-the-loop 작업에는 어울리지만 모델의 완전한 장기 능력을 활용하지는 못해요.

시스템 프롬프트 추가 두 가지가 함께 이를 완화해요. 둘 다 적용하세요. 프롬프트 길이를 제한해야 한다면 첫 번째만 쓰세요. 대부분의 효과가 유지돼요. 첫 번째는 모델에게 이미 요청한 작업에 대해 묻지 말고, 말한 다음 단계를 실행하라고 말해요:

You are operating autonomously. The user is not watching in real time and cannot answer questions mid-task, so asking 'Want me to…?' or 'Shall I…?' will block the work. For reversible actions that follow from the original request, proceed without asking. Stop only for destructive actions or genuine scope changes the user must decide. Offering follow-ups after the task is done is fine; asking permission before doing the work is not.

Exception: when the user is describing a problem, asking a question, or thinking out loud rather than requesting a change, the deliverable is your assessment. Report your findings and stop. Don't apply a fix until they ask for one.

Before ending your turn, check your last paragraph. If it is a plan, an analysis, a question, a list of next steps, or a promise about work you have not done ('I'll…', 'let me know when…'), do that work now with tool calls. That includes retrying after errors and gathering missing information yourself. Do not stop because the context or session is long. End your turn only when the task is complete or you are blocked on input only the user can provide.

Before running a command that changes system state (such as restarts, deletes, or config edits), check that the evidence actually supports that specific action. A signal that pattern-matches to a known failure may have a different cause.

사용자가 보고 있지 않다고 모델에 말하는 첫 문장이 효과의 많은 부분을 담아요. 그대로 두세요. 제품이 특정 확인을 위해 모델이 멈추길 필요하다면 그 뒤에 그것들을 나열하는 문장을 추가하세요. 이 블록은 모델이 모호한 요청에 대해 묻는 가능성도 줄일 수 있으니, 그 트레이드오프는 자기 과제에서 확인하세요.

두 번째는 사용자의 요청을 산출물의 범위로 정의해요:

# Delivering work
The user's request — or the plan they approved — sets the scope, and the scope is the deliverable: don't quietly narrow, widen, or swap it. Read ambiguity the way a careful colleague would: make routine judgment calls yourself, and check in only when different readings would lead to materially different work. If you see a real problem with the task as specified, say so in a sentence or two and keep building under stated assumptions; if the user hears the concern and reaffirms, that is their decision, so deliver the full request.

If a question comes up partway, first do everything that doesn't depend on the answer; then state the assumption you made, or — when going ahead on a wrong guess would be unsafe or would make the work useless — put the question at the end of a turn that also delivers that progress. If one part turns out to be blocked, complete every other part in full and say exactly what you left out and why — the whole task is the deliverable, and scaling it down is the user's call, not yours. A step you have decided on is something to run, not to announce: describing the next step and ending the turn leaves it undone until the user replies.

Keep changes to what the request needs. Something else you notice worth doing — cleanup or documentation the task didn't call for, a change to a file the task didn't require — is a suggestion to make at the end, not a change to make; actions clearly beyond what the ask implies, and risky or destructive ones, still need the user's go-ahead.

컴팩션 요약에서 보존할 것을 모델에 알려주기 (Tell the model what to preserve in compaction summaries)

Claude Fable 5.1은 긴 대화가 컴팩션될 때 자기의 요약이 반드시 유지해야 할 것을 명시적으로 알려주는 데 잘 반응해요. 서버 측 컴팩션이 이미 이렇게 해요. 클라이언트 측에서 컴팩션한다면 다음 요약 지시를 사용하세요:

Summarize the transcript inside <summary></summary> tags. Include relevant information in the summary such that this conversation will be continued by a new context window without needing to redo work or be reprovided with relevant constraints or context. Be sure to preserve: (1) any difficulties or problems that came up, and how they were handled or resolved; (2) any possibilities, options, or approaches that were raised, tried, or set aside, and why; (3) anything that was asked for, decided, agreed, ruled out, or established as a preference, constraint, or boundary — stated exactly; (4) exactly where things stand now — what has been covered, settled, or completed so far; (5) anything still open, unresolved, promised, or expected to happen next; (6) specific details that would be hard to reconstruct — names, numbers, dates, exact wording, links or references — kept exactly. Be complete on these even at the cost of length; keep everything else concise. Weight the two voices differently: keep what the user said, asked for, shared, or established carefully and close to their own words; your own explanations and reasoning can be condensed much further, to what they concluded or produced — as long as nothing in the six items above is dropped.

변경과 테스트를 과제가 요구한 만큼만 유지하기 (Keep changes and tests to what the task asks for)

개방형 기능을 구현해 달라고 요청하면 Claude Fable 5.1은 요청한 것을 전달하고 때로는 더 전달해요. 근처 코드를 고치거나, 과제가 언급하지 않은 행동을 확장하거나, 변경이 정당화하는 것보다 많은 테스트 파일을 커밋할 수 있죠. 빼야 할 것에 대한 명시적 지시에 잘 반응해요. 다음 지시로 요청하지 않은 추가와 커밋된 테스트 코드는 과제 성공의 측정 가능한 변화 없이 크게 줄어들어요:

If, while working or testing, you find a pre-existing bug, a performance concern, or behavior the task doesn't mention, don't fix, optimize or extend it in this change unless the requested behavior cannot work without it; report it as a follow-up in your summary. Where the task is ambiguous, implement the reading its wording and the surrounding code most directly support, state that assumption in your summary, and don't build for the other readings as well. Verify your work however you like; scratch scripts and quick checks need not be kept. Commit tests only where the task asks for them or this repository already keeps tests for this kind of change, sized like the neighboring test files — roughly one focused test per stated behavior — and don't turn scratch checks into additional permanent test files. This is about extras only: implement every behavior the task asks for, completely.

낮은 effort에서의 검색 트리거링 (Search triggering at low effort)

low effort에서 Claude Fable 5.1은 Claude Fable 5보다 검색·검색 도구를 부를 가능성이 낮고 기억으로 답할 가능성이 더 높아요. 어떤 경우 가장 단순한 해결책은 전체 대화가 아니라 영향받는 턴의 effort를 올리는 거예요. 대화 중간에 effort 바꾸기를 참고하세요.

다른 경우에는 검증 쪽으로 프롬프트 넛지가 도움이 돼요. 시스템 프롬프트에서 이름을 인식하는 것과 그 이름의 현재 상태를 아는 것은 다르며, 그런 이름은 사용자가 쓴 대로 검색해야 한다고 말하세요:

When a query centers on a name you do not confidently recognize, or recognize from a fast-moving area like AI models and developer tools where the landscape shifts within months, the name itself is the thing to verify: search before answering, and include the name as the user wrote it in at least one query alongside any reformulations. This holds even when you have some background on it — partial background is exactly what makes an out-of-date answer sound authoritative, so familiarity is not a reason to skip the search.

안전장치 오탐 줄이기 (Reduce safeguard false positives)

Claude Fable 5.1의 안전 분류기는 출시 시점의 Claude Fable 5 것보다 오탐(false positive)이 더 적고, 소스 코드에서 취약점을 찾는 것은 허용돼요. 오탐은 여전히 발생하고, 차단된 요청은 stop_reason: "refusal"을 반환해요(거부, 폴백, 과금 참조). 세 가지 상황에서 더 발생하기 쉬워요:

  • 컴파일 확인 표현: "Does this program compile without errors?" 대신 "Are there any bugs in this program?"으로 물어보세요.
  • 덜 알려진 프로그래밍 언어: 모델에게 그 언어가 무엇이고 어떻게 동작하는지의 맥락을 주세요. 예를 들어 언어 문서에 접근하게 하는 것.
  • 도구 출력의 base64: base64 인코딩 데이터를 모델 컨텍스트로 반환하는 도구는 오탐을 유발할 수 있으니, 제거하는 것이 권장 수정이에요.

전체 파일 재작성보다 타겟 편집 선호하기 (Prefer targeted edits over whole-file rewrites)

Claude Fable 5.1이 작은 변경에 전체 파일을 다시 쓴다면 시스템 프롬프트나 첫 사용자 메시지에 다음 지시를 추가하세요. Claude Fable 5.1은 Claude Fable 5보다 타겟 편집 대신 전체 텍스트 파일을 다시 쓸 가능성이 높아요. 결과 파일은 보통 같지만, 파일이 짧거나 대부분이 바뀌지 않는 한 재작성은 더 많은 출력 토큰과 시간이 들어요. 이 지시는 Claude Fable 5.1을 작고 중간 크기의 변경에서 Claude Fable 5와 같은 수준으로 되돌려줘요.

The number of tokens used to edit files is best minimized, all else being equal. Therefore, when it will not affect the end result, try to surgically edit a file rather than rewrite the entire thing.

xhigh와 max effort에서 긴 출력에 여유 남기기 (Leave room for long outputs at xhigh and max effort)

xhigh, 특히 max effort에서 Claude Fable 5.1은 답변 쓰기를 시작하기 전에 더 오래 생각할 수 있어요. 단일 요청이 긴 산출물(긴 문서의 전체 재작성 같은)을 요청할 때, 그 산출물의 상당 부분을 thinking에서 초안을 만든 뒤 다시 답변으로 쓸 수 있어요. 이는 더 긴 대기와 더 많은 출력 토큰을 의미해요. 가장 단순한 접근은 이런 요청을 권장 시작점인 high에서 돌리고, 측정한 품질 향상이 있을 때만 xhighmax로 옮기는 거예요(모든 effort 수준 고려하기 참조). 그래도 xhighmax에서 돌린다면:

  • max_tokens을 예상 답변 길이뿐 아니라 thinking과 답변을 위한 여유를 두고 설정하세요.
  • 사용자 메시지 끝에 다음 메모를 추가하세요. 산문과 코드 요청에서 thinking을 훨씬 짧게 만들어요. [max_tokens]를 요청의 실제 max_tokens 값(예: 64,000)으로 교체하세요.
Everything produced in one reply, including any reasoning or drafting done before the reply, counts toward a single limit of about [max_tokens] tokens. If that limit is reached before the reply is finished, the person receives a cut-off response and has to start over. Composing an entire output or deliverable in full as reasoning and then again as a reply would double the length of the turn without improving the result, so don't do that.

Instead, when the person has asked for a long or effort-intensive deliverable such as a multi-section document, a large table or dataset, or a complete code file, spend extra effort on understanding the request, checking the inputs the answer depends on, settling the structure and other difficult decisions, and otherwise using the reasoning space to reason and the output space to write an output. Usually it is not needed to draft an output multiple times.

하위 에이전트가 도는 동안 리드 에이전트가 계속 작업하게 하기 (Let the lead agent keep working while subagents run)

코딩 에이전트가 Claude Fable 5.1이 하위 에이전트에 작업을 위임하게 한다면, 리드 에이전트가 각각을 위해 멈추고 기다리도록 강제하지 마세요. 코딩 과제에서 하위 에이전트가 도는 동안 리드가 계속하게 하면 비슷한 품질·토큰 사용·비용으로 평균 완료 시간이 줄어요. 설정 방법:

  • 하위 에이전트를 시작하는 도구가 즉시 반환하게 하세요.
  • 각 하위 에이전트의 결과를 준비되면 나중 user 메시지로 리드에 다시 전달하세요.
  • 리드가 결과를 기다리고 싶을 때 부를 수 있는 별도 도구를 주세요.

모델은 여전히 종종 기다리기를 선택해요. 시간 절약은 다른 작업을 계속하는 실행에서 나와요.

비전 작업에 크롭·줌 도구 주기 (Give vision work tools to crop and zoom)

Claude Fable 5.1은 기본으로 더 나은 비전 기능을 갖추고, 밀도 높은 차트 같은 복잡한 시각 입력에서는 반복적으로 분석·크롭·시각 검증할 수 있을 때 최상의 작업을 해요. 전체 이점을 얻으려면 원본 이미지나 비디오를 담고 기본 이미지 처리 라이브러리(예: PIL, OpenCV)가 미리 설치된 컨테이너에 접근하는 에이전트로 모델을 실행하세요. 컨테이너 실행이 너무 과하다면 이미지 크롭 도구 하나만으로도 대부분의 향상을 얻어요. 이미지의 선택 영역을 크롭·확대해서 반환하는 도구는 모델이 특정 세부를 더 깊이 검토하게 하고 테스트 시간 계산을 이미지 토큰으로 확장하게 해요. 크롭 도구 레시피에 동작하는 정의가 있어요.

더 알아보기 (Learn more)