Spensara Spensara Knowledge Base & Documentation Repository

Repository Docs & Knowledge Base

Consolidated Spensara documentation repository (722 Markdown & architecture documents in ../repository)
Built Word Documents (.docx): Spensara Overview (.docx) Capability Assessment (.docx) Brink POS Ingestion Analysis (.docx) The Borg Ingestion Architecture (.docx)

Agent Tooling (71)

Automation workflows, orchestration references, and developer tooling guides.
agent-tooling/.agents/skills/aspire-orchestration/references/safety-guardrails.md 10,553 bytes · Updated Oct 23, 2026 10:44 AM EDT
Raw File

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 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

CommandModeDashboardUse Case
aspire runForeground (interactive)Yes, in terminalHuman developer at terminal
aspire startBackground (detached)No terminal outputAI agents — always prefer this
aspire run --detachBackground (same as start)Yes, separate windowAlternative 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 <name> 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/CS2012Recognize Aspire is holding locks and run aspire stop
pkill dotnet / kill <PID>aspire stop (clean shutdown via the CLI)
rm -rf bin obj to "force" the buildaspire stop, then rebuild
Suggest a rebootaspire stop (single command)
Re-run dotnet build with Aspire still upaspire stop first; prefer resource commands/watch/HMR/debug workflow

What Changed Determines the Action

What ChangedActionCommand
AppHost project (Program.cs, .csproj)Full restartaspire stop → edit → aspire start
.NET service project (.cs files)Rebuild/refresh resource if exposedaspire resource <name> rebuild or the resource's IDE/watch workflow
JavaScript/Python/Go filesUsually no Aspire actionFile watchers/HMR handle it automatically
Configuration (appsettings.json)Check firstaspire 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

IssueWorkaround
aspire start --format json may emit human-readable text before JSON (#15843)Strip non-JSON lines before parsing
aspire stop does NOT support --format json yetUse exit code for success/failure
aspire ps --format Json returns name and displayName fieldsUse displayName for aspire wait — the name field may be rejected (#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:

SituationWhy
Debugging a proxy or sidecarProxies are hidden by default; you need their state to diagnose connectivity
Investigating helper containersHelper containers (e.g. wait-for-it shims, init containers) are hidden
Tracking down migration jobsMigration / seed jobs are typically hidden once they finish
Expected resources are missing from aspire psThe resource may exist but be marked hidden — confirm with --include-hidden before assuming the AppHost is wrong
Parsing for completeness in agent automationA 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, #15071). If it fails, instruct the user to run it from a standard terminal.

Recovery Patterns Summary

Mistake MadeRecovery Steps
Used dotnet run on AppHostKill the process, run aspire start
Used dotnet build and got file locksaspire stop, wait 2s, then dotnet build or aspire start
Used curl polling and got false resultsaspire wait <resource>, then use endpoints from aspire describe
Left Aspire running, now ports conflictaspire stop, then aspire start
Resource won't start after code changeFix code, then use resource commands/watch/HMR/debug workflow or restart the AppHost if the AppHost model changed
Nothing works, environment brokenaspire doctor to diagnose, then follow recommendations