feat: add CommunityToolkit.Aspire.Hosting.Squad - first-class Aspire resource for Squad AI-agent teams#1394
Conversation
|
🚀 Dogfood this PR with:
curl -fsSL https://raw.githubusercontent.com/CommunityToolkit/Aspire/main/eng/scripts/dogfood-pr.sh | bash -s -- 1394Or
iex "& { $(irm https://raw.githubusercontent.com/CommunityToolkit/Aspire/main/eng/scripts/dogfood-pr.ps1) } 1394" |
The original example AppHost only registered a SquadResource. Per the PR review request from CommunityToolkit#1394, the example now also demonstrates end-to-end consumption: a downstream ApiApp project receives the squad://... connection string via .WithReference(squad), parses the team root, and uses Squad.Agents.AI to drive a real 3-turn conversation against the referenced team. What's added ------------ - examples/squad/CommunityToolkit.Aspire.Hosting.Squad.ApiApp/ - ASP.NET minimal API; Program.cs reads ConnectionStrings:research-squad (squad://... format), extracts teamRoot, registers SquadAgent via AddSquadAgent, exposes POST /ask that runs a 3-turn conversation through AgentSession (demonstrates session memory across turns). - examples/squad/CommunityToolkit.Aspire.Hosting.Squad.ServiceDefaults/ - Standard Aspire ServiceDefaults shape (OTel + service discovery + health checks), mirroring the Java example layout. - examples/squad/CommunityToolkit.Aspire.Hosting.Squad.AppHost/ - Updated to AddProject<...>("squad-api").WithReference(researchSquad) - Directory.Packages.props - Added Squad.Agents.AI 0.1.0-preview.3 (centralized version), the preview that ships the SessionConfig/OnPermissionRequest fix needed to drive a real conversation. See bradygaster/squad#1252. - CommunityToolkit.Aspire.slnx - 2 new projects added (ApiApp, ServiceDefaults). - src/CommunityToolkit.Aspire.Hosting.Squad/README.md - New "Consume the team from a service project" section pointing callers at the new examples/squad/ runnable. Local verification ------------------ - dotnet build (Release) of all 5 squad-related projects: 0 warn, 0 err - All 7 unit tests still pass on net10.0 (MTP) - Squad.Agents.AI 0.1.0-preview.3 restores from nuget.org cleanly Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
📦 Update — example now demonstrates end-to-end consumption (commit The example AppHost now also wires a downstream ApiApp via New in
Companion fix shipped upstream: while building the example I hit a runtime bug in Verified locally:
Still draft — happy to flip to ready-for-review once you've had a chance to look. Open questions from the original PR body still apply (example folder layout, simultaneous docs PR, |
|
Bumped to The 0.1.0-preview.3 the original PR pinned to was wedged on There's one temporary footnote — a direct |
There was a problem hiding this comment.
Pull request overview
Note
Copilot was unable to run its full agentic suite in this review.
Adds a new Aspire Community Toolkit integration that models a Squad AI-agent team as a first-class .NET Aspire resource, including dashboard metadata, lifecycle events, and an end-to-end example.
Changes:
- Introduces
SquadResource+AddSquad(...)extension with connection-string support and roster discovery from.squad/team.md. - Adds dashboard integration (properties, lifecycle state transitions, and dashboard commands).
- Adds tests and a runnable example AppHost + ApiApp demonstrating
.WithReference(...)consumption.
Reviewed changes
Copilot reviewed 25 out of 25 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/CommunityToolkit.Aspire.Hosting.Squad.Tests/SquadResourceCreationTests.cs | New tests for resource registration, roster parsing, and connection string shape. |
| tests/CommunityToolkit.Aspire.Hosting.Squad.Tests/CommunityToolkit.Aspire.Hosting.Squad.Tests.csproj | New test project referencing the Squad integration project. |
| tests/CommunityToolkit.Aspire.Hosting.Squad.Tests/AssemblyInfo.cs | Adds an InternalsVisibleTo attribute (currently applied to the test assembly). |
| src/CommunityToolkit.Aspire.Hosting.Squad/api/CommunityToolkit.Aspire.Hosting.Squad.cs | Public API surface file for SquadResource and AddSquad. |
| src/CommunityToolkit.Aspire.Hosting.Squad/SquadTeamAnnotation.cs | Adds an annotation type to carry .squad metadata paths. |
| src/CommunityToolkit.Aspire.Hosting.Squad/SquadResource.cs | Implements the Aspire resource, agent discovery, and squad:// connection string expression. |
| src/CommunityToolkit.Aspire.Hosting.Squad/SquadLifecycleHook.cs | Adds lifecycle subscriber to publish dashboard state and properties. |
| src/CommunityToolkit.Aspire.Hosting.Squad/SquadDashboardProperties.cs | Generates static + live dashboard property snapshots. |
| src/CommunityToolkit.Aspire.Hosting.Squad/SquadBuilderExtensions.cs | Adds AddSquad extension and dashboard commands (open folder, check inbox, Copilot CLI). |
| src/CommunityToolkit.Aspire.Hosting.Squad/README.md | Package README describing usage and roster discovery behavior. |
| src/CommunityToolkit.Aspire.Hosting.Squad/CommunityToolkit.Aspire.Hosting.Squad.csproj | New integration project file (package metadata + Aspire.Hosting reference). |
| examples/squad/CommunityToolkit.Aspire.Hosting.Squad.ServiceDefaults/Extensions.cs | Example service defaults (telemetry/health/discovery) for the sample. |
| examples/squad/CommunityToolkit.Aspire.Hosting.Squad.ServiceDefaults/CommunityToolkit.Aspire.Hosting.Squad.ServiceDefaults.csproj | Example ServiceDefaults project references. |
| examples/squad/CommunityToolkit.Aspire.Hosting.Squad.AppHost/sample-squad/.squad/team.md | Sample Squad roster fixture for the example AppHost. |
| examples/squad/CommunityToolkit.Aspire.Hosting.Squad.AppHost/sample-squad/.squad/agents/ralph/charter.md | Sample agent fixture. |
| examples/squad/CommunityToolkit.Aspire.Hosting.Squad.AppHost/sample-squad/.squad/agents/picard/charter.md | Sample agent fixture. |
| examples/squad/CommunityToolkit.Aspire.Hosting.Squad.AppHost/Properties/launchSettings.json | Launch settings for running the example AppHost. |
| examples/squad/CommunityToolkit.Aspire.Hosting.Squad.AppHost/Program.cs | Example AppHost wiring AddSquad into a downstream ApiApp using .WithReference(...). |
| examples/squad/CommunityToolkit.Aspire.Hosting.Squad.AppHost/CommunityToolkit.Aspire.Hosting.Squad.AppHost.csproj | Example AppHost project file and copying sample-squad to output. |
| examples/squad/CommunityToolkit.Aspire.Hosting.Squad.ApiApp/Properties/launchSettings.json | Launch settings for the example ApiApp. |
| examples/squad/CommunityToolkit.Aspire.Hosting.Squad.ApiApp/Program.cs | Example ApiApp that parses squad://... connection string and runs a 3-turn conversation. |
| examples/squad/CommunityToolkit.Aspire.Hosting.Squad.ApiApp/CommunityToolkit.Aspire.Hosting.Squad.ApiApp.csproj | Example ApiApp package references (Squad.Agents.AI + GitHub.Copilot.SDK). |
| README.md | Adds the Hosting.Squad entry + link targets to the repo-wide integrations table. |
| Directory.Packages.props | Adds central package versions for Squad.Agents.AI and GitHub.Copilot.SDK. |
| CommunityToolkit.Aspire.slnx | Adds the new integration + tests + example projects to the solution. |
Comments suppressed due to low confidence (1)
src/CommunityToolkit.Aspire.Hosting.Squad/SquadBuilderExtensions.cs:1
- The command is named/descried as “Refresh” and claims it “Re-reads .squad/team.md”, but it never actually reads/parses
team.md—it only checks file existence and reports the already-cachedresource.Agents. Either (mandatory) change the description/name to reflect the actual behavior (e.g., “Report Agents”), or (optional) implement a real re-read and publish updated dashboard properties/state.
using Aspire.Hosting.ApplicationModel;
| var content = File.ReadAllText(rosterPath); | ||
| var agents = AgentTableRowRegex.Matches(content) | ||
| .Concat(AgentLineRegex.Matches(content)) | ||
| .Select(m => NormalizeAgentName(m.Groups[1].Value)) | ||
| .Where(agent => IsKnownAgent(teamRoot, agent)) | ||
| .Distinct() | ||
| .ToList(); |
There was a problem hiding this comment.
Addressed in 097369f. Added explicit .Cast<Match>() on both Regex.Matches(...) results so the LINQ Concat/Select binding is unambiguous — defensive against older TFMs where MatchCollection is only non-generic IEnumerable. No behavior change on net10.0 where it already implements IList<Match>.
| startInfo.ArgumentList.Add("/c"); | ||
| startInfo.ArgumentList.Add("start"); | ||
| startInfo.ArgumentList.Add(windowTitle); | ||
| startInfo.ArgumentList.Add("powershell.exe"); | ||
| startInfo.ArgumentList.Add("-NoLogo"); | ||
| startInfo.ArgumentList.Add("-NoExit"); | ||
| startInfo.ArgumentList.Add("-Command"); | ||
| startInfo.ArgumentList.Add(copilotCommand); |
There was a problem hiding this comment.
Addressed in 097369f. Dropped cmd /c start entirely. StartConsoleWindow now launches powershell.exe directly with UseShellExecute=true (which opens a new console window) and sets the title from inside the new shell via \System.Management.Automation.Internal.Host.InternalHost.UI.RawUI.WindowTitle = '...'. No more cmd-quote-stripping fragility, and we keep the friendly window title.
| { | ||
| public static partial class SquadBuilderExtensions | ||
| { | ||
| public static Aspire.Hosting.ApplicationModel.IResourceBuilder<Aspire.Hosting.ApplicationModel.SquadResource> AddSquad(this Aspire.Hosting.IDistributedApplicationBuilder builder, string name, string teamRoot) { throw null; } |
There was a problem hiding this comment.
Addressed in 097369f. Re-ran -t:GenAPIGenerateReferenceAssemblySource; the regenerated file now correctly declares string? teamRoot = null and also picks up SquadTeamAnnotation, which the hand-typed version had missed.
| @@ -0,0 +1 @@ | |||
| [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("CommunityToolkit.Aspire.Hosting.Squad.Tests")] | |||
There was a problem hiding this comment.
Addressed in 097369f — deleted tests/CommunityToolkit.Aspire.Hosting.Squad.Tests/AssemblyInfo.cs. The production csproj already wires <InternalsVisibleTo Include="...Tests" /> correctly, so the test-side attribute was duplicate dead config.
| private static string CreateEmptyTeamRoot() | ||
| { | ||
| var dir = Path.Combine(Path.GetTempPath(), "ctk-aspire-squad-tests", Guid.NewGuid().ToString("N")); | ||
| Directory.CreateDirectory(dir); | ||
| return dir; | ||
| } |
There was a problem hiding this comment.
Addressed in 097369f. SquadResourceCreationTests now implements IDisposable and tracks every temp dir created by the helpers in an instance-scoped _tempRoots list. Dispose() best-effort deletes them recursively, so test residue no longer accumulates in %TEMP% across runs (including CI agents).
|
Bumped to New in 0.3.0 (shipped today from bradygaster/squad#1265) — a typed This commit:
Verified locally: AppHost boots clean, the |
Introduces a new hosting integration that lets a .NET Aspire AppHost model a Squad (https://github.com/bradygaster/squad) AI-agent team as a first-class Aspire resource. Closes CommunityToolkit#1393 What's included --------------- - `src/CommunityToolkit.Aspire.Hosting.Squad/` — the package - `SquadResource : Resource, IResourceWithConnectionString` Auto-discovers the agent roster from `.squad/team.md` (table and bullet-list formats), only keeping names that map to an existing `.squad/agents/{name}/charter.md` to filter typos. Exposes `squad://resource/{name}?teamRoot={...}&agents={csv}&protocol=maf-1.0` so downstream services can consume the team via `.WithReference`. - `SquadBuilderExtensions.AddSquad(name, teamRoot)` decorated with `[AspireExport]` (ATS-compatible). - `SquadLifecycleHook` publishes Spawning -> Active transitions on the dashboard and seeds `SquadDashboardProperties` so the roster, protocol version, and team root show on the resource row. - Dashboard commands wired via `WithCommand` (open Copilot CLI on the team root, etc.). - Public API surface file at `src/CommunityToolkit.Aspire.Hosting.Squad/api/CommunityToolkit.Aspire.Hosting.Squad.cs` per repo convention. - `tests/CommunityToolkit.Aspire.Hosting.Squad.Tests/` — 7 xUnit unit tests covering: resource registration, default roster fallback, guard clauses, table-format roster parsing, bullet-format roster parsing, and connection-string shape. All pass locally on net10.0. - `examples/squad/CommunityToolkit.Aspire.Hosting.Squad.AppHost/` — minimal AppHost example with a self-contained `sample-squad/.squad/` fixture so the example builds and runs without external state. - Solution + root README updates (table row, link refs). Out of scope (planned follow-ups, kept separate for reviewability) ------------------------------------------------------------------ - OpenTelemetry signals (agent spawns, token usage, session metrics). Depends on the Squad telemetry contract still being designed at bradygaster/squad#1144. - Direct `Squad.Agents.AI` consumer-side extension that constructs an MAF `AIAgent` from a referenced squad. The connection-string shape here is the integration point; consumer-side wiring is a separate package design. Local verification ------------------ - `dotnet build` on all three new projects (Release) — 0 warnings, 0 errors - 7/7 tests pass via Microsoft Testing Platform on net10.0 - Example AppHost builds cleanly Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The original example AppHost only registered a SquadResource. Per the PR review request from CommunityToolkit#1394, the example now also demonstrates end-to-end consumption: a downstream ApiApp project receives the squad://... connection string via .WithReference(squad), parses the team root, and uses Squad.Agents.AI to drive a real 3-turn conversation against the referenced team. What's added ------------ - examples/squad/CommunityToolkit.Aspire.Hosting.Squad.ApiApp/ - ASP.NET minimal API; Program.cs reads ConnectionStrings:research-squad (squad://... format), extracts teamRoot, registers SquadAgent via AddSquadAgent, exposes POST /ask that runs a 3-turn conversation through AgentSession (demonstrates session memory across turns). - examples/squad/CommunityToolkit.Aspire.Hosting.Squad.ServiceDefaults/ - Standard Aspire ServiceDefaults shape (OTel + service discovery + health checks), mirroring the Java example layout. - examples/squad/CommunityToolkit.Aspire.Hosting.Squad.AppHost/ - Updated to AddProject<...>("squad-api").WithReference(researchSquad) - Directory.Packages.props - Added Squad.Agents.AI 0.1.0-preview.3 (centralized version), the preview that ships the SessionConfig/OnPermissionRequest fix needed to drive a real conversation. See bradygaster/squad#1252. - CommunityToolkit.Aspire.slnx - 2 new projects added (ApiApp, ServiceDefaults). - src/CommunityToolkit.Aspire.Hosting.Squad/README.md - New "Consume the team from a service project" section pointing callers at the new examples/squad/ runnable. Local verification ------------------ - dotnet build (Release) of all 5 squad-related projects: 0 warn, 0 err - All 7 unit tests still pass on net10.0 (MTP) - Squad.Agents.AI 0.1.0-preview.3 restores from nuget.org cleanly Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…DK direct ref Squad.Agents.AI 0.2.0-preview.4 (released earlier today from bradygaster/squad#1259) brings the package onto Microsoft.Agents.AI.GitHub.Copilot 1.10.0-rc1, which transitively pulls GitHub.Copilot.SDK 1.0.0 GA. The earlier 0.1.0-preview.3 was wedged on SDK 1.0.0-beta.2 + a CLI protocol the current copilot CLI no longer speaks — verified by running the example ApiApp in this branch against a real .squad-initialised team root and seeing the agent successfully read .squad/team.md and dispatch work to multiple specialists. Also add a direct PackageReference to GitHub.Copilot.SDK 1.0.0. This is a temporary workaround: the SDK ships its CLI-binary-download MSBuild targets under build/ (only auto-imported for projects with a *direct* PackageReference), not buildTransitive/. microsoft/agent-framework#6457 (merged 2026-06-10) fixes this at the MAF adapter level — once a MAF preview ships with that change, the direct GitHub.Copilot.SDK ref can be dropped from Directory.Packages.props and the ApiApp csproj. Both edit sites carry an inline comment explaining the lifecycle. Verified: the hosting library, the ApiApp, and the AppHost all build clean, and copilot.exe lands at the expected 'bin/{cfg}/{tfm}/runtimes/{rid}/native/' path in the ApiApp output. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… into ApiApp Squad.Agents.AI 0.3.0-preview.5 (released today from bradygaster/squad#1265) adds a first-class subagent observability surface: a typed OnSubagentTrace callback + an OpenTelemetry ActivitySource named 'Microsoft.Agents.AI.Squad' that emits one span per task-tool dispatch with squad.subagent.name / squad.subagent.display_name / squad.subagent.sdk_agent_id / squad.subagent.reply_preview tags. This commit: * Bumps Squad.Agents.AI from 0.2.0-preview.4 to 0.3.0-preview.5 in Directory.Packages.props. * Wires the new ActivitySource into the ApiApp's OpenTelemetry tracer (one line on top of the AddServiceDefaults() configuration) so every subagent dispatch shows up as its own span in the Aspire dashboard's Traces view, with the subagent name + a preview of its reply attached as span tags. * Adds OnSubagentTrace logging in the ApiApp so subagent lifecycle (spawn, reply, complete) also surfaces in the dashboard's Structured Logs view — both signals are correlated by Activity.Current so the OTel span and the structured log share the same trace id. * Adds a new POST /dispatch endpoint that issues an explicit task-tool dispatch prompt ('use the task tool to dispatch two parallel subagents...'). Hitting this endpoint from the dashboard produces a multi-span trace showing the coordinator + each subagent as separate spans — the headline visibility demo. * Existing POST /ask 3-turn endpoint kept for the session-memory demo. Verified locally: AppHost boots clean, the 'research-squad' resource registers 2 agents (ralph, picard from the sample-squad team), and the ApiApp builds + runs with the new observability wiring against Squad.Agents.AI 0.3.0-preview.5 restored from nuget.org. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…rch + dev)
The previous bundled sample-squad/ had stub one-line charters with nothing to
dispatch to, so /ask returned coordinator role-play and /dispatch produced no
subagent spans in the Aspire dashboard.
Replace it with two real Squad teams created via 'squad init --no-workflows'
and autonomously cast by the Squad coordinator running under 'copilot --yolo':
research-squad/ Matrix-cast AI/ML research team
Morpheus (Lead) - Trinity (ML/Data) - Oracle (Eval) - Tank (Tester)
Stack: Python, PyTorch/TensorFlow, Jupyter, MLflow
dev-squad/ Simpsons-cast full-stack development team
Lisa (Lead) - Marge (Frontend) - Frink (Backend) - Comic Book Guy (Tester)
Both squads have full .squad/ scaffolding: team.md, per-agent charter.md and
history.md, casting registry, decisions ledger, ceremonies, routing, and the
.copilot/ skills the agents read. Runtime state (orchestration-log/, log/,
sessions/, decisions/inbox/) is excluded via the per-squad .gitignore that
'squad init' generates.
AppHost wires both as Aspire resources via .AddSquad('research-squad', ...) and
.AddSquad('dev-squad', ...), and references both from the ApiApp via
.WithReference(researchSquad).WithReference(devSquad). Aspire injects two
connection strings under ConnectionStrings:research-squad and
ConnectionStrings:dev-squad.
ApiApp rewritten to:
- Register one SquadAgent per squad via AddKeyedSquadAgent(serviceKey, ...)
using the keyed-DI overload shipped in Squad.Agents.AI 0.1.0-preview.2
- Resolve the right agent per request via [FromKeyedServices] / ?squad= query
- /ask?squad=research|dev runs a 3-turn conversation against the picked team
- /dispatch?squad=research|dev sends an explicit 'use the task tool to dispatch
two parallel subagents' prompt so we reliably see real subagent spawns (not
inline role-play) and one squad.subagent {Name} OTel span per spawn in the
Aspire dashboard Traces view
- OnSubagentTrace forwards subagent start/done/message to console so the
structured logs line up with the OTel trace timeline
AppHost launchSettings.json also gains DCP_IDE_REQUEST_TIMEOUT_SECONDS=300 to
prevent the 120s DCP/VS run-session race that falls back to bare dotnet.exe
and produces 'Usage: dotnet [path-to-application]' in the ApiApp logs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…st comments
The ApiApp registered SquadAgent under keys 'research-squad' and 'dev-squad'
(the Aspire resource names) but the /ask and /dispatch endpoints take
?squad=research|dev (short form). The dictionary lookup mismatched and both
endpoints returned {"error":"Unknown squad 'research'..."}.
Fix:
- Register each keyed SquadAgent under the short name ('research', 'dev')
- Resolve the longer 'research-squad' / 'dev-squad' name only when reading
the connection string (which IS keyed by the Aspire resource name)
- Drop the unused ResearchSquad/DevSquad const
Also clean up the duplicate using + stale comment block in AppHost/Program.cs
left over from the previous edit.
Verified end-to-end with a live run:
- Both squads come up Active in the dashboard (research: 7 agents discovered;
dev: 6 agents discovered)
- POST /dispatch?squad=research returns the Morpheus + Trinity answers verbatim
- POST /dispatch?squad=dev returns the Lisa + Frink answers verbatim
- ApiApp spawns one copilot.exe child per request (real subagent dispatch, not
inline role-play)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…folder When 'squad init' is run from a folder that has a parent git repo (i.e. our case — the Squad CLI's monorepo detection kicked in because Aspire-1 is a git repo), the CLI places .github/agents/squad.agent.md at the GIT ROOT instead of inside the squad folder. That made the example squads non-self-contained: anyone cloning this repo and pointing SquadFolderPath at research-squad/ or dev-squad/ would have no coordinator agent file (Copilot resolves .github/agents/ relative to the consumer's git root, not ours). For an in-repo example that ships as a finished artifact, the squads need to own their own coordinator. Copy the same squad.agent.md (v0.9.6-insider.3, 71594 bytes) into each squad's own .github/agents/ directory. Also expand each .gitignore to cover Copilot CLI SDK runtime files that get created when a SquadAgent serves a request: .squad/session-state/ .squad/session-store.db .squad/session-store.db-shm .squad/session-store.db-wal The bundled .gitignore from 'squad init' already covered .squad/sessions/ but those SDK-side files use slightly different paths. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two problems with the previous Console.WriteLine approach:
1. Console.WriteLine output did not consistently surface in the Aspire dashboard
when the SDK fired OnSubagentTrace from its background event pump (different
thread, no captured stdout context). The user reported empty Console Logs
even though the callback was firing.
2. The SDK's per-subagent OTel spans (Microsoft.Agents.AI.Squad activity source)
may end up as orphan traces — the SDK's Channel-based event pump can fire
on a thread where Activity.Current has been cleared by the AsyncLocal flow,
so StartActivity has no parent and the span becomes a new root trace
detached from the HTTP request's trace.
Fixes:
- Use ILogger<>'s structured logging via Microsoft.Extensions.Options'
Configure<ILoggerFactory> overload, which injects the fully-built
ILoggerFactory into the SquadAgentOptions post-configure step (runs after
the host is built, before any RunAsync call). Each subagent
start / message / completed lands in the Aspire dashboard's Structured Logs
view with the squad name, subagent name, and message preview as queryable
fields.
- Add an app-owned ActivitySource ("Squad.Hosting.ApiApp") and wrap each
/ask and /dispatch handler in a "squad.dispatch {endpoint} {squad}" span.
This guarantees a single, named root span per request that the user's
trace search can land on, even when the SDK's per-subagent spans become
orphans.
- Inside OnSubagentTrace, also call Activity.Current?.AddEvent(...) so each
subagent lifecycle event (start / message / completed) is attached as an
OpenTelemetry ActivityEvent to whatever span is current at callback time
(the SDK's subagent span when open, the dispatch wrapper otherwise).
Events show up as labelled annotations on the span timeline in the Aspire
dashboard trace detail view.
- Wire the new app source into AddOpenTelemetry().WithTracing(...) alongside
the existing Microsoft.Agents.AI.Squad source.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… ApiApp wiring 0.4.0 (bradygaster/squad#1271, merged today) ships two changes that remove the boilerplate this example needed in 0.3.0: 1. AddKeyedSquadAgent("research-squad") now resolves ConnectionStrings:research-squad (Aspire-injected) directly, with a fallback to the legacy ConnectionStrings:squad-research-squad form. No more manual builder.Configuration.GetConnectionString() + URI parse + feeding SquadFolderPath through a configure callback. 2. EmitSubagentActivities defaults to true. Just call .AddSource(SquadAgentDiagnostics.ActivitySourceName) on the tracer and "squad.subagent {Name}" spans appear in the Aspire dashboard's Traces view with lifecycle ActivityEvents (start / message / completed / failed) annotated on the timeline — without setting OnSubagentTrace at all. OnSubagentTrace becomes a pure customisation hook now, kept here only to surface per-subagent ILogger lines in the Structured Logs view. ApiApp Program.cs: - Drop the foreach that called GetConnectionString + ParseSquadTeamRoot (16 lines) — replaced by a single AddKeyedSquadAgent("{resource}") call per squad, with a separate AddOptions<>().Configure<ILoggerFactory>(...) hop for the OnSubagentTrace ILogger callback. - Drop the local ParseSquadTeamRoot helper — the SDK does the parsing. - Drop the squadTeamRoots dictionary — was a leaky abstraction of the SDK's connection-string handling. Replaced with squadKeysByShortName that just maps the query-param short name ("research") to the keyed-DI key ("research-squad"). Bumped Squad.Agents.AI 0.3.0-preview.5 → 0.4.0-preview.6 in Directory.Packages.props. End-to-end verified live against the published 0.4.0-preview.6 package: - Both squads register cleanly with no manual connection-string handling - POST /dispatch?squad=research returned the Morpheus + Trinity replies verbatim (real task-tool subagent dispatch) - Smaller, cleaner Program.cs that closely mirrors the README Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…verride
When running 'copilot --agent squad' interactively in a terminal, the
coordinator loads .github/agents/squad.agent.md (1023 lines: eager
execution, parallel fan-out, dispatch via task tool) as its system prompt
and follows that contract. When the same Squad team is invoked through
Squad.Agents.AI's SquadAgent, the underlying copilot.exe child process
was using its default agent — so the coordinator had no instructions to
fan out or dispatch, and the terse 'Be concise' Instructions override we
were passing actively encouraged it to role-play the team in a single
reply instead of spawning real subagents.
Fix:
- Pass --agent squad in CliArgs so the CLI selects squad.agent.md as the
agent definition for the spawned session
- Drop the per-squad Instructions override (was overriding the full
squad.agent.md system prompt with one terse line)
Verified live against Squad.Agents.AI 0.4.0-preview.6:
- POST /ask?squad=dev with a Full Mode prompt ('Use the task tool to
dispatch each of Lisa, Marge, Frink and Comic Book Guy in parallel ...')
produced the eager-execution narration ('Waiting for all four agents
to return. Still waiting for Marge.') that only squad.agent.md emits,
AND returned four distinct subagent replies — proof that --agent squad
is active and the task tool is dispatching.
Note: 'team, i want every member to say its name' still gets a single
coordinator reply because squad.agent.md classifies that as Direct Mode
('Who's on the team?' → answer from team.md, no spawn). For the
observability demo, use either POST /dispatch?squad=X or send a Full Mode
prompt to /ask.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
0.5.0-preview.7 introduced a regression: setting sessionConfig.Agent ='squad'
caused a runtime failure ('Custom agent ''squad'' not found') because the SDK
property looks up the SDK's CustomAgents registry, NOT the on-disk
.github/agents/*.agent.md files.
0.5.1-preview.8 (bradygaster/squad#1277) reverts to the original --agent CLI
flag approach which actually reads the on-disk agent definition the same
way 'copilot --agent squad' does interactively.
Verified live: POST /dispatch?squad=dev now returns distinct subagent voices
(Lisa: 'A good software architecture makes change boring ...';
Frink: 'A well-designed API's most important property is a clear, stable
contract that makes correct usage obvious and reliable—glavin!'),
which confirms (a) --agent squad is now being passed through, (b)
squad.agent.md is loaded as the coordinator's system prompt, and (c) real
subagent dispatch via the task tool is happening (not coordinator role-play).
The ApiApp's Program.cs already drops the manual --agent CLI workaround from
the previous 0.5.0 upgrade commit, so this is a version-only bump.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ld break
ApiApp:
- Drop /dispatch. The endpoint was just /ask with a hardcoded "Use the task
tool to dispatch ..." prompt; same SquadAgent.RunAsync path, same coordinator,
same span/log surface — the only difference was the prompt. Removing it
makes the API surface honest: there's one endpoint, the caller owns the
prompt, the coordinator picks the mode (Direct / Lightweight / Full).
- GET / now returns a copy-paste "sample_prompts" menu (roster_recall_direct_mode,
multi_turn_memory, dispatch_full_mode_research, dispatch_full_mode_dev),
each shaped like the /ask body so the user can paste straight through to
reproduce every mode and see what the corresponding spans look like.
- Drop the unused AskRequest.Default sample (the / index now owns sample prompts).
- Rename the wrapper span "squad.dispatch ask {squad}" -> "squad.ask {squad}".
AppHost build break:
Earlier interactive sessions created scratch C# projects inside the squads
(e.g. research-squad/UserNameApp/, dev-squad/UserNameApp/) when the user
asked the team to "write a program in C3 / C#". The Aspire AppHost SDK's
default <Compile>, <Content>, <None>, and <EmbeddedResource> globs sweep
any .cs / .csproj at any depth under the AppHost project folder, which
caused two cascading build errors on the next F5:
CS8802: Only one compilation unit can have top-level statements.
CS0579: Duplicate AssemblyInfo attributes.
Fix in two layers:
1. AppHost.csproj: explicit <Compile|Content|None|EmbeddedResource Remove>
on research-squad\** and dev-squad\**. The squads are scaffolding-only
(.squad/, .github/, .copilot/, .mcp.json) and should never participate
in the AppHost compile graph.
2. Each squad's .gitignore: append *.csproj / *.fsproj / *.vbproj / bin/
/ obj/ guards so future scratch projects don't get committed either.
Also delete the existing UserNameApp/ scratch dirs from both squads.
Squad.slnx:
New focused solution file — just src/CommunityToolkit.Aspire.Hosting.Squad,
the AppHost, the ApiApp, the ServiceDefaults, and the tests. Trims the
noise out of F5 / VS solution explorer for anyone who clones the example
and only wants to run the Squad bits without loading the rest of the
Aspire repo's projects.
Verified live against Squad.Agents.AI 0.5.1-preview.8:
- GET / lists 4 sample_prompts including the full-mode dispatch prompts
- POST /ask?squad=dev with the dispatch_full_mode_dev sample produced Lisa
+ Frink replies with their distinct specialist voices, confirming real
subagent dispatch through the task tool.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
7d2535a to
a2bc309
Compare
Five inline review comments from the Copilot reviewer, all addressed: 1. SquadResource.cs: Cast MatchCollection to IEnumerable<Match> Explicit .Cast<Match>() on both Regex.Matches results so the LINQ Concat/Select binding is unambiguous across TFMs (older targets expose MatchCollection only as non-generic IEnumerable). No behavior change on net10.0 where it already implements IList<Match>; defensive clarity only. 2. SquadBuilderExtensions.StartConsoleWindow: drop fragile cmd /c start invocation The previous approach used 'cmd /c start <windowTitle> powershell.exe ...', which fails or misbehaves when the title contains spaces (e.g., 'Copilot - research-squad'): cmd's quote-stripping causes 'start' to parse the title fragment as part of the command. Now launches powershell.exe directly with UseShellExecute=true and sets the title from inside the new shell via \System.Management.Automation.Internal.Host.InternalHost.UI.RawUI.WindowTitle, removing all cmd/start parsing fragility while preserving the friendly window title. 3. api/*.cs: regenerated to match the real public signature The hand-typed surface declared 'string teamRoot' (required) but the implementation exposes 'string? teamRoot = null'. Re-ran GenAPIGenerateReferenceAssemblySource; the file now correctly shows the nullable optional parameter and also picks up SquadTeamAnnotation, which the previous version had missed. 4. tests/AssemblyInfo.cs: removed InternalsVisibleTo on the test assembly grants visibility to itself (no-op). The production csproj already wires InternalsVisibleTo to the test assembly correctly; the test-side file was duplicate dead config. 5. tests/SquadResourceCreationTests.cs: deterministic temp cleanup Class now implements IDisposable and tracks every temp dir created by the helpers (instance-scoped _tempRoots). Dispose() best-effort deletes them recursively so test residue no longer accumulates in %TEMP% across runs. Tests: 7/7 pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Round of fixes for the Copilot review shipped in 097369f. All 5 inline comments addressed (replies posted on each thread):
Local validation: 7/7 tests pass on net10.0 / Release. Ready for another look. |
What
Adds a
CommunityToolkit.Aspire.Hosting.Squadintegration that lets a .NET Aspire AppHost model a Squad AI-agent team as a first-class Aspire resource.This PR was proposed in #1393.
Why
Squad is a multi-agent CLI orchestration framework whose teams live under a
.squad/workspace folder. Today there's no idiomatic way to surface a Squad team in the Aspire dashboard or pass team identity into downstream service projects. This integration provides one — a logicalResourcethat auto-discovers the roster, exposes it as dashboard metadata, and implementsIResourceWithConnectionStringso service projects can.WithReference(squad).The companion package
Squad.Agents.AI(Microsoft Agent Framework adapter, shipped from the upstream Squad repo, OIDC-published from theSquadnuget.org org) is what consuming services use to orchestrate the referenced team via an MAFAIAgent.What's in this PR
Integration (
src/)SquadResource(Resource, IResourceWithConnectionString)AddSquad(name, teamRoot)extension onAspire.HostingnamespaceSquadLifecycleHook— auto-discovers roster from.squad/team.md(table + bullet format), filters to agents with acharter.mdsquad://resource/{name}?teamRoot={path}&agents={csv}&protocol=maf-1.0[AspireExport]on the public extension method (ASPIREEXPORT008 clean)README.mdfor the NuGet descriptionInternalsVisibleTofor the test projectTests (
tests/)7 xUnit tests in
CommunityToolkit.Aspire.Hosting.Squad.Tests— all passing on net10.0 (MTP via the test exe;dotnet testhits the documented VSTest deprecation noise).Example (
examples/squad/)The example is now an end-to-end demonstration of the full Squad-on-Aspire story, not just a "does it build" smoke test:
AddSquad:research-squad— AI/ML research team cast from The Matrix (Morpheus, Trinity, Oracle, Tank)dev-squad— full-stack development team cast from The Simpsons (Lisa, Marge, Frink, Comic Book Guy).squad/team.md, per-agent charters, casting registry, etc.) committed to the repo so the example is self-contained — anyone cloning gets a working multi-team setup with zerosquad initceremony..WithReference(researchSquad).WithReference(devSquad)and exposes a singlePOST /ask?squad=research|devendpoint. The Aspire-injected connection strings are picked up automatically (Squad.Agents.AI 0.4.0+looks upConnectionStrings:{name}directly — no manual parsing needed).Squad.slnxfocused solution file at the repo root so F5 / VS solution explorer doesn't load the whole Aspire repo.Observability surface
Visible in the Aspire dashboard with zero consumer wiring beyond
AddSource:Microsoft.Agents.AI.Squad(emitted by Squad.Agents.AI 0.4.0+)squad.subagent {Name}span pertask-tool dispatch, withsquad.subagent.start/squad.subagent.message/squad.subagent.completed/squad.subagent.failedActivityEvents annotated on the span timeline. Plus tags:squad.subagent.name,squad.subagent.display_name,squad.subagent.sdk_agent_id,squad.subagent.reply_preview.Squad.Hosting.ApiApp(this app)squad.ask {squad}wrapper span per request — gives the trace tree a clear named root.Squad.Subagent.*)>> start/<< done/msg from {Name}log lines via ILogger.Sample prompts menu
GET /returns a copy-paste menu of prompts that demonstrate each of Squad's coordinator-mode behaviors (Direct, Lightweight, Full), so the demo flow is obvious without reading the source.Real-world driver
This integration drove three small but high-impact improvements upstream in
Squad.Agents.AI, all merged and shipped to NuGet in the past 24h while this PR was open:Squad.Agents.AI 0.4.0: default-on OpenTelemetry spans + Aspire-style connection-string lookup (consumers no longer need to wireOnSubagentTracejust to get spans, andAddKeyedSquadAgent("research-squad")resolvesConnectionStrings:research-squaddirectly).Squad.Agents.AI 0.5.0: auto-inject the coordinator agent definition so the wrapped session behaves the same ascopilot --agent squadfrom a terminal.Squad.Agents.AI 0.5.1: fix the 0.5.0 SDK-property approach (turned out the SDK property looks up an in-memory registry, not on-disk agent files).This PR consumes
Squad.Agents.AI 0.5.1-preview.8(the latest shipped preview).Local verification
All on Windows / .NET 10.0.301 SDK / Aspire 13.4 (centrally pinned):
End-to-end against the live AppHost:
research-squadanddev-squadshow up as Active in the dashboard with their rosters discovered.POST /ask?squad=devwith thedispatch_full_mode_devsample prompt dispatched real subagents — Lisa (Tech Lead) and Frink (Backend) both replied in their distinct specialist voices (Frink's reply ended in "glavin!" — pure Professor Frink), confirming thetask-tool dispatch path through--agent squadis wired correctly.squad.subagent {Lisa}andsquad.subagent {Frink}spans appeared in the dashboard Traces view with the full set of timeline ActivityEvents.Tutorial / docs
Updated walkthrough including the Aspire flow: https://gist.github.com/tamirdresher/d0e38cbadd962de18e8373706eccad97
(
docs/create-integration.mdsays full docs go in a separate PR tomicrosoft/aspire.dev. Happy to open that simultaneously, or wait for this to land first — let me know your preference.)PR checklist
feat/hosting-squad)main(just rebased — 0 commits behind, 12 ahead)aspire.devpage is a follow-up perdocs/create-integration.md)Allow edits by maintainerscheckedCloses #1393
Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com