diff --git a/src/Squad.Agents.AI/README.md b/src/Squad.Agents.AI/README.md
index a6dff3e0c..485695b26 100644
--- a/src/Squad.Agents.AI/README.md
+++ b/src/Squad.Agents.AI/README.md
@@ -152,15 +152,17 @@ Parsed URI query keys: `teamRoot`, `cliPath`, `cwd`, `cliArgs` (semicolon-separa
## 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.
+`SquadAgent` exists to wrap a Squad coordinator team, so it passes `--agent squad` to the underlying `copilot.exe` child process by default. That tells the CLI 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 CLI 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 |
+| Default — `squad.agent.md` exists | Auto-adds `--agent squad` |
+| `squad.agent.md` not found at `.github/agents/` | Silently skips `--agent` (graceful degradation for not-yet-initialized teams; logs a Debug message) |
+| Consumer sets `AgentFileName = "data"` (and file exists) | Auto-adds `--agent data` |
+| Consumer sets `AgentFileName = null` | Skips `--agent` entirely |
+| Consumer already supplied `--agent X` in `CliArgs` | Respects the explicit value; does not add a second `--agent` |
+
+> SDK note: `SessionConfigBase.Agent` exists but looks up the name in the SDK's `CustomAgents` registry, NOT in `.github/agents/*.agent.md` files on disk. The CLI `--agent` flag is currently the only path that reads the on-disk agent definition.
## Subagent observability — first-class OpenTelemetry
@@ -208,7 +210,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. |
+| `AgentFileName` | Name of the agent definition under `.github/agents/{name}.agent.md` to load via the CLI's `--agent` flag. Defaults to `"squad"`. Set to `null` to skip auto-injection; the file-existence check makes this safe to leave on for not-yet-initialized teams. |
| `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 e2187fc5b..3657a6800 100644
--- a/src/Squad.Agents.AI/Squad.Agents.AI.csproj
+++ b/src/Squad.Agents.AI/Squad.Agents.AI.csproj
@@ -17,7 +17,7 @@
MITREADME.mdtrue
- 0.5.0
+ 0.5.1
diff --git a/src/Squad.Agents.AI/SquadAgent.cs b/src/Squad.Agents.AI/SquadAgent.cs
index afdca9653..3511f54c8 100644
--- a/src/Squad.Agents.AI/SquadAgent.cs
+++ b/src/Squad.Agents.AI/SquadAgent.cs
@@ -134,36 +134,6 @@ 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 };
@@ -273,6 +243,51 @@ private static CopilotClient CreateCopilotClient(SquadAgentOptions options, ILog
combinedCliArgs.Add("--allow-all");
}
+ // ── Default coordinator agent selection ────────────────────────────
+ // SquadAgent wraps a Squad coordinator team. The CLI's `--agent squad`
+ // flag is what teaches the coordinator to eager-execute, fan out, and
+ // dispatch via the task tool by loading `.github/agents/squad.agent.md`
+ // as the agent definition. Without it the CLI uses its built-in generic
+ // agent and the coordinator role-plays responses inline instead of
+ // spawning real subagents — producing SDK behaviour that does NOT match
+ // running `copilot --agent squad` interactively against the same team
+ // root.
+ //
+ // Note on SessionConfig.Agent: that SDK property exists but selects
+ // from the SDK's CustomAgents registry, NOT from `.github/agents/*.agent.md`
+ // files (verified at v1.0.0 — setting it without populating CustomAgents
+ // produces `Custom agent 'squad' not found`). The CLI `--agent` flag is
+ // currently the only path that reads the on-disk agent definition.
+ //
+ // We auto-add `--agent {AgentFileName}` (default "squad") unless:
+ // 1. The host already supplied --agent explicitly in CliArgs
+ // 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`)
+ // ───────────────────────────────────────────────────────────────────
+ bool hostSuppliedAgent = options.CliArgs.Any(a =>
+ string.Equals(a, "--agent", StringComparison.OrdinalIgnoreCase));
+ if (!hostSuppliedAgent && !string.IsNullOrWhiteSpace(options.AgentFileName))
+ {
+ var defaultAgentTeamRoot = options.Cwd ?? options.SquadFolderPath;
+ if (!string.IsNullOrWhiteSpace(defaultAgentTeamRoot))
+ {
+ var agentFilePath = Path.Combine(defaultAgentTeamRoot, ".github", "agents", $"{options.AgentFileName}.agent.md");
+ if (File.Exists(agentFilePath))
+ {
+ combinedCliArgs.Add("--agent");
+ combinedCliArgs.Add(options.AgentFileName);
+ }
+ else
+ {
+ logger?.LogDebug(
+ "SquadAgentOptions.AgentFileName is '{AgentFileName}' but the file was not found at '{AgentFilePath}'. Skipping --agent argument; the CLI will fall back to its default agent.",
+ options.AgentFileName, agentFilePath);
+ }
+ }
+ }
+
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 a696c1174..f9d0ab619 100644
--- a/src/Squad.Agents.AI/SquadAgentOptions.cs
+++ b/src/Squad.Agents.AI/SquadAgentOptions.cs
@@ -83,31 +83,30 @@ 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.
+ /// Gets or sets the Copilot CLI agent definition file (under .github/agents/) the
+ /// underlying copilot.exe child process should load via the CLI's --agent
+ /// flag. 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.
+ /// The whole reason exists is to wrap a Squad coordinator team,
+ /// so the SDK passes --agent squad by default. 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.
///
///
- /// 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.
+ /// 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 skips the
+ /// --agent argument so the CLI can still start with its 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.
+ /// Set to (or whitespace) to disable the auto-inject entirely. Set
+ /// to a different name (e.g. "data") to load a custom agent file. If the consumer
+ /// already supplied --agent in , that explicit value wins and
+ /// the default is not added.
///
///
public string? AgentFileName { get; set; } = "squad";
diff --git a/test/Squad.Agents.AI.Tests/SquadAgentDefaultAgentFlagTests.cs b/test/Squad.Agents.AI.Tests/SquadAgentDefaultAgentFlagTests.cs
new file mode 100644
index 000000000..e335a1066
--- /dev/null
+++ b/test/Squad.Agents.AI.Tests/SquadAgentDefaultAgentFlagTests.cs
@@ -0,0 +1,220 @@
+using System.Reflection;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Xunit;
+
+namespace Squad.Agents.AI.Tests;
+
+///
+/// Tests for the 0.5.x behaviour where SquadAgent auto-injects
+/// --agent {AgentFileName} into the underlying copilot.exe CLI args, so
+/// the wrapped agent picks up .github/agents/squad.agent.md by default
+/// — matching copilot --agent squad from a terminal.
+///
+///
+/// Note: an earlier 0.5.0 attempt set SessionConfig.Agent instead, which
+/// turned out to look up the name in the SDK's CustomAgents registry
+/// rather than from .github/agents/*.agent.md on disk (produces
+/// Custom agent 'squad' not found). The CLI's --agent flag is
+/// currently the only path that reads the on-disk agent definition, so 0.5.1
+/// reverts to the CLI-args approach.
+///
+public class SquadAgentDefaultAgentFlagTests : IDisposable
+{
+ private readonly string _tempRoot;
+
+ public SquadAgentDefaultAgentFlagTests()
+ {
+ // 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_AutoInjectsAgentSquad_WhenSquadAgentMdExists()
+ {
+ ScaffoldAgentFile("squad");
+
+ var agent = CreateAgent(opts =>
+ {
+ opts.SquadFolderPath = _tempRoot;
+ opts.Cwd = _tempRoot;
+ });
+
+ var args = GetConnectionArgs(agent);
+ AssertContainsAgent(args, "squad");
+ }
+
+ [Fact]
+ public void AddSquadAgent_DoesNotInjectAgent_WhenAgentFileMissing()
+ {
+ // No .github/agents/squad.agent.md scaffolded — the SDK should NOT
+ // pass --agent squad because the CLI would error on a missing file.
+ var agent = CreateAgent(opts =>
+ {
+ opts.SquadFolderPath = _tempRoot;
+ opts.Cwd = _tempRoot;
+ });
+
+ var args = GetConnectionArgs(agent);
+ Assert.DoesNotContain("--agent", args);
+ }
+
+ [Fact]
+ public void AddSquadAgent_RespectsExplicitAgentInCliArgs()
+ {
+ // Scaffold the default file so we'd auto-inject if the consumer hadn't
+ // already supplied --agent.
+ ScaffoldAgentFile("squad");
+ ScaffoldAgentFile("data");
+
+ var agent = CreateAgent(opts =>
+ {
+ opts.SquadFolderPath = _tempRoot;
+ opts.Cwd = _tempRoot;
+ opts.CliArgs.Add("--agent");
+ opts.CliArgs.Add("data");
+ });
+
+ var args = GetConnectionArgs(agent);
+
+ // Exactly ONE --agent in the args, and it's "data" (the user-supplied one).
+ var agentIndices = args.Select((a, i) => (a, i))
+ .Where(t => string.Equals(t.a, "--agent", StringComparison.OrdinalIgnoreCase))
+ .Select(t => t.i)
+ .ToList();
+ Assert.Single(agentIndices);
+ Assert.Equal("data", args[agentIndices[0] + 1]);
+ }
+
+ [Fact]
+ public void AddSquadAgent_RespectsCustomAgentFileName_WhenFileExists()
+ {
+ ScaffoldAgentFile("data");
+
+ var agent = CreateAgent(opts =>
+ {
+ opts.SquadFolderPath = _tempRoot;
+ opts.Cwd = _tempRoot;
+ opts.AgentFileName = "data";
+ });
+
+ var args = GetConnectionArgs(agent);
+ AssertContainsAgent(args, "data");
+ }
+
+ [Fact]
+ public void AddSquadAgent_DoesNotInjectAgent_WhenAgentFileNameIsNull()
+ {
+ // Opt-out: setting AgentFileName=null disables the auto-inject even if
+ // squad.agent.md exists. Used when the consumer wants to drive the
+ // session with a custom SystemMessage instead of a charter file.
+ ScaffoldAgentFile("squad");
+
+ var agent = CreateAgent(opts =>
+ {
+ opts.SquadFolderPath = _tempRoot;
+ opts.Cwd = _tempRoot;
+ opts.AgentFileName = null;
+ });
+
+ var args = GetConnectionArgs(agent);
+ Assert.DoesNotContain("--agent", args);
+ }
+
+ [Fact]
+ public void AddSquadAgent_DoesNotInjectAgent_WhenAgentFileNameIsWhitespace()
+ {
+ ScaffoldAgentFile("squad");
+
+ var agent = CreateAgent(opts =>
+ {
+ opts.SquadFolderPath = _tempRoot;
+ opts.Cwd = _tempRoot;
+ opts.AgentFileName = " ";
+ });
+
+ var args = GetConnectionArgs(agent);
+ Assert.DoesNotContain("--agent", args);
+ }
+
+ // ── 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();
+ }
+
+ private static IList GetConnectionArgs(SquadAgent agent)
+ {
+ var client = GetField