You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Extension of #5. Deterministic refresh only works if the canvas knows how its data was produced. Today that knowledge evaporates after the turn: the LLM interprets the user's request, makes tool calls, gets responses, and composes the tree — and none of that pipeline is persisted with the canvas. Only the resulting tree (with its dataRefs) survives.
This issue proposes binding the query to the canvas: persist the resolved data-production recipe — the concrete tool calls with their final parameters, the response→primitive data mapping, and the composition decisions — as a first-class part of the canvas, so the canvas can be re-materialized deterministically, with no LLM in the loop.
Motivation
Deterministic refresh for data-driven canvases (no LLM turn) #5's canvas_refresh covers the easy half: re-resolving dataRefs that point at refreshable sources. But much canvas data is not a single addressable ref — it is the output of tool calls the LLM issued during the turn (search results, aggregations, cross-service joins, computed series feeding a chart). Without recording those calls, that data is frozen forever.
Determinism is the point: the same recorded recipe replayed against the same sources must yield the same canvas modulo newer data. The LLM's only job was figuring the recipe out once; re-running it should never need interpretation again.
Canvas recipe. When a turn produces or patches a canvas, Tier 2 persists alongside the tree: the ordered set of tool calls (tool id, resolved/concrete parameters — no free-text placeholders), the mapping from each response to the primitive data it populated (by stable primitive id), and any deterministic post-processing applied (sort, limit, aggregation expressed as data ops, not prose).
Refresh = replay.canvas_refresh (Deterministic refresh for data-driven canvases (no LLM turn) #5) executes the recipe: re-run the recorded tool calls with the recorded parameters, re-apply the recorded mappings, diff against the current revision, emit patch ops. The LLM is not invoked.
Parameterization, not re-interpretation. Recipe parameters that are time-relative ("last 30 days") are stored as resolved expressions (now − 30d), not as the frozen values, so replay moves the window deterministically. The set of allowed expressions is small and closed — anything that would need interpretation stays frozen and is marked non-refreshable.
Drift handling. If a recorded tool call fails on replay (tool removed, schema changed, permission revoked), the affected primitives get a defined stale/error state and the client offers the escalation path from Deterministic refresh for data-driven canvases (no LLM turn) #5: one explicit LLM turn to rebuild the recipe. The new recipe replaces the old one.
Determinism contract. Recipes record the ops-catalog version and tool versions they were built against; replay refuses (with the drift path above) rather than silently producing different semantics on a version mismatch.
Open questions
Granularity: one recipe per canvas, or per data-bearing primitive (a table and a chart on the same surface may have independent refresh cadences and failure modes)? Per-primitive looks right and matches Deterministic refresh for data-driven canvases (no LLM turn) #5's subtree scope.
How are non-deterministic tools marked? The tool/ops catalog likely needs a deterministic: boolean (or replayable) flag so Tier 2 knows what may enter a recipe.
Recipe visibility: do we show users what a refresh will do (the recorded calls) for trust/debugging — e.g., in a canvas inspector pane?
Summary
Extension of #5. Deterministic refresh only works if the canvas knows how its data was produced. Today that knowledge evaporates after the turn: the LLM interprets the user's request, makes tool calls, gets responses, and composes the tree — and none of that pipeline is persisted with the canvas. Only the resulting tree (with its
dataRefs) survives.This issue proposes binding the query to the canvas: persist the resolved data-production recipe — the concrete tool calls with their final parameters, the response→primitive data mapping, and the composition decisions — as a first-class part of the canvas, so the canvas can be re-materialized deterministically, with no LLM in the loop.
Motivation
canvas_refreshcovers the easy half: re-resolvingdataRefs that point at refreshable sources. But much canvas data is not a single addressable ref — it is the output of tool calls the LLM issued during the turn (search results, aggregations, cross-service joins, computed series feeding achart). Without recording those calls, that data is frozen forever.Proposed behavior
id), and any deterministic post-processing applied (sort, limit, aggregation expressed as data ops, not prose).canvas_refresh(Deterministic refresh for data-driven canvases (no LLM turn) #5) executes the recipe: re-run the recorded tool calls with the recorded parameters, re-apply the recorded mappings, diff against the current revision, emit patch ops. The LLM is not invoked.now − 30d), not as the frozen values, so replay moves the window deterministically. The set of allowed expressions is small and closed — anything that would need interpretation stays frozen and is marked non-refreshable.Open questions
tableand acharton the same surface may have independent refresh cadences and failure modes)? Per-primitive looks right and matches Deterministic refresh for data-driven canvases (no LLM turn) #5's subtree scope.deterministic: boolean(orreplayable) flag so Tier 2 knows what may enter a recipe.Affected areas
Related
dataRefdata; attach as sub-issue/follow-up)