Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 9 additions & 7 deletions src/Squad.Agents.AI/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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. |
Expand Down
2 changes: 1 addition & 1 deletion src/Squad.Agents.AI/Squad.Agents.AI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<Version>0.5.0</Version>
<Version>0.5.1</Version>
</PropertyGroup>
<ItemGroup>
<Compile Remove="samples/**/*.cs" />
Expand Down
75 changes: 45 additions & 30 deletions src/Squad.Agents.AI/SquadAgent.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 };
Expand Down Expand Up @@ -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
Expand Down
35 changes: 17 additions & 18 deletions src/Squad.Agents.AI/SquadAgentOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -83,31 +83,30 @@
public string AgentName { get; set; } = "Squad";

/// <summary>
/// Gets or sets the Squad coordinator agent definition file (under <c>.github/agents/</c>) to
/// load as the wrapped session's agent. Defaults to <c>"squad"</c>, which selects
/// <c>.github/agents/squad.agent.md</c> — the Squad coordinator system prompt that drives
/// eager execution, parallel fan-out, and dispatch through the <c>task</c> tool.
/// Gets or sets the Copilot CLI agent definition file (under <c>.github/agents/</c>) the
/// underlying <c>copilot.exe</c> child process should load via the CLI's <c>--agent</c>
/// flag. Defaults to <c>"squad"</c>, which selects <c>.github/agents/squad.agent.md</c>
/// — the Squad coordinator system prompt that drives eager execution, parallel fan-out,
/// and dispatch through the <c>task</c> tool.
/// </summary>
/// <remarks>
/// <para>
/// The whole reason <see cref="SquadAgent"/> exists is to wrap a Squad coordinator team, so
/// the SDK sets <see cref="GitHub.Copilot.SessionConfigBase.Agent"/> 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 <c>copilot --agent squad</c> (CLI) and <c>SquadAgent.RunAsync</c> (SDK) that this
/// default eliminates.
/// The whole reason <see cref="SquadAgent"/> exists is to wrap a Squad coordinator team,
/// so the SDK passes <c>--agent squad</c> 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 <c>copilot --agent squad</c> (CLI)
/// and <c>SquadAgent.RunAsync</c> (SDK) that this default eliminates.
/// </para>
/// <para>
/// When the named file does not exist at
/// <c>{team-root}/.github/agents/{AgentFileName}.agent.md</c> (e.g. the team root is not
/// Squad-initialized yet), the SDK silently leaves <see cref="GitHub.Copilot.SessionConfigBase.Agent"/>
/// unset so the session can still start with the SDK's default agent.
/// When the named file does not exist at <c>{team-root}/.github/agents/{AgentFileName}.agent.md</c>
/// (e.g. the team root is not Squad-initialized yet), the SDK silently skips the
/// <c>--agent</c> argument so the CLI can still start with its default agent.
/// </para>
/// <para>
/// Set to <see langword="null"/> (or whitespace) to disable the auto-selection entirely. Set
/// to a different name (e.g. <c>"data"</c>) to load a custom agent file. To override the
/// final value, set <c>sessionConfig.Agent</c> from inside <see cref="ConfigureSession"/> —
/// that callback runs after this default is applied.
/// Set to <see langword="null"/> (or whitespace) to disable the auto-inject entirely. Set
/// to a different name (e.g. <c>"data"</c>) to load a custom agent file. If the consumer
/// already supplied <c>--agent</c> in <see cref="CliArgs"/>, that explicit value wins and
/// the default is not added.
/// </para>
/// </remarks>
public string? AgentFileName { get; set; } = "squad";
Expand Down Expand Up @@ -145,7 +144,7 @@
/// Gets or sets a delegate that customizes the <see cref="SessionConfig"/> used to
/// construct the inner <see cref="Microsoft.Agents.AI.AIAgent"/>. The delegate runs
/// after Squad has applied its defaults (including an <c>ApproveAll</c>
/// <see cref="SessionConfig.OnPermissionRequest"/> handler and the

Check warning on line 147 in src/Squad.Agents.AI/SquadAgentOptions.cs

View workflow job for this annotation

GitHub Actions / .NET ubuntu-latest

XML comment has cref attribute 'OnPermissionRequest' that could not be resolved

Check warning on line 147 in src/Squad.Agents.AI/SquadAgentOptions.cs

View workflow job for this annotation

GitHub Actions / .NET ubuntu-latest

XML comment has cref attribute 'OnPermissionRequest' that could not be resolved

Check warning on line 147 in src/Squad.Agents.AI/SquadAgentOptions.cs

View workflow job for this annotation

GitHub Actions / .NET windows-latest

XML comment has cref attribute 'OnPermissionRequest' that could not be resolved

Check warning on line 147 in src/Squad.Agents.AI/SquadAgentOptions.cs

View workflow job for this annotation

GitHub Actions / .NET windows-latest

XML comment has cref attribute 'OnPermissionRequest' that could not be resolved
/// <see cref="Instructions"/> as the appended system message), so it can override or
/// extend any session-scoped setting such as the permission handler, tool list,
/// model name, or hooks.
Expand Down Expand Up @@ -203,7 +202,7 @@
/// {
/// if (trace.Kind == SquadAgentTraceEventKind.SubagentStarted)
/// Console.WriteLine($"[spawn] {trace.SubagentName} (id={trace.SdkAgentId})");
/// else if (trace.Kind == SquadAgentTraceEventKind.AssistantMessage && trace.SdkAgentId is not null)

Check warning on line 205 in src/Squad.Agents.AI/SquadAgentOptions.cs

View workflow job for this annotation

GitHub Actions / .NET ubuntu-latest

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 205 in src/Squad.Agents.AI/SquadAgentOptions.cs

View workflow job for this annotation

GitHub Actions / .NET ubuntu-latest

XML comment has badly formed XML -- 'The character(s) '&' cannot be used at this location.'

Check warning on line 205 in src/Squad.Agents.AI/SquadAgentOptions.cs

View workflow job for this annotation

GitHub Actions / .NET ubuntu-latest

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 205 in src/Squad.Agents.AI/SquadAgentOptions.cs

View workflow job for this annotation

GitHub Actions / .NET ubuntu-latest

XML comment has badly formed XML -- 'The character(s) '&' cannot be used at this location.'

Check warning on line 205 in src/Squad.Agents.AI/SquadAgentOptions.cs

View workflow job for this annotation

GitHub Actions / .NET windows-latest

XML comment has badly formed XML -- 'The character(s) '&' cannot be used at this location.'

Check warning on line 205 in src/Squad.Agents.AI/SquadAgentOptions.cs

View workflow job for this annotation

GitHub Actions / .NET windows-latest

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 205 in src/Squad.Agents.AI/SquadAgentOptions.cs

View workflow job for this annotation

GitHub Actions / .NET windows-latest

XML comment has badly formed XML -- 'The character(s) '&' cannot be used at this location.'

Check warning on line 205 in src/Squad.Agents.AI/SquadAgentOptions.cs

View workflow job for this annotation

GitHub Actions / .NET windows-latest

XML comment has badly formed XML -- 'Whitespace is not allowed at this location.'

Check warning on line 205 in src/Squad.Agents.AI/SquadAgentOptions.cs

View workflow job for this annotation

GitHub Actions / .NET windows-latest

XML comment has badly formed XML -- 'The character(s) '&' cannot be used at this location.'
/// Console.WriteLine($"[{trace.SdkAgentId}] {trace.Content}");
/// };
/// </code>
Expand Down
Loading
Loading