Skip to main content
rule official · GitHub

copilot-sdk-go — GitHub Copilot rule: what it does, how to install

Instructions for developing Go applications with GitHub Copilot SDK. Guides the assistant on client initialization, streaming sessions, and event channels.

Install into GitHub Copilot · Manually: put the files in place All methods and platforms ↓
.github/instructions/copilot-sdk-go.instructions.md
Open on GitHub

What copilot-sdk-go does

The rule provides guidelines for writing code with github.com/github/copilot-sdk/go. It applies automatically to **.go and go.mod files. Instructions direct the assistant to stick to the Go standard library without adding third-party packages.

The SDK targets Go 1.21 or later. Without the GitHub Copilot CLI in PATH, the client fails immediately. The rule specifies how to configure process flags via ClientOptions, handle Start failures, and shut down the server with Stop.

Session state relies on concurrent primitives. Events are consumed with channel signals, waiting until SessionIdle arrives before unblocking. The SDK remains in technical preview, which makes breaking changes possible between versions.

What it helps with

  • initialize copilot.NewClient and configure CLI options
  • manage agent sessions and resume them by ID
  • handle streaming message deltas using Go channels
  • declare custom agent tools via copilot.Tool

Example prompts for the agent

  • Create a Copilot SDK session using model gpt-5 and wait for SessionIdle via a channel.
  • Initialize copilot.NewClient with manual server control and a call to Stop().

How to install copilot-sdk-go

Manually: put the files in place

.github/instructions/copilot-sdk-go.instructions.md

The applyTo field sets which files it applies to

Step by step, with commands for each platform: AGENTS.md vs CLAUDE.md: what they are and which one to use

Questions about copilot-sdk-go

How do I install copilot-sdk-go in GitHub Copilot?

Add the rule file to your repository at .github/instructions/copilot-sdk-go.instructions.md.

What does copilot-sdk-go do?

It instructs the AI assistant on how to structure code with github.com/github/copilot-sdk/go. The rule enforces channel-based event handling, session lifecycles, and standard library patterns.

What are the prerequisites for this SDK?

The project requires Go 1.21 or later. You must also have the GitHub Copilot CLI installed and accessible in your system PATH.

Source text

copilot-sdk-go.instructions.md · MIT
---
applyTo: "**.go, go.mod"
description: "This file provides guidance on building Go applications using GitHub Copilot SDK."
name: "GitHub Copilot SDK Go Instructions"
---

## Core Principles

- The SDK is in technical preview and may have breaking changes
- Requires Go 1.21 or later
- Requires GitHub Copilot CLI installed and in PATH
- Uses goroutines and channels for concurrent operations
- No external dependencies beyond the standard library

## Installation

Always install via Go modules:

```bash
go get github.com/github/copilot-sdk/go
```

## Client Initialization

### Basic Client Setup

```go
import "github.com/github/copilot-sdk/go"

client := copilot.NewClient(nil)
if err := client.Start(); err != nil {
    log.Fatal(err)
}
defer client.Stop()
```

### Client Configuration Options

When creating a CopilotClient, use `ClientOptions`:

- `CLIPath` - Path to CLI executable (default: "copilot" from PATH)
- `CLIUrl` - URL of existing CLI server (e.g., "localhost:8080"). When provided, client won't spawn a process
- `Port` - Server port (default: 0 for random)
- `UseStdio` - Use stdio transport instead of TCP (default: true)
- `LogLevel` - Log level (default: "info")
- `AutoStart` - Auto-start server (default: true, use pointer: `boolPtr(true)`)
- `AutoRestart` - Auto-restart on crash (default: true, use pointer: `boolPtr(true)`)
- `Cwd` - Working directory for the CLI process
- `Env` - Environment variables for the CLI process ([]string)

### Manual Server Control

For explicit control:

```go
autoStart := false
client := copilot.NewClient(&copilot.ClientOptions{AutoStart: &autoStart})
if err := client.Start(); err != nil {
    log.Fatal(err)
}
// Use client...
client.Stop()
```

Use `ForceStop()` when `Stop()` takes too long.

## Session Management

### Creating Sessions

Use `SessionConfig` for configuration:

```go
session, err := client.CreateSession(&copilot.SessionConfig{
	OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
    Model: "gpt-5",
    Streaming: true,
    Tools: []copilot.Tool{...},
    SystemMessage: &copilot.SystemMessageConfig{ ... },
    AvailableTools: []string{"tool1", "tool2"},
    ExcludedTools: []string{"tool3"},
    Provider: &copilot.ProviderConfig{ ... },
})
if err != nil {
    log.Fatal(err)
}
```

### Session Config Options

- `SessionID` - Custom session ID
- `Model` - Model name ("gpt-5", "claude-sonnet-4.5", etc.)
- `Tools` - Custom tools exposed to the CLI ([]Tool)
- `SystemMessage` - System message customization (\*SystemMessageConfig)
- `AvailableTools` - Allowlist of tool names ([]string)
- `ExcludedTools` - Blocklist of tool names ([]string)
- `Provider` - Custom API provider configuration (BYOK) (\*ProviderConfig)
- `Streaming` - Enable streaming response chunks (bool)
- `MCPServers` - MCP server configurations
- `CustomAgents` - Custom agent configurations
- `ConfigDir` - Config directory override
- `SkillDirectories` - Skill directories ([]string)
- `DisabledSkills` - Disabled skills ([]string)

### Resuming Sessions

```go
session, err := client.ResumeSession("session-id", &copilot.ResumeSessionConfig{OnPermissionRequest: copilot.PermissionHandler.ApproveAll})
// Or with options:
session, err := client.ResumeSessionWithOptions("session-id", &copilot.ResumeSessionConfig{ ... })
```

### Session Operations

- `session.SessionID` - Get session identifier (string)
- `session.Send(copilot.MessageOptions{Prompt: "...", Attachments: []copilot.Attachment{...}})` - Send message, returns (messageID string, error)
- `session.SendAndWait(options, timeout)` - Send and wait for idle, returns (\*SessionEvent, error)
- `session.Abort()` - Abort current processing, returns error
- `session.GetMessages()` - Get all events/messages, returns ([]SessionEvent, error)
- `session.Destroy()` - Clean up session, returns error

## Event Handling

### Event Subscription Pattern

ALWAYS use channels or done signals to wait for session events:

```go
done := make(chan struct{})

unsubscribe := session.On(func(evt copilot.SessionEvent) {
    switch evt.Type {
    case copilot.AssistantMessage:
        fmt.Println(*evt.Data.Content)
    case copilot.SessionIdle:
        close(done)
    }
})
defer unsubscribe()

session.Send(copilot.MessageOptions{Prompt: "..."})
<-done
```

### Unsubscribing from Events

The `On()` method returns a function that unsubscribes:

```go
unsubscribe := session.On(func(evt copilot.SessionEvent) {
    // handler
})
// Later...
unsubscribe()
```

### Event Types

Use type switches for event handling:

```go
session.On(func(evt copilot.SessionEvent) {
    switch evt.Type {
    case copilot.UserMessage:
        // Handle user message
    case copilot.AssistantMessage:
        if evt.Data.Content != nil {
            fmt.Println(*evt.Data.Content)
        }
    case copilot.ToolExecutionStart:
        // Tool execution started
    case copilot.ToolExecutionComplete:
        // Tool execution completed
    case copilot.SessionStart:
        // Session started
    case copilot.SessionIdle:
        // Session is idle (processing complete)
    case copilot.SessionError:
        if evt.Data.Message != nil {
            fmt.Println("Error:", *evt.Data.Message)
        }
    }
})
```

## Streaming Responses

### Enabling Streaming

Set `Streaming: true` in SessionConfig:

```go
session, err := client.CreateSession(&copilot.SessionConfig{
	OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
    Model: "gpt-5",
    Streaming: true,
})
```

### Handling Streaming Events

Handle both delta events (incremental) and final events:

```go
done := make(chan struct{})

session.On(func(evt copilot.SessionEvent) {
    switch evt.Type {
    case copilot.AssistantMessageDelta:
        // Incremental text chunk
        if evt.Data.DeltaContent != nil {
            fmt.Print(*evt.Data.DeltaContent)
        }
    case copilot.AssistantReasoningDelta:
        // Incremental reasoning chunk (model-dependent)
        if evt.Data.DeltaContent != nil {
            fmt.Print(*evt.Data.DeltaContent)
        }
    case copilot.AssistantMessage:
        // Final complete message
        fmt.Println("\n--- Final ---")
        if evt.Data.Content != nil {
            fmt.Println(*evt.Data.Content)
        }
    case copilot.AssistantReasoning:
        // Final reasoning content
        fmt.Println("--- Reasoning ---")
        if evt.Data.Content != nil {
            fmt.Println(*evt.Data.Content)
        }
    case copilot.SessionIdle:
        close(done)
    }
})

session.Send(copilot.MessageOptions{Prompt: "Tell me a story"})
<-done
```

Note: Final events (`AssistantMessage`, `AssistantReasoning`) are ALWAYS sent regardless of streaming setting.

## Custom Tools

### Defining Tools

```go
session, err := client.CreateSession(&copilot.SessionConfig{
	OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
    Model: "gpt-5",
    Tools: []copilot.Tool{
        {
            Name:        "lookup_issue",
            Description: "Fetch issue details from tracker",
            Parameters: map[string]interface{}{
                "type": "object",
                "properties": map[string]interface{}{
                    "id": map[string]interface{}{
                        "type":        "string",
                        "description": "Issue ID",
                    },
                },
                "required": []string{"id"},
            },
            Handler: func(inv copilot.ToolInvocation) (copilot.ToolResult, error) {
                args := inv.Arguments.(map[string]interface{})
                issueID := args["id"].(string)

                issue, err := fetchIssue(issueID)
                if err != nil {
                    return copilot.ToolResult{}, err
                }

                return copilot.ToolResult{
                    TextResultForLLM: fmt.Sprintf("Issue: %v", issue),
                    ResultType:       "success",
                    ToolTelemetry:    map[string]interface{}{},
                }, nil
            },
        },
    },
})
```

### Tool Return Types

- Return `ToolResult` struct with fields:
  - `TextResultForLLM` (string) - Result text for the LLM
  - `ResultType` (string) - "success" or "failure"
  - `Error` (string, optional) - Internal error message (not shown to LLM)
  - `ToolTelemetry` (map[string]interface{}) - Telemetry data

### Tool Execution Flow

When Copilot invokes a tool, the client automatically:

1. Runs your handler function
2. Returns the ToolResult
3. Responds to the CLI

## System Message Customization

### Append Mode (Default - Preserves Guardrails)

```go
session, err := client.CreateSession(&copilot.SessionConfig{
	OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
    Model: "gpt-5",
    SystemMessage: &copilot.SystemMessageConfig{
        Mode: "append",
        Content: `
<workflow_rules>
- Always check for security vulnerabilities
- Suggest performance improvements when applicable
</workflow_rules>
`,
    },
})
```

### Replace Mode (Full Control - Removes Guardrails)

```go
session, err := client.CreateSession(&copilot.SessionConfig{
	OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
    Model: "gpt-5",
    SystemMessage: &copilot.SystemMessageConfig{
        Mode:    "replace",
        Content: "You are a helpful assistant.",
    },
})
```

## File Attachments

Attach files to messages using `Attachment`:

```go
messageID, err := session.Send(copilot.MessageOptions{
    Prompt: "Analyze this file",
    Attachments: []copilot.Attachment{
        {
            Type:        "file",
            Path:        "/path/to/file.go",
            DisplayName: "My File",
        },
    },
})
```

## Message Delivery Modes

Use the `Mode` field in `MessageOptions`:

- `"enqueue"` - Queue message for processing
- `"immediate"` - Process message immediately

```go
session.Send(copilot.MessageOptions{
    Prompt: "...",
    Mode:   "enqueue",
})
```

## Multiple Sessions

Sessions are independent and can run concurrently:

```go
session1, _ := client.CreateSession(&copilot.SessionConfig{
	OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
	Model:               "gpt-5",
})
session2, _ := client.CreateSession(&copilot.SessionConfig{
	OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
	Model:               "claude-sonnet-4.5",
})

session1.Send(copilot.MessageOptions{Prompt: "Hello from session 1"})
session2.Send(copilot.MessageOptions{Prompt: "Hello from session 2"})
```

## Bring Your Own Key (BYOK)

Use custom API providers by configuring `ProviderConfig`:

```go
session, err := client.CreateSession(&copilot.SessionConfig{
	OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
    Provider: &copilot.ProviderConfig{
        Type:    "openai",
        BaseURL: "https://api.openai.com/v1",
        APIKey:  "your-api-key",
    },
})
```

## Session Lifecycle Management

### Checking Connection State

```go
state := client.GetState()
// Returns: "disconnected", "connecting", "connected", or "error"
```

## Error Handling

### Standard Exception Handling

```go
session, err := client.CreateSession(&copilot.SessionConfig{
	OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
})
if err != nil {
    log.Fatalf("Failed to create session: %v", err)
}

_, err = session.Send(copilot.MessageOptions{Prompt: "Hello"})
if err != nil {
    log.Printf("Failed to send: %v", err)
}
```

### Session Error Events

Monitor `SessionError` type for runtime errors:

```go
session.On(func(evt copilot.SessionEvent) {
    if evt.Type == copilot.SessionError {
        if evt.Data.Message != nil {
            fmt.Fprintf(os.Stderr, "Session Error: %s\n", *evt.Data.Message)
        }
    }
})
```

## Connectivity Testing

Use `Ping` to verify server connectivity:

```go
resp, err := client.Ping("test message")
if err != nil {
    log.Printf("Server unreachable: %v", err)
} else {
    log.Printf("Server responded at %d", resp.Timestamp)
}
```

## Resource Cleanup

### Cleanup with Defer

ALWAYS use `defer` for cleanup:

```go
client := copilot.NewClient(nil)
if err := client.Start(); err != nil {
    log.Fatal(err)
}
defer client.Stop()

session, err := client.CreateSession(&copilot.SessionConfig{OnPermissionRequest: copilot.PermissionHandler.ApproveAll})
if err != nil {
    log.Fatal(err)
}
defer session.Destroy()
```

### Manual Cleanup

If not using defer:

```go
client := copilot.NewClient(nil)
err := client.Start()
if err != nil {
    log.Fatal(err)
}

session, err := client.CreateSession(&copilot.SessionConfig{OnPermissionRequest: copilot.PermissionHandler.ApproveAll})
if err != nil {
    client.Stop()
    log.Fatal(err)
}

// Use session...

session.Destroy()
errors := client.Stop()
for _, err := range errors {
    log.Printf("Cleanup error: %v", err)
}
```

## Best Practices

1. **Always use `defer`** for cleanup of clients and sessions
2. **Use channels** to wait for SessionIdle event
3. **Handle SessionError** events for robust error handling
4. **Use type switches** for event handling
5. **Enable streaming** for better UX in interactive scenarios
6. **Provide descriptive tool names and descriptions** for better model understanding
7. **Call unsubscribe functions** when no longer needed
8. **Use SystemMessageConfig with Mode: "append"** to preserve safety guardrails
9. **Handle both delta and final events** when streaming is enabled
10. **Check nil pointers** in event data (Content, Message, etc. are pointers)

## Common Patterns

### Simple Query-Response

```go
client := copilot.NewClient(nil)
if err := client.Start(); err != nil {
    log.Fatal(err)
}
defer client.Stop()

session, err := client.CreateSession(&copilot.SessionConfig{
	OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
	Model:               "gpt-5",
})
if err != nil {
    log.Fatal(err)
}
defer session.Destroy()

done := make(chan struct{})

session.On(func(evt copilot.SessionEvent) {
    if evt.Type == copilot.AssistantMessage && evt.Data.Content != nil {
        fmt.Println(*evt.Data.Content)
    } else if evt.Type == copilot.SessionIdle {
        close(done)
    }
})

session.Send(copilot.MessageOptions{Prompt: "What is 2+2?"})
<-done
```

### Multi-Turn Conversation

```go
session, _ := client.CreateSession(&copilot.SessionConfig{OnPermissionRequest: copilot.PermissionHandler.ApproveAll})
defer session.Destroy()

sendAndWait := func(prompt string) error {
    done := make(chan struct{})
    var eventErr error

    unsubscribe := session.On(func(evt copilot.SessionEvent) {
        switch evt.Type {
        case copilot.AssistantMessage:
            if evt.Data.Content != nil {
                fmt.Println(*evt.Data.Content)
            }
        case copilot.SessionIdle:
            close(done)
        case copilot.SessionError:
            if evt.Data.Message != nil {
                eventErr = fmt.Errorf(*evt.Data.Message)
            }
        }
    })
    defer unsubscribe()

    if _, err := session.Send(copilot.MessageOptions{Prompt: prompt}); err != nil {
        return err
    }
    <-done
    return eventErr
}

sendAndWait("What is the capital of France?")
sendAndWait("What is its population?")
```

### SendAndWait Helper

```go
// Use built-in SendAndWait for simpler synchronous interaction
response, err := session.SendAndWait(copilot.MessageOptions{
    Prompt: "What is 2+2?",
}, 0) // 0 uses default 60s timeout

if err != nil {
    log.Printf("Error: %v", err)
}
if response != nil && response.Data.Content != nil {
    fmt.Println(*response.Data.Content)
}
```

### Tool with Struct Return Type

```go
type UserInfo struct {
    ID    string `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
    Role  string `json:"role"`
}

session, _ := client.CreateSession(&copilot.SessionConfig{
	OnPermissionRequest: copilot.PermissionHandler.ApproveAll,
    Tools: []copilot.Tool{
        {
            Name:        "get_user",
            Description: "Retrieve user information",
            Parameters: map[string]interface{}{
                "type": "object",
                "properties": map[string]interface{}{
                    "user_id": map[string]interface{}{
                        "type":        "string",
                        "description": "User ID",
                    },
                },
                "required": []string{"user_id"},
            },
            Handler: func(inv copilot.ToolInvocation) (copilot.ToolResult, error) {
                args := inv.Arguments.(map[string]interface{})
                userID := args["user_id"].(string)

                user := UserInfo{
                    ID:    userID,
                    Name:  "John Doe",
                    Email: "[email protected]",
                    Role:  "Developer",
                }

                jsonBytes, _ := json.Marshal(user)
                return copilot.ToolResult{
                    TextResultForLLM: string(jsonBytes),
                    ResultType:       "success",
                    ToolTelemetry:    map[string]interface{}{},
                }, nil
            },
        },
    },
})
```

© github · MIT · github/awesome-copilot

Similar IDE rules

All IDE rules for Agent setup: memory, context, orchestration →

From the same set732

The whole set →
copilot-thought-logging This rule makes Copilot keep a Copilot-Processing.md file with a plan and progress marks, so you can watch the run and steer it. ★ 40,418repo stars copilot-sdk-python Coding instructions for GitHub Copilot when building Python apps with github-copilot-sdk. Guides async client initialization, session setup, tools, and streaming events. ★ 40,418repo stars copilot-sdk-nodejs Guidance for Node.js and TypeScript apps built with the GitHub Copilot SDK, covering client startup, sessions, events, and streamed output. ★ 40,418repo stars copilot-sdk-java A rule that tells Copilot how to build Java apps on the GitHub Copilot SDK: client setup, sessions, async calls. ★ 40,418repo stars copilot-sdk-csharp This rule guides C# work with the GitHub Copilot SDK, from client setup to session events. Use it when adding SDK features to a project. ★ 40,418repo stars convert-jpa-to-spring-data-cosmos A step-by-step Copilot rule for moving a Spring Boot app off JPA and onto Azure Cosmos DB with Spring Data Cosmos. Useful once the migration is already decided. ★ 40,418repo stars convert-cassandra-to-spring-data-cosmos Step-by-step instructions for converting Spring Boot Cassandra projects to Azure Cosmos DB with spring-data-cosmos. Applies to Java code, build files, and application properties. ★ 40,418repo stars context7 This rule brings Context7 into tasks that depend on current, version-specific API documentation. It is useful when the workspace does not contain the needed external reference. ★ 40,418repo stars

Project sponsors

Companies that keep this analytics open
Gold sponsor

Impressions, clicks and code copies — open statistics. We keep the numbers public for the same reason NeBlask itself is public. Become a sponsor