Summary
Add a read-only way for Aspire CLI and external tools to attach to an already-running deployment/pipeline execution and observe progress, using the auxiliary AppHost backchannel.
The target CLI experience is:
aspire do --watch
aspire do --watch --format json
--watch must not start a new pipeline execution. It should attach to the current pipeline execution for the selected running AppHost.
Motivation
Today, tools can show live deployment progress only if they launch aspire do <step> or aspire deploy themselves and parse the owning CLI process output. A viewer should be able to attach after a deployment has already started, similar to how existing commands attach to a running AppHost for resources/logs.
Existing attach-style commands already use the auxiliary backchannel and AppHost discovery machinery. Deployment/pipeline execution currently uses a private CLI-owned publish backchannel, so it does not provide multi-client discovery, replay, or current execution state for late observers.
Proposed command behavior
Attach to the active pipeline execution for the selected AppHost. If there is no active execution, report that none is running.
For v1, assume a single active pipeline execution per AppHost. Running parallel deployments for the same project/AppHost is not a primary scenario, and --watch should not require users to understand or pass an execution ID.
If the AppHost ever reports multiple active executions, the CLI should treat that as an ambiguity/error in v1 rather than designing the main UX around that case.
aspire do --watch --format json
Emit a snapshot-first NDJSON event stream suitable for tools and live pipeline viewers.
Useful optional flags:
--apphost <path> # select the running AppHost, matching existing attach commands
--since <sequence> # resume from a known event sequence
--replay all|none|tail # default: all available replay
--tail <count> # when replay=tail
Required data model
Add an AppHost-side pipeline execution registry exposed over the auxiliary backchannel behind a new capability, for example pipeline-observe.v1.
Each AppHost should expose its current active pipeline execution, if any. Internally, the AppHost may still use a stable execution identifier/correlation ID for event envelopes, tests, logging, and future-proofing, but the v1 CLI should not require or center the UX around passing that ID.
Each execution should have:
- Internal correlation/execution ID
- Operation (
publish, deploy, destroy, do)
- Target step, if any
- Started/completed timestamps
- Current status
- Step graph snapshot
- Current step/task status maps
- Redacted input/resource metadata
- Bounded replay buffer with monotonic sequence numbers
- Multi-observer fan-out for live events
Attach clients are observers only. They must not answer prompts, own process lifetime, or keep the AppHost alive after the owning CLI exits.
JSON/NDJSON stream shape
aspire do --watch --format json should emit one JSON object per line.
Every object should use a stable envelope:
{
"schema": "aspire.pipeline.event.v1",
"sequence": 42,
"timestamp": "2026-05-10T06:02:14.392Z",
"execution": {
"operation": "deploy",
"targetStep": "deploy"
},
"event": {
"type": "step",
"id": "deploy-api",
"status": "running"
}
}
The first event from watch should be a full snapshot:
{
"schema": "aspire.pipeline.event.v1",
"sequence": 128,
"timestamp": "2026-05-10T06:02:10.000Z",
"execution": {
"operation": "deploy",
"targetStep": "deploy",
"status": "running",
"startedAt": "2026-05-10T06:01:50.000Z",
"completedAt": null
},
"event": {
"type": "snapshot",
"replay": {
"firstSequence": 94,
"lastSequence": 128,
"isTruncated": false
},
"steps": [
{
"id": "build-api",
"name": "Build api",
"resourceName": "api",
"dependsOn": [],
"status": "succeeded",
"startedAt": "2026-05-10T06:01:51.000Z",
"completedAt": "2026-05-10T06:01:58.000Z",
"message": "Build completed"
}
],
"tasks": [],
"inputs": [],
"resources": []
}
}
Expected event types:
snapshot
step
task
log
prompt — observe only; observerCanRespond should be false
complete
replay-truncated
Suggested normalized statuses:
pending
running
succeeded
warning
failed
skipped
cancelled
unknown
Example human output
For an interactive terminal, aspire do --watch should render a human-readable live progress view similar to the owning deployment command, but clearly in observer mode:
Watching active pipeline execution for AppHost: MyApp.AppHost
Operation: deploy
Target step: deploy
✓ Build api
✓ Build web
⠋ Deploy api
• Pushing container image
• Updating Azure Container App
○ Deploy web
○ Publish outputs
[12:02:16] [api] Pushed image myregistry.azurecr.io/api:latest
[12:02:19] [api] Updating revision...
When the pipeline completes:
Watching active pipeline execution for AppHost: MyApp.AppHost
Operation: deploy
Target step: deploy
✓ Build api
✓ Build web
✓ Deploy api
✓ Deploy web
✓ Publish outputs
Deployment completed in 1m 15s.
If no pipeline is running:
No active pipeline execution found for AppHost: MyApp.AppHost.
If multiple active executions are ever reported by the AppHost, v1 should fail clearly rather than requiring users to choose an execution ID:
Multiple active pipeline executions were reported for AppHost: MyApp.AppHost. Watching multiple executions is not supported yet.
Example JSON stream output
For tools, aspire do --watch --format json should emit snapshot-first NDJSON. The first line gives the viewer enough information to build the graph immediately:
{"schema":"aspire.pipeline.event.v1","sequence":12,"timestamp":"2026-05-10T06:02:10.000Z","execution":{"operation":"deploy","targetStep":"deploy","status":"running","startedAt":"2026-05-10T06:01:50.000Z","completedAt":null},"event":{"type":"snapshot","replay":{"firstSequence":1,"lastSequence":12,"isTruncated":false},"steps":[{"id":"build-api","name":"Build api","resourceName":"api","dependsOn":[],"status":"succeeded","startedAt":"2026-05-10T06:01:51.000Z","completedAt":"2026-05-10T06:01:58.000Z","message":"Build completed"},{"id":"deploy-api","name":"Deploy api","resourceName":"api","dependsOn":["build-api"],"status":"running","startedAt":"2026-05-10T06:01:59.000Z","completedAt":null,"message":"Deploying api"}],"tasks":[{"id":"deploy-api.push-image","stepId":"deploy-api","name":"Push container image","status":"running","message":"Pushing image"}],"inputs":[{"name":"subscriptionId","kind":"parameter","required":true,"hasValue":true,"isSecret":false,"value":"00000000-0000-0000-0000-000000000000"},{"name":"databasePassword","kind":"parameter","required":true,"hasValue":true,"isSecret":true,"value":null}],"resources":[{"name":"api","type":"project.v0"}]}}
Subsequent lines are incremental updates:
{"schema":"aspire.pipeline.event.v1","sequence":13,"timestamp":"2026-05-10T06:02:14.392Z","execution":{"operation":"deploy","targetStep":"deploy"},"event":{"type":"step","id":"deploy-api","name":"Deploy api","resourceName":"api","status":"running","message":"Deploying api"}}
{"schema":"aspire.pipeline.event.v1","sequence":14,"timestamp":"2026-05-10T06:02:15.102Z","execution":{"operation":"deploy","targetStep":"deploy"},"event":{"type":"task","id":"deploy-api.push-image","stepId":"deploy-api","name":"Push container image","status":"running","message":"Pushing image"}}
{"schema":"aspire.pipeline.event.v1","sequence":15,"timestamp":"2026-05-10T06:02:16.500Z","execution":{"operation":"deploy","targetStep":"deploy"},"event":{"type":"log","stepId":"deploy-api","taskId":"deploy-api.push-image","level":"information","message":"Pushed image myregistry.azurecr.io/api:latest","category":"Aspire.Hosting.Publishing"}}
{"schema":"aspire.pipeline.event.v1","sequence":16,"timestamp":"2026-05-10T06:03:05.000Z","execution":{"operation":"deploy","targetStep":"deploy","status":"succeeded","startedAt":"2026-05-10T06:01:50.000Z","completedAt":"2026-05-10T06:03:05.000Z"},"event":{"type":"complete","status":"succeeded","message":"Deployment completed","durationMs":75000}}
This shape lets a viewer build the graph from snapshot.steps[].dependsOn, update nodes from step events, update sub-operations from task events, attach log lines to the relevant step/task, and finish when it receives complete.
Tools this enables
This feature is intended to make external deployment observers possible without requiring those tools to spawn and own aspire do or parse terminal output.
Examples:
- A dedicated live pipeline viewer, such as
tjwald/aspire-pipeline-viewer, can attach to a deployment that is already running and render the step graph/statuses directly from structured events.
- IDE extensions can show deployment progress in a tree/graph view while the deployment continues in another terminal.
- Dashboard or local developer tools can display deployment status alongside resource/log views.
- CI or automation wrappers can consume the NDJSON stream and produce richer progress annotations without becoming the process that starts the deployment.
Implementation notes
Likely files/classes involved:
src/Aspire.Hosting/Pipelines/PipelineActivityReporter.cs
src/Aspire.Hosting/Publishing/PipelineExecutor.cs
src/Aspire.Hosting/Backchannel/AuxiliaryBackchannelRpcTarget.cs
src/Aspire.Hosting/Backchannel/BackchannelDataTypes.cs
src/Aspire.Hosting/DistributedApplicationBuilder.cs
src/Aspire.Hosting/Backchannel/BackchannelLoggerProvider.cs as a replay/fan-out pattern to copy
src/Aspire.Cli/Backchannel/IAppHostAuxiliaryBackchannel.cs
src/Aspire.Cli/Backchannel/AppHostAuxiliaryBackchannel.cs
src/Aspire.Cli/Backchannel/BackchannelJsonSerializerContext.cs
src/Aspire.Cli/Commands/DoCommand.cs
src/Aspire.Cli/Commands/PipelineCommandBase.cs
Tests should cover:
- Execution registry snapshot/replay/fan-out
- Status normalization
- Replay truncation behavior
- Auxiliary RPC capability and watch behavior
- Serializer context coverage
aspire do --watch attach behavior
- JSON/NDJSON output shape
- No-active-execution behavior
- Multiple-active-execution ambiguity/error behavior, if the server can report that state
Compatibility and lifecycle constraints
- Follow the backchannel compatibility guidance in
docs/specs/cli-backchannel.md.
- Use additive request/response DTOs and capability negotiation.
- Preserve old/new CLI-AppHost compatibility.
- Do not expose secret input values.
- Do not make attach clients owners of the deployment process.
- Do not replay prompts as actionable prompts.
- Bounded replay must report truncation so viewers can show partial history honestly.
Summary
Add a read-only way for Aspire CLI and external tools to attach to an already-running deployment/pipeline execution and observe progress, using the auxiliary AppHost backchannel.
The target CLI experience is:
--watchmust not start a new pipeline execution. It should attach to the current pipeline execution for the selected running AppHost.Motivation
Today, tools can show live deployment progress only if they launch
aspire do <step>oraspire deploythemselves and parse the owning CLI process output. A viewer should be able to attach after a deployment has already started, similar to how existing commands attach to a running AppHost for resources/logs.Existing attach-style commands already use the auxiliary backchannel and AppHost discovery machinery. Deployment/pipeline execution currently uses a private CLI-owned publish backchannel, so it does not provide multi-client discovery, replay, or current execution state for late observers.
Proposed command behavior
aspire do --watchAttach to the active pipeline execution for the selected AppHost. If there is no active execution, report that none is running.
For v1, assume a single active pipeline execution per AppHost. Running parallel deployments for the same project/AppHost is not a primary scenario, and
--watchshould not require users to understand or pass an execution ID.If the AppHost ever reports multiple active executions, the CLI should treat that as an ambiguity/error in v1 rather than designing the main UX around that case.
aspire do --watch --format jsonEmit a snapshot-first NDJSON event stream suitable for tools and live pipeline viewers.
Useful optional flags:
Required data model
Add an AppHost-side pipeline execution registry exposed over the auxiliary backchannel behind a new capability, for example
pipeline-observe.v1.Each AppHost should expose its current active pipeline execution, if any. Internally, the AppHost may still use a stable execution identifier/correlation ID for event envelopes, tests, logging, and future-proofing, but the v1 CLI should not require or center the UX around passing that ID.
Each execution should have:
publish,deploy,destroy,do)Attach clients are observers only. They must not answer prompts, own process lifetime, or keep the AppHost alive after the owning CLI exits.
JSON/NDJSON stream shape
aspire do --watch --format jsonshould emit one JSON object per line.Every object should use a stable envelope:
{ "schema": "aspire.pipeline.event.v1", "sequence": 42, "timestamp": "2026-05-10T06:02:14.392Z", "execution": { "operation": "deploy", "targetStep": "deploy" }, "event": { "type": "step", "id": "deploy-api", "status": "running" } }The first event from
watchshould be a full snapshot:{ "schema": "aspire.pipeline.event.v1", "sequence": 128, "timestamp": "2026-05-10T06:02:10.000Z", "execution": { "operation": "deploy", "targetStep": "deploy", "status": "running", "startedAt": "2026-05-10T06:01:50.000Z", "completedAt": null }, "event": { "type": "snapshot", "replay": { "firstSequence": 94, "lastSequence": 128, "isTruncated": false }, "steps": [ { "id": "build-api", "name": "Build api", "resourceName": "api", "dependsOn": [], "status": "succeeded", "startedAt": "2026-05-10T06:01:51.000Z", "completedAt": "2026-05-10T06:01:58.000Z", "message": "Build completed" } ], "tasks": [], "inputs": [], "resources": [] } }Expected event types:
snapshotsteptasklogprompt— observe only;observerCanRespondshould be falsecompletereplay-truncatedSuggested normalized statuses:
pendingrunningsucceededwarningfailedskippedcancelledunknownExample human output
For an interactive terminal,
aspire do --watchshould render a human-readable live progress view similar to the owning deployment command, but clearly in observer mode:When the pipeline completes:
If no pipeline is running:
If multiple active executions are ever reported by the AppHost, v1 should fail clearly rather than requiring users to choose an execution ID:
Example JSON stream output
For tools,
aspire do --watch --format jsonshould emit snapshot-first NDJSON. The first line gives the viewer enough information to build the graph immediately:{"schema":"aspire.pipeline.event.v1","sequence":12,"timestamp":"2026-05-10T06:02:10.000Z","execution":{"operation":"deploy","targetStep":"deploy","status":"running","startedAt":"2026-05-10T06:01:50.000Z","completedAt":null},"event":{"type":"snapshot","replay":{"firstSequence":1,"lastSequence":12,"isTruncated":false},"steps":[{"id":"build-api","name":"Build api","resourceName":"api","dependsOn":[],"status":"succeeded","startedAt":"2026-05-10T06:01:51.000Z","completedAt":"2026-05-10T06:01:58.000Z","message":"Build completed"},{"id":"deploy-api","name":"Deploy api","resourceName":"api","dependsOn":["build-api"],"status":"running","startedAt":"2026-05-10T06:01:59.000Z","completedAt":null,"message":"Deploying api"}],"tasks":[{"id":"deploy-api.push-image","stepId":"deploy-api","name":"Push container image","status":"running","message":"Pushing image"}],"inputs":[{"name":"subscriptionId","kind":"parameter","required":true,"hasValue":true,"isSecret":false,"value":"00000000-0000-0000-0000-000000000000"},{"name":"databasePassword","kind":"parameter","required":true,"hasValue":true,"isSecret":true,"value":null}],"resources":[{"name":"api","type":"project.v0"}]}}Subsequent lines are incremental updates:
{"schema":"aspire.pipeline.event.v1","sequence":13,"timestamp":"2026-05-10T06:02:14.392Z","execution":{"operation":"deploy","targetStep":"deploy"},"event":{"type":"step","id":"deploy-api","name":"Deploy api","resourceName":"api","status":"running","message":"Deploying api"}} {"schema":"aspire.pipeline.event.v1","sequence":14,"timestamp":"2026-05-10T06:02:15.102Z","execution":{"operation":"deploy","targetStep":"deploy"},"event":{"type":"task","id":"deploy-api.push-image","stepId":"deploy-api","name":"Push container image","status":"running","message":"Pushing image"}} {"schema":"aspire.pipeline.event.v1","sequence":15,"timestamp":"2026-05-10T06:02:16.500Z","execution":{"operation":"deploy","targetStep":"deploy"},"event":{"type":"log","stepId":"deploy-api","taskId":"deploy-api.push-image","level":"information","message":"Pushed image myregistry.azurecr.io/api:latest","category":"Aspire.Hosting.Publishing"}} {"schema":"aspire.pipeline.event.v1","sequence":16,"timestamp":"2026-05-10T06:03:05.000Z","execution":{"operation":"deploy","targetStep":"deploy","status":"succeeded","startedAt":"2026-05-10T06:01:50.000Z","completedAt":"2026-05-10T06:03:05.000Z"},"event":{"type":"complete","status":"succeeded","message":"Deployment completed","durationMs":75000}}This shape lets a viewer build the graph from
snapshot.steps[].dependsOn, update nodes fromstepevents, update sub-operations fromtaskevents, attach log lines to the relevant step/task, and finish when it receivescomplete.Tools this enables
This feature is intended to make external deployment observers possible without requiring those tools to spawn and own
aspire door parse terminal output.Examples:
tjwald/aspire-pipeline-viewer, can attach to a deployment that is already running and render the step graph/statuses directly from structured events.Implementation notes
Likely files/classes involved:
src/Aspire.Hosting/Pipelines/PipelineActivityReporter.cssrc/Aspire.Hosting/Publishing/PipelineExecutor.cssrc/Aspire.Hosting/Backchannel/AuxiliaryBackchannelRpcTarget.cssrc/Aspire.Hosting/Backchannel/BackchannelDataTypes.cssrc/Aspire.Hosting/DistributedApplicationBuilder.cssrc/Aspire.Hosting/Backchannel/BackchannelLoggerProvider.csas a replay/fan-out pattern to copysrc/Aspire.Cli/Backchannel/IAppHostAuxiliaryBackchannel.cssrc/Aspire.Cli/Backchannel/AppHostAuxiliaryBackchannel.cssrc/Aspire.Cli/Backchannel/BackchannelJsonSerializerContext.cssrc/Aspire.Cli/Commands/DoCommand.cssrc/Aspire.Cli/Commands/PipelineCommandBase.csTests should cover:
aspire do --watchattach behaviorCompatibility and lifecycle constraints
docs/specs/cli-backchannel.md.