A copy-paste, end-to-end walkthrough that takes a working Windows app and registers it as an Agent Launcher: the App Action, agent.json, the package-manifest extension, and odr.
What is a Windows Agent Launcher?
A Windows Agent Launcher is a registered entry point that lets any Windows app expose an AI agent so that other apps and system experiences — Start, search, Copilot surfaces — can discover and invoke it through one standard mechanism instead of custom per-agent integration code. This windows agent launcher tutorial shows you how to take a working app and register it as a launcher end to end, with copy-paste C#, agent.json, and package-manifest XML.
Microsoft frames it cleanly in the overview docs: “An Agent Launcher is a registered entry point for an AI agent on Windows. Without Agent Launchers, each experience would need custom integration code for every agent—whether through Model Context Protocol (MCP), App Actions, or proprietary APIs.” The launcher solves that by giving apps a “unified registration and discovery mechanism where apps register their agents once.”
Agent Launchers sit on top of the App Actions on Windows framework, and they got a much bigger spotlight at Build 2026, where Microsoft positioned Windows itself as an agent platform and open-sourced the Windows Agent Framework under the MIT license. The byteiota recap put it directly: “The Windows Agent Framework — open-sourced under MIT today — is the mechanism.” The Agent Launcher is the on-ramp that gets your existing app into that mechanism.
Every ranking page on this topic right now is either the raw Microsoft Learn reference schema or a Build 2026 news recap. None of them walk a real app from zero to a registered, discoverable launcher and stop at the failure points. That is the gap this tutorial fills.

You need Windows 11 with SDK 10.0.26100.0 or greater, Visual Studio 2022 (17.6+) with the Windows App development workload, and the Microsoft.AI.Actions NuGet package. Critically, your app must have package identity (an MSIX-packaged WinUI 3, WPF, or WinForms app) — App Actions and Agent Launchers cannot register from an unpackaged app.
The three pieces every Agent Launcher needs
An Agent Launcher is exactly three artifacts that have to agree with each other: an App Action that exposes agentName and prompt inputs, an agent.json definition manifest that points at that action, and an app extension declaration in your package manifest that tells Windows where the manifest lives. Get all three aligned and your agent appears in the On-Device Registry; miss one and it silently does not.
The single most important relationship in the whole feature is one string match. As the schema reference states, “The value of the action_id field in the agent definition manifest must match the id field specified in the action definition manifest for an action included in the same app package.” Keep that in mind through every step below.
Here is the full inventory before we build it piece by piece.
| Piece | File / location | What it does | Key field |
|---|---|---|---|
| App Action | C# class with [ActionProvider]; generates registration.json | Exposes the invocable action with agentName + prompt inputs | id (e.g. ZavaAgentAction) |
| Agent definition | agent.json (e.g. Assets/agentRegistration.json) | Declares display metadata and links to the action | action_id must equal the action’s id |
| App extension | Package.appxmanifest uap3:Extension | Registers the agent with Windows and points at agent.json | Name = com.microsoft.windows.ai.agentInfo |
Step 1: Wire an App Action with agentName and prompt entities
To act as an Agent Launcher, your App Action must expose one TextActionEntity named agentName and one named prompt, and every single input combination must accept both of those entities. This is the rule the reference docs state but never make loud enough, and it is the most common reason a perfectly valid-looking action fails launcher validation.
Start from a packaged WinUI 3 app and add the Microsoft.AI.Actions NuGet package. Then add an action provider class. The Microsoft.AI.Actions source generator reads .NET attributes and auto-generates the underlying IActionProvider implementation plus the action definition JSON at build time, so you write strongly typed C# instead of hand-editing JSON. Here is the action provider, lifted straight from the agent-launcher guidance:
Notice the two [WindowsActionInputCombination] attributes. The first accepts agentName + prompt; the second adds attachedFile but still includes agentName and prompt. That is the rule in practice — you can add optional inputs, but you can never drop the two required ones from any combination. The UsesGenerativeAI = true flag marks the action as agentic.
One more requirement from the overview docs that is easy to miss: “Invoking the App Action should open an application where the user can actively interact with the agent, not just complete work in the background.” Agent Launchers are for interactive, multi-turn agents — not silent automation. If your action just does work headlessly, it is an App Action, not an Agent Launcher.
Every input combination must accept both agentName and prompt TextActionEntity inputs. If you add a combination that omits either one, the action will register as a normal App Action but will not qualify as an Agent Launcher — and you will get no error telling you why.
using Microsoft.AI.Actions.Annotations;
using System.Threading.Tasks;
using Windows.AI.Actions;
namespace ZavaAgentProvider
{
[ActionProvider]
public sealed class ZavaAgentActionsProvider
{
[WindowsAction(
Id = "ZavaAgentAction",
Description = "Start an agent for Zava",
Icon = "ms-resource://Files/Assets/ZavaLogo.png",
UsesGenerativeAI = true
)]
[WindowsActionInputCombination(
Inputs = ["agentName", "prompt"],
Description = "Start Zava Agent with '${agentName.Text}'."
)]
[WindowsActionInputCombination(
Inputs = ["agentName", "prompt", "attachedFile"],
Description = "Start Zava Agent with '${agentName.Text}' and additional context."
)]
public async Task StartZavaAgent(
[Entity(Name = "agentName")] string agentName,
[Entity(Name = "prompt")] string prompt,
[Entity(Name = "attachedFile")] FileActionEntity? attachedFile,
InvocationContext context)
{
// Your agent invocation logic here
await InvokeAgentAsync(agentName, prompt, attachedFile);
}
public async Task InvokeAgentAsync(string agentName, string prompt, FileActionEntity? attachedFile)
{
if (attachedFile != null)
{
await Task.Run(() => $"Starting agent '{agentName}' with prompt '{prompt}' and file context");
}
else
{
await Task.Run(() => $"Starting agent '{agentName}' with prompt '{prompt}'");
}
}
}
}
Step 2: Author agent.json (the agent definition manifest)
The agent.json agent definition manifest is a small file with seven properties — manifest_version, version, name, display_name, description, placeholder_text, and icon — plus the all-important action_id that links it to the App Action you just wrote. This is the heart of the agent.json windows schema, and it is shorter than people expect.
Create a JSON file in your project’s Assets folder (the agent-launcher docs name it agentRegistration.json). Every field below is verbatim from the reference example, and action_id is set to “ZavaAgentAction” — exactly matching the Id on the [WindowsAction] attribute from Step 1.
display_name, description, and icon all support localization through the ms-resource:// URI scheme, which resolves to .resw string resources (C#) or .rc resources (C++). placeholder_text is the one optional property — it is the reference query Windows shows the user, for example “What can you help me with?”. Everything else is required.
Here is the full property reference so you can author the manifest without round-tripping to the docs.
{
"manifest_version": "0.1.0",
"version": "1.0.0",
"name": "Zava.ZavaAgent",
"display_name": "ms-resource://zavaAgentDisplayName",
"description": "ms-resource://zavaAgentDescription",
"placeholder_text": "ms-resource://zavaAgentPlaceHolderText",
"icon": "ms-resource://Files/Assets/ZavaLogo.png",
"action_id": "ZavaAgentAction"
}
| Property | Required | Localizable | Notes |
|---|---|---|---|
| manifest_version | Yes | No | Schema version. Current value is “0.1.0”. |
| version | Yes | No | Your agent’s semantic version, e.g. “1.0.0”. |
| name | Yes | No | Unique reverse-domain identifier, e.g. “Zava.ZavaAgent”. Not localizable; must be unique in your package. |
| display_name | Yes | Yes | User-facing name. Use ms-resource:// for localization. |
| description | Yes | Yes | User-facing description of what the agent does. |
| placeholder_text | No | Yes | Reference query shown to the user. |
| icon | Yes | Yes | Agent icon; resolves by theme, contrast, scale, and target size. |
| action_id | Yes | No | Must equal the id of an App Action in the same package. |
Step 3: Set Build Action = Content and Copy if newer (the buried gotcha)
agent.json must be added to your project with Build Action set to Content and Copy to Output Directory set to Copy if newer — if you skip this, the file never makes it into your package, and static registration finds nothing. The reference page states it in one easy-to-miss sentence: this file “must be included in your project with the Build Action set to ‘Content’ and Copy to Output Directory set to ‘Copy if newer’.”
In a C# project you do this in the file’s Properties pane: right-click agentRegistration.json in Solution Explorer, select Properties, and set Copy to Output Directory to “Copy if newer” (or “Copy always”). For C++ projects, the equivalent goes in the project file:
This is the step that costs people an afternoon. The agent.json is valid, the App Action works, the manifest looks right — but odr agent-info list returns nothing because the JSON never landed in the output directory the package ships. Check this first whenever a statically registered agent fails to appear.
<Content Include="Assets\agentRegistration.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
“The agent.json is valid, the App Action works, the manifest looks right — but the registry returns nothing because the JSON never landed in the package.”
The Build Action = Content trap
Step 4: Declare the app extension in the package manifest
To register your app as an agent on Windows statically, add a uap3:Extension to Package.appxmanifest with the AppExtension Name set to com.microsoft.windows.ai.agentInfo, and point its Registration element at the package-relative path of your agent.json. This is the windows agent framework agent.json wiring that ties everything to the OS.
If you followed the App Actions tutorial, your manifest already has one uap3:Extension with Name com.microsoft.windows.ai.actions that registers the action itself (its Registration points at the generated registration.json). For the launcher you add a second, separate extension for the agentInfo. The docs are explicit: “Each statically registered Agent Launcher should have its own AppExtension entry.”
Make sure the uap3 namespace is declared on the root Package element (xmlns:uap3=”http://schemas.microsoft.com/appx/manifest/uap/windows10/3″). The PublicFolder attribute (“Assets”) plus the Registration value (“agentRegistration.json”) together form the package-relative path to your manifest — so PublicFolder must match where the file actually lands after the Copy-if-newer step from Step 3.
Here is the agentInfo extension to add inside your Application element’s Extensions block.
Your final manifest has two uap3:Extension entries: com.microsoft.windows.ai.actions (registers the App Action and its registration.json) and com.microsoft.windows.ai.agentInfo (registers the Agent Launcher and its agentRegistration.json). They are siblings, and one does not replace the other.
<uap3:Extension Category="windows.appExtension">
<uap3:AppExtension
Name="com.microsoft.windows.ai.agentInfo"
Id="ZavaAgent"
DisplayName="Zava Agent"
PublicFolder="Assets">
<uap3:Properties>
<Registration>agentRegistration.json</Registration>
</uap3:Properties>
</uap3:AppExtension>
</uap3:Extension>
Static vs dynamic: register with odr agent-info add
Agent Launchers register two ways: statically through the package manifest at install time, or dynamically at runtime with odr agent-info add
Run dynamic registration from inside your packaged app by shelling out to odr.exe. The command takes the path to your agent registration JSON and returns a JSON response with the assigned agent_id and an extended_error of 0 on success:
One hard constraint from the docs: “Due to package identity requirements, you can’t use agent-info add and agent-info remove from an unpackaged app. You must run these commands from within a packaged application that also contains the associated App Action.” To pull an agent back out, call odr agent-info remove with the same path — and note you can only remove agents your own package added dynamically.
Pros
Cons
using System.Diagnostics;
ProcessStartInfo startInfo = new ProcessStartInfo
{
FileName = "odr.exe",
Arguments = $"agent-info add \"<path to agentDefinition.json>\"",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true
};
using Process process = new Process { StartInfo = startInfo };
process.Start();
string output = await process.StandardOutput.ReadToEndAsync();
string error = await process.StandardError.ReadToEndAsync();
await process.WaitForExitAsync();
// output -> { "agent_id": "ZavaAgent_cw5n1h2txyewy_Zava.ZavaAgent", "extended_error": 0 }
Step 5: Verify it appears with odr agent-info list
Ship it: three artifacts, one matching string
Treat odr agent-info list as your acceptance test: if your agent is not in that JSON array with the correct package_family_name and action_id, no Windows surface can launch it. After building and deploying your packaged app (static) or running odr agent-info add (dynamic), open a terminal and list the registry.
A successful registration returns an array where each entry carries the package_family_name and action_id you use together to identify and invoke the action, plus the resolved metadata from your agent.json. You can also pass icon qualifiers — odr agent-info list –theme dark,light –scale 200 — to confirm your themed and scaled icons resolve correctly.
If the command returns an empty array, work backward through the checklist: did agent.json copy to the output directory (Step 3); does PublicFolder + Registration point at the real file (Step 4); does action_id match the App Action id (Steps 1 and 2). For dynamic failures, read the extended_error field — a non-zero value is an HRESULT, and the message field describes it. For example E_INVALIDARG (0x80070057) fires on a bad JSON path or a double registration, and E_NOTFOUND (0x80070490) on removing an agent that was never registered.
odr agent-info list
# Expected (abridged) output:
# {
# "package_family_name": "ZavaPackageFamilyName",
# "action_id": "ZavaAgentAction",
# "id": "ZavaAgent_cw5n1h2txyewy_Zava.ZavaAgent",
# "name": "Zava.ZavaAgent",
# "display_name": "Zava Agent",
# "version": "1.0.0"
# }
odr agent-info list returns an empty array
Almost always one of three things. (1) agent.json never reached the package — confirm Build Action = Content and Copy to Output Directory = Copy if newer. (2) PublicFolder in the manifest does not match where the file actually lands, so the Registration path is dead — make PublicFolder and your project folder agree. (3) The manifest extension Name is wrong — it must be com.microsoft.windows.ai.agentInfo, not the actions Name.Agent appears but cannot be invoked / action not found
The action_id in agent.json does not match the id of any App Action in the same package. Open the generated registration.json and confirm the action’s id (the Id on the [WindowsAction] attribute) exactly matches action_id in agent.json — case-sensitive.My edits to the generated JSON keep disappearing on build
The Microsoft.AI.Actions source generator regenerates the action definition file every build, overwriting manual edits (allowedAppInvokers is the classic casualty). Set GenerateActionRegistrationManifest to false in your .csproj to preserve hand edits, then re-enable only when you intentionally regenerate.E_INVALIDARG (0x80070057) from odr agent-info add
Either the JSON path you passed is invalid, or you are trying to register an agent that is already registered. Verify the absolute path resolves inside the package, and call odr agent-info remove first if you are re-adding.agent-info add fails from my console app
You are running it from an unpackaged process. odr agent-info add and remove require package identity and must run from a packaged app that also contains the associated App Action. Move the call into your MSIX-packaged app.Builder’s take
I build agent infrastructure for a living (Cyntr and Loomfeed), and the Agent Launcher model is the most interesting thing Microsoft shipped at Build 2026 for app developers. It is also the most under-documented. Here is what actually matters once you get past the reference pages:
- The whole feature is three artifacts that must agree on one string: action_id in agent.json must equal the id of your App Action. Ninety percent of ‘my agent does not show up’ bugs are a mismatch here or a stale generated registration.json overwriting your edits.
- The agentName + prompt rule is non-negotiable and easy to get wrong: every input combination must accept both. If you add an attachedFile combination, it still needs agentName and prompt, or the launcher silently fails validation.
- The buried gotcha that wastes an afternoon: agent.json needs Build Action = Content and Copy to Output Directory = Copy if newer. Miss it and the file never reaches your package, so static registration finds nothing.
- Static vs dynamic registration is a real product decision, not a footnote. Gate paid agents behind dynamic odr agent-info add after a license check; ship free ones statically in the manifest.
- Treat odr agent-info list as your unit test. If your agent is not in that JSON array with the right package_family_name and action_id, no surface can launch it, full stop.
Frequently asked questions
Register it as an Agent Launcher with three artifacts: an App Action that exposes agentName and prompt TextActionEntity inputs, an agent.json definition manifest whose action_id matches that action’s id, and a uap3:Extension in your package manifest with Name com.microsoft.windows.ai.agentInfo pointing at the agent.json. Then verify with odr agent-info list. Your app must be MSIX-packaged with package identity.
Eight properties: manifest_version (“0.1.0”), version (semantic, e.g. “1.0.0”), name (unique reverse-domain id), display_name, description, optional placeholder_text, icon, and action_id. All but placeholder_text are required. display_name, description, and icon support ms-resource:// localization. action_id must equal the id of an App Action in the same package.
Those two TextActionEntity inputs are what qualify an App Action as an Agent Launcher. The system passes the agent’s name and the user’s prompt through them when it invokes the agent. The strict rule is that every input combination on the action must accept both agentName and prompt — you can add optional entities like attachedFile, but you can never drop the two required ones.
Run odr agent-info add “
The most common cause is that agent.json was not set to Build Action = Content and Copy to Output Directory = Copy if newer, so it never reached the package. Next most common: the manifest PublicFolder plus Registration path do not point at the real file, the extension Name is not com.microsoft.windows.ai.agentInfo, or action_id does not match the App Action id. Work through those four in order.
Yes. App Actions and Agent Launchers require package identity, so your app must be MSIX-packaged (a packaged WinUI 3, WPF, or WinForms desktop app). You cannot register an Agent Launcher, nor run odr agent-info add or remove, from an unpackaged app. Target Windows SDK 10.0.26100.0 or greater.
Primary sources
- Get started with Agent Launchers on Windows — Microsoft Learn
- Agent definition JSON schema for Agent Launchers on Windows — Microsoft Learn
- Agent Launchers on Windows overview — Microsoft Learn
- Get started with App Actions on Windows — Microsoft Learn
- Microsoft Build 2026: Windows Is Now an Agent Platform — byteiota
Last updated: June 3, 2026. Related: Agent Infrastructure.