### Turn-scoped system messages
The whole hunk
from line 12, old and new numbered
/
lines
from line 12
1212
1313Mid-conversation system messages close that gap. You append a `{"role": "system"}` message at the point in the conversation where the new instruction becomes relevant, instead of editing the top-level `system` field. The cached prefix stays the same, so the next request still reads it from cache, and the new instruction is still applied as a system instruction rather than as ordinary user text.
1414
15This page covers two features: mid-conversation system messages, and [mid-conversation tool changes](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages#mid-conversation-tool-changes), a beta introduced with Claude Opus 5 that applies the same approach to the `tools` array.
16
1715<Note>
1816 Mid-conversation system messages are available on the Claude API, [Claude in Amazon Bedrock](https://platform.claude.com/docs/en/build-with-claude/claude-in-amazon-bedrock), and [Google Cloud](https://platform.claude.com/docs/en/build-with-claude/claude-on-vertex-ai).
1917
20 This feature is available on Claude Fable 5, [Claude Mythos 5](https://anthropic.com/glasswing), Claude Opus 4.8, and Claude Opus 5. No beta header is required for mid-conversation system messages. This feature is not available on Claude Sonnet 5; use the top-level `system` field instead.
18 This feature is available on Claude Fable 5.1, [Claude Mythos 5.1](https://anthropic.com/glasswing), Claude Fable 5, [Claude Mythos 5](https://anthropic.com/glasswing), Claude Opus 4.8, and Claude Opus 5. No beta header is required for mid-conversation system messages. This feature is not available on Claude Sonnet 5. Use the top-level `system` field there instead.
2119
22 Mid-conversation tool changes are in beta and require the `mid-conversation-tool-changes-2026-07-01` beta header. They are available on Claude Fable 5, Claude Mythos 5, Claude Opus 4.8, and Claude Opus 5, on the Claude API, Amazon Bedrock, and Google Cloud. They are not available on Claude Sonnet 5.
20 [Mid-conversation tool changes](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages#mid-conversation-tool-changes) are in beta and require the `mid-conversation-tool-changes-2026-07-01` beta header. They are available on the same models, on the Claude API, Amazon Bedrock, and Google Cloud.
21
22 [Turn-scoped system messages](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages#turn-scoped-system-messages) (`clear_at`) are in beta and require the `mid-conversation-system-clear-at-2026-08-21` beta header, on the same models and platforms as mid-conversation system messages.
2323</Note>
2424
2525## Mid-conversation tool changes
2626
27The `tools` array sits even earlier in the hashed request prefix than the top-level `system` field, so editing it invalidates the [prompt cache](https://platform.claude.com/docs/en/build-with-claude/prompt-caching) for the entire conversation. Mid-conversation tool changes, a beta introduced with Claude Opus 5, are the tools counterpart to mid-conversation system messages. Instead of fixing the tool list for the lifetime of the conversation, you change which tools are offered to the model between turns: declare the full tool set in `tools` up front, then use `tool_addition` and `tool_removal` blocks to offer a tool to the model, or withdraw it, from a specific point in the conversation onward. The `tools` array itself never changes, so the cached prefix stays intact.
27The `tools` array sits even earlier in the hashed request prefix than the top-level `system` field, so editing it invalidates the [prompt cache](https://platform.claude.com/docs/en/build-with-claude/prompt-caching) for the entire conversation. Mid-conversation tool changes are the tools counterpart to mid-conversation system messages. Instead of fixing the tool list for the lifetime of the conversation, you change which tools are offered to the model between turns: declare the full tool set in `tools` up front, then use `tool_addition` and `tool_removal` blocks to offer a tool to the model, or withdraw it, from a specific point in the conversation onward. The `tools` array itself never changes, so the cached prefix stays intact.
2828
2929`tool_addition` and `tool_removal` are content blocks in the `content` array of a `role: "system"` message, and they can be mixed with `text` blocks in the same message. The message follows the same placement rules as any mid-conversation system message (see [Limitations](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages#limitations)), and the change applies from that point in the conversation onward. Each block's `tool` field references a tool rather than defining one: `{"type": "tool_reference", "name": "..."}` names a tool declared in the request's `tools` array, and [MCP connector](https://platform.claude.com/docs/en/agents-and-tools/mcp-connector) tools can be referenced individually with `mcp_tool_reference` (`server_name` and `name`) or as a whole toolset with `mcp_toolset_reference` (`server_name`). Referencing a name that is not declared in `tools` returns a 400 error.
3030
from line 446
446446 ```
447447</CodeGroup>
448448
449Mid-conversation tool changes are in beta. To use them, include the beta header `mid-conversation-tool-changes-2026-07-01` in your requests. They are available on Claude Fable 5, Claude Mythos 5, Claude Opus 4.8, and Claude Opus 5, on the Claude API, Amazon Bedrock, and Google Cloud.
449Mid-conversation tool changes are in beta. To use them, include the beta header `mid-conversation-tool-changes-2026-07-01` in your requests.
450450
451451## When to use a mid-conversation system message
452452
from line 460
460460
461461* **Mid-session policy or persona changes.** A long agentic session needs a new constraint ("from now on, write all SQL as parameterized queries") after dozens of cached turns. Adding it to the top-level `system` field would re-process the entire history.
462462* **Per-turn context that must be authoritative.** You want to inject a freshness note, a session deadline, or a tool-availability change with system-level weight, and it changes too often to live in the cached prefix.
463* **Per-turn reminders that shouldn't pile up.** A harness nudges the model after each batch of tool results ("request independent reads together", "the user hasn't heard from you in a while") and wants the model to see only the newest copy. A [turn-scoped system message](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages#turn-scoped-system-messages) renders for one turn and then costs nothing, without deleting anything from the history.
463464* **State changes your application observes.** Your application notices something Claude should treat as an operator-level fact: files changed on disk, the user toggled an auto-approve setting, available tools changed, or the remaining token budget dropped below a threshold.
464* **User input that should not interrupt an agentic loop.** A user types a follow-up while Claude is still executing tools for the previous request. Relaying it as a system message after the next tool result lets Claude fold the new input into the work it is already doing, instead of treating it as a fresh request to switch to. See [Placement after tool results](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages#placement-after-tool-results) below.
465* **User input that should not interrupt an agentic loop.** A user types a follow-up while Claude is still executing tools for the previous request. Relaying it as a system message after the next tool result lets Claude fold the new input into the work it is already doing, instead of treating it as a fresh request to switch to. See [Placement after tool results](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages#placement-after-tool-results).
465466* **Mode switches that grant standing permissions.** A session-level mode can use a mid-conversation system message to grant standing consent to an expensive capability, such as automatically launching multiagent workflows, with a short refresher every several turns and an exit notice when the mode is turned off. For a worked example, see [Build an orchestration mode](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-effort-example).
466467
467468In all of these cases you could put the instruction in a regular `user` message, and Claude does follow instructions that arrive in user turns. The difference is priority: a `user` message is treated as coming from the end user, while a `system` message is treated as coming from you, the application operator. When the two conflict, system instructions take precedence, so use the `system` role for operator-level facts and constraints that should hold even if the end user asks for something different. A mid-conversation system message keeps that operator-level priority without paying the cache-miss cost of editing the top-level `system` field.
from line 473
472473
473474You can still set the top-level `system` field for instructions that should apply to the entire conversation. Reserve mid-conversation system messages for instructions that only become relevant later, or that you want to add without invalidating the cached prefix.
474475
476A `role: "system"` message can also carry `output_config.effort` to change the [effort](https://platform.claude.com/docs/en/build-with-claude/effort) level from the next `user` turn on. This is in beta on Claude Fable 5.1, Claude Mythos 5.1, and Claude Opus 5 on the Claude API and requires the `mid-conversation-output-config-2026-07-01` beta header. See [Per-message effort](https://platform.claude.com/docs/en/build-with-claude/effort#change-effort-mid-conversation-beta).
477
475478<CodeGroup>
476479 ```bash cURL
477480 curl https://api.anthropic.com/v1/messages \
from line 814
811814
812815This pattern is for relaying input from the conversation's own end user. Do not use it to pass tool output, retrieved documents, or other third-party content; keep that content in `tool_result` blocks (see [Limitations](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages#limitations)).
813816
817### Turn-scoped system messages
818
819To scope a `role: "system"` message to the current turn, set its `clear_at` field. It takes one of two values:
820
821* `"never"` (the default): the message renders at its position on every request that includes it. Omitting the field is identical.
822* `"next_user_message"`: the message is **turn-scoped**. Its text renders only while no `role: "user"` message comes after it in `messages`. A user message that carries only `tool_result` blocks counts as a user message here. Once a later user message exists, the message is **cleared**: it stays in the array but renders nothing and costs no input tokens, on that request and every later one.
823
824Turn-scoped system messages are in beta. Include the [beta header](https://platform.claude.com/docs/en/api/beta-headers) `mid-conversation-system-clear-at-2026-08-21`. Without it, `clear_at` is rejected as an unknown field.
825
826```json
827{
828 "role": "system",
829 "clear_at": "next_user_message",
830 "content": "First privately list what you need next; then request every item that doesn't depend on another's result in this one response."
831}
832```
833
834The main use is a per-turn reminder in a tool loop. Append the reminder after the `tool_result` message each time you want the model to see it, and leave every earlier copy where it is. The model sees only the copies that come after the last user message, so the reminder never piles up. Nothing earlier in `messages` changes, so the [prompt cache](https://platform.claude.com/docs/en/build-with-claude/prompt-caching) keeps matching. On Claude Fable 5.1 this also keeps later [thinking blocks valid](https://platform.claude.com/docs/en/build-with-claude/thinking#preserved-in-conversation): deleting an earlier reminder would change the conversation before those blocks and fail the conversation check, while a cleared message stays in the array and leaves that conversation unchanged.
835
836The following request is a later step of an agent loop. `messages[3]` rendered on the earlier request, when it was the last message in the array. Once `messages[5]` (a later user message) exists, `messages[3]` is cleared: the cleared message stays in the array, so the conversation before the thinking block in `messages[4]` is unchanged, but the model no longer sees its text. `messages[6]` and `messages[7]` both render, in order.
837
838```json
839{
840 "model": "claude-fable-5-1",
841 "max_tokens": 16000,
842 "messages": [
843 { "role": "user", "content": "Fix the failing test." },
844 {
845 "role": "assistant",
846 "content": [
847 { "type": "thinking", "thinking": "", "signature": "..." },
848 {
849 "type": "tool_use",
850 "id": "toolu_01",
851 "name": "read_file",
852 "input": { "path": "test_auth.py" }
853 }
854 ]
855 },
856 {
857 "role": "user",
858 "content": [{ "type": "tool_result", "tool_use_id": "toolu_01", "content": "..." }]
859 },
860 {
861 "role": "system",
862 "clear_at": "next_user_message",
863 "content": "Request independent reads in one turn."
864 },
865 {
866 "role": "assistant",
867 "content": [
868 { "type": "thinking", "thinking": "", "signature": "..." },
869 {
870 "type": "tool_use",
871 "id": "toolu_02",
872 "name": "read_file",
873 "input": { "path": "auth.py" }
874 },
875 {
876 "type": "tool_use",
877 "id": "toolu_03",
878 "name": "read_file",
879 "input": { "path": "tokens.py" }
880 }
881 ]
882 },
883 {
884 "role": "user",
885 "content": [
886 { "type": "tool_result", "tool_use_id": "toolu_02", "content": "..." },
887 {
888 "type": "tool_result",
889 "tool_use_id": "toolu_03",
890 "content": "...",
891 "cache_control": { "type": "ephemeral" }
892 }
893 ]
894 },
895 {
896 "role": "system",
897 "clear_at": "next_user_message",
898 "content": "Request independent reads in one turn."
899 },
900 {
901 "role": "system",
902 "clear_at": "next_user_message",
903 "content": "The shell exited with status 137."
904 }
905 ]
906}
907```
908
909Rules for turn-scoped messages:
910
911* **Re-send cleared messages verbatim.** A cleared message is still part of the conversation history. Rebuilding it from current state (a fresh token count, a timestamp), dropping it as redundant, or changing its `clear_at` value is an edit to an earlier message. The prompt cache misses from that point, and on Claude Fable 5.1 every thinking block produced after it fails the [conversation check](https://platform.claude.com/docs/en/build-with-claude/thinking#preserved-in-conversation).
912* **Text only.** `content` is one or more `text` blocks (or a string). `tool_addition` and `tool_removal` blocks return a 400 error on a turn-scoped message, and so does `output_config`. Use a separate `role: "system"` message without `clear_at` for those.
913* **No `cache_control` on its blocks.** A cleared message is never part of a cache key, so a breakpoint on it could never match. Put the breakpoint on the last block of the preceding user turn instead, as the example does. The top-level [automatic caching](https://platform.claude.com/docs/en/build-with-claude/prompt-caching#automatic-caching) field skips turn-scoped messages when it picks a breakpoint. On the request that clears a message, the reusable cached prefix ends at the user turn before it, so only the one assistant turn between that message and the new user message is reprocessed.
914* **Placement rules still apply**, cleared or not. A turn-scoped message must follow a `user` turn (or an `assistant` turn ending in a server tool result) and precede an `assistant` turn or end the array, like any mid-conversation system message. One that ends the array always renders. One followed directly by another `user` message is a 400 error, not a cleared message: put all of a tool round's results in one user message and the reminders after it.
915* **Assistant turns don't clear it.** A prefilled or [paused](https://platform.claude.com/docs/en/build-with-claude/handling-stop-reasons#pause-turn) assistant turn after the message, or a server-side tool loop, adds no user message, so the message still renders on that continuation. To keep a reminder in view through a client-side tool loop, append it again after each `tool_result` message.
916* **Token counting follows what renders.** A cleared message adds nothing to `usage.input_tokens` or to a [token count](https://platform.claude.com/docs/en/build-with-claude/token-counting).
917* **Imported history.** In a transcript you construct in one step (few-shot examples, a migrated conversation), a turn-scoped message that already has an assistant turn and a user message after it is cleared from the first request and never renders. That is the right state for a per-turn reminder you are carrying over. Leave `clear_at` unset only on a message the model should see on every request.
918
919The validation errors are:
920
921```text wrap
922messages.3.clear_at: Extra inputs are not permitted
923messages.3.clear_at: clear_at is only permitted on role 'system' messages
924messages.3.clear_at: Input should be 'next_user_message' or 'never'
925messages.3: a turn-scoped system message supports text blocks only (clear_at: 'next_user_message')
926messages.3: output_config is not permitted on a turn-scoped system message (clear_at: 'next_user_message')
927messages.3.content.0: cache_control is not permitted on a turn-scoped system message (clear_at: 'next_user_message')
928```
929
930The first is the error returned without the beta header. On Amazon Bedrock and Google Cloud, pass the beta value as described in [Beta headers](https://platform.claude.com/docs/en/api/beta-headers).
931
932Through the SDKs, set `clear_at` on the `role: "system"` entry in `messages` and send the beta header. The following example appends a turn-scoped reminder after the user turn; on the next request, once a later user message exists, the reminder stays in the array but no longer renders:
933
934<CodeGroup>
935 ```bash cURL
936 curl https://api.anthropic.com/v1/messages \
937 -H "x-api-key: $ANTHROPIC_API_KEY" \
938 -H "anthropic-version: 2023-06-01" \
939 -H "anthropic-beta: mid-conversation-system-clear-at-2026-08-21" \
940 -H "content-type: application/json" \
941 -d '{
942 "model": "claude-fable-5-1",
943 "max_tokens": 4096,
944 "messages": [
945 {"role": "user", "content": "Draft a short status update on the database migration for the team channel."},
946 {"role": "system", "clear_at": "next_user_message", "content": "The reader is on call: keep this reply under 50 words."}
947 ]
948 }'
949 ```
950
951 ```bash CLI
952 ant beta:messages create --beta mid-conversation-system-clear-at-2026-08-21 \
953 --transform 'content.#(type=="text").text' --raw-output <<'YAML'
954 model: claude-fable-5-1
955 max_tokens: 4096
956 messages:
957 - role: user
958 content: Draft a short status update on the database migration for the team channel.
959 # Turn-scoped reminder: renders for this turn, then clears once a later user message exists.
960 - role: system
961 clear_at: next_user_message
962 content: "The reader is on call: keep this reply under 50 words."
963 YAML
964 ```
965
966 ```python Python
967 client = anthropic.Anthropic()
968
969 response = client.beta.messages.create(
970 model="claude-fable-5-1",
971 max_tokens=4096,
972 messages=[
973 {
974 "role": "user",
975 "content": "Draft a short status update on the database migration for the team channel.",
976 },
977 # Turn-scoped reminder: renders for this turn, then clears once a later user message exists.
978 {
979 "role": "system",
980 "clear_at": "next_user_message",
981 "content": "The reader is on call: keep this reply under 50 words.",
982 },
983 ],
984 betas=["mid-conversation-system-clear-at-2026-08-21"],
985 )
986
987 for block in response.content:
988 if block.type == "text":
989 print(block.text)
990 ```
991
992 ```typescript TypeScript
993 const client = new Anthropic();
994
995 const response = await client.beta.messages.create({
996 model: "claude-fable-5-1",
997 max_tokens: 4096,
998 messages: [
999 {
1000 role: "user",
1001 content: "Draft a short status update on the database migration for the team channel."
1002 },
1003 // Turn-scoped reminder: renders for this turn, then clears once a later user message exists.
1004 {
1005 role: "system",
1006 clear_at: "next_user_message",
1007 content: "The reader is on call: keep this reply under 50 words."
1008 }
1009 ],
1010 betas: ["mid-conversation-system-clear-at-2026-08-21"]
1011 });
1012
1013 for (const block of response.content) {
1014 if (block.type === "text") {
1015 console.log(block.text);
1016 }
1017 }
1018 ```
1019
1020 ```csharp C#
1021 using Anthropic.Models.Beta;
1022 using Anthropic.Models.Beta.Messages;
1023
1024 AnthropicClient client = new();
1025
1026 var response = await client.Beta.Messages.Create(new MessageCreateParams
1027 {
1028 Model = "claude-fable-5-1",
1029 MaxTokens = 4096,
1030 Messages =
1031 [
1032 new() { Role = Role.User, Content = "Draft a short status update on the database migration for the team channel." },
1033 // Turn-scoped reminder: renders for this turn, then clears once a later user message exists.
1034 new()
1035 {
1036 Role = Role.System,
1037 ClearAt = ClearAt.NextUserMessage,
1038 Content = "The reader is on call: keep this reply under 50 words.",
1039 },
1040 ],
1041 Betas = [AnthropicBeta.MidConversationSystemClearAt2026_08_21],
1042 });
1043
1044 foreach (var block in response.Content)
1045 {
1046 if (block.TryPickText(out var textBlock))
1047 {
1048 Console.WriteLine(textBlock.Text);
1049 }
1050 }
1051 ```
1052
1053 ```go Go
1054 client := anthropic.NewClient()
1055
1056 response, err := client.Beta.Messages.New(context.Background(), anthropic.BetaMessageNewParams{
1057 Model: "claude-fable-5-1",
1058 MaxTokens: 4096,
1059 Messages: []anthropic.BetaMessageParam{
1060 anthropic.NewBetaUserMessage(anthropic.NewBetaTextBlock("Draft a short status update on the database migration for the team channel.")),
1061 // Turn-scoped reminder: renders for this turn, then clears once a later user message exists.
1062 {
1063 Role: anthropic.BetaMessageParamRoleSystem,
1064 ClearAt: anthropic.BetaMessageParamClearAtNextUserMessage,
1065 Content: []anthropic.BetaContentBlockParamUnion{anthropic.NewBetaTextBlock("The reader is on call: keep this reply under 50 words.")},
1066 },
1067 },
1068 Betas: []anthropic.AnthropicBeta{anthropic.AnthropicBetaMidConversationSystemClearAt2026_08_21},
1069 })
1070 if err != nil {
1071 log.Fatal(err)
1072 }
1073
1074 for _, block := range response.Content {
1075 if textBlock, ok := block.AsAny().(anthropic.BetaTextBlock); ok {
1076 fmt.Println(textBlock.Text)
1077 }
1078 }
1079 ```
1080
1081 ```java Java
1082 import com.anthropic.models.beta.AnthropicBeta;
1083 import com.anthropic.models.beta.messages.BetaMessage;
1084 import com.anthropic.models.beta.messages.BetaMessageParam;
1085 import com.anthropic.models.beta.messages.MessageCreateParams;
1086
1087 void main() {
1088 AnthropicClient client = AnthropicOkHttpClient.fromEnv();
1089
1090 MessageCreateParams params = MessageCreateParams.builder()
1091 .model("claude-fable-5-1")
1092 .maxTokens(4096L)
1093 .addBeta(AnthropicBeta.MID_CONVERSATION_SYSTEM_CLEAR_AT_2026_08_21)
1094 .addUserMessage("Draft a short status update on the database migration for the team channel.")
1095 // Turn-scoped reminder: renders for this turn, then clears once a later user message exists.
1096 .addMessage(BetaMessageParam.builder()
1097 .role(BetaMessageParam.Role.SYSTEM)
1098 .clearAt(BetaMessageParam.ClearAt.NEXT_USER_MESSAGE)
1099 .content("The reader is on call: keep this reply under 50 words.")
1100 .build())
1101 .build();
1102
1103 BetaMessage response = client.beta().messages().create(params);
1104 response.content().stream()
1105 .flatMap(block -> block.text().stream())
1106 .forEach(textBlock -> IO.println(textBlock.text()));
1107 }
1108 ```
1109
1110 ```php PHP
1111 use Anthropic\Beta\AnthropicBeta;
1112 use Anthropic\Beta\Messages\BetaMessageParam;
1113 use Anthropic\Client;
1114
1115 $client = new Client();
1116
1117 $response = $client->beta->messages->create(
1118 model: 'claude-fable-5-1',
1119 maxTokens: 4096,
1120 messages: [
1121 BetaMessageParam::with(role: 'user', content: 'Draft a short status update on the database migration for the team channel.'),
1122 // Turn-scoped reminder: renders for this turn, then clears once a later user message exists.
1123 BetaMessageParam::with(
1124 role: 'system',
1125 clearAt: 'next_user_message',
1126 content: 'The reader is on call: keep this reply under 50 words.',
1127 ),
1128 ],
1129 betas: [AnthropicBeta::MID_CONVERSATION_SYSTEM_CLEAR_AT_2026_08_21],
1130 );
1131
1132 foreach ($response->content as $block) {
1133 if ($block->type === 'text') {
1134 echo $block->text, PHP_EOL;
1135 }
1136 }
1137 ```
1138
1139 ```ruby Ruby
1140 client = Anthropic::Client.new
1141
1142 response = client.beta.messages.create(
1143 model: "claude-fable-5-1",
1144 max_tokens: 4096,
1145 messages: [
1146 {role: "user", content: "Draft a short status update on the database migration for the team channel."},
1147 # Turn-scoped reminder: renders for this turn, then clears once a later user message exists.
1148 {role: "system", clear_at: :next_user_message, content: "The reader is on call: keep this reply under 50 words."}
1149 ],
1150 betas: [Anthropic::AnthropicBeta::MID_CONVERSATION_SYSTEM_CLEAR_AT_2026_08_21]
1151 )
1152
1153 response.content.each do |block|
1154 puts block.text if block.type == :text
1155 end
1156 ```
1157</CodeGroup>
1158
8141159## Combining with prompt caching
8151160
8161161Mid-conversation system messages and [prompt caching](https://platform.claude.com/docs/en/build-with-claude/prompt-caching) are designed to be used together:
from line 1165
8201165* **Append the system message after the breakpoint.** Because it comes after the cached prefix, it does not change the prefix hash and the cache still hits.
8211166* **A mid-conversation system message is itself cacheable.** Once it is in the conversation, it becomes part of the stable history. On the next turn you can move your cache breakpoint past it (or rely on [automatic caching](https://platform.claude.com/docs/en/build-with-claude/prompt-caching#automatic-caching) to do so) and the system message is read from cache like any other turn.
8221167
823Avoid editing or removing a mid-conversation system message that has already been sent. Like any other change to earlier messages, that invalidates the cache from that point forward. If the instruction needs to evolve, append a new system message rather than rewriting the old one. Consecutive system messages are accepted and treated as a single system section, which follows the same placement rule as a whole.
1168Avoid editing or removing a mid-conversation system message that has already been sent. Like any other change to earlier messages, that invalidates the cache from that point forward. On Claude Fable 5.1 it also invalidates the [thinking blocks](https://platform.claude.com/docs/en/build-with-claude/thinking#preserved-in-conversation) in every later assistant turn. For guidance that should apply to one turn only, use a [turn-scoped system message](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages#turn-scoped-system-messages) and leave it in place. If the instruction needs to evolve, append a new system message rather than rewriting the old one. Consecutive system messages are accepted and treated as a single system section, which follows the same placement rule as a whole.
8241169
8251170## Limitations
8261171
827* **Not for the first message.** A `system` message cannot be the first entry in `messages`. Use the top-level `system` field for instructions that apply from the very start.
828* **Placement is constrained.** A `system` message must immediately follow a `user` turn (including a `user` turn that carries `tool_result` blocks) or an `assistant` turn ending in a server tool result, and must precede an `assistant` turn or end the array. It cannot sit between a `tool_use` block and its `tool_result`. Placing it elsewhere returns a 400 error.
1172* **Not for the first message.** A `system` message that carries content cannot be the first entry in `messages`. Use the top-level `system` field for instructions that apply from the very start.
1173* **Placement is constrained.** A `system` message that carries content (`text`, `tool_addition`, or `tool_removal` blocks) must immediately follow a `user` turn (including a `user` turn that carries `tool_result` blocks) or an `assistant` turn ending in a server tool result, and must precede an `assistant` turn or end the array. It cannot sit between a `tool_use` block and its `tool_result`. Placing it elsewhere returns a 400 error. A message with empty `content` that only sets [`output_config.effort`](https://platform.claude.com/docs/en/build-with-claude/effort#change-effort-mid-conversation-beta) renders nothing at its position and is accepted anywhere in `messages`, including first or between an `assistant` turn and a `user` turn. Consecutive `system` messages are judged together, so adding a text-carrying message next to an effort-only one makes the whole group follow the content rule.
1174* **Turn-scoped messages are text-only and re-sent verbatim.** A `clear_at: "next_user_message"` message carries no `tool_addition`, `tool_removal`, `output_config`, or `cache_control`, and once cleared it must stay in `messages` byte-for-byte on later requests. See [Turn-scoped system messages](https://platform.claude.com/docs/en/build-with-claude/mid-conversation-system-messages#turn-scoped-system-messages).
8291175* **Not a place for untrusted content.** Claude treats system content as operator instructions and follows it. Do not place text from outside the conversation, such as raw tool output, retrieved documents, or web content, directly in a system message; doing so gives that text operator-level authority. Keep that data in `tool_result` blocks and continue to follow [Mitigate jailbreaks and prompt injections](https://platform.claude.com/docs/en/test-and-evaluate/strengthen-guardrails/mitigate-jailbreaks).
8301176
8311177## Related