diff --git a/frontend/src/lib/components/AgentResultDisplay.svelte b/frontend/src/lib/components/AgentResultDisplay.svelte
index 80df9d530a..935c32ebe9 100644
--- a/frontend/src/lib/components/AgentResultDisplay.svelte
+++ b/frontend/src/lib/components/AgentResultDisplay.svelte
@@ -2,13 +2,13 @@
import type { Snippet } from 'svelte'
import { Badge } from '$lib/components/common'
import GfmMarkdown from './GfmMarkdown.svelte'
- import AgentActions from './AgentActions.svelte'
+ import AgentTrace from './AgentTrace.svelte'
import { formatTokenCount, summarizeAgentResult, type AgentResult } from './aiAgentResult'
interface Props {
result: AgentResult
/** The answer, or what the agent did to get there; JSON is the viewer's own toggle. */
- view: 'answer' | 'actions'
+ view: 'answer' | 'trace'
workspaceId?: string
/**
* How to render an answer that is not text. An `output_schema` makes `output`
@@ -27,8 +27,8 @@
- {#if view === 'actions'}
-
+ {#if view === 'trace'}
+
{:else if textOutput !== undefined}
{#if textOutput === ''}
The agent returned no answer
diff --git a/frontend/src/lib/components/AgentActions.svelte b/frontend/src/lib/components/AgentTrace.svelte
similarity index 93%
rename from frontend/src/lib/components/AgentActions.svelte
rename to frontend/src/lib/components/AgentTrace.svelte
index 3ff1be8ea2..4724f2b056 100644
--- a/frontend/src/lib/components/AgentActions.svelte
+++ b/frontend/src/lib/components/AgentTrace.svelte
@@ -8,7 +8,7 @@
import ToolContentDisplay from './copilot/chat/ToolContentDisplay.svelte'
import WebSearchSourcesDisplay from './copilot/chat/WebSearchSourcesDisplay.svelte'
import GfmMarkdown from './GfmMarkdown.svelte'
- import { buildAgentActions, type AgentActionEntry } from './agentActions'
+ import { buildAgentTrace, type AgentTraceEntry } from './agentTrace'
import type { AgentMessage } from './aiAgentResult'
import { SvelteMap, SvelteSet } from 'svelte/reactivity'
@@ -19,7 +19,7 @@
let { messages, workspaceId }: Props = $props()
- const entries = $derived(buildAgentActions(messages))
+ const entries = $derived(buildAgentTrace(messages))
let expanded = new SvelteSet
()
// A tool's own job holds what the envelope does not: its logs, how long it
@@ -45,7 +45,7 @@
}
}
- function toggle(index: number, entry: AgentActionEntry) {
+ function toggle(index: number, entry: AgentTraceEntry) {
if (expanded.delete(index)) {
return
}
@@ -61,7 +61,7 @@
return typeof job === 'object' ? job : undefined
}
- function toolLabel(entry: Extract): string {
+ function toolLabel(entry: Extract): string {
const job = jobOf(entry.jobId)
const duration = job?.['duration_ms']
return duration === undefined
diff --git a/frontend/src/lib/components/DisplayResult.svelte b/frontend/src/lib/components/DisplayResult.svelte
index ed1bb9664d..229d1d442c 100644
--- a/frontend/src/lib/components/DisplayResult.svelte
+++ b/frontend/src/lib/components/DisplayResult.svelte
@@ -58,7 +58,7 @@
import type { MarkupTrust } from './apps/markupTrust'
import AgentResultDisplay from './AgentResultDisplay.svelte'
import AgentStreamDisplay from './AgentStreamDisplay.svelte'
- import AgentActions from './AgentActions.svelte'
+ import AgentTrace from './AgentTrace.svelte'
import { isAgentStream, parseAgentErrorMessages, parseAgentResult } from './aiAgentResult'
const TABLE_MAX_SIZE = 5000000
@@ -165,7 +165,7 @@
}: Props = $props()
let s3FileDisplayRawMode = $state(false)
/** Which half of an agent result is showing; JSON is `forceJson`, as for any kind. */
- let agentView: 'answer' | 'actions' = $state('answer')
+ let agentView: 'answer' | 'trace' = $state('answer')
/** The partial conversation a max-iterations failure carries, if this is one. */
let agentErrorMessages = $derived(parseAgentErrorMessages(result))
@@ -308,7 +308,7 @@
}
// Classified before the size caps below: an agent's answer stays small
- // however long its conversation grows, so a run with many actions
+ // however long its conversation grows, so a run with a long trace
// must not fall back to the JSON tree that hides the answer inside it.
// `largeObject` is still set honestly, so switching to JSON gets the
// same too-big handling as any other oversized result.
@@ -818,15 +818,15 @@
{#if !hideAsJson && !['json', 's3object'].includes(resultKind ?? '') && typeof result === 'object'} {
forceJson = ev.detail === 'json'
- if (ev.detail === 'actions' || ev.detail === 'pretty') {
- agentView = ev.detail === 'actions' ? 'actions' : 'answer'
+ if (ev.detail === 'trace' || ev.detail === 'pretty') {
+ agentView = ev.detail === 'trace' ? 'trace' : 'answer'
}
}}
>
@@ -835,7 +835,7 @@
{:else if resultKind === 'aiagent'}
-
+
{:else}
{/if}
@@ -1022,8 +1022,8 @@
whole reason to look at such a run, so it is added under the error
rather than replacing it. -->
{/if}
{#if !isTest && language === 'bun'}
diff --git a/frontend/src/lib/components/agentActions.test.ts b/frontend/src/lib/components/agentTrace.test.ts
similarity index 91%
rename from frontend/src/lib/components/agentActions.test.ts
rename to frontend/src/lib/components/agentTrace.test.ts
index 66eed1d91d..4d63666736 100644
--- a/frontend/src/lib/components/agentActions.test.ts
+++ b/frontend/src/lib/components/agentTrace.test.ts
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'
-import { buildAgentActions } from './agentActions'
+import { buildAgentTrace } from './agentTrace'
import type { AgentMessage } from './aiAgentResult'
// The worker splits one tool call across two messages: the assistant message
@@ -34,9 +34,9 @@ const messages: AgentMessage[] = [
{ role: 'assistant', content: 'eu-central-1 is down.', agent_action: { type: 'message' } }
]
-describe('buildAgentActions', () => {
+describe('buildAgentTrace', () => {
it('joins a tool call to the arguments on the message that requested it', () => {
- expect(buildAgentActions(messages)).toEqual([
+ expect(buildAgentTrace(messages)).toEqual([
{
kind: 'tool',
name: 'query_metrics',
@@ -49,7 +49,7 @@ describe('buildAgentActions', () => {
})
it('keeps an MCP call, whose arguments live on the action itself', () => {
- const entries = buildAgentActions([
+ const entries = buildAgentTrace([
{
role: 'tool',
content: 'sunny',
@@ -74,7 +74,7 @@ describe('buildAgentActions', () => {
})
it('carries web search citations onto the entry', () => {
- const entries = buildAgentActions([
+ const entries = buildAgentTrace([
{
role: 'assistant',
content: 'Postgres 17 changed the default.',
@@ -94,9 +94,9 @@ describe('buildAgentActions', () => {
// The prompt and the question are the step's inputs, shown as inputs. A replayed
// turn comes back from memory without its tag, and crediting this run with an
// answer a previous one gave would be a lie about what happened.
- it('keeps only what this run did', () => {
+ it('traces only what this run did', () => {
expect(
- buildAgentActions([
+ buildAgentTrace([
{ role: 'system', content: 'You are an SRE assistant.' },
{ role: 'user', content: 'Which region is broken?' },
{ role: 'assistant', content: 'Answered in an earlier turn, replayed from memory.' },
diff --git a/frontend/src/lib/components/agentActions.ts b/frontend/src/lib/components/agentTrace.ts
similarity index 84%
rename from frontend/src/lib/components/agentActions.ts
rename to frontend/src/lib/components/agentTrace.ts
index 418db8520f..bf6194b1ce 100644
--- a/frontend/src/lib/components/agentActions.ts
+++ b/frontend/src/lib/components/agentTrace.ts
@@ -2,17 +2,16 @@ import type { WebSearchSource } from './copilot/chat/shared'
import type { AgentMessage } from './aiAgentResult'
/**
- * One thing an agent did. Built from the envelope alone: the tool arguments come
- * from the assistant message that asked for the call, and the result from the
- * `tool` message that answered it, so the list renders without waiting on any
- * request. A tool's child job is enrichment (logs, duration, whether it
- * succeeded), not what makes the row.
+ * One entry in the trace of an agent run. Built from the envelope alone: the tool
+ * arguments come from the assistant message that asked for the call, and the
+ * result from the `tool` message that answered it, so the trace renders without
+ * waiting on any request. A tool's child job is enrichment (logs, duration,
+ * whether it succeeded), not what makes the row.
*
* The prompt and the user's question are deliberately absent. They are inputs to
- * the step, shown as inputs, and a run is not a conversation the viewer is part
- * of — it is a record of what the agent did with them.
+ * the step and are shown as inputs; the trace is what the agent did with them.
*/
-export type AgentActionEntry =
+export type AgentTraceEntry =
| { kind: 'assistant'; content: string; sources?: WebSearchSource[] }
| { kind: 'search'; content: string; sources?: WebSearchSource[] }
| {
@@ -54,7 +53,7 @@ function sourcesOf(message: AgentMessage): WebSearchSource[] | undefined {
return sources.length > 0 ? sources : undefined
}
-export function buildAgentActions(messages: AgentMessage[]): AgentActionEntry[] {
+export function buildAgentTrace(messages: AgentMessage[]): AgentTraceEntry[] {
// The arguments live on the assistant message that requested the call, while
// the action tag and the result live on the `tool` message answering it, so
// the two are joined by `tool_call_id`.
@@ -67,7 +66,7 @@ export function buildAgentActions(messages: AgentMessage[]): AgentActionEntry[]
}
}
- const entries: AgentActionEntry[] = []
+ const entries: AgentTraceEntry[] = []
for (const message of messages) {
const action = message.agent_action
if (action?.type === 'tool_call') {