Trace Patterns Since v4.1.0
Agent activity patterns assembled from Trace and Angular Material, shown in an assistant message as a thread would render them.
None of these patterns is a component. Each one is Trace rows with application markup: icons and statuses mapped from your agent's state, with code blocks, chips or alerts where the detail calls for them. Copy the markup and bind it to your own data.
Agent Steps
One collapsible per reasoning step, with the step number in the icon slot. While the model streams, the last line takes running.
<div buiTrace>
@for (step of steps(); track step.id; let index = $index, last = $last) {
<div buiTraceCollapsible>
<button buiTraceCollapsibleTrigger>
<span buiTraceLine [status]="last && streaming() ? 'running' : undefined">
<span buiTraceIcon class="text-xs tabular-nums">{{ index + 1 }}</span>
<span buiTraceTitle>{{ step.title }}</span>
</span>
</button>
<ng-template buiTraceCollapsiblePanel>
<div buiTraceCollapsibleContent>{{ step.details }}</div>
</ng-template>
</div>
}
</div>Checkpoint
A row after an assistant turn marks a point to return to: a bookmark line and a tiny Restore button between two dividers. Restore drops every message after that turn.
@for (message of messages(); track message.id; let index = $index) {
<div buiMessage [align]="message.from === 'user' ? 'end' : 'start'">
<div buiMessageContent>
@if (message.from === 'user') {
<div buiMessageBubble>{{ message.text }}</div>
} @else {
{{ message.text }}
}
</div>
</div>
@if (message.checkpoint) {
<div class="flex items-center gap-3">
<mat-divider class="flex-1" />
<span buiTraceLine>
<mat-icon buiTraceIcon svgIcon="bookmark" />
<span buiTraceTitle>{{ message.checkpoint }}</span>
</span>
<button matButton class="tiny" (click)="restore(index)">Restore</button>
<mat-divider class="flex-1" />
</div>
}
}Citations
A collapsible line counts the sources an answer drew on. Each source is a link wrapping a line, with a globe icon and the domain as the description.
<div buiTrace>
<div buiTraceCollapsible>
<button buiTraceCollapsibleTrigger>
<span buiTraceLine>
<mat-icon buiTraceIcon svgIcon="book-open" />
<span buiTraceTitle>Used {{ sources().length }} sources</span>
</span>
</button>
<ng-template buiTraceCollapsiblePanel>
<div buiTraceCollapsibleContent>
<div buiTrace>
@for (source of sources(); track source.url) {
<a target="_blank" [href]="source.url">
<span buiTraceLine>
<mat-icon buiTraceIcon svgIcon="globe" />
<span buiTraceTitle>{{ source.title }}</span>
<span buiTraceDescription>{{ source.domain }}</span>
</span>
</a>
}
</div>
</div>
</ng-template>
</div>
</div>Command Output
A collapsible line with the command as a monospace title and the exit code as the description. The output streams into a code block inside.
<div buiTrace>
<div buiTraceCollapsible [expanded]="true">
<button buiTraceCollapsibleTrigger>
<span buiTraceLine [status]="running() ? 'running' : undefined">
<mat-icon buiTraceIcon svgIcon="square-terminal" />
<span buiTraceTitle class="font-mono text-base">{{ command }}</span>
@if (!running()) {
<span buiTraceDescription>exit {{ exitCode() }}</span>
}
</span>
</button>
<ng-template buiTraceCollapsiblePanel>
<div buiTraceCollapsibleContent>
<div buiCodeBlock>
<pre buiCodeBlockContent lang="ansi" [code]="output()"></pre>
</div>
</div>
</ng-template>
</div>
</div>Confirmation
An alert outside the trace asks before a tool runs, with the question, the input and Approve and Deny buttons. The answer replaces it with a tool call row.
<div buiTrace>
@if (answer() === 'approved') {
<span buiTraceLine>
<mat-icon buiTraceIcon svgIcon="check" />
<span buiTraceTitle>Applied migration 0042</span>
</span>
} @else if (answer() === 'denied') {
<span buiTraceLine status="pending">
<mat-icon buiTraceIcon svgIcon="ban" />
<span buiTraceTitle>Migration 0042 denied</span>
</span>
}
</div>
@if (!answer()) {
<div buiAlert>
<mat-icon buiAlertIcon svgIcon="database" />
<div buiAlertContent>
<div buiAlertTitle>Apply migration 0042 to staging?</div>
<div buiAlertDescription>Touches 2 tables and 14,207 rows.</div>
<div buiAlertActions>
<button matButton class="primary small" (click)="answer.set('approved')">
Approve
</button>
<button matButton class="small" (click)="answer.set('denied')">
Deny
</button>
</div>
</div>
</div>
}Delegated Agent
Work handed to a subagent is one collapsible line whose content is a nested trace of the subagent's own tool calls.
<div buiTrace>
<div buiTraceCollapsible>
<button buiTraceCollapsibleTrigger>
<span buiTraceLine>
<mat-icon buiTraceIcon svgIcon="bot" />
<span buiTraceTitle>Research agent</span>
<span buiTraceDescription>{{ calls().length }} tool calls</span>
</span>
</button>
<ng-template buiTraceCollapsiblePanel>
<div buiTraceCollapsibleContent>
<div buiTrace>
@for (call of calls(); track call.id) {
<span buiTraceLine>
<mat-icon buiTraceIcon [svgIcon]="call.icon" />
<span buiTraceTitle>{{ call.title }}</span>
<span buiTraceDescription>{{ call.description }}</span>
</span>
}
</div>
</div>
</ng-template>
</div>
</div>Error Details
A collapsible error line holds the stack, either as a code block or as one line per frame with the location as the description.
<div buiTrace>
<div buiTraceCollapsible>
<button buiTraceCollapsibleTrigger>
<span buiTraceLine status="error">
<mat-icon buiTraceIcon svgIcon="bug" />
<span buiTraceTitle>{{ error().message }}</span>
<span buiTraceDescription>{{ error().frames.length }} frames</span>
</span>
</button>
<ng-template buiTraceCollapsiblePanel>
<div buiTraceCollapsibleContent>
<div buiTrace>
@for (frame of error().frames; track frame.location) {
<span buiTraceLine>
<mat-icon buiTraceIcon svgIcon="file-code" />
<span buiTraceTitle class="font-mono text-base">{{ frame.name }}</span>
<span buiTraceDescription>{{ frame.location }}</span>
</span>
}
</div>
</div>
</ng-template>
</div>
</div>File Changes
A collapsible line with the progress as the description and a chip set of the touched files inside.
<div buiTrace>
<div buiTraceCollapsible>
<button buiTraceCollapsibleTrigger>
<span buiTraceLine>
<mat-icon buiTraceIcon svgIcon="check" />
<span buiTraceTitle>Updated the billing templates</span>
<span buiTraceDescription>{{ done() }} of {{ files().length }} files</span>
</span>
</button>
<ng-template buiTraceCollapsiblePanel>
<div buiTraceCollapsibleContent>
<mat-chip-set>
@for (file of files(); track file) {
<mat-chip>{{ file }}</mat-chip>
}
</mat-chip-set>
</div>
</ng-template>
</div>
</div>Job Queue
A collapsible line with the waiting count as the description, and the jobs inside as done, running and pending lines.
<div buiTrace>
<div buiTraceCollapsible>
<button buiTraceCollapsibleTrigger>
<span buiTraceLine>
<mat-icon buiTraceIcon svgIcon="list-checks" />
<span buiTraceTitle>Billing queue</span>
<span buiTraceDescription>{{ queued() }} queued</span>
</span>
</button>
<ng-template buiTraceCollapsiblePanel>
<div buiTraceCollapsibleContent>
<div buiTrace>
<span buiTraceLine>
<mat-icon buiTraceIcon svgIcon="check" />
<span buiTraceTitle>Send the reminder for INV-1042</span>
</span>
<span buiTraceLine status="running">
<mat-progress-spinner buiTraceIcon mode="indeterminate" diameter="16" />
<span buiTraceTitle>Generate the October statement</span>
</span>
<span buiTraceLine status="pending">
<mat-icon buiTraceIcon svgIcon="clock" />
<span buiTraceTitle>Sync payments with the ledger</span>
</span>
</div>
</div>
</ng-template>
</div>
</div>Run Plan
One line per step: a check for done, a spinner with running, a circle with pending, and a collapsible error line that explains a failure.
<div buiTrace>
<span buiTraceLine>
<mat-icon buiTraceIcon svgIcon="check" />
<span buiTraceTitle>Export August invoices to CSV</span>
</span>
<div buiTraceCollapsible>
<button buiTraceCollapsibleTrigger>
<span buiTraceLine status="error">
<mat-icon buiTraceIcon svgIcon="circle-x" />
<span buiTraceTitle>Upload the export</span>
<span buiTraceDescription>2 attempts</span>
</span>
</button>
<ng-template buiTraceCollapsiblePanel>
<div buiTraceCollapsibleContent>The API key expired on September 30.</div>
</ng-template>
</div>
<span buiTraceLine status="running">
<mat-progress-spinner buiTraceIcon mode="indeterminate" diameter="16" />
<span buiTraceTitle>Save the export to the shared drive</span>
</span>
<span buiTraceLine status="pending">
<mat-icon buiTraceIcon svgIcon="circle" />
<span buiTraceTitle>Email the finance team a link</span>
</span>
</div>Test Run
One collapsible line per suite with its pass and fail counts. Failing tests are nested collapsible error lines with the assertion in a code block.
<div buiTrace>
@for (suite of suites(); track suite.name) {
<div buiTraceCollapsible [expanded]="suite.failed > 0">
<button buiTraceCollapsibleTrigger>
<span buiTraceLine [status]="suite.failed ? 'error' : undefined">
<mat-icon buiTraceIcon [svgIcon]="suite.failed ? 'circle-x' : 'circle-check'" />
<span buiTraceTitle class="font-mono text-base">{{ suite.name }}</span>
<span buiTraceDescription>{{ suite.failed }} failed, {{ suite.passed }} passed</span>
</span>
</button>
<ng-template buiTraceCollapsiblePanel>
<div buiTraceCollapsibleContent>
<div buiTrace>
@for (test of suite.tests; track test.name) {
@if (test.failure) {
<div buiTraceCollapsible [expanded]="true">
<button buiTraceCollapsibleTrigger>
<span buiTraceLine status="error">
<mat-icon buiTraceIcon svgIcon="x" />
<span buiTraceTitle>{{ test.name }}</span>
</span>
</button>
<ng-template buiTraceCollapsiblePanel>
<div buiTraceCollapsibleContent>
<div buiCodeBlock>
<pre buiCodeBlockContent [code]="test.failure"></pre>
</div>
</div>
</ng-template>
</div>
} @else {
<span buiTraceLine>
<mat-icon buiTraceIcon svgIcon="check" />
<span buiTraceTitle>{{ test.name }}</span>
</span>
}
}
</div>
</div>
</ng-template>
</div>
}
</div>Tool Call
The state of the tool part maps to the line's title, icon and status, and the call's input and output go in the content as code blocks.
<div buiTrace>
<div buiTraceCollapsible>
<button buiTraceCollapsibleTrigger>
<span buiTraceLine [status]="parts[call.state].status">
@if (call.state === 'running') {
<mat-progress-spinner buiTraceIcon mode="indeterminate" diameter="16" />
} @else {
<mat-icon buiTraceIcon [svgIcon]="parts[call.state].icon" />
}
<span buiTraceTitle>{{ parts[call.state].title }}</span>
</span>
</button>
<ng-template buiTraceCollapsiblePanel>
<div buiTraceCollapsibleContent>
<div buiCodeBlock>
<pre buiCodeBlockContent lang="json" [code]="call.input"></pre>
</div>
<div buiCodeBlock>
<pre buiCodeBlockContent lang="json" [code]="call.output"></pre>
</div>
</div>
</ng-template>
</div>
</div>Keep the mapping from state to row in one place.
import { TraceStatus } from '@/bui/ui/trace';
type ToolState = 'running' | 'done' | 'failed' | 'denied';
const parts: Record<ToolState, { title: string; icon: string; status?: TraceStatus }> = {
running: { title: 'Applying migration 0042', icon: 'database', status: 'running' },
done: { title: 'Applied migration 0042', icon: 'check' },
failed: { title: 'Migration 0042 failed', icon: 'circle-x', status: 'error' },
denied: { title: 'Migration 0042 denied', icon: 'ban', status: 'pending' },
};