Skip to main content

Posts

Showing posts from 2026

Why my Azure DevOps scheduled pipeline never ran

I set up a scheduled pipeline in Azure DevOps. The YAML was valid. No errors on save. I waited patiently for the cron to fire. Nothing happened… The culprit turned out to be a single line I'd added for a completely legitimate reason trigger: none . The setup The pipeline looked roughly like this: trigger: none schedules: - cron: "0 2 * * 1-5" displayName: Nightly weekday build branches: include: - main always: true The intent was straightforward: I didn't want CI runs on every push, so I explicitly disabled that with trigger: none . And I wanted the pipeline to run on a schedule. Seems fine, right? Except it never ran. What's actually happening Here's the thing that isn't obvious until you read the docs carefully (or waste an afternoon debugging): in Azure DevOps YAML pipelines, trigger is specifically the CI trigger — the thing that fires on code pushes. schedules is a completely separate concept. So whe...

GitHub Copilot SDK Deep Dive: CopilotClientMode

By default, the CopilotClient starts in CopilotCli mode. That means the full Copilot CLI persona is active — which includes a lot: All built-in tools available (subject to the ToolSet filtering you do per session) Host integration enabled: the CLI picks up your local ~/.copilot/ config, agents directory, plugins, and AGENTS.md files if they exist The default system prompt with the full Copilot identity Co-author trailers added to git commits Storage backed by the local filesystem at the default path For a developer using the SDK on their own machine to automate their own workflows, this is perfect. The agent behaves exactly like Copilot CLI would interactively. Context flows in from their environment, their local agent configs are respected, their Copilot persona is preserved. The multi-tenant problem The moment you start running the SDK as a service — where one process handles sessions for multiple users or tenants — default mode becomes a liability. ...

Shining a light on .NET versions across our organisation with OpenTelemetry – The Azure Monitor edition

In a previous post I showed how to add the .NET runtime version as an OpenTelemetry resource attribute: ResourceBuilder.CreateEmpty() .AddService($"{_sofaSettings.ApplicationName}-{_sofaSettings.EnvironmentName}") .AddAttributes(new Dictionary<string, object> { ["deployment.environment"] = _sofaSettings.EnvironmentName, ["service.name"] = _sofaSettings.ApplicationName, ["runtime.dotnet.version"] = Environment.Version.ToString() }) .Build(); The idea was clean: attach facts about what is running directly to the resource, and let OpenTelemetry carry them along with every trace, metric, and log automatically. Unfortunately, there is a catch. The problem Azure Monitor's OpenTelemetry exporter only maps a fixed set of well-known resource attributes onto Application Insights fields. service.name and service.namespace become Cloud Role Name, service.instance.id becomes Cloud Role Insta...

GitHub Copilot SDK Deep Dive: Controlling built-in tools with toolset

This post is part of a follow-up series to my GitHub Copilot SDK blog series . After wrapping up the main series I was left with a list of features that deserved more than a passing mention. This is the second post about the built-in tools. When you create a session using the SDK, the agent has access to two distinct categories of tools: Custom tools are the ones you define yourself — your CopilotTool.DefineTool(...) registrations, the available skills and MCP tools. These are the application-specific capabilities you build. Built-in tools are what the Copilot CLI brings to the table out of the box: file reading, file writing, shell execution, web fetching, web search, and a handful of others. These power the agentic loop that makes Copilot useful without you having to implement everything from scratch. By default, when you call CreateSessionAsync , both categories are available. And by default, built-in tools that could cause side effects — writing files, executing sh...

GitHub Copilot SDK Deep Dive: Surgical system prompt customization

This post is part of a follow-up series to my GitHub Copilot SDK blog series . After wrapping up the main series I was left with a list of features that deserved more than a passing mention. First up: SystemMessageConfig , and specifically the mode that most tutorials gloss right over. The temptation of Replace When you first discover that the Copilot SDK lets you control the system prompt, the obvious instinct is to reach for SystemMessageMode.Replace . Full control, clean slate, no surprises — what's not to like? var session = await client.CreateSessionAsync(new SessionConfig { Model = "gpt-5", SystemMessage = new SystemMessageConfig { Mode = SystemMessageMode.Replace, Content = "You are a helpful assistant." } }); However there is a big problem with adding this line. When you replace the system prompt wholesale you are not just customizing Copilot, you are evicting it. The carefully tuned defaults around tool use, safet...

AgentsView: A practical look at your AI agent sessions

With the change to token based billing, I pay a lot more attention on what my AI agents are doing. Although some info is available, I was missing the tools to evaluate my token consumption and have an in-depth understanding of what's going on. Until I discovered AgentsView. AgentsView is a local-first desktop and web app that reads those files and gives you a proper UI to work with them: browse sessions, search across all message content, see tool calls and thinking blocks, and get a quick health grade for each session. Let's install it and see what it actually looks like. Installation There are a few ways in, pick whatever fits your workflow. As a Windows user I decided to grab the latest .exe or .AppImage from the GitHub Releases page . The desktop app is fully bundled and auto-updates. Executing the downloaded executable will walk you through an installation wizard. Remark: There is also a CLI which uses the same data directory. Running it If you went the...

Ship your C# MCP Server as a one-click bundle with MCPB

You built an MCP server in C#. It works great on your machine. Now you want to share it with colleagues, publish it to the community, or ship it as part of a product. The problem? Every time someone wants to use a local MCP server, they have to clone a repo, install runtimes, hand-edit a JSON config file, get the path wrong, edit it again... you know the drill. MCP Bundles ( .mcpb ) solve exactly that. They're the .vsix of the MCP ecosystem: a single file that a supporting app (like Claude for Desktop) opens with one click to present a guided install dialog. No terminal, no JSON editing, no "works on my machine." This post walks through taking a C# MCP server binary and packaging it into a distributable .mcpb file from scratch. What's inside a .mcpb file? Before touching anything, it helps to understand what you're building. A .mcpb file is just a ZIP archive with a specific structure: my-server.mcpb (ZIP file) ├── manifest.json ← required: ...

Level Up Your Copilot CLI Statusline with Oh My Posh

In previous posts, I covered how to customize the GitHub Copilot CLI statusline. First with the default options , then with a dynamic script . Today we're taking it a step further: integrating Oh My Posh to bring full prompt theming support to your Copilot CLI session. Oh My Posh has native support for GitHub Copilot CLI, so you get all its theming power (Nerd Font icons, color gradients, diamond-style segments,…) rendering right inside the Copilot CLI statusline. What Is Oh My Posh? Oh My Posh is a cross-shell prompt engine that lets you define richly styled prompts using a JSON (or YAML/TOML) configuration file. You probably know it from PowerShell or bash, but it also ships a dedicated copilot subcommand specifically for integration with GitHub Copilot CLI's statusLine feature. Prerequisites Oh My Posh installed ( winget install JanDeDobbeleer.OhMyPosh   see docs ) A Nerd Font installed and set as your terminal font (for icons to render correctly) Gi...

Safely injecting a JSON configuration object into a Razor Page

While reviewing an ASP.NET Core Razor page application that needed to share server-side configuration with client-side JavaScript, I noticed the following approach to inject a JSON object: <script> var featureFlags= @Html.Raw(Model.FeatureFlagsJson); </script> It works — until it doesn't. This post walks through the right way to do it, why the naive approach can blow up in your face, and what the production-safe pattern looks like. Remark: There was a mistake in the original version of this post. I updated it with a fixed version of the code Why the naive approach is dangerous Directly interpolating server-side values into a <script> block creates an XSS (Cross-Site Scripting) vector . If any value in your config object contains characters like </script> , " , or ' , the browser can interpret that as the end of your script tag — or worse, execute attacker-controlled code. Consider this innocent-looking config value: public string ...

Multi-agent patterns with the GitHub Copilot SDK (continued)

This final post continues on the multi-agent path: instead of one agent doing more things, we compose multiple agents doing the right things. Yesterday I demonstrated how to do this inside the Copilot SDK itself, today we look the broader ecosystem and I’ll show you how to integrate your Copilot SDK agent in the Microsoft Agent Framework. Microsoft Agent Framework The Microsoft Agent Framework(MAF) is the unified successor to Semantic Kernel and AutoGen. It provides a standard interface for building, orchestrating, and deploying AI agents. Dedicated integration packages let you wrap a Copilot SDK client as a first-class MAF agent — interchangeable with any other agent provider in the framework. The key distinction from Part 1: custom agents inside the SDK work within a single Copilot session, with the Copilot runtime as orchestrator. MAF operates at a higher level — it can compose a Copilot SDK agent with agents backed by Azure OpenAI, Anthropic, or any other provider, using st...

Multi-agent patterns with the GitHub Copilot SDK

We've covered a lot of ground in this series: sessions and lifecycle, deployment and scaling, MCP integration, and skills. Each post added a new capability layer to a single agent. This post changes the shape of the problem: instead of one agent doing more things, we compose multiple agents doing the right things. There are two different levels at which you can do this. Inside the Copilot SDK itself, custom agents let the Copilot runtime orchestrate specialised sub-agents automatically within a single session. Beyond the SDK, the Microsoft Agent Framework lets you compose Copilot SDK agents with agents from any other provider in structured multi-agent workflows. In this post we stay inside the SDK. Our next and final post will look at the broader ecosystem and Microsoft Agent Framework integration. The problem with a single agent A single session with a broad system prompt is fine for many use cases. But as tasks grow more complex, the cracks show: A general agent need...