From bbe4052d43ba109b4e19a1ef7ffc2fa21da6741d Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Thu, 12 Mar 2026 20:53:33 -0700 Subject: [PATCH 1/4] Add fine-grained system prompt customization (customize mode) Add a new 'customize' mode for systemMessage configuration, enabling SDK consumers to selectively override individual sections of the CLI system prompt while preserving the rest. This sits between the existing 'append' and 'replace' modes. 9 configurable sections: identity, tone, tool_efficiency, environment_context, code_change_rules, guidelines, safety, tool_instructions, custom_instructions. 4 override actions per section: replace, remove, append, prepend. Unknown section IDs are handled gracefully: content-bearing overrides are appended to additional instructions with a warning, and remove on unknown sections is silently ignored. Types and constants added to all 4 SDK languages (TypeScript, Python, Go, .NET). Documentation updated across all READMEs and getting-started guide. Companion runtime PR: github/copilot-agent-runtime#4751 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/getting-started.md | 24 +++++++++- dotnet/README.md | 28 +++++++++++ dotnet/src/Client.cs | 1 + dotnet/src/SdkProtocolVersion.cs | 5 +- dotnet/src/Types.cs | 79 ++++++++++++++++++++++++++++++-- go/README.md | 5 +- go/types.go | 33 +++++++++++-- nodejs/README.md | 40 +++++++++++++++- nodejs/src/index.ts | 5 +- nodejs/src/types.ts | 71 +++++++++++++++++++++++++++- nodejs/test/e2e/session.test.ts | 27 +++++++++++ python/README.md | 5 +- python/copilot/__init__.py | 14 ++++++ python/copilot/types.py | 55 ++++++++++++++++++++-- 14 files changed, 371 insertions(+), 21 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 15f11e8b7..220fc66f1 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -1251,7 +1251,7 @@ const session = await client.createSession({ ### Customize the System Message -Control the AI's behavior and personality: +Control the AI's behavior and personality by appending instructions: ```typescript const session = await client.createSession({ @@ -1261,6 +1261,28 @@ const session = await client.createSession({ }); ``` +For more fine-grained control, use `mode: "customize"` to override individual sections of the system prompt while preserving the rest: + +```typescript +const session = await client.createSession({ + systemMessage: { + mode: "customize", + sections: { + tone: { action: "replace", content: "Respond in a warm, professional tone. Be thorough in explanations." }, + code_change_rules: { action: "remove" }, + guidelines: { action: "append", content: "\n* Always cite data sources" }, + }, + content: "Focus on financial analysis and reporting.", + }, +}); +``` + +Available section IDs: `identity`, `tone`, `tool_efficiency`, `environment_context`, `code_change_rules`, `guidelines`, `safety`, `tool_instructions`, `custom_instructions`. + +Each override supports four actions: `replace`, `remove`, `append`, and `prepend`. Unknown section IDs are handled gracefully — content is appended to additional instructions and a warning is emitted; `remove` on unknown sections is silently ignored. + +See the language-specific SDK READMEs for examples in [TypeScript](../nodejs/README.md), [Python](../python/README.md), [Go](../go/README.md), and [C#](../dotnet/README.md). + --- ## Connecting to an External CLI Server diff --git a/dotnet/README.md b/dotnet/README.md index 712323c0c..f881e2925 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -488,6 +488,34 @@ var session = await client.CreateSessionAsync(new SessionConfig }); ``` +#### Customize Mode + +Use `Mode = SystemMessageMode.Customize` to selectively override individual sections of the prompt while preserving the rest: + +```csharp +var session = await client.CreateSessionAsync(new SessionConfig +{ + Model = "gpt-5", + SystemMessage = new SystemMessageConfig + { + Mode = SystemMessageMode.Customize, + Sections = new Dictionary + { + [SystemPromptSections.Tone] = new() { Action = SectionOverrideAction.Replace, Content = "Respond in a warm, professional tone. Be thorough in explanations." }, + [SystemPromptSections.CodeChangeRules] = new() { Action = SectionOverrideAction.Remove }, + [SystemPromptSections.Guidelines] = new() { Action = SectionOverrideAction.Append, Content = "\n* Always cite data sources" }, + }, + Content = "Focus on financial analysis and reporting." + } +}); +``` + +Available section IDs are defined as constants on `SystemPromptSections`: `Identity`, `Tone`, `ToolEfficiency`, `EnvironmentContext`, `CodeChangeRules`, `Guidelines`, `Safety`, `ToolInstructions`, `CustomInstructions`. + +Each section override supports four actions: `Replace`, `Remove`, `Append`, and `Prepend`. Unknown section IDs are handled gracefully: content is appended to additional instructions, and `Remove` overrides are silently ignored. + +#### Replace Mode + For full control (removes all guardrails), use `Mode = SystemMessageMode.Replace`: ```csharp diff --git a/dotnet/src/Client.cs b/dotnet/src/Client.cs index a9ad1fccd..49f95324c 100644 --- a/dotnet/src/Client.cs +++ b/dotnet/src/Client.cs @@ -1683,6 +1683,7 @@ private static LogLevel MapLevel(TraceEventType eventType) [JsonSerializable(typeof(ProviderConfig))] [JsonSerializable(typeof(ResumeSessionRequest))] [JsonSerializable(typeof(ResumeSessionResponse))] + [JsonSerializable(typeof(SectionOverride))] [JsonSerializable(typeof(SessionMetadata))] [JsonSerializable(typeof(SystemMessageConfig))] [JsonSerializable(typeof(ToolCallResponseV2))] diff --git a/dotnet/src/SdkProtocolVersion.cs b/dotnet/src/SdkProtocolVersion.cs index f3d8f04c5..889af460b 100644 --- a/dotnet/src/SdkProtocolVersion.cs +++ b/dotnet/src/SdkProtocolVersion.cs @@ -16,8 +16,5 @@ internal static class SdkProtocolVersion /// /// Gets the SDK protocol version. /// - public static int GetVersion() - { - return Version; - } + public static int GetVersion() => Version; } diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index 84e7feaed..b63663f94 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -968,7 +968,71 @@ public enum SystemMessageMode Append, /// Replace the default system message entirely. [JsonStringEnumMemberName("replace")] - Replace + Replace, + /// Override individual sections of the system prompt. + [JsonStringEnumMemberName("customize")] + Customize +} + +/// +/// Specifies the operation to perform on a system prompt section. +/// +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum SectionOverrideAction +{ + /// Replace the section content entirely. + [JsonStringEnumMemberName("replace")] + Replace, + /// Remove the section from the prompt. + [JsonStringEnumMemberName("remove")] + Remove, + /// Append content after the existing section. + [JsonStringEnumMemberName("append")] + Append, + /// Prepend content before the existing section. + [JsonStringEnumMemberName("prepend")] + Prepend +} + +/// +/// Override operation for a single system prompt section. +/// +public class SectionOverride +{ + /// + /// The operation to perform on this section. + /// + public SectionOverrideAction Action { get; set; } + + /// + /// Content for the override. Optional for all actions. Ignored for remove. + /// + public string? Content { get; set; } +} + +/// +/// Known system prompt section identifiers for the "customize" mode. +/// +public static class SystemPromptSections +{ + /// Agent identity preamble and mode statement. + public const string Identity = "identity"; + /// Response style, conciseness rules, output formatting preferences. + public const string Tone = "tone"; + /// Tool usage patterns, parallel calling, batching guidelines. + public const string ToolEfficiency = "tool_efficiency"; + /// CWD, OS, git root, directory listing, available tools. + public const string EnvironmentContext = "environment_context"; + /// Coding rules, linting/testing, ecosystem tools, style. + public const string CodeChangeRules = "code_change_rules"; + /// Tips, behavioral best practices, behavioral guidelines. + public const string Guidelines = "guidelines"; + /// Environment limitations, prohibited actions, security policies. + public const string Safety = "safety"; + /// Per-tool usage instructions. + public const string ToolInstructions = "tool_instructions"; + /// Repository and organization custom instructions. + public const string CustomInstructions = "custom_instructions"; } /// @@ -977,13 +1041,21 @@ public enum SystemMessageMode public class SystemMessageConfig { /// - /// How the system message is applied (append or replace). + /// How the system message is applied (append, replace, or customize). /// public SystemMessageMode? Mode { get; set; } + /// - /// Content of the system message. + /// Content of the system message. Used by append and replace modes. + /// In customize mode, additional content appended after all sections. /// public string? Content { get; set; } + + /// + /// Section-level overrides for customize mode. + /// Keys are section identifiers (see ). + /// + public Dictionary? Sections { get; set; } } /// @@ -2061,6 +2133,7 @@ public class SetForegroundSessionResponse [JsonSerializable(typeof(SessionLifecycleEvent))] [JsonSerializable(typeof(SessionLifecycleEventMetadata))] [JsonSerializable(typeof(SessionListFilter))] +[JsonSerializable(typeof(SectionOverride))] [JsonSerializable(typeof(SessionMetadata))] [JsonSerializable(typeof(SetForegroundSessionResponse))] [JsonSerializable(typeof(SystemMessageConfig))] diff --git a/go/README.md b/go/README.md index f87c3d1b8..7490a4deb 100644 --- a/go/README.md +++ b/go/README.md @@ -149,7 +149,10 @@ Event types: `SessionLifecycleCreated`, `SessionLifecycleDeleted`, `SessionLifec - `ReasoningEffort` (string): Reasoning effort level for models that support it ("low", "medium", "high", "xhigh"). Use `ListModels()` to check which models support this option. - `SessionID` (string): Custom session ID - `Tools` ([]Tool): Custom tools exposed to the CLI -- `SystemMessage` (\*SystemMessageConfig): System message configuration +- `SystemMessage` (\*SystemMessageConfig): System message configuration. Supports three modes: + - **append** (default): Appends `Content` after the SDK-managed prompt + - **replace**: Replaces the entire prompt with `Content` + - **customize**: Selectively override individual sections via `Sections` map (keys: `SectionIdentity`, `SectionTone`, `SectionToolEfficiency`, `SectionEnvironmentContext`, `SectionCodeChangeRules`, `SectionGuidelines`, `SectionSafety`, `SectionToolInstructions`, `SectionCustomInstructions`; values: `SectionOverride` with `Action` and optional `Content`) - `Provider` (\*ProviderConfig): Custom API provider configuration (BYOK). See [Custom Providers](#custom-providers) section. - `Streaming` (bool): Enable streaming delta events - `InfiniteSessions` (\*InfiniteSessionConfig): Automatic context compaction configuration diff --git a/go/types.go b/go/types.go index fd9968e3e..c5ac33fb8 100644 --- a/go/types.go +++ b/go/types.go @@ -111,6 +111,27 @@ func Float64(v float64) *float64 { return &v } +// Known system prompt section identifiers for the "customize" mode. +const ( + SectionIdentity = "identity" + SectionTone = "tone" + SectionToolEfficiency = "tool_efficiency" + SectionEnvironmentContext = "environment_context" + SectionCodeChangeRules = "code_change_rules" + SectionGuidelines = "guidelines" + SectionSafety = "safety" + SectionToolInstructions = "tool_instructions" + SectionCustomInstructions = "custom_instructions" +) + +// SectionOverride defines an override operation for a single system prompt section. +type SectionOverride struct { + // Action is the operation to perform: "replace", "remove", "append", or "prepend". + Action string `json:"action"` + // Content for the override. Optional for all actions. Ignored for "remove". + Content string `json:"content,omitempty"` +} + // SystemMessageAppendConfig is append mode: use CLI foundation with optional appended content. type SystemMessageAppendConfig struct { // Mode is optional, defaults to "append" @@ -129,11 +150,15 @@ type SystemMessageReplaceConfig struct { } // SystemMessageConfig represents system message configuration for session creation. -// Use SystemMessageAppendConfig for default behavior, SystemMessageReplaceConfig for full control. -// In Go, use one struct or the other based on your needs. +// - Append mode (default): SDK foundation + optional custom content +// - Replace mode: Full control, caller provides entire system message +// - Customize mode: Section-level overrides with graceful fallback +// +// In Go, use one struct and set fields appropriate for the desired mode. type SystemMessageConfig struct { - Mode string `json:"mode,omitempty"` - Content string `json:"content,omitempty"` + Mode string `json:"mode,omitempty"` + Content string `json:"content,omitempty"` + Sections map[string]SectionOverride `json:"sections,omitempty"` } // PermissionRequestResultKind represents the kind of a permission request result. diff --git a/nodejs/README.md b/nodejs/README.md index af37b27bf..48d2a1292 100644 --- a/nodejs/README.md +++ b/nodejs/README.md @@ -458,7 +458,45 @@ const session = await client.createSession({ }); ``` -The SDK auto-injects environment context, tool instructions, and security guardrails. The default CLI persona is preserved, and your `content` is appended after SDK-managed sections. To change the persona or fully redefine the prompt, use `mode: "replace"`. +The SDK auto-injects environment context, tool instructions, and security guardrails. The default CLI persona is preserved, and your `content` is appended after SDK-managed sections. To change the persona or fully redefine the prompt, use `mode: "replace"` or `mode: "customize"`. + +#### Customize Mode + +Use `mode: "customize"` to selectively override individual sections of the prompt while preserving the rest: + +```typescript +import { SYSTEM_PROMPT_SECTIONS } from "@anthropic-ai/sdk"; +import type { SectionOverride, SystemPromptSection } from "@anthropic-ai/sdk"; + +const session = await client.createSession({ + model: "gpt-5", + systemMessage: { + mode: "customize", + sections: { + // Replace the tone/style section + tone: { action: "replace", content: "Respond in a warm, professional tone. Be thorough in explanations." }, + // Remove coding-specific rules + code_change_rules: { action: "remove" }, + // Append to existing guidelines + guidelines: { action: "append", content: "\n* Always cite data sources" }, + }, + // Additional instructions appended after all sections + content: "Focus on financial analysis and reporting.", + }, +}); +``` + +Available section IDs: `identity`, `tone`, `tool_efficiency`, `environment_context`, `code_change_rules`, `guidelines`, `safety`, `tool_instructions`, `custom_instructions`. Use the `SYSTEM_PROMPT_SECTIONS` constant for descriptions of each section. + +Each section override supports four actions: +- **`replace`** — Replace the section content entirely +- **`remove`** — Remove the section from the prompt +- **`append`** — Add content after the existing section +- **`prepend`** — Add content before the existing section + +Unknown section IDs are handled gracefully: content from `replace`/`append`/`prepend` overrides is appended to additional instructions, and `remove` overrides are silently ignored. + +#### Replace Mode For full control (removes all guardrails), use `mode: "replace"`: diff --git a/nodejs/src/index.ts b/nodejs/src/index.ts index 214b80050..a717d8837 100644 --- a/nodejs/src/index.ts +++ b/nodejs/src/index.ts @@ -10,7 +10,7 @@ export { CopilotClient } from "./client.js"; export { CopilotSession, type AssistantMessageEvent } from "./session.js"; -export { defineTool, approveAll } from "./types.js"; +export { defineTool, approveAll, SYSTEM_PROMPT_SECTIONS } from "./types.js"; export type { ConnectionState, CopilotClientOptions, @@ -31,6 +31,7 @@ export type { PermissionRequest, PermissionRequestResult, ResumeSessionConfig, + SectionOverride, SessionConfig, SessionEvent, SessionEventHandler, @@ -44,7 +45,9 @@ export type { SessionMetadata, SystemMessageAppendConfig, SystemMessageConfig, + SystemMessageCustomizeConfig, SystemMessageReplaceConfig, + SystemPromptSection, TelemetryConfig, TraceContext, TraceContextProvider, diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index 9576b6925..8a6bcf8d5 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -272,6 +272,50 @@ export interface ToolCallResponsePayload { result: ToolResult; } +/** + * Known system prompt section identifiers for the "customize" mode. + * Each section corresponds to a distinct part of the system prompt. + */ +export type SystemPromptSection = + | "identity" + | "tone" + | "tool_efficiency" + | "environment_context" + | "code_change_rules" + | "guidelines" + | "safety" + | "tool_instructions" + | "custom_instructions"; + +/** Section metadata for documentation and tooling. */ +export const SYSTEM_PROMPT_SECTIONS: Record = { + identity: { description: "Agent identity preamble and mode statement" }, + tone: { description: "Response style, conciseness rules, output formatting preferences" }, + tool_efficiency: { description: "Tool usage patterns, parallel calling, batching guidelines" }, + environment_context: { description: "CWD, OS, git root, directory listing, available tools" }, + code_change_rules: { description: "Coding rules, linting/testing, ecosystem tools, style" }, + guidelines: { description: "Tips, behavioral best practices, behavioral guidelines" }, + safety: { description: "Environment limitations, prohibited actions, security policies" }, + tool_instructions: { description: "Per-tool usage instructions" }, + custom_instructions: { description: "Repository and organization custom instructions" }, +}; + +/** + * Override operation for a single system prompt section. + */ +export interface SectionOverride { + /** The operation to perform on this section. */ + action: "replace" | "remove" | "append" | "prepend"; + + /** + * Content for the override. Optional for all actions. + * - For replace, omitting content replaces with an empty string. + * - For append/prepend, content is added before/after the existing section. + * - Ignored for the remove action. + */ + content?: string; +} + /** * Append mode: Use CLI foundation with optional appended content (default). */ @@ -298,12 +342,37 @@ export interface SystemMessageReplaceConfig { content: string; } +/** + * Customize mode: Override individual sections of the system prompt. + * Keeps the SDK-managed prompt structure while allowing targeted modifications. + */ +export interface SystemMessageCustomizeConfig { + mode: "customize"; + + /** + * Override specific sections of the system prompt by section ID. + * Unknown section IDs gracefully fall back: content-bearing overrides are appended + * to additional instructions, and "remove" on unknown sections is a silent no-op. + */ + sections?: Partial>; + + /** + * Additional content appended after all sections. + * Equivalent to append mode's content field — provided for convenience. + */ + content?: string; +} + /** * System message configuration for session creation. * - Append mode (default): SDK foundation + optional custom content * - Replace mode: Full control, caller provides entire system message + * - Customize mode: Section-level overrides with graceful fallback */ -export type SystemMessageConfig = SystemMessageAppendConfig | SystemMessageReplaceConfig; +export type SystemMessageConfig = + | SystemMessageAppendConfig + | SystemMessageReplaceConfig + | SystemMessageCustomizeConfig; /** * Permission request types from the server diff --git a/nodejs/test/e2e/session.test.ts b/nodejs/test/e2e/session.test.ts index 1eb8a175d..dbcbed8bb 100644 --- a/nodejs/test/e2e/session.test.ts +++ b/nodejs/test/e2e/session.test.ts @@ -96,6 +96,33 @@ describe("Sessions", async () => { expect(systemMessage).toEqual(testSystemMessage); // Exact match }); + it("should create a session with customized systemMessage config", async () => { + const customTone = "Respond in a warm, professional tone. Be thorough in explanations."; + const appendedContent = "Always mention quarterly earnings."; + const session = await client.createSession({ + onPermissionRequest: approveAll, + systemMessage: { + mode: "customize", + sections: { + tone: { action: "replace", content: customTone }, + code_change_rules: { action: "remove" }, + }, + content: appendedContent, + }, + }); + + const assistantMessage = await session.sendAndWait({ prompt: "Who are you?" }); + expect(assistantMessage?.data.content).toBeDefined(); + + // Validate the system message sent to the model + const traffic = await openAiEndpoint.getExchanges(); + const systemMessage = getSystemMessage(traffic[0]); + expect(systemMessage).toContain(customTone); + expect(systemMessage).toContain(appendedContent); + // The code_change_rules section should have been removed + expect(systemMessage).not.toContain(""); + }); + it("should create a session with availableTools", async () => { const session = await client.createSession({ onPermissionRequest: approveAll, diff --git a/python/README.md b/python/README.md index 6d1c81281..9d8991884 100644 --- a/python/README.md +++ b/python/README.md @@ -140,7 +140,10 @@ CopilotClient( - `reasoning_effort` (str): Reasoning effort level for models that support it ("low", "medium", "high", "xhigh"). Use `list_models()` to check which models support this option. - `session_id` (str): Custom session ID - `tools` (list): Custom tools exposed to the CLI -- `system_message` (dict): System message configuration +- `system_message` (dict): System message configuration. Supports three modes: + - **append** (default): Appends `content` after the SDK-managed prompt + - **replace**: Replaces the entire prompt with `content` + - **customize**: Selectively override individual sections via `sections` dict (keys: `"identity"`, `"tone"`, `"tool_efficiency"`, `"environment_context"`, `"code_change_rules"`, `"guidelines"`, `"safety"`, `"tool_instructions"`, `"custom_instructions"`; values: `SectionOverride` with `action` and optional `content`) - `streaming` (bool): Enable streaming delta events - `provider` (dict): Custom API provider configuration (BYOK). See [Custom Providers](#custom-providers) section. - `infinite_sessions` (dict): Automatic context compaction configuration diff --git a/python/copilot/__init__.py b/python/copilot/__init__.py index c25ea4021..7f185f99e 100644 --- a/python/copilot/__init__.py +++ b/python/copilot/__init__.py @@ -27,6 +27,7 @@ PingResponse, ProviderConfig, ResumeSessionConfig, + SectionOverride, SessionConfig, SessionContext, SessionEvent, @@ -34,6 +35,12 @@ SessionMetadata, StopError, SubprocessConfig, + SystemMessageAppendConfig, + SystemMessageConfig, + SystemMessageCustomizeConfig, + SystemMessageReplaceConfig, + SystemPromptSection, + SYSTEM_PROMPT_SECTIONS, TelemetryConfig, Tool, ToolHandler, @@ -65,6 +72,7 @@ "PingResponse", "ProviderConfig", "ResumeSessionConfig", + "SectionOverride", "SessionConfig", "SessionContext", "SessionEvent", @@ -72,6 +80,12 @@ "SessionMetadata", "StopError", "SubprocessConfig", + "SystemMessageAppendConfig", + "SystemMessageConfig", + "SystemMessageCustomizeConfig", + "SystemMessageReplaceConfig", + "SystemPromptSection", + "SYSTEM_PROMPT_SECTIONS", "TelemetryConfig", "Tool", "ToolHandler", diff --git a/python/copilot/types.py b/python/copilot/types.py index af124bb0a..f92d3baaf 100644 --- a/python/copilot/types.py +++ b/python/copilot/types.py @@ -6,7 +6,7 @@ from collections.abc import Awaitable, Callable from dataclasses import KW_ONLY, dataclass, field -from typing import Any, Literal, NotRequired, TypedDict +from typing import Any, Literal, NotRequired, Required, TypedDict # Import generated SessionEvent types from .generated.session_events import ( @@ -202,7 +202,41 @@ class Tool: # System message configuration (discriminated union) -# Use SystemMessageAppendConfig for default behavior, SystemMessageReplaceConfig for full control +# Use SystemMessageAppendConfig for default behavior, +# SystemMessageReplaceConfig for full control, +# or SystemMessageCustomizeConfig for section-level overrides. + +# Known system prompt section identifiers for the "customize" mode. +SystemPromptSection = Literal[ + "identity", + "tone", + "tool_efficiency", + "environment_context", + "code_change_rules", + "guidelines", + "safety", + "tool_instructions", + "custom_instructions", +] + +SYSTEM_PROMPT_SECTIONS: dict[SystemPromptSection, str] = { + "identity": "Agent identity preamble and mode statement", + "tone": "Response style, conciseness rules, output formatting preferences", + "tool_efficiency": "Tool usage patterns, parallel calling, batching guidelines", + "environment_context": "CWD, OS, git root, directory listing, available tools", + "code_change_rules": "Coding rules, linting/testing, ecosystem tools, style", + "guidelines": "Tips, behavioral best practices, behavioral guidelines", + "safety": "Environment limitations, prohibited actions, security policies", + "tool_instructions": "Per-tool usage instructions", + "custom_instructions": "Repository and organization custom instructions", +} + + +class SectionOverride(TypedDict, total=False): + """Override operation for a single system prompt section.""" + + action: Required[Literal["replace", "remove", "append", "prepend"]] + content: NotRequired[str] class SystemMessageAppendConfig(TypedDict, total=False): @@ -224,8 +258,21 @@ class SystemMessageReplaceConfig(TypedDict): content: str -# Union type - use one or the other -SystemMessageConfig = SystemMessageAppendConfig | SystemMessageReplaceConfig +class SystemMessageCustomizeConfig(TypedDict, total=False): + """ + Customize mode: Override individual sections of the system prompt. + Keeps the SDK-managed prompt structure while allowing targeted modifications. + """ + + mode: Required[Literal["customize"]] + sections: NotRequired[dict[SystemPromptSection, SectionOverride]] + content: NotRequired[str] + + +# Union type - use one based on your needs +SystemMessageConfig = ( + SystemMessageAppendConfig | SystemMessageReplaceConfig | SystemMessageCustomizeConfig +) # Permission result types From d20c1da5382d190559df9dd77fe425903b5fa5ca Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Fri, 13 Mar 2026 09:18:39 -0700 Subject: [PATCH 2/4] Address PR review feedback: fix docs, add examples and E2E tests - Fix incorrect package name in nodejs/README.md (@anthropic-ai/sdk -> @github/copilot-sdk) - Add standalone 'System Message Customization' sections with full code examples to Python and Go READMEs (matching TypeScript/.NET) - Add E2E tests for customize mode to Python, Go, and .NET (matching existing Node.js E2E test coverage) - Fix 'end of the prompt' wording in docs to 'additional instructions' Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- dotnet/test/SessionTests.cs | 31 +++++++++++++++++++++ go/README.md | 46 +++++++++++++++++++++++++++++++ go/internal/e2e/session_test.go | 45 +++++++++++++++++++++++++++++++ nodejs/README.md | 4 +-- python/README.md | 48 +++++++++++++++++++++++++++++++++ python/e2e/test_session.py | 29 ++++++++++++++++++++ 6 files changed, 201 insertions(+), 2 deletions(-) diff --git a/dotnet/test/SessionTests.cs b/dotnet/test/SessionTests.cs index 8cd4c84e5..cf5313ef2 100644 --- a/dotnet/test/SessionTests.cs +++ b/dotnet/test/SessionTests.cs @@ -91,6 +91,37 @@ public async Task Should_Create_A_Session_With_Replaced_SystemMessage_Config() Assert.Equal(testSystemMessage, GetSystemMessage(traffic[0])); } + [Fact] + public async Task Should_Create_A_Session_With_Customized_SystemMessage_Config() + { + var customTone = "Respond in a warm, professional tone. Be thorough in explanations."; + var appendedContent = "Always mention quarterly earnings."; + var session = await CreateSessionAsync(new SessionConfig + { + SystemMessage = new SystemMessageConfig + { + Mode = SystemMessageMode.Customize, + Sections = new Dictionary + { + [SystemPromptSections.Tone] = new() { Action = SectionOverrideAction.Replace, Content = customTone }, + [SystemPromptSections.CodeChangeRules] = new() { Action = SectionOverrideAction.Remove }, + }, + Content = appendedContent + } + }); + + await session.SendAsync(new MessageOptions { Prompt = "Who are you?" }); + var assistantMessage = await TestHelper.GetFinalAssistantMessageAsync(session); + Assert.NotNull(assistantMessage); + + var traffic = await Ctx.GetExchangesAsync(); + Assert.NotEmpty(traffic); + var systemMessage = GetSystemMessage(traffic[0]); + Assert.Contains(customTone, systemMessage); + Assert.Contains(appendedContent, systemMessage); + Assert.DoesNotContain("", systemMessage); + } + [Fact] public async Task Should_Create_A_Session_With_AvailableTools() { diff --git a/go/README.md b/go/README.md index 7490a4deb..b58bef580 100644 --- a/go/README.md +++ b/go/README.md @@ -179,6 +179,52 @@ Event types: `SessionLifecycleCreated`, `SessionLifecycleDeleted`, `SessionLifec - `Bool(v bool) *bool` - Helper to create bool pointers for `AutoStart` option +### System Message Customization + +Control the system prompt using `SystemMessage` in session config: + +```go +session, err := client.CreateSession(ctx, &copilot.SessionConfig{ + SystemMessage: &copilot.SystemMessageConfig{ + Content: "Always check for security vulnerabilities before suggesting changes.", + }, +}) +``` + +The SDK auto-injects environment context, tool instructions, and security guardrails. The default CLI persona is preserved, and your `Content` is appended after SDK-managed sections. To change the persona or fully redefine the prompt, use `Mode: "replace"` or `Mode: "customize"`. + +#### Customize Mode + +Use `Mode: "customize"` to selectively override individual sections of the prompt while preserving the rest: + +```go +session, err := client.CreateSession(ctx, &copilot.SessionConfig{ + SystemMessage: &copilot.SystemMessageConfig{ + Mode: "customize", + Sections: map[string]copilot.SectionOverride{ + // Replace the tone/style section + copilot.SectionTone: {Action: "replace", Content: "Respond in a warm, professional tone. Be thorough in explanations."}, + // Remove coding-specific rules + copilot.SectionCodeChangeRules: {Action: "remove"}, + // Append to existing guidelines + copilot.SectionGuidelines: {Action: "append", Content: "\n* Always cite data sources"}, + }, + // Additional instructions appended after all sections + Content: "Focus on financial analysis and reporting.", + }, +}) +``` + +Available section constants: `SectionIdentity`, `SectionTone`, `SectionToolEfficiency`, `SectionEnvironmentContext`, `SectionCodeChangeRules`, `SectionGuidelines`, `SectionSafety`, `SectionToolInstructions`, `SectionCustomInstructions`. + +Each section override supports four actions: +- **`replace`** — Replace the section content entirely +- **`remove`** — Remove the section from the prompt +- **`append`** — Add content after the existing section +- **`prepend`** — Add content before the existing section + +Unknown section IDs are handled gracefully: content from `replace`/`append`/`prepend` overrides is appended to additional instructions, and `remove` overrides are silently ignored. + ## Image Support The SDK supports image attachments via the `Attachments` field in `MessageOptions`. You can attach images by providing their file path: diff --git a/go/internal/e2e/session_test.go b/go/internal/e2e/session_test.go index c3c9cc009..7a2f1d20f 100644 --- a/go/internal/e2e/session_test.go +++ b/go/internal/e2e/session_test.go @@ -184,6 +184,51 @@ func TestSession(t *testing.T) { } }) + t.Run("should create a session with customized systemMessage config", func(t *testing.T) { + ctx.ConfigureForTest(t) + + customTone := "Respond in a warm, professional tone. Be thorough in explanations." + appendedContent := "Always mention quarterly earnings." + session, err := client.CreateSession(t.Context(), &copilot.SessionConfig{ + OnPermissionRequest: copilot.PermissionHandler.ApproveAll, + SystemMessage: &copilot.SystemMessageConfig{ + Mode: "customize", + Sections: map[string]copilot.SectionOverride{ + copilot.SectionTone: {Action: "replace", Content: customTone}, + copilot.SectionCodeChangeRules: {Action: "remove"}, + }, + Content: appendedContent, + }, + }) + if err != nil { + t.Fatalf("Failed to create session: %v", err) + } + + _, err = session.SendAndWait(t.Context(), copilot.MessageOptions{Prompt: "Who are you?"}) + if err != nil { + t.Fatalf("Failed to send message: %v", err) + } + + // Validate the system message sent to the model + traffic, err := ctx.GetExchanges() + if err != nil { + t.Fatalf("Failed to get exchanges: %v", err) + } + if len(traffic) == 0 { + t.Fatal("Expected at least one exchange") + } + systemMessage := getSystemMessage(traffic[0]) + if !strings.Contains(systemMessage, customTone) { + t.Errorf("Expected system message to contain custom tone, got %q", systemMessage) + } + if !strings.Contains(systemMessage, appendedContent) { + t.Errorf("Expected system message to contain appended content, got %q", systemMessage) + } + if strings.Contains(systemMessage, "") { + t.Error("Expected system message to NOT contain code_change_instructions (it was removed)") + } + }) + t.Run("should create a session with availableTools", func(t *testing.T) { ctx.ConfigureForTest(t) diff --git a/nodejs/README.md b/nodejs/README.md index 48d2a1292..48a60c9af 100644 --- a/nodejs/README.md +++ b/nodejs/README.md @@ -465,8 +465,8 @@ The SDK auto-injects environment context, tool instructions, and security guardr Use `mode: "customize"` to selectively override individual sections of the prompt while preserving the rest: ```typescript -import { SYSTEM_PROMPT_SECTIONS } from "@anthropic-ai/sdk"; -import type { SectionOverride, SystemPromptSection } from "@anthropic-ai/sdk"; +import { SYSTEM_PROMPT_SECTIONS } from "@github/copilot-sdk"; +import type { SectionOverride, SystemPromptSection } from "@github/copilot-sdk"; const session = await client.createSession({ model: "gpt-5", diff --git a/python/README.md b/python/README.md index 9d8991884..fae57b5ec 100644 --- a/python/README.md +++ b/python/README.md @@ -179,6 +179,54 @@ unsubscribe() - `session.foreground` - A session became the foreground session in TUI - `session.background` - A session is no longer the foreground session +### System Message Customization + +Control the system prompt using `system_message` in session config: + +```python +session = await client.create_session( + system_message={ + "content": "Always check for security vulnerabilities before suggesting changes." + } +) +``` + +The SDK auto-injects environment context, tool instructions, and security guardrails. The default CLI persona is preserved, and your `content` is appended after SDK-managed sections. To change the persona or fully redefine the prompt, use `mode: "replace"` or `mode: "customize"`. + +#### Customize Mode + +Use `mode: "customize"` to selectively override individual sections of the prompt while preserving the rest: + +```python +from copilot import SYSTEM_PROMPT_SECTIONS + +session = await client.create_session( + system_message={ + "mode": "customize", + "sections": { + # Replace the tone/style section + "tone": {"action": "replace", "content": "Respond in a warm, professional tone. Be thorough in explanations."}, + # Remove coding-specific rules + "code_change_rules": {"action": "remove"}, + # Append to existing guidelines + "guidelines": {"action": "append", "content": "\n* Always cite data sources"}, + }, + # Additional instructions appended after all sections + "content": "Focus on financial analysis and reporting.", + } +) +``` + +Available section IDs: `"identity"`, `"tone"`, `"tool_efficiency"`, `"environment_context"`, `"code_change_rules"`, `"guidelines"`, `"safety"`, `"tool_instructions"`, `"custom_instructions"`. Use the `SYSTEM_PROMPT_SECTIONS` dict for descriptions of each section. + +Each section override supports four actions: +- **`replace`** — Replace the section content entirely +- **`remove`** — Remove the section from the prompt +- **`append`** — Add content after the existing section +- **`prepend`** — Add content before the existing section + +Unknown section IDs are handled gracefully: content from `replace`/`append`/`prepend` overrides is appended to additional instructions, and `remove` overrides are silently ignored. + ### Tools Define tools with automatic JSON schema generation using the `@define_tool` decorator and Pydantic models: diff --git a/python/e2e/test_session.py b/python/e2e/test_session.py index a2bc33bdb..3dd753333 100644 --- a/python/e2e/test_session.py +++ b/python/e2e/test_session.py @@ -86,6 +86,35 @@ async def test_should_create_a_session_with_replaced_systemMessage_config( system_message = _get_system_message(traffic[0]) assert system_message == test_system_message # Exact match + async def test_should_create_a_session_with_customized_systemMessage_config( + self, ctx: E2ETestContext + ): + custom_tone = "Respond in a warm, professional tone. Be thorough in explanations." + appended_content = "Always mention quarterly earnings." + session = await ctx.client.create_session( + { + "system_message": { + "mode": "customize", + "sections": { + "tone": {"action": "replace", "content": custom_tone}, + "code_change_rules": {"action": "remove"}, + }, + "content": appended_content, + }, + "on_permission_request": PermissionHandler.approve_all, + } + ) + + assistant_message = await session.send_and_wait({"prompt": "Who are you?"}) + assert assistant_message is not None + + # Validate the system message sent to the model + traffic = await ctx.get_exchanges() + system_message = _get_system_message(traffic[0]) + assert custom_tone in system_message + assert appended_content in system_message + assert "" not in system_message + async def test_should_create_a_session_with_availableTools(self, ctx: E2ETestContext): session = await ctx.client.create_session( { From 0348b8ebf193d5ce73f7d76990c5243d8948d3e1 Mon Sep 17 00:00:00 2001 From: Mackinnon Buck Date: Tue, 17 Mar 2026 09:12:10 -0700 Subject: [PATCH 3/4] Add last_instructions configurable section Expose lastInstructions as a customizable section across all 4 SDKs, addressing review feedback about duplicate tool-efficiency blocks. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/getting-started.md | 2 +- dotnet/README.md | 2 +- dotnet/src/Types.cs | 2 ++ go/README.md | 4 ++-- go/types.go | 1 + nodejs/README.md | 2 +- nodejs/src/types.ts | 4 +++- python/README.md | 4 ++-- python/copilot/types.py | 2 ++ 9 files changed, 15 insertions(+), 8 deletions(-) diff --git a/docs/getting-started.md b/docs/getting-started.md index 220fc66f1..f6161cd9d 100644 --- a/docs/getting-started.md +++ b/docs/getting-started.md @@ -1277,7 +1277,7 @@ const session = await client.createSession({ }); ``` -Available section IDs: `identity`, `tone`, `tool_efficiency`, `environment_context`, `code_change_rules`, `guidelines`, `safety`, `tool_instructions`, `custom_instructions`. +Available section IDs: `identity`, `tone`, `tool_efficiency`, `environment_context`, `code_change_rules`, `guidelines`, `safety`, `tool_instructions`, `custom_instructions`, `last_instructions`. Each override supports four actions: `replace`, `remove`, `append`, and `prepend`. Unknown section IDs are handled gracefully — content is appended to additional instructions and a warning is emitted; `remove` on unknown sections is silently ignored. diff --git a/dotnet/README.md b/dotnet/README.md index f881e2925..2c0c864f8 100644 --- a/dotnet/README.md +++ b/dotnet/README.md @@ -510,7 +510,7 @@ var session = await client.CreateSessionAsync(new SessionConfig }); ``` -Available section IDs are defined as constants on `SystemPromptSections`: `Identity`, `Tone`, `ToolEfficiency`, `EnvironmentContext`, `CodeChangeRules`, `Guidelines`, `Safety`, `ToolInstructions`, `CustomInstructions`. +Available section IDs are defined as constants on `SystemPromptSections`: `Identity`, `Tone`, `ToolEfficiency`, `EnvironmentContext`, `CodeChangeRules`, `Guidelines`, `Safety`, `ToolInstructions`, `CustomInstructions`, `LastInstructions`. Each section override supports four actions: `Replace`, `Remove`, `Append`, and `Prepend`. Unknown section IDs are handled gracefully: content is appended to additional instructions, and `Remove` overrides are silently ignored. diff --git a/dotnet/src/Types.cs b/dotnet/src/Types.cs index b63663f94..15c64f33c 100644 --- a/dotnet/src/Types.cs +++ b/dotnet/src/Types.cs @@ -1033,6 +1033,8 @@ public static class SystemPromptSections public const string ToolInstructions = "tool_instructions"; /// Repository and organization custom instructions. public const string CustomInstructions = "custom_instructions"; + /// End-of-prompt instructions: parallel tool calling, persistence, task completion. + public const string LastInstructions = "last_instructions"; } /// diff --git a/go/README.md b/go/README.md index b58bef580..dbffa7e90 100644 --- a/go/README.md +++ b/go/README.md @@ -152,7 +152,7 @@ Event types: `SessionLifecycleCreated`, `SessionLifecycleDeleted`, `SessionLifec - `SystemMessage` (\*SystemMessageConfig): System message configuration. Supports three modes: - **append** (default): Appends `Content` after the SDK-managed prompt - **replace**: Replaces the entire prompt with `Content` - - **customize**: Selectively override individual sections via `Sections` map (keys: `SectionIdentity`, `SectionTone`, `SectionToolEfficiency`, `SectionEnvironmentContext`, `SectionCodeChangeRules`, `SectionGuidelines`, `SectionSafety`, `SectionToolInstructions`, `SectionCustomInstructions`; values: `SectionOverride` with `Action` and optional `Content`) + - **customize**: Selectively override individual sections via `Sections` map (keys: `SectionIdentity`, `SectionTone`, `SectionToolEfficiency`, `SectionEnvironmentContext`, `SectionCodeChangeRules`, `SectionGuidelines`, `SectionSafety`, `SectionToolInstructions`, `SectionCustomInstructions`, `SectionLastInstructions`; values: `SectionOverride` with `Action` and optional `Content`) - `Provider` (\*ProviderConfig): Custom API provider configuration (BYOK). See [Custom Providers](#custom-providers) section. - `Streaming` (bool): Enable streaming delta events - `InfiniteSessions` (\*InfiniteSessionConfig): Automatic context compaction configuration @@ -215,7 +215,7 @@ session, err := client.CreateSession(ctx, &copilot.SessionConfig{ }) ``` -Available section constants: `SectionIdentity`, `SectionTone`, `SectionToolEfficiency`, `SectionEnvironmentContext`, `SectionCodeChangeRules`, `SectionGuidelines`, `SectionSafety`, `SectionToolInstructions`, `SectionCustomInstructions`. +Available section constants: `SectionIdentity`, `SectionTone`, `SectionToolEfficiency`, `SectionEnvironmentContext`, `SectionCodeChangeRules`, `SectionGuidelines`, `SectionSafety`, `SectionToolInstructions`, `SectionCustomInstructions`, `SectionLastInstructions`. Each section override supports four actions: - **`replace`** — Replace the section content entirely diff --git a/go/types.go b/go/types.go index c5ac33fb8..605c3dd2f 100644 --- a/go/types.go +++ b/go/types.go @@ -122,6 +122,7 @@ const ( SectionSafety = "safety" SectionToolInstructions = "tool_instructions" SectionCustomInstructions = "custom_instructions" + SectionLastInstructions = "last_instructions" ) // SectionOverride defines an override operation for a single system prompt section. diff --git a/nodejs/README.md b/nodejs/README.md index 48a60c9af..c860b5b47 100644 --- a/nodejs/README.md +++ b/nodejs/README.md @@ -486,7 +486,7 @@ const session = await client.createSession({ }); ``` -Available section IDs: `identity`, `tone`, `tool_efficiency`, `environment_context`, `code_change_rules`, `guidelines`, `safety`, `tool_instructions`, `custom_instructions`. Use the `SYSTEM_PROMPT_SECTIONS` constant for descriptions of each section. +Available section IDs: `identity`, `tone`, `tool_efficiency`, `environment_context`, `code_change_rules`, `guidelines`, `safety`, `tool_instructions`, `custom_instructions`, `last_instructions`. Use the `SYSTEM_PROMPT_SECTIONS` constant for descriptions of each section. Each section override supports four actions: - **`replace`** — Replace the section content entirely diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index 8a6bcf8d5..11250341e 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -285,7 +285,8 @@ export type SystemPromptSection = | "guidelines" | "safety" | "tool_instructions" - | "custom_instructions"; + | "custom_instructions" + | "last_instructions"; /** Section metadata for documentation and tooling. */ export const SYSTEM_PROMPT_SECTIONS: Record = { @@ -298,6 +299,7 @@ export const SYSTEM_PROMPT_SECTIONS: Record Date: Tue, 17 Mar 2026 13:13:59 -0700 Subject: [PATCH 4/4] Fix lint: prettier formatting, Python import order and line length Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- nodejs/src/types.ts | 5 ++++- python/copilot/__init__.py | 4 ++-- python/copilot/types.py | 4 +++- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/nodejs/src/types.ts b/nodejs/src/types.ts index 11250341e..e5fcdb4ff 100644 --- a/nodejs/src/types.ts +++ b/nodejs/src/types.ts @@ -299,7 +299,10 @@ export const SYSTEM_PROMPT_SECTIONS: Record