Build an Agent UI That Explains Its State with Angular Signals A developer outlined a pattern for building agent user interfaces in Angular that model runtime states explicitly rather than collapsing them into a single loading spinner. The approach derives UI state from an observable event stream using Angular signals and computed(), distinguishing states such as planning, using a tool, awaiting approval, recovering, blocked, completed, and failed, while guarding against stale events from cancelled runs via run IDs. Most chat interfaces have three states: empty, loading, and finished. Agent workflows have many more. An agent may be planning, waiting for a tool, requesting approval, retrying a recoverable error, or suppressing an unsafe action. If the UI represents all of those as one spinner, users cannot tell whether the system is working, blocked, or about to change something. The better model is not “messages plus loading.” It is an explicit state machine. Start with observable runtime states—not the model's hidden reasoning: type AgentUiState = | { kind: "idle" } | { kind: "planning" } | { kind: "using tool"; tool: string } | { kind: "waiting for approval"; proposalId: string } | { kind: "recovering"; attempt: number } | { kind: "blocked"; reasonCode: string } | { kind: "completed"; outcome: string } | { kind: "failed"; message: string }; This vocabulary should come from actual runtime events. Do not fabricate a “thinking” narrative that implies access to private chain-of-thought. Angular signals work well when the event stream is the source of truth and presentation is derived from it: js import { computed, signal } from "@angular/core"; type AgentEvent = { type: "run started" | "tool started" | "approval required" | "retry started" | "run completed" | "run failed"; tool?: string; proposalId?: string; attempt?: number; outcome?: string; message?: string; }; const events = signal