Skip to content

Commit f5ebd7e

Browse files
committed
viewer v1: typed Ticket + Customer widgets in the scene pane
The scene pane now renders typed scenegrad scenes through proper widgets when the shape is recognized — Ticket on the left (8/12), Customer on the right (4/12). Falls back to the JSON-tree-with-diff for arbitrary shapes (any other env's scenes). Widgets (lives in viewer/widgets/, will promote to scenecast when stable): - Ticket card * Header: ID + subject + color-coded status pill (slate=new, blue=investigating, emerald=auto-resolved, amber=escalated-t2, red=escalated-vip) * Customer chip + KB-match chip when matched * Quoted issue body * Reply callout with [CRITICAL]/[HIGH]/[URGENT] prefix tags styled as inline colored prefix blocks - Customer card * Tier badge: free=slate, pro=sky, enterprise=gold * LTV (formatted as $480k / $1.2M) * Prior-incidents counter, color-coded by severity * Empty-state placeholder when enrich hasn't run yet Per-tool granularity fix: Previous trajectory: agent batched enrich + search_kb in one Anthropic step, so kb_match appeared at step 1 alongside enrich. Trajectory read as 4 cleanly-separated tool calls but the scene transitions were conflated. Fix: pass providerOptions.anthropic.disableParallelToolUse=true so the model picks one tool per turn. Plus wrap each tool's execute() in a `recorded` helper that calls watcher.recordStep right after the world mutation — gives one trajectory step per tool invocation regardless of how AI SDK's onStepFinish batches. Result: trajectory now shows step 0 read_ticket → empty ticket step 1 enrich_with_account → status:investigating + enriched data step 2 search_kb → kb_match:KB-101 appears step 3 escalate_vip → status:escalated-vip + reply Each step has a single visible transformation. Watching it scrub is the wow. Demo gif regenerated. 1.4MB, 12s, 900px. The Ticket card's red status pill at step 3 is the punchline.
1 parent 8d677fd commit f5ebd7e

8 files changed

Lines changed: 360 additions & 35 deletions

File tree

docs/demo.gif

-239 KB
Loading

examples/support-triage-aisdk.ts

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -103,63 +103,73 @@ const watcher = observe<Ticket>({
103103
// Tools — Vercel AI SDK native
104104
// ---------------------------------------------------------------------------
105105

106+
// Helper: wrap a tool's execute so scenegrad records one trajectory step
107+
// per tool invocation (not per AI SDK "step" which can batch tool calls).
108+
function recorded<I, O>(name: string, exec: (input: I) => Promise<O> | O) {
109+
return async (input: I) => {
110+
const result = await exec(input);
111+
await watcher.recordStep({ tool: { name, args: input as any } });
112+
return result;
113+
};
114+
}
115+
106116
const tools = {
107117
read_ticket: tool({
108118
description: "Read the current ticket details.",
109119
inputSchema: z.object({}),
110-
execute: async () => world.ticket,
120+
execute: recorded("read_ticket", async () => world.ticket),
111121
}),
112122

113123
enrich_with_account: tool({
114124
description: "Look up the customer's account tier, LTV, and incident history.",
115125
inputSchema: z.object({
116126
customer_id: z.string().describe("e.g. acme-corp"),
117127
}),
118-
execute: async ({ customer_id }) => {
128+
execute: recorded("enrich_with_account", async ({ customer_id }) => {
119129
const profile = world.customer_db[customer_id];
120130
if (!profile) return { error: "customer not found" };
121131
world.ticket.enriched = profile;
122132
world.ticket.status = "investigating";
123133
return profile;
124-
},
134+
}),
125135
}),
126136

127137
search_kb: tool({
128138
description: "Search the knowledge base for articles matching keywords from the ticket.",
129139
inputSchema: z.object({
130140
keywords: z.array(z.string()).describe("e.g. ['webhook', '504']"),
131141
}),
132-
execute: async ({ keywords }) => {
142+
execute: recorded("search_kb", async ({ keywords }) => {
133143
const matches = world.kb.filter(article =>
134144
keywords.some(k => article.matches.some(m => m.toLowerCase().includes(k.toLowerCase()))));
135145
const best = matches[0];
136146
world.ticket.kb_match = best?.id ?? "no-match";
137147
return { matches, best_match: best };
138-
},
148+
}),
139149
}),
140150

141151
auto_resolve: tool({
142152
description: "Auto-resolve the ticket with a KB-based reply. Only use for non-enterprise + clear KB match.",
143153
inputSchema: z.object({
144154
reply: z.string().describe("The auto-response to send"),
145155
}),
146-
execute: async ({ reply }) => {
156+
execute: recorded("auto_resolve", async ({ reply }) => {
147157
world.ticket.reply = reply;
148158
world.ticket.status = "auto-resolved";
149159
return { ok: true, status: "auto-resolved" };
150-
},
160+
}),
151161
}),
152162

153163
escalate_t2: tool({
154164
description: "Escalate to Tier-2 engineering support. Use for non-VIP technical issues.",
155165
inputSchema: z.object({
156166
reason: z.string(),
157167
}),
158-
execute: async ({ reason }) => {
168+
execute: recorded("escalate_t2", async ({ reason }) => {
159169
world.ticket.reply = `Escalated to T2: ${reason}`;
160170
world.ticket.status = "escalated-t2";
161171
return { ok: true, status: "escalated-t2" };
162-
},
172+
}),
163173
}),
164174

165175
escalate_vip: tool({
@@ -168,11 +178,11 @@ const tools = {
168178
reason: z.string(),
169179
urgency: z.enum(["high", "critical"]),
170180
}),
171-
execute: async ({ reason, urgency }) => {
181+
execute: recorded("escalate_vip", async ({ reason, urgency }) => {
172182
world.ticket.reply = `[${urgency.toUpperCase()}] Escalated to VIP: ${reason}`;
173183
world.ticket.status = "escalated-vip";
174184
return { ok: true, status: "escalated-vip" };
175-
},
185+
}),
176186
}),
177187
};
178188

@@ -198,6 +208,11 @@ const result = await generateText({
198208
model: anthropic("claude-haiku-4-5"),
199209
stopWhen: stepCountIs(8),
200210
tools,
211+
// Disable parallel tool calls so the trajectory shows one tool per step
212+
// instead of batching enrich + search_kb in a single model turn.
213+
providerOptions: {
214+
anthropic: { disableParallelToolUse: true },
215+
},
201216

202217
// The system prompt is regenerated on every step via `prepareStep` —
203218
// so the agent always sees fresh status from scenegrad.
@@ -221,15 +236,9 @@ const result = await generateText({
221236
};
222237
},
223238

224-
// scenegrad's only line of integration: re-snapshot the world after each tool result.
225-
onStepFinish: async ({ toolCalls }) => {
226-
for (const call of toolCalls ?? []) {
227-
await watcher.recordStep({
228-
tool: { name: call.toolName, args: (call as any).input ?? (call as any).args ?? {} },
229-
});
230-
}
231-
if (!toolCalls || toolCalls.length === 0) await watcher.recordStep({});
232-
},
239+
// (scenegrad's recordStep happens inside each tool's execute — see the
240+
// `recorded` helper above. That gives one trajectory step per tool call,
241+
// even when AI SDK batches multiple tool calls in one model step.)
233242

234243
prompt: `Triage ticket ${world.ticket.id}. Read it, enrich it, search the KB, then route.`,
235244
});
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
{"trace_id":"36e8d3b0d8456ca65eab2a9d02c72d7b","span_id":"3bab7e75f1a9452d","parent_span_id":null,"name":"support-triage.TKT-9341.haiku-4-5","start_time_ns":1777809583601000000,"end_time_ns":1777809591127000000,"kind":0,"status":{"code":0},"attributes":{"bench.task_id":"support-triage-TKT-9341","bench.solver":"observer","bench.model":"claude-haiku-4-5","bench.success":true,"bench.steps":5,"bench.d_initial":4,"bench.d_final":0,"bench.duration_ms":7526},"events":[{"name":"scene.set","time_ns":1777809584685000000,"attributes":{"scene.key":"tool","scene.kind":"intent","scene.value":"{\"tool\":{\"name\":\"read_ticket\",\"args\":{}}}","scene.value.type":"json","scene.value.size":0,"scene.commit_hash":"","scene.description":"read_ticket"}},{"name":"scene.set","time_ns":1777809584685000000,"attributes":{"scene.key":"distance","scene.kind":"actual","scene.value":"{\"d_before\":4,\"d_after\":4,\"delta\":0}","scene.value.type":"json","scene.value.size":0,"scene.commit_hash":"","scene.description":"step 0"}},{"name":"scene.set","time_ns":1777809584685000000,"attributes":{"scene.key":"scene","scene.kind":"actual","scene.value":"{\"id\":\"TKT-9341\",\"subject\":\"Webhook deliveries failing with 504\",\"body\":\"We've had 12 webhook timeouts since 2pm UTC. Our integration is critical for our checkout flow. Please advise ASAP.\",\"customer\":\"acme-corp\",\"status\":\"new\"}","scene.value.type":"json","scene.value.size":228,"scene.commit_hash":"","scene.description":"world state after step 0"}},{"name":"scene.set","time_ns":1777809586009000000,"attributes":{"scene.key":"tool","scene.kind":"intent","scene.value":"{\"tool\":{\"name\":\"enrich_with_account\",\"args\":{\"customer_id\":\"acme-corp\"}}}","scene.value.type":"json","scene.value.size":0,"scene.commit_hash":"","scene.description":"enrich_with_account"}},{"name":"scene.set","time_ns":1777809586009000000,"attributes":{"scene.key":"distance","scene.kind":"actual","scene.value":"{\"d_before\":4,\"d_after\":2,\"delta\":2}","scene.value.type":"json","scene.value.size":0,"scene.commit_hash":"","scene.description":"step 1"}},{"name":"scene.set","time_ns":1777809586009000000,"attributes":{"scene.key":"scene","scene.kind":"actual","scene.value":"{\"id\":\"TKT-9341\",\"subject\":\"Webhook deliveries failing with 504\",\"body\":\"We've had 12 webhook timeouts since 2pm UTC. Our integration is critical for our checkout flow. Please advise ASAP.\",\"customer\":\"acme-corp\",\"status\":\"investigating\",\"enriched\":{\"tier\":\"enterprise\",\"ltv_usd\":480000,\"prior_incidents\":3},\"kb_match\":\"KB-101\"}","scene.value.type":"json","scene.value.size":328,"scene.commit_hash":"","scene.description":"world state after step 1"}},{"name":"scene.set","time_ns":1777809586009000000,"attributes":{"scene.key":"tool","scene.kind":"intent","scene.value":"{\"tool\":{\"name\":\"search_kb\",\"args\":{\"keywords\":[\"webhook\",\"504\",\"timeout\"]}}}","scene.value.type":"json","scene.value.size":0,"scene.commit_hash":"","scene.description":"search_kb"}},{"name":"scene.set","time_ns":1777809586009000000,"attributes":{"scene.key":"distance","scene.kind":"actual","scene.value":"{\"d_before\":2,\"d_after\":2,\"delta\":0}","scene.value.type":"json","scene.value.size":0,"scene.commit_hash":"","scene.description":"step 2"}},{"name":"scene.set","time_ns":1777809586009000000,"attributes":{"scene.key":"scene","scene.kind":"actual","scene.value":"{\"id\":\"TKT-9341\",\"subject\":\"Webhook deliveries failing with 504\",\"body\":\"We've had 12 webhook timeouts since 2pm UTC. Our integration is critical for our checkout flow. Please advise ASAP.\",\"customer\":\"acme-corp\",\"status\":\"investigating\",\"enriched\":{\"tier\":\"enterprise\",\"ltv_usd\":480000,\"prior_incidents\":3},\"kb_match\":\"KB-101\"}","scene.value.type":"json","scene.value.size":328,"scene.commit_hash":"","scene.description":"world state after step 2"}},{"name":"scene.set","time_ns":1777809589117000000,"attributes":{"scene.key":"tool","scene.kind":"intent","scene.value":"{\"tool\":{\"name\":\"escalate_vip\",\"args\":{\"reason\":\"Enterprise customer acme-corp reporting webhook 504 timeouts affecting critical checkout flow (12 failures since 2pm UTC). LTV $480k, 3 prior incidents. KB match available (KB-101) but escalation to VIP required per enterprise policy.\",\"urgency\":\"critical\"}}}","scene.value.type":"json","scene.value.size":0,"scene.commit_hash":"","scene.description":"escalate_vip"}},{"name":"scene.set","time_ns":1777809589117000000,"attributes":{"scene.key":"distance","scene.kind":"actual","scene.value":"{\"d_before\":2,\"d_after\":0,\"delta\":2}","scene.value.type":"json","scene.value.size":0,"scene.commit_hash":"","scene.description":"step 3"}},{"name":"scene.set","time_ns":1777809589117000000,"attributes":{"scene.key":"scene","scene.kind":"actual","scene.value":"{\"id\":\"TKT-9341\",\"subject\":\"Webhook deliveries failing with 504\",\"body\":\"We've had 12 webhook timeouts since 2pm UTC. Our integration is critical for our checkout flow. Please advise ASAP.\",\"customer\":\"acme-corp\",\"status\":\"escalated-vip\",\"enriched\":{\"tier\":\"enterprise\",\"ltv_usd\":480000,\"prior_incidents\":3},\"kb_match\":\"KB-101\",\"reply\":\"[CRITICAL] Escalated to VIP: Enterprise customer acme-corp reporting webhook 504 timeouts affecting critical checkout flow (12 failures since 2pm UTC). LTV $480k, 3 prior incidents. KB match available (KB-101) but escalation to VIP required per enterprise policy.\"}","scene.value.type":"json","scene.value.size":602,"scene.commit_hash":"","scene.description":"world state after step 3"}},{"name":"scene.set","time_ns":1777809591127000000,"attributes":{"scene.key":"distance","scene.kind":"actual","scene.value":"{\"d_before\":0,\"d_after\":0,\"delta\":0}","scene.value.type":"json","scene.value.size":0,"scene.commit_hash":"","scene.description":"step 4"}},{"name":"scene.set","time_ns":1777809591127000000,"attributes":{"scene.key":"scene","scene.kind":"actual","scene.value":"{\"id\":\"TKT-9341\",\"subject\":\"Webhook deliveries failing with 504\",\"body\":\"We've had 12 webhook timeouts since 2pm UTC. Our integration is critical for our checkout flow. Please advise ASAP.\",\"customer\":\"acme-corp\",\"status\":\"escalated-vip\",\"enriched\":{\"tier\":\"enterprise\",\"ltv_usd\":480000,\"prior_incidents\":3},\"kb_match\":\"KB-101\",\"reply\":\"[CRITICAL] Escalated to VIP: Enterprise customer acme-corp reporting webhook 504 timeouts affecting critical checkout flow (12 failures since 2pm UTC). LTV $480k, 3 prior incidents. KB match available (KB-101) but escalation to VIP required per enterprise policy.\"}","scene.value.type":"json","scene.value.size":602,"scene.commit_hash":"","scene.description":"world state after step 4"}}]}
1+
{"trace_id":"abe4449ed36af09d708ad7bade62e3f5","span_id":"89bc177d94c2501d","parent_span_id":null,"name":"support-triage.TKT-9341.haiku-4-5","start_time_ns":1777812420861000000,"end_time_ns":1777812426131000000,"kind":0,"status":{"code":0},"attributes":{"bench.task_id":"support-triage-TKT-9341","bench.solver":"observer","bench.model":"claude-haiku-4-5","bench.success":true,"bench.steps":4,"bench.d_initial":4,"bench.d_final":0,"bench.duration_ms":5270},"events":[{"name":"scene.set","time_ns":1777812421933000000,"attributes":{"scene.key":"tool","scene.kind":"intent","scene.value":"{\"tool\":{\"name\":\"read_ticket\",\"args\":{}}}","scene.value.type":"json","scene.value.size":0,"scene.commit_hash":"","scene.description":"read_ticket"}},{"name":"scene.set","time_ns":1777812421933000000,"attributes":{"scene.key":"distance","scene.kind":"actual","scene.value":"{\"d_before\":4,\"d_after\":4,\"delta\":0}","scene.value.type":"json","scene.value.size":0,"scene.commit_hash":"","scene.description":"step 0"}},{"name":"scene.set","time_ns":1777812421933000000,"attributes":{"scene.key":"scene","scene.kind":"actual","scene.value":"{\"id\":\"TKT-9341\",\"subject\":\"Webhook deliveries failing with 504\",\"body\":\"We've had 12 webhook timeouts since 2pm UTC. Our integration is critical for our checkout flow. Please advise ASAP.\",\"customer\":\"acme-corp\",\"status\":\"new\"}","scene.value.type":"json","scene.value.size":228,"scene.commit_hash":"","scene.description":"world state after step 0"}},{"name":"scene.set","time_ns":1777812422952000000,"attributes":{"scene.key":"tool","scene.kind":"intent","scene.value":"{\"tool\":{\"name\":\"enrich_with_account\",\"args\":{\"customer_id\":\"acme-corp\"}}}","scene.value.type":"json","scene.value.size":0,"scene.commit_hash":"","scene.description":"enrich_with_account"}},{"name":"scene.set","time_ns":1777812422952000000,"attributes":{"scene.key":"distance","scene.kind":"actual","scene.value":"{\"d_before\":4,\"d_after\":3,\"delta\":1}","scene.value.type":"json","scene.value.size":0,"scene.commit_hash":"","scene.description":"step 1"}},{"name":"scene.set","time_ns":1777812422952000000,"attributes":{"scene.key":"scene","scene.kind":"actual","scene.value":"{\"id\":\"TKT-9341\",\"subject\":\"Webhook deliveries failing with 504\",\"body\":\"We've had 12 webhook timeouts since 2pm UTC. Our integration is critical for our checkout flow. Please advise ASAP.\",\"customer\":\"acme-corp\",\"status\":\"investigating\",\"enriched\":{\"tier\":\"enterprise\",\"ltv_usd\":480000,\"prior_incidents\":3}}","scene.value.type":"json","scene.value.size":308,"scene.commit_hash":"","scene.description":"world state after step 1"}},{"name":"scene.set","time_ns":1777812423976000000,"attributes":{"scene.key":"tool","scene.kind":"intent","scene.value":"{\"tool\":{\"name\":\"search_kb\",\"args\":{\"keywords\":[\"webhook\",\"504\",\"timeout\",\"deliveries\"]}}}","scene.value.type":"json","scene.value.size":0,"scene.commit_hash":"","scene.description":"search_kb"}},{"name":"scene.set","time_ns":1777812423976000000,"attributes":{"scene.key":"distance","scene.kind":"actual","scene.value":"{\"d_before\":3,\"d_after\":2,\"delta\":1}","scene.value.type":"json","scene.value.size":0,"scene.commit_hash":"","scene.description":"step 2"}},{"name":"scene.set","time_ns":1777812423976000000,"attributes":{"scene.key":"scene","scene.kind":"actual","scene.value":"{\"id\":\"TKT-9341\",\"subject\":\"Webhook deliveries failing with 504\",\"body\":\"We've had 12 webhook timeouts since 2pm UTC. Our integration is critical for our checkout flow. Please advise ASAP.\",\"customer\":\"acme-corp\",\"status\":\"investigating\",\"enriched\":{\"tier\":\"enterprise\",\"ltv_usd\":480000,\"prior_incidents\":3},\"kb_match\":\"KB-101\"}","scene.value.type":"json","scene.value.size":328,"scene.commit_hash":"","scene.description":"world state after step 2"}},{"name":"scene.set","time_ns":1777812426130999800,"attributes":{"scene.key":"tool","scene.kind":"intent","scene.value":"{\"tool\":{\"name\":\"escalate_vip\",\"args\":{\"reason\":\"Enterprise customer (acme-corp, $480K LTV) experiencing critical webhook delivery failures (504 timeouts) impacting checkout flow. 12 incidents since 2pm UTC. Prior incident history: 3. Requires immediate VIP escalation.\",\"urgency\":\"critical\"}}}","scene.value.type":"json","scene.value.size":0,"scene.commit_hash":"","scene.description":"escalate_vip"}},{"name":"scene.set","time_ns":1777812426130999800,"attributes":{"scene.key":"distance","scene.kind":"actual","scene.value":"{\"d_before\":2,\"d_after\":0,\"delta\":2}","scene.value.type":"json","scene.value.size":0,"scene.commit_hash":"","scene.description":"step 3"}},{"name":"scene.set","time_ns":1777812426130999800,"attributes":{"scene.key":"scene","scene.kind":"actual","scene.value":"{\"id\":\"TKT-9341\",\"subject\":\"Webhook deliveries failing with 504\",\"body\":\"We've had 12 webhook timeouts since 2pm UTC. Our integration is critical for our checkout flow. Please advise ASAP.\",\"customer\":\"acme-corp\",\"status\":\"escalated-vip\",\"enriched\":{\"tier\":\"enterprise\",\"ltv_usd\":480000,\"prior_incidents\":3},\"kb_match\":\"KB-101\",\"reply\":\"[CRITICAL] Escalated to VIP: Enterprise customer (acme-corp, $480K LTV) experiencing critical webhook delivery failures (504 timeouts) impacting checkout flow. 12 incidents since 2pm UTC. Prior incident history: 3. Requires immediate VIP escalation.\"}","scene.value.type":"json","scene.value.size":588,"scene.commit_hash":"","scene.description":"world state after step 3"}}]}

viewer/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,7 +141,7 @@ <h2 class="font-semibold mb-3">Load a trajectory</h2>
141141
<span class="inline-block w-2 h-2 bg-emerald-200 rounded-sm align-middle mr-1"></span> changed this step
142142
</div>
143143
</div>
144-
<div id="scene-pane" class="text-sm font-mono">
144+
<div id="scene-pane" class="text-sm">
145145
<div class="text-slate-400 italic">no scene captured</div>
146146
</div>
147147
</div>

0 commit comments

Comments
 (0)