# Safety Guardrails — Aspire Agent Rules > **Purpose**: Detailed explanation of why each guardrail exists, what goes wrong when violated, and how to recover. ## Why These Rules Exist Issue [#15801](https://github.com/microsoft/aspire/issues/15801) documented 5 specific failures when AI agents work in Aspire projects without guidance. Every rule below directly prevents one or more of these failures. --- ## Rule 1: ALWAYS `aspire start` — NEVER `dotnet run` ### Why `dotnet run` Is Dangerous for AppHosts The AppHost project is an **orchestrator**, not a regular .NET app. Running it with `dotnet run`: - **Bypasses the Aspire CLI orchestration layer** — resources don't get managed lifecycle - **No dashboard** — the Aspire developer dashboard won't launch - **No backchannel** — `aspire wait`, `aspire logs`, `aspire describe` won't work - **No resource management** — can't operate on individual resources - **Port conflicts** — resources start without coordinated port allocation - **No cleanup** — orphaned processes when the host exits ### Correct Pattern ```bash # ✅ Start the Aspire app aspire start # ✅ Verify it's running aspire ps ``` ### `aspire run` vs `aspire start` | Command | Mode | Dashboard | Use Case | |---------|------|-----------|----------| | `aspire run` | Foreground (interactive) | Yes, in terminal | Human developer at terminal | | `aspire start` | Background (detached) | No terminal output | **AI agents — always prefer this** | | `aspire run --detach` | Background (same as start) | Yes, separate window | Alternative to `aspire start` | For AI agents, **always use `aspire start`** — it runs in the background and returns control to the agent. ### Recovery If `dotnet run` Was Used ```bash # Kill the dotnet process manually (find PID first) # Then start correctly: aspire start ``` --- ## Rule 2: ALWAYS `aspire wait` — NEVER `curl` Polling ### Why `curl` Polling Is Wrong Aspire tracks resource readiness internally through health checks, dependency graphs, and startup ordering. Manual HTTP polling: - **Doesn't know the correct port** — Aspire assigns ports dynamically - **Doesn't respect dependency ordering** — a resource may respond to HTTP before its dependencies are ready - **Creates false negatives** — health endpoint may not exist or may return 503 during startup - **Wastes time** — retry loops with arbitrary delays vs. event-driven readiness ### Correct Pattern ```bash # ✅ Wait for a specific resource aspire wait apiservice # ✅ Wait with extended timeout for slow resources aspire wait postgres --timeout 120 # ✅ Wait then interact aspire wait apiservice && curl $(aspire describe apiservice --format Json | jq -r '.endpoints[0].url') ``` ### Machine-Readable Output Always use `--format Json` when parsing output programmatically: ```bash # ✅ Get resource endpoints as JSON aspire describe --format Json # ❌ Never scrape text output aspire describe | grep "http" # fragile, format may change ``` --- ## Rule 3: Prefer Resource Commands / Watch — NEVER `dotnet build` With Aspire Running ### Why `dotnet build` Causes File-Lock Errors When Aspire is running, it holds file locks on: - Output assemblies (`.dll`, `.pdb`) of managed resources - Build intermediates in `obj/` directories - NuGet package caches being read by running processes Running `dotnet build` while Aspire holds these locks produces: ``` error MSB3491: Could not write to output file 'bin/Debug/net10.0/MyService.dll' error CS2012: Cannot open 'obj/Debug/net10.0/MyService.dll' for writing ``` The agent then concludes the project is "un-buildable" — a false conclusion. ### Recovery If `dotnet build` Was Used and Locks Hit When you see `MSB3491` / `CS2012` / "file in use" / "another process is using": ```bash # ✅ Single correct recovery aspire stop # release all Aspire-held locks # Then EITHER use a resource-scoped command if Aspire is still up + one resource changed: aspire resource rebuild # OR: aspire start # if AppHost code changed or Aspire was fully stopped ``` > 🔒 **`aspire stop` is the ONLY first step.** Do not `pkill dotnet`, do not delete > `bin/`/`obj/`, do not "reboot to release the lock", and do not tell the user the > project has a permanent build failure. The cause is always the same — Aspire is > holding the output files — and the fix is always `aspire stop`. | ❌ NEVER (file-lock recovery) | ✅ ALWAYS | |------------------------------|----------| | Say the project has a permanent build failure when you see `MSB3491`/`CS2012` | Recognize Aspire is holding locks and run `aspire stop` | | `pkill dotnet` / `kill ` | `aspire stop` (clean shutdown via the CLI) | | `rm -rf bin obj` to "force" the build | `aspire stop`, then rebuild | | Suggest a reboot | `aspire stop` (single command) | | Re-run `dotnet build` with Aspire still up | `aspire stop` first; prefer resource commands/watch/HMR/debug workflow | ### What Changed Determines the Action | What Changed | Action | Command | |--------------|--------|---------| | AppHost project (Program.cs, .csproj) | Full restart | `aspire stop` → edit → `aspire start` | | .NET service project (.cs files) | Rebuild/refresh resource if exposed | `aspire resource rebuild` or the resource's IDE/watch workflow | | JavaScript/Python/Go files | Usually no Aspire action | File watchers/HMR handle it automatically | | Configuration (appsettings.json) | Check first | `aspire describe` then decide | --- ## Rule 4: Use `aspire stop` For Cleanup — NEVER Leave Unwanted Processes Running ### Why Cleanup Matters Aspire orchestrates multiple processes (your services, databases, message brokers, etc.). Leaving them running causes: - **Port conflicts** — next `aspire start` fails because ports are occupied - **File locks** — can't build or modify service code - **Resource consumption** — databases, Redis, etc. consuming memory - **Stale state** — old code running while you've made changes - **Orphaned containers** — Docker containers left running ### Correct Pattern ```bash # ✅ Stop when cleanup is requested or the user did not ask to keep it running aspire stop # ✅ Verify everything stopped aspire ps # should show no running resources ``` ### Recovery from Orphaned Processes ```bash # Check if anything is still running aspire ps # If aspire ps shows nothing but ports are blocked: # The previous instance may have crashed. Start fresh: aspire start # will clean up orphaned state ``` --- ## Rule 5: ALWAYS `--format Json` for Machine-Readable Output ### Why JSON Output Matters for Agents Text output is formatted for humans and may change between versions. JSON output is: - **Stable** — structured contract unlikely to break - **Parseable** — `jq`, Python, or any JSON parser works - **Complete** — includes fields not shown in text output ### Examples ```bash # ✅ Machine-readable resource list aspire ps --format Json # ✅ Get specific resource details aspire describe apiservice --format Json # ✅ Parse with jq aspire describe --format Json | jq '.resources[] | select(.state == "Running")' ``` ### ⚠️ Known JSON Output Issues | Issue | Workaround | |-------|-----------| | `aspire start --format json` may emit human-readable text before JSON ([#15843](https://github.com/microsoft/aspire/issues/15843)) | Strip non-JSON lines before parsing | | `aspire stop` does NOT support `--format json` yet | Use exit code for success/failure | | `aspire ps --format Json` returns `name` and `displayName` fields | Use `displayName` for `aspire wait` — the `name` field may be rejected ([#15842](https://github.com/microsoft/aspire/issues/15842)) | ### Hidden Resources and `--include-hidden` `aspire ps`, `aspire describe`, and other CLI commands **filter out resources marked as hidden in the AppHost** (proxies, helper containers, migration jobs, etc.). This filtering is correct for normal workflows — agents and humans see only the resources they care about, not the implementation scaffolding. Use `--include-hidden` when: | Situation | Why | |-----------|-----| | Debugging a proxy or sidecar | Proxies are hidden by default; you need their state to diagnose connectivity | | Investigating helper containers | Helper containers (e.g. wait-for-it shims, init containers) are hidden | | Tracking down migration jobs | Migration / seed jobs are typically hidden once they finish | | Expected resources are missing from `aspire ps` | The resource may exist but be marked hidden — confirm with `--include-hidden` before assuming the AppHost is wrong | | Parsing for completeness in agent automation | A full-graph view requires explicit opt-in | ```bash # ✅ Normal flow — filtered (correct for most tasks) aspire ps --format Json # ✅ Debugging / completeness — include hidden resources aspire ps --include-hidden --format Json aspire describe --include-hidden --format Json ``` If a user reports "I can't see my proxy / migration / helper container," reach for `--include-hidden` before assuming the AppHost is misconfigured. --- ## Rule 6: ALWAYS `--non-interactive` for Agent Commands ### Why It Matters AI agents run in non-interactive terminals. Some Aspire CLI commands may prompt for confirmation or input. Always pass `--non-interactive` to prevent hangs: ```bash # ✅ Agent-safe commands aspire start --non-interactive aspire deploy --non-interactive aspire agent init --non-interactive ``` > ⚠️ **Known issue**: `aspire agent init --non-interactive` is broken in some versions ([#16264](https://github.com/microsoft/aspire/issues/16264), [#15071](https://github.com/microsoft/aspire/issues/15071)). If it fails, instruct the user to run it from a standard terminal. --- ## Recovery Patterns Summary | Mistake Made | Recovery Steps | |-------------|---------------| | Used `dotnet run` on AppHost | Kill the process, run `aspire start` | | Used `dotnet build` and got file locks | `aspire stop`, wait 2s, then `dotnet build` or `aspire start` | | Used `curl` polling and got false results | `aspire wait `, then use endpoints from `aspire describe` | | Left Aspire running, now ports conflict | `aspire stop`, then `aspire start` | | Resource won't start after code change | Fix code, then use resource commands/watch/HMR/debug workflow or restart the AppHost if the AppHost model changed | | Nothing works, environment broken | `aspire doctor` to diagnose, then follow recommendations |