James Montemagno
James Montemagno

Live, Love, Bike, and Code.

Live, Love, Bike, and Code

Share


Tags


James Montemagno

Building Better Apps with GitHub Copilot Custom Agents

Custom agents are here to customize and optimize how coding agents work on your code, here is my experiment with the WinForms agent.

When building Hotkey Typer, a Windows Forms app that types predefined snippets via hotkey, I discovered the power of GitHub Copilot's new custom agents feature. What started as a simple refactoring request turned into an eye-opening demonstration of how specialized AI agents can dramatically improve code quality and implementation speed.

What Are Custom Agents?

Custom agents are specialized versions of GitHub Copilot coding agent that you can tailor to your unique workflows, coding conventions, and use cases. Instead of repeatedly providing the same instructions and context, custom agents allow you to define specialized agents that act like tailored teammates—following your standards, using the right tools, and implementing team-specific practices.

Think of custom agents as expert consultants embedded directly in your development workflow. They're defined using simple Markdown files (called agent profiles) that specify:

Where You Can Use Custom Agents

Once created, custom agents are available wherever you use Copilot coding agent:

Each interaction maintains consistency based on the agent's defined profile, ensuring targeted support tailored to your specific needs.

Screenshot 2025-11-02 at 9.32.11 AM.png

Where to Configure Custom Agents

You can define agent profiles at different levels:

The agent profile format is straightforward:

---
name: readme-creator
description: Agent specializing in creating and improving README files
---

You are a documentation specialist focused on README files. Your scope is limited to 
README files or other related documentation files only - do not modify or analyze code files.

Focus on the following instructions:
- Create and update README.md files with clear project descriptions
- Structure README sections logically: overview, installation, usage, contributing
- Write scannable content with proper headings and formatting
- Add appropriate badges, links, and navigation elements

Discovering the WinForms Expert Agent

The real power of custom agents became apparent when I discovered the awesome-copilot repository, a curated collection of custom agents maintained by GitHub. This repository contains specialized agents for various frameworks and technologies, including C#, WinForms, arm Migration, Dynatrace, LaunchDarkly, Neon, PagerDuty, and more!

Among these gems, I found the WinForms Expert agent—a sophisticated agent specifically designed for .NET Windows Forms development. This agent encodes decades of WinForms best practices into a single, reusable profile.

What Makes the WinForms Expert Special?

The WinForms Expert agent is a comprehensive guide that understands the nuanced challenges of WinForms development. It addresses several critical areas:

Designer Code vs. Regular Code: One of WinForms' unique challenges is managing two distinct code contexts—designer-generated serialization code and modern business logic. The agent knows exactly which C# features are allowed in each context.

Modern .NET Patterns: The agent is fully updated for .NET 8-10, including:

Layout Best Practices: Comprehensive guidance on using TableLayoutPanel and FlowLayoutPanel for responsive, DPI-aware layouts that work across different screen sizes and scaling factors.

CodeDOM Serialization Management: Critical rules for property serialization in the WinForms designer, preventing common pitfalls with [DefaultValue] attributes and ShouldSerialize*() methods.

Exception Handling: Proper patterns for async event handlers and application-level exception handling to prevent process crashes.

The agent's instructions are remarkably detailed, covering everything from control naming conventions to accessibility requirements. It's like having a senior WinForms architect reviewing every line of code.

Adding the WinForms Expert to My Repository

Adding a custom agent from the awesome-copilot repository is remarkably simple:

  1. Create the .github/agents/ directory in your repository
  2. Copy the agent profile (a .md file) into this directory
  3. Commit and push—that's it!

For Hotkey Typer, I copied the WinFormsExpert.agent.md file into .github/agents/, making it immediately available to Copilot throughout my repository.

The Real-World Test: Multiple Snippets Feature

I had opened Issue #3 requesting a new feature: support for managing multiple reusable text snippets instead of just a single predefined block. Users needed to switch between different prompts, code templates, and demo scripts quickly without manual settings editing.

The feature requirements included:

First Attempt: Without the Custom Agent (PR #7)

I first assigned the standard Copilot coding agent to Issue #3, resulting in Pull Request #7. The implementation was functional:

However, there were notable issues:

While functional, it felt like code written by someone proficient in general C# but not deeply experienced with WinForms specifics.

Second Attempt: With the WinForms Expert Agent (PR #8)

Then I closed PR #7 and reassigned Issue #3 to Copilot, but this time explicitly mentioning the WinForms Expert agent. The results in Pull Request #8 were dramatically better:

Improved UI Organization:

Snippet: [────────Dropdown (300px)────────▼]
         [New] [Copy] [Rename] [Delete]
         
[Text Editor (460x135)]

[Speed slider, checkboxes, file options]

[Save]
[Minimize] [Stop]        Status: Active

The agent reorganized the layout following WinForms best practices:

Enhanced User Experience:

Code Quality Improvements:

Even Better Documentation:
The agent included detailed diagrams of the UI layout directly in the PR description, making the implementation immediately understandable.

Key Code Differences: Before and After

Let me show you some concrete examples of the improvements the WinForms Expert agent made:

1. InputDialog Helper Class

PR #7 (Without WinForms Expert): Used an inline dialog creation pattern with repetitive code in each button handler.

PR #8 (With WinForms Expert): Created a dedicated, reusable InputDialog class following WinForms best practices:

internal class InputDialog : Form
{
    private readonly TextBox txtInput;
    private readonly Button btnOK;
    private readonly Button btnCancel;
    
    public string InputText => txtInput.Text;
    
    public InputDialog(string prompt, string title, string defaultValue = "")
    {
        Text = title;
        FormBorderStyle = FormBorderStyle.FixedDialog;
        StartPosition = FormStartPosition.CenterParent;
        MaximizeBox = false;
        MinimizeBox = false;
        ClientSize = new Size(350, 120);
        
        // Control setup...
        
        // Select all text when dialog opens
        Load += (s, e) => txtInput.SelectAll();
    }
    
    public static string? Show(string prompt, string title, string defaultValue = "")
    {
        using var dialog = new InputDialog(prompt, title, defaultValue);
        return dialog.ShowDialog() == DialogResult.OK ? dialog.InputText : null;
    }
}

This is a textbook WinForms pattern: proper disposal with using, nullable return type, static factory method, and proper dialog configuration.

2. Settings Migration Pattern

PR #7 (Without WinForms Expert): Used a "fresh start" approach that ignored existing settings if loading failed.

PR #8 (With WinForms Expert): Implemented proper backward-compatible migration:

// Migration: Convert old single PredefinedText to snippet if no snippets exist
if (settings.Snippets == null || settings.Snippets.Count == 0)
{
    string contentToMigrate = !string.IsNullOrEmpty(settings.PredefinedText) 
        ? settings.PredefinedText 
        : DefaultSnippetContent;
    snippets = new List<TextSnippet>
    {
        new TextSnippet
        {
            Id = DefaultSnippetId,
            Name = DefaultSnippetName,
            Content = contentToMigrate,
            LastUsed = DateTime.Now
        }
    };
    activeSnippetId = DefaultSnippetId;
}
else
{
    snippets = settings.Snippets;
    activeSnippetId = settings.ActiveSnippetId;
    
    // Validate active snippet exists
    if (string.IsNullOrEmpty(activeSnippetId) || 
        !snippets.Any(s => s.Id == activeSnippetId))
    {
        activeSnippetId = snippets.FirstOrDefault()?.Id;
    }
}

This handles the upgrade path gracefully, preserving user data instead of discarding it.

3. UI Layout in Designer Code

PR #7 (Without WinForms Expert): Controls placed with absolute positioning and cramped spacing:

var btnNewSnippet = new Button
{
    Text = "New",
    Location = new Point(335, 82),
    Size = new Size(35, 25),  // Cramped button
    Font = new Font("Segoe UI", 8F)
};

PR #8 (With WinForms Expert): Better organized with logical grouping and proper sizing:

// Snippet selector ComboBox - wider, more prominent
var cmbSnippets = new ComboBox
{
    Name = "cmbSnippets",
    Location = new Point(85, 82),
    Size = new Size(300, 23),  // 300px width for readability
    DropDownStyle = ComboBoxStyle.DropDownList,
    Font = new Font("Segoe UI", 9F)
};

// Snippet management buttons - moved under dropdown for logical grouping
var btnNewSnippet = new Button
{
    Name = "btnNewSnippet",
    Text = "New",
    Location = new Point(85, 110),
    Size = new Size(70, 25),  // Proper button size
    Font = new Font("Segoe UI", 9F)
};

The WinForms Expert agent understood that:

4. Validation Logic with User Feedback

PR #7: Basic validation without proper user feedback:

if (string.IsNullOrWhiteSpace(name)) return;
if (snippets.Any(s => s.Name.Equals(name, StringComparison.OrdinalIgnoreCase)))
{
    MessageBox.Show("A snippet with this name already exists.", 
        "Duplicate Name", MessageBoxButtons.OK, MessageBoxIcon.Warning);
    return;
}

PR #8: Enhanced validation with trim and separate empty-string check:

// Validate: trim and check uniqueness (case-insensitive)
name = name.Trim();
if (string.IsNullOrEmpty(name))
{
    MessageBox.Show("Snippet name cannot be empty.", 
        "Invalid Name", MessageBoxButtons.OK, MessageBoxIcon.Warning);
    return;
}

if (snippets.Any(s => string.Equals(s.Name, name, StringComparison.OrdinalIgnoreCase)))
{
    MessageBox.Show("A snippet with this name already exists.", 
        "Duplicate Name", MessageBoxButtons.OK, MessageBoxIcon.Warning);
    return;
}

The WinForms Expert understood the importance of:

5. Modern C# Features in Business Logic

PR #8 consistently used modern C# patterns throughout:

// File-scoped namespace
namespace HotkeyTyper;

// Target-typed new
private List<TextSnippet> snippets = new();

// Expression-bodied member
public string InputText => txtInput.Text;

// Nullable reference types
public static string? Show(string prompt, string title, string defaultValue = "")

// Using declaration (proper disposal)
using var dialog = new InputDialog(prompt, title, defaultValue);

While PR #7 had some modern features, PR #8 applied them consistently and appropriately.

6. Comprehensive Unit Tests

PR #8 included thorough migration tests that PR #7 lacked:

[Fact]
public void OldSettings_MigratesToSnippets()
{
    // Arrange - Old settings format with single PredefinedText
    string oldSettingsJson = """
    {
        "PredefinedText": "Hello, World!",
        "TypingSpeed": 7,
        "HasCode": true
    }
    """;
    
    var oldSettings = JsonSerializer.Deserialize<AppSettings>(oldSettingsJson);
    
    // Act & Assert - Settings should be migrated
    Assert.NotNull(oldSettings);
    Assert.Null(oldSettings.Snippets);
    Assert.Equal("Hello, World!", oldSettings.PredefinedText);
}

These tests ensure the backward compatibility works correctly—a crucial detail for production applications.

The Visible Difference

The before-and-after images tell the story:

Without WinForms Expert:

With WinForms Expert:

The agent even provided visual diagrams showing the layout structure, demonstrating deep understanding of WinForms design patterns.

The Impact: Quality and Velocity

The difference between PR #7 and PR #8 wasn't just cosmetic. The WinForms Expert agent delivered:

Better Architecture:

Fewer Iterations:

Knowledge Transfer:

Time Savings:

Getting Started with Custom Agents

Ready to try custom agents in your projects? Here's how:

1. Explore the Awesome-Copilot Repository

Visit github.com/github/awesome-copilot and browse the available agents.

2. Add an Agent to Your Repository

  1. Create .github/agents/ in your repository
  2. Copy the agent .md file from awesome-copilot
  3. Customize if needed (optional)
  4. Commit and push

3. Use the Agent

In GitHub issues, pull requests, or VS Code:

Screenshot 2025-11-02 at 9.33.00 AM.png

4. Create Your Own Agents

Have specific team conventions? Create custom agents that encode:

Full documentation: GitHub Docs - Creating Custom Agents

Key Takeaways

Working with the WinForms Expert agent taught me several valuable lessons:

Specialization Matters: Generic AI coding assistance is good, but specialized agents that understand framework-specific patterns are transformative. The depth of knowledge in the WinForms Expert agent far exceeded what I could convey in ad-hoc prompts.

Consistency Across the Team: Custom agents ensure everyone on your team follows the same patterns and best practices, even for developers less familiar with specific frameworks.

Living Documentation: Agent profiles serve as executable documentation of your team's coding standards—they don't just describe patterns, they enforce them.

Knowledge Amplification: Using specialized agents is a learning experience. Seeing high-quality, pattern-consistent code generated repeatedly helps developers internalize best practices.

Velocity Without Sacrificing Quality: The right custom agent doesn't just generate code faster—it generates better code faster, reducing technical debt and future maintenance burden.

Wrapping Up

The difference between PR #7 (without custom agent) and PR #8 (with WinForms Expert) perfectly illustrates why custom agents represent a significant leap forward in AI-assisted development. It's not about replacing developer expertise—it's about amplifying it with domain-specific knowledge that would otherwise require years of experience to accumulate.

For Hotkey Typer, the WinForms Expert agent helped deliver a feature that not only works but follows industry best practices, is maintainable, accessible, and properly architected for future enhancements. The implementation handles edge cases, uses appropriate design patterns, and would pass a senior code review with minimal comments.

If you're working with WinForms—or any specialized framework—I highly recommend exploring custom agents. The awesome-copilot repository is an excellent starting point, and creating your own team-specific agents is easier than you might think.

Learn More

Custom agents aren't just the future of AI-assisted development—they're available today, and they're already making a significant difference in how we build software. Give them a try, and see the difference specialized expertise can make in your projects!


Have you tried custom agents in your projects? I'd love to hear about your experiences. Share your thoughts and questions on X!

This blog was written with VS Code and Claude Sonnet 4.5 based on a very lovely prompt with images, MCP server access, and more written by ME! I also reviewed and slightly modified the blog.

Copyright © James Montemagno 2025 All rights reserved. Privacy Policy

View Comments