diff --git a/src/Squad.Agents.AI/README.md b/src/Squad.Agents.AI/README.md
index 78a3d89d0..a6dff3e0c 100644
--- a/src/Squad.Agents.AI/README.md
+++ b/src/Squad.Agents.AI/README.md
@@ -150,6 +150,18 @@ squad://localhost?teamRoot=C%3A%5Cteam&cliPath=C%3A%5Ctools%5Ccopilot.exe&cliArg
Parsed URI query keys: `teamRoot`, `cliPath`, `cwd`, `cliArgs` (semicolon-separated), and `env` (`key=value;key2=value2`). Unknown URI host/protocol values are reserved for future use.
+## Coordinator agent selection
+
+`SquadAgent` exists to wrap a Squad coordinator team, so the SDK sets `SessionConfig.Agent` to `"squad"` by default. That's the SDK-native equivalent of running `copilot --agent squad` from a terminal — it tells the wrapped Copilot session to load `.github/agents/squad.agent.md` as the agent definition, which is what teaches the coordinator to eager-execute, fan out, and dispatch through the `task` tool. Without it, the wrapped session uses its built-in generic agent and the coordinator role-plays responses inline instead of spawning real subagents — producing SDK behavior that does NOT match running `copilot --agent squad` interactively against the same team root.
+
+| Scenario | What the SDK does |
+|---|---|
+| Default — `squad.agent.md` exists | Sets `sessionConfig.Agent = "squad"` |
+| `squad.agent.md` not found at `.github/agents/` | Leaves `sessionConfig.Agent` unset (graceful degradation for not-yet-initialized teams) |
+| Consumer sets `AgentFileName = "data"` (and file exists) | Sets `sessionConfig.Agent = "data"` |
+| Consumer sets `AgentFileName = null` | Leaves `sessionConfig.Agent` unset |
+| Consumer overrides `sessionConfig.Agent` inside `ConfigureSession` | The `ConfigureSession` callback runs after the default is applied, so it always wins |
+
## Subagent observability — first-class OpenTelemetry
`Squad.Agents.AI` emits one OpenTelemetry `Activity` per subagent dispatch out of the box. Hosts that subscribe to the activity source see one `squad.subagent {Name}` span per spawn, with the subagent name as a tag and timeline annotations (`squad.subagent.start`, `squad.subagent.message`, `squad.subagent.completed`, `squad.subagent.failed`) marking every state transition.
@@ -196,6 +208,7 @@ builder.Services.AddSquadAgent(o =>
| `ConfigureCopilotClient` | Advanced delegate for customizing `CopilotClientOptions`. Routing properties are guarded; see BYOK section. |
| `TraceEvents` | Enables verbose SDK logging and emits a startup warning when enabled. |
| `AgentName` | Display name for the resulting `AIAgent`; defaults to `Squad`. |
+| `AgentFileName` | Name of the agent definition under `.github/agents/{name}.agent.md` to load via `SessionConfig.Agent` (the SDK equivalent of the Copilot CLI's `--agent` flag). Defaults to `"squad"`. Set to `null` to skip auto-selection; the file-existence check makes this safe to leave on for not-yet-initialized teams. Override `sessionConfig.Agent` from inside `ConfigureSession` for full control. |
| `Instructions` | Optional system instructions passed to the inner Copilot agent. |
| `EmitSubagentActivities` | Whether the SDK opens an OpenTelemetry `Activity` per subagent dispatch and annotates lifecycle events. Defaults to `true`. Set to `false` to handle telemetry from your own `OnSubagentTrace` callback. |
| `OnSubagentTrace` | Optional callback invoked for every typed `SquadAgentTraceEvent`. Independent of `EmitSubagentActivities` — both can be on simultaneously. |
diff --git a/src/Squad.Agents.AI/Squad.Agents.AI.csproj b/src/Squad.Agents.AI/Squad.Agents.AI.csproj
index 40fa8553b..e2187fc5b 100644
--- a/src/Squad.Agents.AI/Squad.Agents.AI.csproj
+++ b/src/Squad.Agents.AI/Squad.Agents.AI.csproj
@@ -17,7 +17,7 @@
MIT
README.md
true
- 0.4.0
+ 0.5.0
diff --git a/src/Squad.Agents.AI/SquadAgent.cs b/src/Squad.Agents.AI/SquadAgent.cs
index c54528063..afdca9653 100644
--- a/src/Squad.Agents.AI/SquadAgent.cs
+++ b/src/Squad.Agents.AI/SquadAgent.cs
@@ -134,6 +134,36 @@ private static SquadAgentState BuildStateInternal(SquadAgentOptions options, ILo
ConfigDirectory = squadConfigDir,
EnableConfigDiscovery = true,
};
+
+ // ── Default coordinator agent selection ────────────────────────────
+ // SquadAgent wraps a Squad coordinator team. The SDK's SessionConfig.Agent
+ // property is the first-class equivalent of the Copilot CLI's --agent flag —
+ // it selects which discovered agent definition (e.g. .github/agents/squad.agent.md)
+ // to load as the session's system prompt. Without it the CLI uses its built-in
+ // generic agent and the coordinator role-plays responses inline instead of
+ // dispatching real subagents — exactly the inconsistency between
+ // `copilot --agent squad` (CLI) and SquadAgent.RunAsync (SDK) that this
+ // default eliminates.
+ //
+ // We set Agent = AgentFileName (default "squad") unless:
+ // 1. The host explicitly configured a different Agent via ConfigureSession
+ // (handled implicitly — ConfigureSession runs after us and wins)
+ // 2. AgentFileName is null or whitespace (explicit opt-out)
+ // 3. The agent file does not exist at the conventional path on disk
+ // (graceful degradation for folders that are not yet Squad-initialized,
+ // where the CLI would error on `--agent squad`)
+ // ───────────────────────────────────────────────────────────────────
+ if (!string.IsNullOrWhiteSpace(options.AgentFileName) && !string.IsNullOrWhiteSpace(teamRoot))
+ {
+ var agentFilePath = Path.Combine(teamRoot, ".github", "agents", $"{options.AgentFileName}.agent.md");
+ if (File.Exists(agentFilePath))
+ {
+ sessionConfig.Agent = options.AgentFileName;
+ }
+ // If the file is missing we leave SessionConfig.Agent unset; the SDK will
+ // start with its default agent, which is what 0.4.x did anyway.
+ }
+
if (!string.IsNullOrEmpty(options.Instructions))
{
sessionConfig.SystemMessage = new SystemMessageConfig { Content = options.Instructions };
@@ -242,6 +272,7 @@ private static CopilotClient CreateCopilotClient(SquadAgentOptions options, ILog
{
combinedCliArgs.Add("--allow-all");
}
+
combinedCliArgs.AddRange(options.CliArgs);
// Only override the SDK's default child-process connection when the consumer
diff --git a/src/Squad.Agents.AI/SquadAgentOptions.cs b/src/Squad.Agents.AI/SquadAgentOptions.cs
index 295c76077..a696c1174 100644
--- a/src/Squad.Agents.AI/SquadAgentOptions.cs
+++ b/src/Squad.Agents.AI/SquadAgentOptions.cs
@@ -82,6 +82,36 @@ public sealed class SquadAgentOptions
///
public string AgentName { get; set; } = "Squad";
+ ///
+ /// Gets or sets the Squad coordinator agent definition file (under .github/agents/) to
+ /// load as the wrapped session's agent. Defaults to "squad", which selects
+ /// .github/agents/squad.agent.md — the Squad coordinator system prompt that drives
+ /// eager execution, parallel fan-out, and dispatch through the task tool.
+ ///
+ ///
+ ///
+ /// The whole reason exists is to wrap a Squad coordinator team, so
+ /// the SDK sets by default. Without it,
+ /// the wrapped session uses its built-in generic agent and the coordinator role-plays
+ /// responses inline instead of dispatching real subagents — exactly the inconsistency
+ /// between copilot --agent squad (CLI) and SquadAgent.RunAsync (SDK) that this
+ /// default eliminates.
+ ///
+ ///
+ /// When the named file does not exist at
+ /// {team-root}/.github/agents/{AgentFileName}.agent.md (e.g. the team root is not
+ /// Squad-initialized yet), the SDK silently leaves
+ /// unset so the session can still start with the SDK's default agent.
+ ///
+ ///
+ /// Set to (or whitespace) to disable the auto-selection entirely. Set
+ /// to a different name (e.g. "data") to load a custom agent file. To override the
+ /// final value, set sessionConfig.Agent from inside —
+ /// that callback runs after this default is applied.
+ ///
+ ///
+ public string? AgentFileName { get; set; } = "squad";
+
///
/// Gets or sets optional system instructions passed to the inner Copilot-backed agent.
///
diff --git a/test/Squad.Agents.AI.Tests/SquadAgentDefaultAgentTests.cs b/test/Squad.Agents.AI.Tests/SquadAgentDefaultAgentTests.cs
new file mode 100644
index 000000000..92469a7ed
--- /dev/null
+++ b/test/Squad.Agents.AI.Tests/SquadAgentDefaultAgentTests.cs
@@ -0,0 +1,194 @@
+
using System.Reflection;
+using GitHub.Copilot;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Xunit;
+
+namespace Squad.Agents.AI.Tests;
+
+///
+/// Tests for the 0.5.0 behaviour where SquadAgent auto-sets
+/// (the SDK's first-class equivalent of
+/// the Copilot CLI's --agent flag) to ,
+/// so the wrapped session picks up .github/agents/squad.agent.md by default —
+/// matching copilot --agent squad from the terminal.
+///
+public class SquadAgentDefaultAgentTests : IDisposable
+{
+ private readonly string _tempRoot;
+
+ public SquadAgentDefaultAgentTests()
+ {
+ // Each test gets its own throwaway team root so file-existence checks
+ // are deterministic and we can scaffold or omit the squad.agent.md file
+ // per scenario without contaminating siblings.
+ _tempRoot = Path.Combine(Path.GetTempPath(), "squad-agents-ai-tests", Guid.NewGuid().ToString("n"));
+ Directory.CreateDirectory(_tempRoot);
+ }
+
+ public void Dispose()
+ {
+ try { Directory.Delete(_tempRoot, recursive: true); }
+ catch { /* best effort */ }
+ }
+
+ private string ScaffoldAgentFile(string agentName)
+ {
+ var dir = Path.Combine(_tempRoot, ".github", "agents");
+ Directory.CreateDirectory(dir);
+ var path = Path.Combine(dir, $"{agentName}.agent.md");
+ File.WriteAllText(path, $"# {agentName}\n\nstub agent file for tests");
+ return path;
+ }
+
+ [Fact]
+ public void Options_AgentFileName_DefaultsToSquad()
+ {
+ var options = new SquadAgentOptions();
+ Assert.Equal("squad", options.AgentFileName);
+ }
+
+ [Fact]
+ public void AddSquadAgent_AutoSetsSessionConfigAgentToSquad_WhenSquadAgentMdExists()
+ {
+ ScaffoldAgentFile("squad");
+
+ var agent = CreateAgent(opts =>
+ {
+ opts.SquadFolderPath = _tempRoot;
+ opts.Cwd = _tempRoot;
+ });
+
+ var sessionConfig = GetInnerSessionConfig(agent);
+ Assert.Equal("squad", sessionConfig.Agent);
+ }
+
+ [Fact]
+ public void AddSquadAgent_LeavesAgentUnset_WhenAgentFileMissing()
+ {
+ // No .github/agents/squad.agent.md scaffolded — the SDK should NOT
+ // set sessionConfig.Agent because the underlying session would error
+ // when asked to load a non-existent agent.
+ var agent = CreateAgent(opts =>
+ {
+ opts.SquadFolderPath = _tempRoot;
+ opts.Cwd = _tempRoot;
+ });
+
+ var sessionConfig = GetInnerSessionConfig(agent);
+ Assert.Null(sessionConfig.Agent);
+ }
+
+ [Fact]
+ public void AddSquadAgent_RespectsCustomAgentFileName_WhenFileExists()
+ {
+ ScaffoldAgentFile("data");
+
+ var agent = CreateAgent(opts =>
+ {
+ opts.SquadFolderPath = _tempRoot;
+ opts.Cwd = _tempRoot;
+ opts.AgentFileName = "data";
+ });
+
+ var sessionConfig = GetInnerSessionConfig(agent);
+ Assert.Equal("data", sessionConfig.Agent);
+ }
+
+ [Fact]
+ public void AddSquadAgent_LeavesAgentUnset_WhenAgentFileNameIsNull()
+ {
+ // Opt-out: setting AgentFileName=null disables the auto-set even if
+ // squad.agent.md exists. Used when the consumer wants to drive the
+ // session with a custom SystemMessage instead of an agent file.
+ ScaffoldAgentFile("squad");
+
+ var agent = CreateAgent(opts =>
+ {
+ opts.SquadFolderPath = _tempRoot;
+ opts.Cwd = _tempRoot;
+ opts.AgentFileName = null;
+ });
+
+ var sessionConfig = GetInnerSessionConfig(agent);
+ Assert.Null(sessionConfig.Agent);
+ }
+
+ [Fact]
+ public void AddSquadAgent_LeavesAgentUnset_WhenAgentFileNameIsWhitespace()
+ {
+ ScaffoldAgentFile("squad");
+
+ var agent = CreateAgent(opts =>
+ {
+ opts.SquadFolderPath = _tempRoot;
+ opts.Cwd = _tempRoot;
+ opts.AgentFileName = " ";
+ });
+
+ var sessionConfig = GetInnerSessionConfig(agent);
+ Assert.Null(sessionConfig.Agent);
+ }
+
+ [Fact]
+ public void AddSquadAgent_ConfigureSession_CanOverrideAutoSetAgent()
+ {
+ // ConfigureSession runs AFTER the default is applied, so consumers retain
+ // full control — they can replace the auto-set value with anything else
+ // (or null it out) from inside their own callback.
+ ScaffoldAgentFile("squad");
+ ScaffoldAgentFile("custom");
+
+ var agent = CreateAgent(opts =>
+ {
+ opts.SquadFolderPath = _tempRoot;
+ opts.Cwd = _tempRoot;
+ opts.ConfigureSession = sessionConfig =>
+ {
+ // Confirm the default was applied before this callback ran.
+ Assert.Equal("squad", sessionConfig.Agent);
+ sessionConfig.Agent = "custom";
+ };
+ });
+
+ var actualSessionConfig = GetInnerSessionConfig(agent);
+ Assert.Equal("custom", actualSessionConfig.Agent);
+ }
+
+ // ── helpers ─────────────────────────────────────────────────────────────
+
+ private static SquadAgent CreateAgent(Action configure)
+ {
+ var services = new ServiceCollection();
+ services.AddSingleton(new ConfigurationBuilder().Build());
+ services.AddLogging();
+ services.AddSquadAgent(opts =>
+ {
+ opts.CliPath = @"C:\fake-copilot\copilot.exe";
+ configure(opts);
+ });
+ return services.BuildServiceProvider().GetRequiredService();
+ }
+
+ ///
+ /// Reach into the SquadAgent → DelegatingAIAgent.InnerAgent → ChatClientAgent.SessionConfig
+ /// to assert against the SessionConfig the SDK was constructed with.
+ ///
+ private static SessionConfig GetInnerSessionConfig(SquadAgent agent)
+ {
+ var innerProp = typeof(Microsoft.Agents.AI.DelegatingAIAgent).GetProperty(
+ "InnerAgent",
+ BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
+ Assert.NotNull(innerProp);
+ var inner = innerProp!.GetValue(agent);
+ Assert.NotNull(inner);
+
+ var configField = inner!.GetType().GetField(
+ "_sessionConfig",
+ BindingFlags.Instance | BindingFlags.NonPublic);
+ Assert.NotNull(configField);
+ var config = configField!.GetValue(inner);
+ Assert.NotNull(config);
+ return (SessionConfig)config!;
+ }
+}