Simulation
An interactive what-if model that runs over time. The agent declares the model; the component runs it as the user moves the inputs.
≈ 1,720 tokens per use (tool 1,273 · guidance 242 · typical payload 205)How token costs are estimated
Example: Simulation
How does my savings grow with $500 a month?
Savings over 10 years
| Month | Balance | Deposited |
|---|---|---|
| 0 | $2,000 | $2,000 |
| 3 | $3,531 | $3,500 |
| 6 | $5,082 | $5,000 |
| 9 | $6,652 | $6,500 |
| 12 | $8,242 | $8,000 |
| 15 | $9,851 | $9,500 |
| 18 | $11,481 | $11,000 |
| 21 | $13,132 | $12,500 |
| 24 | $14,803 | $14,000 |
| 27 | $16,495 | $15,500 |
| 30 | $18,208 | $17,000 |
| 33 | $19,943 | $18,500 |
| 36 | $21,700 | $20,000 |
| 39 | $23,478 | $21,500 |
| 42 | $25,279 | $23,000 |
| 45 | $27,103 | $24,500 |
| 48 | $28,949 | $26,000 |
| 51 | $30,819 | $27,500 |
| 54 | $32,712 | $29,000 |
| 57 | $34,629 | $30,500 |
| 60 | $36,570 | $32,000 |
| 63 | $38,535 | $33,500 |
| 66 | $40,525 | $35,000 |
| 69 | $42,540 | $36,500 |
| 72 | $44,580 | $38,000 |
| 75 | $46,646 | $39,500 |
| 78 | $48,738 | $41,000 |
| 81 | $50,856 | $42,500 |
| 84 | $53,000 | $44,000 |
| 87 | $55,172 | $45,500 |
| 90 | $57,371 | $47,000 |
| 93 | $59,597 | $48,500 |
| 96 | $61,851 | $50,000 |
| 99 | $64,134 | $51,500 |
| 102 | $66,445 | $53,000 |
| 105 | $68,786 | $54,500 |
| 108 | $71,155 | $56,000 |
| 111 | $73,555 | $57,500 |
| 114 | $75,984 | $59,000 |
| 117 | $78,444 | $60,500 |
| 120 | $80,935 | $62,000 |
- Deposited
- $62,000
- Interest earned
- $18,935
When to use
- The question is how something changes over time: savings growth, a loan payoff, a support backlog, stock levels.
- The user wants to try what-ifs, such as a bigger deposit, more staff or an extra payment.
When not to use
- The answer is a single number. Use a Calculator.
- The figures already exist as data. Show them in a Chart instead of simulating them.
- You're unsure how the system behaves from one step to the next. Explain the method in text.
How the model runs
The agent declares inputs (each one gets a slider), state (values carried from step to step) and one step formula per state that gives its next value:
{
"inputs": [{ "id": "deposit", "label": "Monthly deposit", "min": 0, "max": 2000, "step": 50, "default": 500, "unit": "currency" }],
"state": [{ "id": "balance", "label": "Balance", "initial": "initial" }],
"step": [{ "id": "balance", "formula": "balance * (1 + rate / 100 / 12) + deposit" }],
"steps": 120,
"stepUnit": "month"
}| Step | What happens |
|---|---|
| 0 | Each state takes its initial value. Outputs are derived from the state |
| 1, 2, … | Each state's step formula runs on the previous step's state and outputs, the inputs and t (the step being computed). Outputs are derived again |
| Every step | The first step where a milestone's when holds is marked. The run ends after the first step where stopWhen holds |
Formulas use the same safe language as the Calculator: operators + - * / % ^ and comparisons, plus min, max, round, floor, ceil, abs, sqrt, pow, clamp and if. There's no eval, so a model can't run code through a formula. The schema rejects formulas that don't parse or that use names the model doesn't declare. Runs are capped at 1,000 steps.
If a step produces a value that isn't a finite number (for example, a division by zero), the run stops there and the component says "The model produced an invalid value at month 34" instead of drawing a wrong line.
Interaction
- Answer first: the first milestone (or where the run ends, for
stopWhen) is shown large above the chart, e.g. "Backlog cleared: Day 6". Without milestones orstopWhen, the first plotted value at the cursor is the headline. Further milestones are listed under it. - Inputs: below the chart, each with a slider, a number field and a reset button. Every change re-runs the model straight away.
- Baseline: once the user changes an input, the run with the default inputs stays on the chart as a dashed line, and the answer shows the baseline's value on one quiet line, e.g. "Baseline: not reached". At the last step, the values under the chart show their change against it. Set
baseline: falseto turn it off. - Scrubber: Play moves a cursor across the run, and the step slider inspects any step. The values under the chart follow the cursor. With reduced motion, Play jumps to the last step.
- Milestones: the first step each one holds is marked on the chart and stated above it.
descriptionis read to screen readers only. Put assumptions the user must see in the reply.
How the agent should use it
- Start from what the user said. Every input defaults to their numbers, and the default run is the baseline.
- Give each state exactly one step formula, and use only declared IDs and
t. - Use
stopWhenfor runs that end, like a paid-off loan, andmilestonesfor moments worth marking. - Plot one to three series that share a unit.
- Present results as estimates from the stated assumptions, not advice.
- Set
apply: falsewhen the simulation is the whole answer. Otherwise the "Use these numbers" button (or yourapplyLabel) sends the chosen inputs back.
Installation
npx shadcn@latest add https://your-site.com/r/simulation.json
This adds the component, its schema and simulation-engine.ts, which holds the formula language and the model runner.
Usage
import { tool } from "ai"
import { simulationTool } from "@/components/genui/simulation.schema"
const tools = {
[simulationTool.name]: tool({
description: simulationTool.description,
inputSchema: simulationTool.inputSchema,
}),
}import { Simulation } from "@/components/genui/simulation"
function ToolPart({ part, addToolResult }) {
if (part.type !== "tool-run_simulation") return null
return (
<Simulation
{...part.input}
status={part.state === "input-streaming" ? "streaming" : undefined}
result={part.state === "output-available" ? part.output : undefined}
onResult={(result) =>
addToolResult({
tool: "run_simulation",
toolCallId: part.toolCallId,
output: result,
})
}
/>
)
}With apply: false, the simulation is the whole answer, so give the tool an execute that returns immediately. Otherwise the agent receives the inputs and the final value of every state and output:
{
action: "apply",
inputs: { agents: 8, incoming: 180 },
summary: { backlog: 0, queueDays: 0 },
}With LangGraph
Push it from a graph node with ui.push({ name: "run_simulation", props: { toolCallId, ...args } }, { message }), and render the thread's UI messages with the Renderer.
Restoring from history
When a conversation is reloaded, pass the stored result back as result. The component restores the inputs that were sent, read-only, and never calls onResult again. The chart and scrubber stay usable. With the AI SDK, that's the tool call's output:
<Simulation
{...part.input}
result={part.state === "output-available" ? part.output : undefined}
/>Schema
| Prop | Type | Notes |
|---|---|---|
title | string | The question |
description | string? | Screen readers only, e.g. where the defaults come from |
currency | string | ISO 4217 code for currency values. Default USD |
inputs | Input[] | 1–6 sliders |
state | State[] | 1–6 values carried between steps |
step | { id, formula }[] | One formula per state ID |
steps | number | The horizon, 1–1,000 |
stepUnit | string | day, week, month, year or a custom singular label |
outputs | Output[]? | Up to 6 values derived each step |
stopWhen | string? | Ends the run once true |
plot | string[] | 1–3 state or output IDs to chart |
milestones | { label, when }[]? | Up to 4. The first step each holds is marked |
baseline | boolean | Dashed default-input run. Default true |
apply | boolean | Show the apply button. Default true |
applyLabel | string? | Default "Use these numbers" |
Input
| Field | Type | Notes |
|---|---|---|
id | string | Name used in formulas |
label | string | |
min, max, step | number | Slider range |
default | number | Also the baseline value |
unit | string? | currency, percent, number or a suffix such as tickets |
decimals | number? | 0–4. Defaults to the step's precision |
State and Output
| Field | Type | Notes |
|---|---|---|
id | string | Name used in formulas. t is reserved |
label | string | |
initial (state) / formula (output) | string | See "How the model runs" |
unit, decimals | Display |
Host props: status (streaming, ready, applied), onResult, result and className.