Skip to content

Add fine-grained system prompt customization (customize mode)#816

Merged
SteveSandersonMS merged 12 commits intomainfrom
mackinnonbuck/prompt-customization
Mar 20, 2026
Merged

Add fine-grained system prompt customization (customize mode)#816
SteveSandersonMS merged 12 commits intomainfrom
mackinnonbuck/prompt-customization

Conversation

@MackinnonBuck
Copy link
Collaborator

@MackinnonBuck MackinnonBuck commented Mar 13, 2026

Add fine-grained system prompt customization (customize mode)

What

Adds a new "customize" mode for systemMessage configuration across all 4 SDK languages (TypeScript, Python, Go, .NET), enabling SDK consumers to selectively override individual sections of the CLI system prompt while preserving the rest.

This sits between the existing "append" (add to the end) and "replace" (replace everything) modes, offering fine-grained control without requiring consumers to maintain entire prompt copies.

10 configurable sections with 5 actions each (replace, remove, append, prepend, transform):

Section ID What it controls
identity Agent identity preamble and mode statement
tone Response style, conciseness rules, output formatting
tool_efficiency Tool usage patterns, parallel calling, batching
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, conditional instructions
safety Environment limitations, prohibited actions, security policies
tool_instructions Per-tool usage instructions
custom_instructions Repository and organization custom instructions
last_instructions Final instructions appended at the end of the prompt

Graceful handling of unknown sections: If the runtime removes a section in a future update, content from replace/append/prepend overrides is appended to additional instructions with a warning; remove and transform are silently ignored.

Transform support

The transform action enables read-then-write mutations on prompt sections. Unlike other actions which are "write-only", transform passes the current rendered section content to a user-defined callback, and uses the callback's return value as the new content.

This enables:

  • Regex or find-and-replace mutations on existing prompt content
  • Logging/observability — inspect what prompt content leads to specific agent behavior
  • Conditional modifications — read the current content and decide whether/how to change it

The transform is implemented via a systemMessage.transform JSON-RPC callback from the runtime to the SDK, similar to how hooks are invoked. Per-section callbacks are registered locally on the session and dispatched when the runtime requests a transform. Errors in callbacks leave the original section content unchanged.

Why

Addresses #215. SDK consumers need to customize agent behavior (e.g., change identity, adjust tone, remove coding rules for non-coding agents) without replacing the entire system prompt.

Changes

  • TypeScript (nodejs/src/types.ts, nodejs/src/client.ts, nodejs/src/session.ts): SystemPromptSection type, SectionOverride, SectionTransformFn, extractTransformCallbacks helper, systemMessage.transform RPC handler
  • Python (python/copilot/types.py, python/copilot/client.py, python/copilot/session.py): Matching types, _extract_transform_callbacks helper, RPC handler (sync+async callback support)
  • Go (go/types.go, go/client.go, go/session.go): SectionTransformFn type, Transform field on SectionOverride, extractTransformCallbacks helper, RPC handler
  • .NET (dotnet/src/Types.cs, dotnet/src/Client.cs, dotnet/src/Session.cs): Transform delegate on SectionOverride, ExtractTransformCallbacks helper, RPC handler
  • E2E tests: Transform E2E tests for all 4 languages with shared snapshot files
  • Bug fixes: Go generated_rpc.go MCP type casing mismatch, .NET Session.LogAsync missing url parameter

Companion PRs

Usage example (TypeScript)

const session = await client.createSession({
  systemMessage: {
    mode: "customize",
    sections: {
      identity: {
        // Transform: read current content, return modified content
        action: (currentContent) => {
          return currentContent.replace(
            "GitHub Copilot",
            "Acme Corp Assistant (powered by GitHub Copilot)"
          );
        }
      },
      tone: {
        // Static override: replace the entire section
        action: "replace",
        content: "Respond in a warm, professional tone. Be thorough."
      },
      safety: {
        // Transform: log content for observability, return unchanged
        action: async (currentContent) => {
          console.log("Safety prompt:", currentContent);
          return currentContent;
        }
      },
      code_change_rules: { action: "remove" }
    }
  }
});

@github-actions
Copy link
Contributor

Cross-SDK Consistency Review ✅❌

Great work adding the "customize" mode feature across all 4 SDKs! The core implementation is well-aligned across languages with proper naming conventions. However, I've identified some documentation and testing gaps that should be addressed for consistency.


What's Consistent

  1. API Design - All SDKs correctly implement:

    • 9 section IDs with identical string values (identity, tone, tool_efficiency, etc.)
    • 4 action types: replace, remove, append, prepend
    • Proper naming conventions (camelCase for TS/Python, PascalCase for Go constants/.NET, snake_case for Python dict keys)
    • Graceful handling of unknown sections
  2. Type Definitions - Well-structured with language-appropriate patterns:

    • TypeScript: Union types with discriminated modes
    • Python: TypedDict with Required/NotRequired
    • Go: Unified struct with omitempty
    • .NET: Enums + Dictionary<string, SectionOverride>
  3. Export Surface - All SDKs properly export the new types (SectionOverride, SystemPromptSection/constants, SYSTEM_PROMPT_SECTIONS)


Inconsistencies to Address

1. Critical Bug: Wrong Package Name (Node.js README)

  • File: nodejs/README.md:453
  • Issue: Example shows import { ... } from "@anthropic-ai/sdk" instead of "@github/copilot-sdk"
  • Impact: Users will get import errors if they copy-paste this example
  • See inline comment for the fix

2. Documentation Gaps: Python & Go Missing Standalone Examples

  • TypeScript (nodejs/README.md): ✅ Has dedicated "Customize Mode" section with full code example
  • .NET (dotnet/README.md): ✅ Has dedicated "Customize Mode" section with full code example
  • Python (python/README.md): ❌ Only has brief inline description in parameter list (line 122-125)
  • Go (go/README.md): ❌ Only has brief inline description in parameter list (line 151-154)

Suggestion: Add standalone "Customize Mode" subsections to Python and Go READMEs similar to TypeScript/C#, showing:

  • How to import the necessary types/constants
  • A complete working example with 2-3 section overrides
  • List of available section IDs and actions

3. Testing Gap: Only Node.js Has E2E Test

  • Node.js: ✅ Has E2E test in nodejs/test/e2e/session.test.ts (validates system message contains custom tone, doesn't contain removed section)
  • Python: ❌ No E2E test for customize mode
  • Go: ❌ No E2E test for customize mode
  • .NET: ❌ No E2E test for customize mode

Suggestion: Add equivalent E2E tests to Python, Go, and .NET test suites to ensure the feature works correctly and consistently across all SDKs.


📋 Recommended Actions

  1. Fix the import statement in nodejs/README.md (see inline comment)
  2. Add Python README example - Create a "Customize Mode" subsection with a code example showing:
    from copilot import CopilotClient, SectionOverride, SYSTEM_PROMPT_SECTIONS
    
    session = await client.create_session({
        "system_message": {
            "mode": "customize",
            "sections": {
                "tone": {"action": "replace", "content": "..."},
                "code_change_rules": {"action": "remove"},
            }
        }
    })
  3. Add Go README example - Similar subsection with Go syntax
  4. Add E2E tests for Python, Go, and .NET to match the Node.js test coverage

Summary

The core feature implementation is excellent and consistent. The issues are limited to documentation quality (missing examples) and test coverage gaps. Once these are addressed, this PR will maintain full feature parity across all 4 SDKs! 🚀

Generated by SDK Consistency Review Agent for issue #816 ·

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by SDK Consistency Review Agent for issue #816

@github-actions
Copy link
Contributor

✅ Cross-SDK Consistency Review: Excellent Work!

This PR demonstrates outstanding cross-SDK consistency across all 4 language implementations. The "customize mode" feature has been implemented with remarkable attention to detail and consistency.

Summary of Changes

Added fine-grained system prompt customization (mode: "customize") across all 4 SDK languages:

  • TypeScript (Node.js)
  • Python
  • Go
  • .NET (C#)

Consistency Verification

Section Identifiers (All identical across languages)

"identity"
"tone"
"tool_efficiency"
"environment_context"
"code_change_rules"
"guidelines"
"safety"
"tool_instructions"
"custom_instructions"

Action Names (All identical)

  • "replace" — Replace section content
  • "remove" — Remove section from prompt
  • "append" — Add content after existing section
  • "prepend" — Add content before existing section

Mode Name (Consistent)

  • "customize" across all languages

API Structure (Parallel design respecting language conventions)

TypeScript (camelCase):

systemMessage: {
  mode: "customize",
  sections: {
    tone: { action: "replace", content: "..." }
  }
}

Python (snake_case):

system_message={
  "mode": "customize",
  "sections": {
    "tone": {"action": "replace", "content": "..."}
  }
}

Go (exported constants):

SystemMessage: &copilot.SystemMessageConfig{
  Mode: "customize",
  Sections: map[string]copilot.SectionOverride{
    copilot.SectionTone: {Action: "replace", Content: "..."}
  }
}

.NET (PascalCase + enum):

SystemMessage = new SystemMessageConfig {
  Mode = SystemMessageMode.Customize,
  Sections = new Dictionary(string, SectionOverride) {
    [SystemPromptSections.Tone] = new() { 
      Action = SectionOverrideAction.Replace, 
      Content = "..." 
    }
  }
}

Additional Consistency Points

Type definitions - All languages export the new types properly
Documentation - All 5 docs files updated consistently (nodejs/, python/, go/, dotnet/, docs/)
E2E tests - Identical test scenarios across all 4 languages
Section constants - Exported/available for consumers in all languages:

  • TypeScript: SYSTEM_PROMPT_SECTIONS (exported)
  • Python: SYSTEM_PROMPT_SECTIONS (exported)
  • Go: Section* constants (e.g., SectionTone, SectionIdentity)
  • .NET: SystemPromptSections static class with constants

Graceful fallback behavior - Documented consistently across all languages

Verdict

No consistency issues found. This PR is a textbook example of how to maintain feature parity across multi-language SDKs. The implementation respects each language's idioms while maintaining semantic consistency. 🎉

Great work @MackinnonBuck!

Generated by SDK Consistency Review Agent for issue #816 ·

@github-actions
Copy link
Contributor

SDK Consistency Review: System Prompt Customization Feature

I've reviewed this PR for cross-SDK consistency. The implementation is excellent overall with proper feature parity across all 4 language implementations. All SDKs correctly implement:

Core types: SystemMessageConfig, SectionOverride, and the 10 section identifiers
Section constants: All 10 sections (identity, tone, tool_efficiency, etc.) defined consistently
Actions: All 4 override actions (replace, remove, append, prepend)
API surface: Consistent naming patterns (accounting for language conventions)
E2E tests: All 4 SDKs have test coverage for customize mode
Documentation: All 5 docs updated with examples


⚠️ Minor Inconsistency Found: Go Missing Section Descriptions

TypeScript, Python, and .NET all export a catalog/map of section descriptions for documentation and tooling:

  • TypeScript: SYSTEM_PROMPT_SECTIONS (exported from types.ts via index.ts)
  • Python: SYSTEM_PROMPT_SECTIONS (exported from types.py via __init__.py)
  • .NET: Inline XML doc comments on each SystemPromptSections constant

Go only defines the section constants (SectionIdentity, SectionTone, etc.) but does not provide the section descriptions that the other SDKs expose. This makes Go's API slightly less discoverable for SDK consumers who want to understand what each section controls.

Suggested Fix

Add a SectionDescriptions map to Go's types.go:

// SectionDescriptions provides human-readable descriptions for each system prompt section.
// Useful for documentation, UI hints, and runtime inspection.
var SectionDescriptions = map[string]string{
    SectionIdentity:           "Agent identity preamble and mode statement",
    SectionTone:               "Response style, conciseness rules, output formatting preferences",
    SectionToolEfficiency:     "Tool usage patterns, parallel calling, batching guidelines",
    SectionEnvironmentContext: "CWD, OS, git root, directory listing, available tools",
    SectionCodeChangeRules:    "Coding rules, linting/testing, ecosystem tools, style",
    SectionGuidelines:         "Tips, behavioral best practices, behavioral guidelines",
    SectionSafety:             "Environment limitations, prohibited actions, security policies",
    SectionToolInstructions:   "Per-tool usage instructions",
    SectionCustomInstructions: "Repository and organization custom instructions",
    SectionLastInstructions:   "End-of-prompt instructions: parallel tool calling, persistence, task completion",
}

This mirrors the SYSTEM_PROMPT_SECTIONS catalog in TypeScript/Python and makes Go's API surface equivalent to the other SDKs.


Summary

This is a well-executed cross-language feature addition. The only gap is Go's missing section descriptions metadata. Adding it would bring all 4 SDKs to full feature parity. Great work! 🎉

Generated by SDK Consistency Review Agent for issue #816 ·

Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by SDK Consistency Review Agent for issue #816

SectionEnvironmentContext = "environment_context"
SectionCodeChangeRules = "code_change_rules"
SectionGuidelines = "guidelines"
SectionSafety = "safety"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cross-SDK consistency: Consider adding a SectionDescriptions map here to match TypeScript's SYSTEM_PROMPT_SECTIONS and Python's SYSTEM_PROMPT_SECTIONS.

All other SDKs expose section descriptions for documentation/tooling:

  • TypeScript: SYSTEM_PROMPT_SECTIONS (Record<SystemPromptSection, { description: string }>)
  • Python: SYSTEM_PROMPT_SECTIONS (dict[SystemPromptSection, str])
  • .NET: XML doc comments on each SystemPromptSections constant

Suggested addition:

// SectionDescriptions provides human-readable descriptions for each system prompt section.
var SectionDescriptions = map[string]string{
    SectionIdentity:           "Agent identity preamble and mode statement",
    SectionTone:               "Response style, conciseness rules, output formatting preferences",
    SectionToolEfficiency:     "Tool usage patterns, parallel calling, batching guidelines",
    SectionEnvironmentContext: "CWD, OS, git root, directory listing, available tools",
    SectionCodeChangeRules:    "Coding rules, linting/testing, ecosystem tools, style",
    SectionGuidelines:         "Tips, behavioral best practices, behavioral guidelines",
    SectionSafety:             "Environment limitations, prohibited actions, security policies",
    SectionToolInstructions:   "Per-tool usage instructions",
    SectionCustomInstructions: "Repository and organization custom instructions",
    SectionLastInstructions:   "End-of-prompt instructions: parallel tool calling, persistence, task completion",
}

This would make Go's public API equivalent to the other SDKs and improve discoverability for SDK consumers.

@MackinnonBuck MackinnonBuck force-pushed the mackinnonbuck/prompt-customization branch from 28b1bc5 to 0348b8e Compare March 17, 2026 18:46
@github-actions
Copy link
Contributor

✅ Cross-SDK Consistency Review: EXCELLENT

I've completed a thorough review of PR #816 for cross-language SDK consistency. This PR adds the "customize" mode for system prompt customization across all 4 SDK implementations.

Summary

This PR maintains exceptional consistency across all SDKs. The feature has been implemented uniformly with proper attention to language-specific conventions while maintaining semantic parity.


✅ What's Consistent

1. Core Type Definitions

All 4 SDKs define the same structures with language-appropriate naming:

Component TypeScript Python Go .NET
Section type SystemPromptSection (union type) SystemPromptSection (Literal) Section constants (SectionIdentity, etc.) SystemPromptSections (static class)
Override type SectionOverride interface SectionOverride (TypedDict) SectionOverride struct SectionOverride class
Mode enum String literal "customize" Literal "customize" String "customize" SystemMessageMode.Customize enum
Actions String union Literal union String constants SectionOverrideAction enum

2. Section Identifiers

All 10 section identifiers are present in all SDKs with identical string values:

  • identity, tone, tool_efficiency, environment_context, code_change_rules, guidelines, safety, tool_instructions, custom_instructions, last_instructions

3. Actions

All 4 actions are consistently available:

  • replace, remove, append, prepend

4. API Surface

  • All SDKs expose section constants/catalog for consumer use
  • TypeScript exports SYSTEM_PROMPT_SECTIONS constant
  • Python exports SYSTEM_PROMPT_SECTIONS dict
  • Go exports Section* constants
  • .NET exposes SystemPromptSections static class

5. E2E Tests

Each SDK includes an equivalent test that:

  • Creates a session with customize mode
  • Replaces the tone section
  • Removes the code_change_rules section
  • Appends additional content
  • Validates the resulting system message

Test coverage: ✅ TypeScript, ✅ Python, ✅ Go, ✅ .NET

6. Documentation

All 5 documentation files updated with:

  • Usage examples for customize mode
  • Section constant references
  • Action descriptions
  • Graceful handling of unknown sections

🎯 Language Convention Adherence

The PR properly follows each language's idioms:

  • TypeScript: camelCase (systemMessage, sections), exported types and constants
  • Python: snake_case (system_message, sections), TypedDict patterns, exported in __all__
  • Go: PascalCase exports (SectionIdentity), struct tags, map-based section config
  • .NET: PascalCase properties (SystemMessage, Sections), proper enum with [JsonStringEnumMemberName], Dictionary type

📝 Minor Observations

  1. Section metadata structure varies (by design for each language):

    • TypeScript: Record(SystemPromptSection, { description: string })
    • Python: dict[SystemPromptSection, str] (description only)
    • Go: Constants only (no metadata structure)
    • .NET: XML doc comments on constants

    This is appropriate - each language uses its natural documentation/metadata pattern.

  2. Generated code included: The PR includes updates to generated RPC types and session events. These appear to be auto-generated and consistent.


✅ Recommendation

This PR demonstrates excellent cross-SDK consistency. No changes needed from a consistency perspective. The implementation:

  • Maintains feature parity across all 4 languages
  • Follows each language's conventions appropriately
  • Includes comprehensive tests for all SDKs
  • Provides clear, consistent documentation
  • Handles edge cases (unknown sections) uniformly

Great work on maintaining consistency in this multi-language codebase! 🎉

Generated by SDK Consistency Review Agent for issue #816 ·

@MackinnonBuck MackinnonBuck changed the base branch from main to update-copilot-1.0.7 March 17, 2026 19:03
@github-actions
Copy link
Contributor

✅ Cross-SDK Consistency Review

I've reviewed PR #816 for consistency across all four SDK implementations (TypeScript, Python, Go, and .NET). Excellent work! This PR maintains strong feature parity and API consistency.

Summary

The new "customize" mode for system prompt customization is uniformly implemented across all SDKs with:

  • 10 consistent section identifiers across all languages:

    • identity, tone, tool_efficiency, environment_context, code_change_rules, guidelines, safety, tool_instructions, custom_instructions, last_instructions
    • Properly exposed via language-appropriate constructs (TypeScript union type, Python Literal, Go constants prefixed with Section*, .NET constants in SystemPromptSections class)
  • 4 consistent actions for section overrides:

    • replace, remove, append, prepend
    • TypeScript: string literal union
    • Python: Literal["replace", "remove", "append", "prepend"]
    • Go: string field with documentation
    • .NET: SectionOverrideAction enum
  • Parallel API structure:

    • SectionOverride type with action and optional content fields in all languages
    • SystemMessageCustomizeConfig with mode, sections, and optional content fields
    • Naming follows language conventions (camelCase in TypeScript, snake_case in Python, PascalCase in Go exports and .NET)
  • Comprehensive documentation updated in all 5 docs (nodejs/README.md, python/README.md, go/README.md, dotnet/README.md, docs/getting-started.md)

  • E2E tests added for all 4 languages verifying the customize mode functionality

  • Proper exports: All new types are exported from language entry points (index.ts, __init__.py, Go package exports, .NET public types)

Language-Specific Conventions Appropriately Applied

Aspect TypeScript Python Go .NET
Section type SystemPromptSection (union) SystemPromptSection (Literal) String constants (Section*) String constants (SystemPromptSections.*)
Action type String union Literal[...] String field SectionOverrideAction enum
Override struct SectionOverride interface SectionOverride TypedDict SectionOverride struct SectionOverride class
Config struct SystemMessageCustomizeConfig SystemMessageCustomizeConfig Fields in SystemMessageConfig SystemMessageConfig with Mode.Customize
Naming camelCase snake_case PascalCase (exported), camelCase (unexported) PascalCase

No cross-language inconsistencies detected. The implementation properly balances consistency with language-idiomatic design.

Generated by SDK Consistency Review Agent for issue #816 ·

@IeuanWalker
Copy link

@MackinnonBuck is there a way to view/ retrieve the default/ built in value?

Be good to be able to see what the default value is if I perform a "replace", as I might just want to tweak the behavior slightly.

@MackinnonBuck
Copy link
Collaborator Author

@IeuanWalker are you wanting a way to read sections of the system prompt programmatically? This PR does not provide that facility, but we could address it in a follow-up.

@IeuanWalker
Copy link

@MackinnonBuck not specifically programmatically, just some way for us to find the current/ default value

Even if it's just on a doc somewhere.

Atm I might replace one of the values with something else verbose, so be good just too see what the current structure is before replacing it

@github-actions
Copy link
Contributor

Cross-SDK Consistency Review ✅

I've reviewed PR #816 for consistency across all four SDK implementations (TypeScript, Python, Go, .NET). Overall, the implementation maintains excellent consistency. Here's what I found:

Consistent Across All SDKs:

  1. Section IDs - All 10 section identifiers are identical:

    • identity, tone, tool_efficiency, environment_context, code_change_rules, guidelines, safety, tool_instructions, custom_instructions, last_instructions
  2. Actions - All four actions are present and documented:

    • replace, remove, append, prepend (plus transform callbacks)
  3. API Design - Public APIs follow language conventions:

    • TypeScript: systemMessage: { mode: "customize", sections: {...} }
    • Python: system_message={"mode": "customize", "sections": {...}}
    • Go: SystemMessage: &SystemMessageConfig{Mode: "customize", Sections: map[string]SectionOverride{...}}
    • .NET: SystemMessage = new SystemMessageConfig { Mode = SystemMessageMode.Customize, Sections = new Dictionary(string, SectionOverride) {...} }
  4. Transform Callbacks - All SDKs support function-based transformations (appropriately adapted to each language's type system)

  5. Documentation - All four README files have parallel examples demonstrating customize mode

  6. Tests - E2E tests exist for all four SDKs validating transform functionality

  7. Protocol Version - All SDKs correctly set protocol version 3

⚠️ One Inconsistency Found:

RPC Protocol Field Naming - The systemMessage.transform RPC handler uses different JSON field names across SDKs:

  • Go SDK expects: sectionId (lowercase 'i')

    type systemMessageTransformRequest struct {
        SessionID      string `json:"sessionId"`
        SectionID      string `json:"sectionId"`  // ← Note: "sectionId"
        CurrentContent string `json:"currentContent"`
    }
  • Python & .NET SDKs expect: sectionName

    section_name = params.get("sectionName")  # ← Note: "sectionName"

This field name mismatch would cause the Go SDK to fail unmarshaling RPC requests from the runtime if the runtime sends sectionName. Since this PR has companion changes in the runtime (https://github.com/github/copilot-agent-runtime/pull/4751), please verify that the runtime is sending the correct field name that matches what the Go SDK expects, or update the Go SDK to use sectionName for consistency with Python and .NET.

💡 Design Differences (intentional, language-appropriate):

  1. Transform callback API: TypeScript/Python allow the action field to be a function directly (union type), while Go/.NET use a separate Transform field. This is appropriate for each language's type system.

  2. Error handling: When no callback is registered, Node.js/Python return original content gracefully, while Go/.NET raise errors. This is acceptable as it aligns with each language's error-handling philosophy.

  3. RPC handler structure: TypeScript's handler signature suggests batched processing, while Python/Go/.NET process one section at a time. This is likely an internal implementation detail as long as the wire protocol matches.


Recommendation: Please verify the sectionId vs sectionName field naming with the runtime PR to ensure protocol compatibility.

Generated by SDK Consistency Review Agent for issue #816 ·

@MackinnonBuck MackinnonBuck force-pushed the mackinnonbuck/prompt-customization branch from 4102bd1 to daf9337 Compare March 20, 2026 00:54
@MackinnonBuck MackinnonBuck changed the base branch from update-copilot-1.0.7 to main March 20, 2026 00:54
@MackinnonBuck MackinnonBuck force-pushed the mackinnonbuck/prompt-customization branch from daf9337 to 38376d8 Compare March 20, 2026 01:41
}
});

await File.WriteAllTextAsync(Path.Combine(Ctx.WorkDir, "test.txt"), "Hello transform!");
Comment on lines +710 to +713
catch
{
result[sectionId] = new SystemMessageTransformSection { Content = data.Content ?? "" };
}
@github-actions
Copy link
Contributor

✅ Cross-SDK Consistency Review: Excellent Feature Parity

I've completed a comprehensive review of PR #816 across all four SDK implementations (TypeScript, Python, Go, .NET). This PR adds fine-grained system prompt customization through the new "customize" mode.

🎯 Summary: Strong Consistency Across All SDKs

This PR demonstrates excellent cross-language consistency with parallel implementations across all four SDKs. The feature is fully implemented with equivalent APIs, comprehensive E2E tests, and documentation in all languages.


✅ Consistent API Surface

All four SDKs expose identical capabilities:

Feature TypeScript Python Go .NET
Type Name SectionOverride SectionOverride SectionOverride SectionOverride
Static Actions 4 literals 4 literals 4 constants 4 enum values
Transform Support ✅ Async (Promise) ✅ Sync+Async ✅ Sync only ✅ Async (Task)
Section IDs 10 constants 10 constants 10 constants 10 constants
Error Handling Pass-through original Pass-through original Pass-through original Pass-through original
RPC Handler ✅ Registered ✅ Registered ✅ Registered ✅ Registered

Sections Supported (all SDKs): identity, tone, tool_efficiency, environment_context, code_change_rules, guidelines, safety, tool_instructions, custom_instructions, last_instructions


✅ Consistent Testing

All four SDKs include equivalent E2E tests using shared snapshot files:

  • TypeScript: nodejs/test/e2e/system_message_transform.test.ts (3 tests)
  • Python: python/e2e/test_system_message_transform.py (3 tests)
  • Go: go/internal/e2e/system_message_transform_test.go (3 tests)
  • .NET: dotnet/test/SystemMessageTransformTests.cs (3 tests)

Shared snapshots in test/snapshots/system_message_transform/:

  • should_invoke_transform_callbacks_with_section_content.yaml
  • should_apply_transform_modifications_to_section_content.yaml
  • should_work_with_static_overrides_and_transforms_together.yaml

✅ Consistent Documentation

All four README files include parallel examples demonstrating the customize mode:

  • TypeScript: nodejs/README.md — includes transform example with type imports
  • Python: python/README.md — includes transform example with proper imports
  • Go: go/README.md — includes section constants usage
  • .NET: dotnet/README.md — includes PascalCase API examples

Plus comprehensive examples in docs/getting-started.md covering all four languages.


📊 Language-Specific Design Choices (Expected)

Each SDK appropriately adapts to language idioms:

  1. Naming Conventions (appropriate for each language):

    • TypeScript: createSession, systemMessage, SYSTEM_PROMPT_SECTIONS
    • Python: create_session, system_message, SYSTEM_PROMPT_SECTIONS
    • Go: CreateSession, SystemMessage, SectionIdentity
    • .NET: CreateSessionAsync, SystemMessage, SystemPromptSections.Identity
  2. Transform Callback Signatures (language-appropriate):

    • TypeScript: (content: string) => string | Promise(string) — async via Promise
    • Python: Callable[[str], str | Awaitable[str]] — sync+async via runtime detection
    • Go: func(content string) (string, error) — sync with error return (idiomatic Go)
    • .NET: Func(string, Task(string)) — async via Task
  3. Thread Safety (appropriate for each runtime):

    • TypeScript: Event loop (single-threaded)
    • Python: Thread lock for callback registry
    • Go: sync.Mutex for thread-safe access
    • .NET: SemaphoreSlim for async-friendly locking

💡 Notable Implementation Details

  1. Error Handling: All SDKs consistently return original content when transform callbacks fail — graceful degradation without breaking the session.

  2. Python's Unique Feature: Python SDK uniquely supports both sync and async transform callbacks via runtime detection (inspect.isawaitable()), while others require explicit async signatures (TS/.NET) or sync-only (Go).

  3. Go's Sync-Only Approach: Go uses synchronous callback signatures with error returns, which is idiomatic for Go but differs from the async approach in TS/Python/.NET. This is acceptable given Go's lightweight goroutines.

  4. Bug Fixes Included: The PR also fixes two pre-existing bugs:

    • Go: MCP type casing mismatch in generated_rpc.go
    • .NET: Missing url parameter in Session.LogAsync

🎉 Conclusion

This PR maintains excellent cross-SDK consistency while appropriately adapting to each language's idioms and runtime characteristics. The feature is fully implemented with:

  • ✅ Parallel API design across all 4 languages
  • ✅ Comprehensive E2E test coverage with shared snapshots
  • ✅ Complete documentation with language-specific examples
  • ✅ Consistent error handling and graceful degradation
  • ✅ Thread-safe implementations appropriate for each runtime

No consistency issues found. The implementation demonstrates best practices for multi-language SDK development. 🚀

Generated by SDK Consistency Review Agent for issue #816 ·

@MackinnonBuck MackinnonBuck force-pushed the mackinnonbuck/prompt-customization branch 2 times, most recently from d3dee9b to 6b7d32c Compare March 20, 2026 02:28
@github-actions
Copy link
Contributor

🔍 Cross-SDK Consistency Review

I've completed a comprehensive review of PR #816 to ensure consistency across all four SDK implementations (TypeScript, Python, Go, .NET).

✅ Excellent Consistency - No Blocking Issues

This PR demonstrates exceptional cross-language consistency:

Core Feature Parity ✅

All 4 SDKs implement:

  • customize mode for systemMessage
  • ✅ 10 configurable sections (identity, tone, tool_efficiency, environment_context, code_change_rules, guidelines, safety, tool_instructions, custom_instructions, last_instructions)
  • ✅ 4 static actions (replace, remove, append, prepend)
  • ✅ Transform callbacks for dynamic mutations
  • ✅ Graceful handling of unknown sections

API Naming Consistency ✅

Naming follows language conventions appropriately:

  • TypeScript: systemMessage.mode: "customize", SectionOverride, action, SectionTransformFn
  • Python: system_message["mode"] = "customize", SectionOverride, action, SectionTransformFn
  • Go: SystemMessage.Mode = "customize", SectionOverride, Action, SectionTransformFn
  • .NET: SystemMessage.Mode = SystemMessageMode.Customize, SectionOverride, Action, Transform delegate

Implementation Consistency ✅

  • ✅ All SDKs implement systemMessage.transform RPC handler
  • ✅ All use callback registry pattern (transformCallbacks / _transform_callbacks)
  • ✅ All handle callback errors gracefully (return original content on failure)
  • ✅ All support both sync and async transforms where applicable

Testing Parity ✅

  • ✅ E2E tests exist for all 4 languages
  • ✅ Shared snapshot files ensure identical behavior
  • ✅ Tests cover both "invoke callbacks" and "apply modifications" scenarios

📝 Documentation Suggestion: Transform Callbacks Not Documented

While the transform feature is fully implemented and tested across all SDKs, it's not documented in any README or getting-started guide.

What's missing:
Transform callbacks are a powerful feature mentioned in your PR description ("Transform support enables read-then-write mutations..."), but the READMEs only show static actions (replace, remove, append, prepend). Users won't discover they can pass functions to action.

Suggestion: Consider adding transform callback examples to each SDK's README:

// TypeScript
sections: {
    identity: {
        action: (currentContent) => {
            return currentContent.replace("GitHub Copilot", "Acme Assistant");
        }
    }
}
# Python (supports both sync and async)
"sections": {
    "identity": {
        "action": lambda content: content.replace("GitHub Copilot", "Acme Assistant")
    }
}
// Go
Sections: map[string]copilot.SectionOverride{
    copilot.SectionIdentity: {
        Transform: func(content string) (string, error) {
            return strings.Replace(content, "GitHub Copilot", "Acme Assistant", 1), nil
        },
    },
}
// C#
Sections = new Dictionary(string, SectionOverride)
{
    [SystemPromptSections.Identity] = new() {
        Transform = async (content) => {
            return content.Replace("GitHub Copilot", "Acme Assistant");
        }
    }
}

Summary

No consistency issues found. This PR maintains excellent feature parity and API design consistency across all four SDKs. The only suggestion is to document the transform callback feature so users can discover this powerful capability. 🎉

Generated by SDK Consistency Review Agent for issue #816 ·

@SteveSandersonMS SteveSandersonMS force-pushed the mackinnonbuck/prompt-customization branch from 6b7d32c to 150eb33 Compare March 20, 2026 11:51
@github-actions
Copy link
Contributor

SDK Consistency Review ✅

This PR adds the "customize" mode and "transform" action for system prompt customization across all 4 SDK implementations. Great work on maintaining consistency!

✅ Cross-Language Consistency

The implementation is highly consistent across all SDKs:

  • Types: All SDKs define the same 10 section IDs (identity, tone, tool_efficiency, environment_context, code_change_rules, guidelines, safety, tool_instructions, custom_instructions, last_instructions)
  • Actions: All SDKs implement the same 5 actions (replace, remove, append, prepend, transform)
  • Transform callbacks: Properly implemented in all 4 languages:
    • TypeScript: SectionTransformFn = (currentContent: string) => string | Promise(string)
    • Python: SectionTransformFn = Callable[[str], str | Awaitable[str]]
    • Go: SectionTransformFn func(currentContent string) (string, error)
    • .NET: Transform delegate Func(string, Task(string))
  • RPC handlers: All SDKs correctly implement systemMessage.transform RPC handling with error recovery
  • E2E tests: Comprehensive test coverage across all 4 languages using shared snapshot files

📝 Documentation Gap: Transform Action Not Documented

While the transform action is fully implemented and tested, it's not documented in any README or getting-started guide:

  • ✅ All READMEs mention "four actions" and list: replace, remove, append, prepend
  • ❌ None mention the transform action (the 5th action)
  • docs/getting-started.md also says "four actions"

Expected: Documentation should mention all 5 actions and include examples showing how to use transform callbacks for read-then-write mutations, logging/observability, and conditional modifications (as described in the PR description).

Suggested fix: Add a subsection to the "Customize Mode" documentation in all 4 READMEs and docs/getting-started.md that explains the transform action with code examples in each language showing:

  1. Basic transform usage (modifying section content)
  2. Observability/logging use case (reading content without changing it)
  3. Error handling behavior (failures leave original content unchanged)

This would match the excellent examples already present in the E2E tests and bring the documentation up to the same quality as the implementation.


Summary: The cross-SDK consistency for the customize mode implementation is excellent. The only issue is that the transform action needs to be added to the user-facing documentation.

Generated by SDK Consistency Review Agent for issue #816 ·

MackinnonBuck and others added 5 commits March 20, 2026 13:22
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>
- 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>
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>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds a new 'transform' action to SectionOverride that enables read-then-write
mutation of system prompt sections via callbacks. The SDK intercepts function-
valued actions before serialization, stores the callbacks locally, and handles
the batched systemMessage.transform JSON-RPC callback from the runtime.

Changes across all 4 SDKs (TypeScript, Python, Go, .NET):
- Types: SectionTransformFn, SectionOverrideAction (TS/Python), Transform
  field (Go/.NET), SectionOverrideAction constants (Go)
- Client: extractTransformCallbacks helper, transform callback registration,
  systemMessage.transform RPC handler
- Session: transform callback storage and batched dispatch with error handling
- E2E tests and shared snapshot YAML files

Wire protocol: single batched RPC call with all transform sections, matching
the runtime implementation in copilot-agent-runtime PR #5103.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@SteveSandersonMS SteveSandersonMS force-pushed the mackinnonbuck/prompt-customization branch from 150eb33 to a422b3c Compare March 20, 2026 13:23
@github-actions
Copy link
Contributor

Cross-SDK Consistency Review ✅

I've reviewed PR #816 for consistency across all four SDK implementations (TypeScript, Python, Go, .NET). Excellent work! This PR demonstrates exemplary cross-language feature parity for the new "customize mode" system prompt customization feature.

Summary of Changes

This PR adds fine-grained system prompt customization capabilities across all 4 SDKs, enabling users to selectively override individual sections of the CLI system prompt while preserving the rest. The implementation is consistent and complete across all languages.

✅ Consistent Type Definitions

All 4 languages define the same 10 configurable sections with identical string identifiers:

Section ID TypeScript Python Go .NET
identity "identity" "identity" SectionIdentity = "identity" SystemPromptSections.Identity = "identity"
tone "tone" "tone" SectionTone = "tone" SystemPromptSections.Tone = "tone"
tool_efficiency "tool_efficiency" "tool_efficiency" SectionToolEfficiency = "tool_efficiency" SystemPromptSections.ToolEfficiency = "tool_efficiency"
(and 7 more)

✅ Consistent API Design

Section Override Actions - All 4 languages support the same actions with consistent semantics:

  • TypeScript: "replace", "remove", "append", "prepend", or callback function
  • Python: "replace", "remove", "append", "prepend", or callable
  • Go: SectionActionReplace, SectionActionRemove, SectionActionAppend, SectionActionPrepend, or Transform field
  • .NET: SectionOverrideAction.Replace, .Remove, .Append, .Prepend, or Transform delegate

Transform Callbacks - All languages implement the same callback pattern:

  • TypeScript: SectionTransformFn = (currentContent: string) => string | Promise(string)
  • Python: SectionTransformFn = Callable[[str], str | Awaitable[str]]
  • Go: SectionTransformFn func(currentContent string) (string, error)
  • .NET: Transform = Func(string, Task(string))

Each correctly handles both sync and async callbacks where applicable (Python, TypeScript, .NET).

✅ Consistent RPC Implementation

All 4 SDKs implement the systemMessage.transform JSON-RPC callback mechanism:

  • TypeScript: _handleSystemMessageTransform in session.ts
  • Python: _handle_system_message_transform in session.py
  • Go: Transform handling via extractTransformCallbacks in client.go/session.go
  • .NET: HandleSystemMessageTransformAsync in Session.cs

✅ Consistent Documentation

All 4 READMEs include:

  • "Customize Mode" section with code examples
  • List of available section IDs (using language-appropriate constants/identifiers)
  • Explanation of the 4 static actions (replace, remove, append, prepend)
  • Graceful fallback behavior for unknown sections
  • Examples showing multiple section overrides

✅ Consistent E2E Tests

All 4 languages have matching E2E test files in their respective test directories:

  • nodejs/test/e2e/system_message_transform.test.ts
  • python/e2e/test_system_message_transform.py
  • go/internal/e2e/system_message_transform_test.go
  • dotnet/test/SystemMessageTransformTests.cs

All tests use the same shared snapshot files in test/snapshots/system_message_transform/, ensuring behavioral consistency.

🎯 Language Convention Compliance

The implementation correctly follows each language's naming conventions:

  • TypeScript: camelCase (e.g., systemMessage, createSession)
  • Python: snake_case (e.g., system_message, create_session)
  • Go: PascalCase for exported (e.g., SectionIdentity, SystemMessageConfig), camelCase for unexported
  • .NET: PascalCase (e.g., SystemPromptSections.Identity, CreateSessionAsync)

📝 Minor Observations (non-blocking)

  1. Transform callback documentation: While the code is consistent, I didn't find detailed "Transform Support" sections in the READMEs explaining the read-then-write mutation pattern. This is mentioned in the PR description but might be valuable to add to the SDK documentation. (This is a documentation enhancement suggestion, not a consistency issue.)

  2. Error handling: Go's Transform function signature returns (string, error), which is idiomatic. Other languages handle errors via exceptions/rejections. This is appropriate and consistent with language conventions.

Conclusion

No consistency issues found. This PR successfully adds the "customize mode" feature with complete feature parity across all 4 SDK implementations. The API design, type definitions, RPC handling, documentation, and tests are all consistent and follow language-specific conventions appropriately.

Great work on maintaining consistency across this multi-language SDK! 🎉

Generated by SDK Consistency Review Agent for issue #816 ·

SteveSandersonMS and others added 2 commits March 20, 2026 13:48
Co-Authored-By: Copilot <223556219+Copilot@users.noreply.github.com>
Generate the missing snapshot file that the 'should create a session
with customized systemMessage config' test requires across all SDK
languages (Node, Python, Go, .NET).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Contributor

Cross-SDK Consistency Review ✅

I've completed a comprehensive review of this PR for cross-language SDK consistency. Overall, this PR maintains excellent consistency across all 4 SDK implementations (TypeScript, Python, Go, .NET). Here's my detailed analysis:

✅ Excellent Consistency Achievements

  1. Complete Feature Parity: All 4 SDKs implement the customize mode with identical capabilities
  2. API Design: Section identifiers, action types, and behavior are consistent across languages
  3. Transform Implementation: All SDKs correctly implement transform callbacks with idiomatic signatures:
    • TypeScript: (content: string) => string | Promise(string)
    • Python: Callable[[str], str | Awaitable[str]]
    • Go: func(content string) (string, error)
    • .NET: Func(string, Task(string))
  4. Testing: All 4 languages have E2E tests with shared YAML snapshots
  5. Wire Protocol: All SDKs correctly handle transform callbacks by extracting them locally and sending action: "transform" over the wire
  6. JSON Serialization: Go and .NET properly exclude Transform field from JSON serialization (json:"-" and [JsonIgnore])

📝 Minor Documentation Inconsistency (Not Blocking)

The transform action is fully implemented and tested in all 4 languages, but is not documented in user-facing documentation:

  • docs/getting-started.md (line 1267): States "Each override supports four actions: replace, remove, append, and prepend" — missing transform
  • go/README.md (line 223): States "Each section override supports four actions" — missing transform
  • dotnet/README.md (line 536): States "Each section override supports four actions" — missing transform
  • nodejs/README.md and python/README.md: Don't mention the number of actions, but also don't include transform examples

The PR description correctly lists 5 actions and includes a transform example, so this appears to be an oversight in the documentation portions.

📚 Recommendation

Consider adding transform documentation/examples to:

  1. docs/getting-started.md - Add transform example showing how to read and modify section content
  2. Language-specific READMEs - Add examples demonstrating transform for observability, conditional modifications, or regex replacements

The transform feature itself is fully implemented and works correctly across all SDKs — this is purely a documentation gap that could help users discover this powerful capability.

🎯 Conclusion

This PR successfully maintains cross-SDK consistency. The implementation, testing, and API design are all excellent. The documentation gap noted above is minor and doesn't affect the functionality. Great work on ensuring feature parity across all 4 languages! 🚀

Generated by SDK Consistency Review Agent for issue #816 ·

@SteveSandersonMS SteveSandersonMS marked this pull request as ready for review March 20, 2026 13:55
@SteveSandersonMS SteveSandersonMS requested a review from a team as a code owner March 20, 2026 13:55
Copilot AI review requested due to automatic review settings March 20, 2026 13:55
- Add blank // comment line between doc example and extractTransformCallbacks
  function doc comment in go/client.go (required by go fmt)
- Fix ruff import sorting in python/copilot/__init__.py

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new systemMessage.mode = "customize" across the TypeScript, Python, Go, and .NET SDKs, enabling section-level system prompt overrides plus a systemMessage.transform JSON-RPC callback so SDK consumers can read/modify rendered prompt sections without replacing the whole prompt.

Changes:

  • Introduces “customize” system message config types and section override models (incl. transform callbacks) across all 4 SDKs.
  • Implements transform-callback extraction (wire-safe payload + local callback registry) and a systemMessage.transform RPC handler in each SDK.
  • Adds cross-language E2E coverage (new tests + shared snapshots) and updates docs/READMEs to describe customize mode.

Reviewed changes

Copilot reviewed 32 out of 32 changed files in this pull request and generated 6 comments.

Show a summary per file
File Description
docs/getting-started.md Documents systemMessage customize mode usage in the main getting-started guide.
dotnet/README.md Adds .NET customize-mode documentation and examples.
dotnet/src/Client.cs Extracts transform callbacks before serialization; registers RPC handler for systemMessage.transform.
dotnet/src/SdkProtocolVersion.cs Minor refactor to expression-bodied GetVersion.
dotnet/src/Session.cs Stores transform callbacks and applies them in systemMessage.transform handler.
dotnet/src/Types.cs Adds Customize mode, section override types, section constants, and transform RPC response models.
dotnet/test/SessionTests.cs Adds E2E test for customized system message config.
dotnet/test/SystemMessageTransformTests.cs Adds E2E tests validating transform callback invocation and behavior.
go/README.md Adds Go customize-mode documentation and examples.
go/client.go Extracts transform callbacks for wire payload; registers systemMessage.transform handler.
go/internal/e2e/session_test.go Adds E2E test for customized system message config.
go/internal/e2e/system_message_transform_test.go Adds E2E tests for transform callback invocation/behavior.
go/session.go Stores transform callbacks and applies them on systemMessage.transform requests.
go/types.go Adds section constants, override types, and customize-mode fields on SystemMessageConfig.
nodejs/README.md Adds Node customize-mode documentation and examples.
nodejs/src/client.ts Extracts transform callbacks and registers systemMessage.transform request handler.
nodejs/src/index.ts Exports new section metadata/types from the public entrypoint.
nodejs/src/session.ts Stores transform callbacks and applies them per section for transform RPC.
nodejs/src/types.ts Adds customize-mode types: sections, overrides, transform callback shape, and metadata.
nodejs/test/e2e/session.test.ts Adds E2E test for customized system message config.
nodejs/test/e2e/system_message_transform.test.ts Adds E2E tests validating transform callback invocation and behavior.
python/README.md Adds Python customize-mode documentation and examples.
python/copilot/__init__.py Re-exports new system-message customization symbols.
python/copilot/client.py Extracts transform callbacks pre-serialization; registers systemMessage.transform handler.
python/copilot/session.py Stores transform callbacks and applies them in transform RPC handling (sync/async).
python/copilot/types.py Adds customize-mode types, section identifiers, and transform callback typing.
python/e2e/test_session.py Adds Python E2E test for customized system message config.
python/e2e/test_system_message_transform.py Adds Python E2E tests for transform callback invocation and behavior.
test/snapshots/session/should_create_a_session_with_customized_systemmessage_config.yaml Snapshot for customize-mode session creation behavior.
test/snapshots/system_message_transform/should_apply_transform_modifications_to_section_content.yaml Snapshot validating transform modification effects.
test/snapshots/system_message_transform/should_invoke_transform_callbacks_with_section_content.yaml Snapshot validating transform callbacks are invoked with content.
test/snapshots/system_message_transform/should_work_with_static_overrides_and_transforms_together.yaml Snapshot validating static overrides + transforms compose correctly.

Comment on lines +263 to +268
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

Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Customize mode documentation lists only four actions, but Python also supports per-section transform via function-valued action (sync or async) for read/modify/write of section content. Please document the transform capability here (and mention action can be a callable) so consumers can discover it.

Suggested change
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
Each section override supports these 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
- **`transform`** — Provide a callable `action` (sync or async) that receives the current section content and returns the new content, allowing read/modify/write of the section.
In Python, the `action` value may be either a string (`"replace"`, `"remove"`, `"append"`, `"prepend"`) or a callable used as a per-section transform. When a callable is provided, it is invoked with the existing section text and its return value becomes the updated section content.

Copilot uses AI. Check for mistakes.
Comment on lines +223 to +227
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
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This README lists four section override actions, but Go also supports per-section transforms via SectionOverride.Transform (serialized as action "transform"). Please document transform here so users know they can inspect/modify rendered section content at runtime.

Copilot uses AI. Check for mistakes.
Comment on lines +536 to +537
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.

Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section documents only Replace/Remove/Append/Prepend, but the C# SDK also supports per-section transforms via SectionOverride.Transform (serialized as action transform). Please document Transform here (and how it interacts with Action) so consumers can use the read/modify/write capability.

Suggested change
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.
Each section override supports four built-in `Action` values: `Replace`, `Remove`, `Append`, and `Prepend`. Unknown section IDs are handled gracefully: content is appended to additional instructions, and `Remove` overrides are silently ignored.
For advanced scenarios, `SectionOverride` also exposes an optional `Transform` delegate that lets you read and modify the existing section text. When `Transform` is set, it is used instead of the `Action`/`Content` pair and is serialized as an action of type `transform` in the underlying JSON-RPC payloads. For a given section override you should typically set either `Action`/`Content` or `Transform`, but not both.

Copilot uses AI. Check for mistakes.

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.
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This guide says customize-mode overrides support only four actions, but the SDK also supports per-section transform callbacks (read current section content, return modified content). Please add transform to the documented actions and include a brief example so readers can discover and correctly use it.

Copilot uses AI. Check for mistakes.
Comment on lines +91 to +104
{
"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?"})
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CopilotClient.create_session is keyword-only (requires on_permission_request= etc.), so passing a single config dict positionally will raise a TypeError. Also, CopilotSession.send_and_wait takes a prompt string, not a { "prompt": ... } dict. Update this test to call create_session with keyword args and pass a string to send_and_wait (or use the correct method that accepts MessageOptions if one exists).

Suggested change
{
"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?"})
on_permission_request=PermissionHandler.approve_all,
system_message={
"mode": "customize",
"sections": {
"tone": {"action": "replace", "content": custom_tone},
"code_change_rules": {"action": "remove"},
},
"content": appended_content,
},
)
assistant_message = await session.send_and_wait("Who are you?")

Copilot uses AI. Check for mistakes.
Comment on lines +506 to +510
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
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This section lists only four override actions, but the SDK also supports per-section transform callbacks in customize mode (function-valued action in TS). The README should document transform as a supported action and briefly describe how it works/what it returns so consumers can discover it.

Suggested change
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
Each section override supports five 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
- **`transform`** — Provide a function-valued `action` (customize mode only) that receives the current section (ID and content) and returns either a new string (to replace the content) or a full `SectionOverride`; returning `undefined` leaves the section unchanged.

Copilot uses AI. Check for mistakes.
SteveSandersonMS and others added 2 commits March 20, 2026 14:05
Use str() to ensure transform callback result is typed as str,
fixing the invalid-assignment error from ty type checker at
session.py:689.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The create_session() method was refactored to keyword-only params.
Update the customized systemMessage test to use keyword arguments
instead of a positional dict, and fix send_and_wait() call to pass
prompt as a positional string.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Contributor

Cross-SDK Consistency Review ✅

I've reviewed PR #816 for consistency across all four SDK implementations (TypeScript, Python, Go, and .NET).

Summary: Excellent Cross-Language Consistency

This PR successfully implements the fine-grained system prompt customization feature across all four SDKs with remarkable consistency. The implementation appropriately adapts to each language's idioms while maintaining API parity.


✅ Consistent Implementation

Section Identifiers (10 total)
All languages define the same sections with consistent naming:

  • identity, tone, tool_efficiency, environment_context, code_change_rules, guidelines, safety, tool_instructions, custom_instructions, last_instructions

Override Actions
All languages support: replace, remove, append, prepend + transform callbacks

Language-Appropriate Patterns

  • TypeScript/Python: Union type approach (action can be string OR function)
  • Go/.NET: Separate fields (Action string/enum + Transform callback field)

Both patterns are idiomatic and semantically equivalent.

Transform Callbacks
All languages support transform with appropriate signatures:

  • TypeScript: (content: string) => string | Promise(string)
  • Python: Callable[[str], str | Awaitable[str]] (with sync/async detection)
  • Go: func(content string) (string, error)
  • .NET: Func(string, Task(string))

Tests
All four languages have E2E tests for both customize mode and transform:

  • nodejs/test/e2e/system_message_transform.test.ts
  • python/e2e/test_system_message_transform.py
  • go/internal/e2e/system_message_transform_test.go
  • dotnet/test/SystemMessageTransformTests.cs

Documentation
All language READMEs document customize mode with consistent examples showing section overrides.


📝 Minor Documentation Gap (Non-Blocking)

The transform feature is implemented and tested in all four SDKs but is not documented in any of the language READMEs or docs/getting-started.md.

While the PR description includes a transform usage example:

identity: {
  action: (currentContent) => {
    return currentContent.replace("GitHub Copilot", "Acme Corp Assistant");
  }
}

This pattern isn't shown in the SDK documentation files. Since this affects all four SDKs equally, it's a consistent gap rather than a cross-language inconsistency.

Suggestion: Consider adding a "Transform Actions" subsection to the README examples in a follow-up PR, showing how users can:

  • Inspect section content for observability
  • Apply regex/string mutations
  • Conditionally modify content

Verdict

This PR maintains excellent cross-SDK consistency. All four language implementations expose the same capabilities with language-appropriate patterns. The minor documentation gap for transform is consistently missing across all SDKs, so there's no inconsistency to address in this PR.

Great work on maintaining feature parity across a complex multi-language codebase! 🎉

Generated by SDK Consistency Review Agent for issue #816 ·

The 'should apply transform modifications' tests previously only verified
that the transform callback was invoked, not that the transformed content
actually reached the model. Now all 4 SDKs assert that TRANSFORM_MARKER
appears in the system message captured from HTTP traffic.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
}
});

await File.WriteAllTextAsync(Path.Combine(Ctx.WorkDir, "hello.txt"), "Hello!");
}
});

await File.WriteAllTextAsync(Path.Combine(Ctx.WorkDir, "combo.txt"), "Combo test!");
@github-actions
Copy link
Contributor

Cross-SDK Consistency Review: ✅ Excellent with Minor Documentation Gap

I've reviewed PR #816 for cross-language SDK consistency, and I'm pleased to report that the implementation is exceptionally consistent across all four SDK implementations (TypeScript, Python, Go, and .NET). The feature parity is excellent.

✅ What's Consistent

1. Core Types & API Surface

  • All 4 SDKs define the same 10 section identifiers: identity, tone, tool_efficiency, environment_context, code_change_rules, guidelines, safety, tool_instructions, custom_instructions, last_instructions
  • All 4 SDKs implement the same section override actions: replace, remove, append, prepend, and transform
  • Naming follows proper language conventions:
    • TypeScript: SystemPromptSection, SectionOverride, SectionTransformFn (camelCase)
    • Python: SystemPromptSection, SectionOverride, SectionTransformFn (snake_case)
    • Go: SectionOverrideAction, SectionOverride, SectionTransformFn (PascalCase for exported, constants like SectionIdentity)
    • .NET: SystemPromptSections, SectionOverride, SectionOverrideAction (PascalCase)

2. Transform Callback Implementation

  • All 4 SDKs implement the transform action via callbacks consistently:
    • TypeScript: action: (currentContent: string) => string | Promise(string)
    • Python: action: Callable[[str], str | Awaitable[str]] (supports both sync and async)
    • Go: Transform: func(currentContent string) (string, error)
    • .NET: Transform: Func(string, Task(string))
  • All 4 SDKs properly separate transform callbacks from wire payloads via extractTransformCallbacks / ExtractTransformCallbacks / _extract_transform_callbacks
  • All 4 SDKs register the systemMessage.transform RPC handler
  • All 4 SDKs have comprehensive E2E tests for transform functionality

3. Documentation Examples

  • All 4 language READMEs include parallel "Customize Mode" examples with the same semantic structure
  • Usage examples demonstrate replace, remove, and append actions consistently

⚠️ Documentation Gap: Transform Action Not Documented

Issue: The transform action is fully implemented and tested across all 4 SDKs, but it's not documented in any of the READMEs or getting-started guide.

Files affected:

  • nodejs/README.md:506 — says "four actions" (should be five)
  • python/README.md:263 — says "four actions" (should be five)
  • go/README.md:223 — says "four actions" (should be five)
  • dotnet/README.md:536 — says "four actions" (should be four, but Transform enum value exists)
  • docs/getting-started.md:1266 — says "four actions" (should be five)

Impact: SDK consumers won't discover the powerful transform feature unless they read the PR description or dive into the type definitions. This is particularly important because transform enables:

  • Read-then-write mutations on prompt sections
  • Logging/observability of prompt content
  • Conditional modifications based on runtime state

Recommendation: Add a "Transform Action" subsection to each README after the existing "Customize Mode" section, showing language-idiomatic examples of:

  1. Basic transform (read current content, return modified content)
  2. Logging/observability use case (inspect content, return unchanged)
  3. Error handling (for Go and other languages where applicable)

Example snippet structure for TypeScript:

// Transform: read current content, return modified content
identity: {
    action: (currentContent) => {
        return currentContent.replace("GitHub Copilot", "Acme Corp Assistant");
    }
},
// Logging/observability: inspect content, return unchanged
safety: {
    action: async (currentContent) => {
        console.log("Safety prompt:", currentContent);
        return currentContent;
    }
}

Summary

This PR demonstrates excellent cross-SDK consistency. The implementation is parallel across all four languages, properly accounting for language idioms. The only gap is documentation: the transform feature should be added to the README examples so consumers can discover and use this powerful capability.

Decision: No code changes required for consistency. I recommend adding transform documentation in a follow-up PR or updating the READMEs in this PR before merging.

Generated by SDK Consistency Review Agent for issue #816 ·

The systemMessageTransformRequest and systemMessageTransformResponse
used anonymous structs without json tags, causing Content to serialize
as uppercase 'Content' instead of lowercase 'content'. The CLI expects
lowercase, so transform results were silently ignored.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Contributor

Cross-SDK Consistency Review ✅

Great work implementing this feature consistently across all 4 SDKs! The implementation demonstrates excellent cross-language consistency:

✅ What's Consistent

  1. Type Definitions - All SDKs define the same 10 section identifiers with identical semantics:

    • TypeScript: SystemPromptSection union type
    • Python: SystemPromptSection Literal type
    • Go: String constants (SectionIdentity, SectionTone, etc.)
    • .NET: Static class SystemPromptSections with constants
  2. Transform Callback Implementation - All SDKs implement transform callbacks with the same pattern:

    • Extract callbacks before sending to CLI
    • Register systemMessage.transform RPC handler
    • Handle both sync and async callbacks (Python, .NET)
    • Error handling leaves original content unchanged
  3. E2E Test Coverage - All 4 languages have parallel test cases:

    • should_invoke_transform_callbacks_with_section_content
    • should_apply_transform_modifications_to_section_content
    • should_work_with_static_overrides_and_transforms_together
  4. API Naming - Follows proper language conventions:

    • TypeScript: camelCase (systemMessage, SectionTransformFn)
    • Python: snake_case (system_message, SectionTransformFn)
    • Go: PascalCase for exports (SystemMessageConfig, SectionTransformFn)
    • .NET: PascalCase (SystemMessageConfig, Transform delegate)

⚠️ Documentation Gap - Missing Transform Action in READMEs

Issue: All 4 SDK READMEs state "Each section override supports four actions" but the code actually implements FIVE actions (replace, remove, append, prepend, and transform).

Affected files:

  • nodejs/README.md (line 506-510)
  • python/README.md (line 263-267)
  • go/README.md (line 223-227)
  • dotnet/README.md (line 536)

The PR description demonstrates transform usage beautifully:

identity: {
  action: (currentContent) => {
    return currentContent.replace("GitHub Copilot", "Acme Corp Assistant");
  }
}

But this capability is never mentioned in the SDK READMEs. Users reading the documentation won't discover they can use transform callbacks.

Recommendation: Add a fifth bullet point and a code example in each README's "Customize Mode" section showing how to use transform callbacks for:

  • Read-then-modify transformations
  • Logging/observability
  • Conditional modifications

This would bring the documentation into alignment with the implementation and help users discover this powerful feature.


Overall Assessment: Aside from the documentation gap, this PR demonstrates excellent engineering discipline with consistent cross-SDK implementation. Once the README documentation is updated to include the transform action, this will be in great shape! 🚀

Generated by SDK Consistency Review Agent for issue #816 ·

@SteveSandersonMS SteveSandersonMS merged commit 005b780 into main Mar 20, 2026
34 checks passed
Copy link
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generated by SDK Consistency Review Agent for issue #816

- **`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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README lists "four actions" but the code actually supports five: replace, remove, append, prepend, and transform.

The transform action is a key feature of this PR (mentioned in the PR description) that allows callbacks to read and modify section content dynamically. Consider adding:

Each section override supports five 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
- **Transform callback** — Pass a function `(currentContent: string) => string | Promise(string)` as the `action` to read and modify section content dynamically

Example with transform:
\```typescript
identity: {
  action: (content: string) => {
    // Log for observability
    console.log("Identity prompt:", content);
    // Modify and return
    return content.replace("GitHub Copilot", "Acme Assistant");
  }
}
\```

This would help users discover the transform feature without having to read the source code or PR descriptions.


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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README lists "four actions" but the code supports five: Replace, Remove, Append, Prepend, and transform via the Transform property.

Consider updating to:

Each section override supports five actions: `Replace`, `Remove`, `Append`, `Prepend`, and **transform callbacks via the `Transform` property**. 

The `Transform` property accepts a `Func(string, Task(string))` delegate that receives the current section content and returns the modified content:

\```csharp
SystemMessage = new SystemMessageConfig
{
    Mode = SystemMessageMode.Customize,
    Sections = new Dictionary(string, SectionOverride)
    {
        [SystemPromptSections.Identity] = new SectionOverride
        {
            Transform = async (content) =>
            {
                // Log for observability
                Console.WriteLine($"Identity: {content}");
                // Modify and return
                return content.Replace("GitHub Copilot", "Acme Assistant");
            }
        }
    }
}
\```

Unknown section IDs are handled gracefully: content is appended to additional instructions, and `Remove` overrides are silently ignored.

- **`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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README lists "four actions" but the code supports five: replace, remove, append, prepend, and transform callbacks via the Transform field.

Consider updating to:

Each section override supports five 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
- **Transform callback** — Set the `Transform` field to a `SectionTransformFn` that receives current content and returns modified content

Example with transform:
\```go
SystemMessage: &copilot.SystemMessageConfig{
    Mode: "customize",
    Sections: map[string]copilot.SectionOverride{
        copilot.SectionIdentity: {
            Transform: func(currentContent string) (string, error) {
                // Log for observability
                log.Printf("Identity: %s", currentContent)
                // Modify and return
                modified := strings.ReplaceAll(currentContent, "GitHub Copilot", "Acme Assistant")
                return modified, nil
            },
        },
    },
}
\```

- **`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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The README lists "four actions" but the code supports five: replace, remove, append, prepend, and transform callbacks.

Consider updating to:

Each section override supports five 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
- **Transform callback** — Pass a callable `(str) -> str | Awaitable[str]` as the `action` to read and modify section content

Example with transform:
\```python
async def customize_identity(content: str) -> str:
    # Log for observability
    print(f"Identity section: {content}")
    # Modify content
    return content.replace("GitHub Copilot", "Acme Assistant")

session = await client.create_session(
    system_message={
        "mode": "customize",
        "sections": {
            "identity": {"action": customize_identity}
        }
    }
)
\```

@SteveSandersonMS SteveSandersonMS deleted the mackinnonbuck/prompt-customization branch March 20, 2026 15:54
SteveSandersonMS added a commit to brettcannon/copilot-sdk that referenced this pull request Mar 20, 2026
Add back SectionTransformFn type, _extract_transform_callbacks helper,
_handle_system_message_transform handler, and systemMessage.transform
RPC registration that were part of PR github#816 but lost during rebase.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
github-merge-queue bot pushed a commit that referenced this pull request Mar 20, 2026
* Remove `copilot.types`
Along the way, simplify `copilot.__init__` to only export the high-level API.

* fix: reorder import statements in test_telemetry.py

* fix: ruff format client.py and session.py

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: update PermissionHandler import path in transform test

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* Fixes after rebase

* fix: use keyword params directly in create_session/resume_session bodies

Remove cfg dict intermediary and use keyword parameters directly,
fixing ty type checker errors where cfg.get() returned Any | None
and shadowed the typed parameter variables.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: restore system message transform support lost during rebase

Add back SectionTransformFn type, _extract_transform_callbacks helper,
_handle_system_message_transform handler, and systemMessage.transform
RPC registration that were part of PR #816 but lost during rebase.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

* fix: restore missing SystemMessageCustomizeConfig and related types

The customize mode types (SystemPromptSection, SYSTEM_PROMPT_SECTIONS,
SectionOverride, SystemMessageCustomizeConfig) were dropped when
types.py was deleted but not re-added to session.py. This also moves
SectionTransformFn and SectionOverrideAction before SectionOverride
so the definitions flow in dependency order.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Steve Sanderson <SteveSandersonMS@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants