Skip to content

Commit b41323a

Browse files
committed
feat(ca-demo): frozen middle results record their workflow lineage
User question exposed the gap: middle results were stored BESIDE the frozen workflow (linked only by the inspector's heuristic), not structurally attached to it. The artifact now records plan_hash and produced_by_step at freeze time; revisions inherit the lineage. Design kept deliberate: the plan envelope stays a question-agnostic TEMPLATE (embedding per-question instances would bloat and conflate it) — middle results are per-task-input instances that REFERENCE their plan. The inspector attaches artifacts to workflow cards by recorded lineage (heuristic fallback for pre-lineage records) and renders the lineage on each card. Lineage pinned by tests incl. revision inheritance.
1 parent 9592610 commit b41323a

3 files changed

Lines changed: 74 additions & 18 deletions

File tree

contributing/samples/workflows/authored_workflow_ca_demo/bq_ca_planner/agent.py

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1444,6 +1444,8 @@ def _freeze_sql(
14441444
bytes_processed: int = 0,
14451445
feedback: str | None = None,
14461446
previous: dict | None = None,
1447+
plan_hash: str | None = None,
1448+
produced_by_step: str | None = None,
14471449
) -> str:
14481450
"""Freeze (or revise) the validated SQL for a question.
14491451
@@ -1476,6 +1478,14 @@ def _freeze_sql(
14761478
"bytes_processed": int(bytes_processed or 0),
14771479
"validated_at": _now_iso(),
14781480
})
1481+
# WORKFLOW LINEAGE: the middle result is an instance of a specific plan's
1482+
# step for a specific question — record which plan (hash) and which step
1483+
# produced it, so the artifact is structurally attached to the frozen
1484+
# workflow, not just stored beside it. Revisions inherit the lineage.
1485+
if plan_hash:
1486+
rec["plan_hash"] = plan_hash
1487+
if produced_by_step:
1488+
rec["produced_by_step"] = produced_by_step
14791489
with open(path, "w") as f:
14801490
json.dump(rec, f, indent=1)
14811491
return path
@@ -1836,20 +1846,22 @@ async def plan_and_run(ctx: Context, node_input):
18361846
# a frozen plan — freeze its dry-run-validated output so replays of
18371847
# this exact question are numerically deterministic (and feedback can
18381848
# amend it auditably).
1839-
checked = next(
1849+
checked_step, checked = next(
18401850
(
1841-
v
1842-
for v in interp.state.values()
1851+
(k, v)
1852+
for k, v in interp.state.items()
18431853
if isinstance(v, dict) and v.get("valid") is True and v.get("sql")
18441854
),
1845-
None,
1855+
(None, None),
18461856
)
18471857
if checked:
18481858
_freeze_sql(
18491859
task["question"],
18501860
checked["sql"],
18511861
engine=str(checked.get("engine", "bigquery")),
18521862
bytes_processed=checked.get("bytes_processed", 0),
1863+
plan_hash=spec_hash,
1864+
produced_by_step=checked_step,
18531865
)
18541866
yield _msg(
18551867
"🧊 **SQL frozen** for this question — re-ask it (any session)"

contributing/samples/workflows/authored_workflow_ca_demo/plan_inspector.py

Lines changed: 42 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -176,17 +176,31 @@ def _kv(label, value, why, tag, tag_label) -> str:
176176
)
177177

178178

179-
def _sql_producing_step_ids(spec) -> list:
180-
"""The plan steps whose validated results the store freezes (the drafting
181-
loop in the ask-a-question plan)."""
182-
ids = []
179+
def _sql_producing_step_ids(spec, mid_results=()) -> list:
180+
"""The plan steps whose validated results froze. Prefer the lineage the
181+
artifacts RECORDED (produced_by_step); fall back to the drafting-loop
182+
heuristic for records frozen before lineage existed."""
183+
recorded = {
184+
r.get("produced_by_step")
185+
for r in mid_results
186+
if r.get("produced_by_step")
187+
}
188+
step_ids = {s.get("id") for s in spec.get("steps", [])}
189+
# lineage may point INSIDE a loop body — surface the loop in that case
183190
for s in spec.get("steps", []):
184-
sid = str(s.get("id", "")).lower()
185-
if s.get("kind") == "loop_until" and any(
186-
h in sid for h in _SQL_PRODUCER_HINTS
187-
):
188-
ids.append(s.get("id"))
189-
return ids
191+
if s.get("kind") == "loop_until":
192+
body_ids = {b.get("id") for b in s.get("body", [])}
193+
if recorded & body_ids:
194+
recorded.add(s.get("id"))
195+
ids = [i for i in recorded if i in step_ids]
196+
if ids:
197+
return ids
198+
return [
199+
s.get("id")
200+
for s in spec.get("steps", [])
201+
if s.get("kind") == "loop_until"
202+
and any(h in str(s.get("id", "")).lower() for h in _SQL_PRODUCER_HINTS)
203+
]
190204

191205

192206
def _mid_result(rec: dict) -> str:
@@ -206,8 +220,16 @@ def _mid_result(rec: dict) -> str:
206220
f" <code>{rec.get('sql_hash', '')[:16]}</code> · validated"
207221
f" {str(rec.get('validated_at', ''))[:19]} ·"
208222
f" engine {html.escape(str(rec.get('engine', '')))} ·"
209-
f" {len(revs)} human revision(s)</div>"
210-
f"<details><summary>the validated artifact (SQL, in this instance)"
223+
f" {len(revs)} human revision(s)"
224+
+ (
225+
" · <b>lineage:</b> plan"
226+
f" <code>{html.escape(str(rec['plan_hash']))}</code>, step"
227+
f" <code>{html.escape(str(rec.get('produced_by_step', '?')))}</code>"
228+
if rec.get("plan_hash")
229+
else ""
230+
)
231+
+ "</div>"
232+
"<details><summary>the validated artifact (SQL, in this instance)"
211233
f"</summary><pre>{html.escape(rec.get('sql', ''))}</pre></details>"
212234
+ (rev_html or "")
213235
+ "</div>"
@@ -216,7 +238,7 @@ def _mid_result(rec: dict) -> str:
216238

217239
def _plan_card(name: str, env: dict, mid_results=()) -> str:
218240
spec = env.get("spec", {})
219-
frozen_ids = _sql_producing_step_ids(spec) if mid_results else []
241+
frozen_ids = _sql_producing_step_ids(spec, mid_results) if mid_results else []
220242
parts = [
221243
f'<div class="card"><h2 style="margin-top:0">Frozen workflow:'
222244
f" {html.escape(name)} — “{html.escape(spec.get('goal', ''))}”</h2>",
@@ -440,7 +462,13 @@ def main() -> str:
440462

441463
cards = timeline
442464
for name, env in envs.items():
443-
attach = in_session if name == "sequence" else ()
465+
short = str(env.get("spec_hash", ""))[:12]
466+
attach = [
467+
r
468+
for r in in_session
469+
if r.get("plan_hash") == short
470+
or (not r.get("plan_hash") and name == "sequence")
471+
]
444472
cards += _plan_card(name, env, attach)
445473
if other:
446474
cards += (

contributing/samples/workflows/authored_workflow_ca_demo/test_ca_demo_agent.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1139,6 +1139,22 @@ def test_sql_freezing_roundtrip_and_revision_history(tmp_path, monkeypatch):
11391139
assert "calendar quarters" in rec2["revisions"][0]["feedback"]
11401140
# unknown question -> None
11411141
assert demo._load_frozen_sql("never asked") is None
1142+
# WORKFLOW LINEAGE: the middle result records which plan + step produced
1143+
# it, and revisions inherit the lineage (the artifact is structurally
1144+
# attached to the frozen workflow, not just stored beside it).
1145+
demo._freeze_sql(
1146+
"lineage q",
1147+
"SELECT 1",
1148+
plan_hash="abc123def456",
1149+
produced_by_step="sqlgen",
1150+
)
1151+
rec3 = demo._load_frozen_sql("lineage q")
1152+
assert rec3["plan_hash"] == "abc123def456"
1153+
assert rec3["produced_by_step"] == "sqlgen"
1154+
demo._freeze_sql("lineage q", "SELECT 2", feedback="tweak it", previous=rec3)
1155+
rec4 = demo._load_frozen_sql("lineage q")
1156+
assert rec4["plan_hash"] == "abc123def456" # lineage survives revisions
1157+
assert len(rec4["revisions"]) == 1
11421158

11431159

11441160
def test_frozen_sql_replay_plan_is_static_and_clean():

0 commit comments

Comments
 (0)