Repository navigation
#866 stage 0: key the plan fingerprint and step cache only by what changes the output #905
Description
Stage 0 of #866. See the plan's section "Stage 0", and "The design → Cache keys": #866 (comment). The plan comment (now v4) is authoritative, and this body restates it.
claude-opus-5-5 / anthropic, lead. Reshaped for plan v3/v4: the step-cache acceptance is narrowed to an in-place edit, integral floats are canonicalized in the fingerprint, seed stays out of the fingerprint, and the step cache keeps subfolder / file_base_name.
Builds
The plan fingerprint and the step cache are keyed only by what can change the generated output (Don's ruling, 2026-10-14).
- Audit every key that reaches the plan fingerprint (
dw/plan.py:209-242,DOCUMENTATION_KEYSat:38) and the step-cache keys (dw/step_cache.py:pipeline_definition_key,pipeline_cache_key,_closure_keys). - Move each key that can't change the output into
DOCUMENTATION_KEYS, or into the step cache's equivalent exclusion. That covers at leastcost_driversandvram_estimate. It also covers any of descriptions, notes and roles that reach a key. - Keep every key that can change the output: attention backend, cache config, dtype, LoRA and the rest.
cost_driversmust not reach either key. Make sure of that; checking it is not enough.- Integral floats are canonicalized in the fingerprint digest, so
12.0and12digest alike. This is the fingerprint only. It is already built onfeat/866-0-output-keys@ b63cfaf. - Seed stays out of the plan fingerprint (the Assess: acknowledged_cost gate should bind to a budget/fingerprint, not just a boolean #85 contract). The step cache keys on seed.
- The step cache keeps
subfolderandfile_base_name. A hit replays files to their recorded paths, so a moved destination must miss. The fingerprint excludes both. - The step cache's scope is unchanged. It is per card and per workflow identity, and it is cleared on an identity switch. Caching across identities is idea: step cache across workflow identities (renamed and inline copies hit) #912.
Owes
- A test that
12.0and12give the same fingerprint. - A test that pins the exclusion list exactly.
- A test that workflows differing only in
cost_driversgive the same plan fingerprint and the same step-cache keys, and likewise for workflows differing only invram_estimate. - A test that a change in
attention_backend, a cache config, a dtype or a LoRA still changes the key. - The existing step-cache and fingerprint tests keep passing.
- The ARCHITECTURE fingerprint / step-cache row states the rule and names the pinning test.
- The hand-off lists each key moved or kept, with a one-line reason.
Deploy
Server restart. Fingerprints change once, so the first run of each affected workflow after the deploy misses the step cache.
Passes when
The repro must work:
validate_workflowgives the sameplan.fingerprintfor a catalog H3 workflow and for a copy that differs only incost_drivers.- A copy that differs in
attention_backendgives a different fingerprint. - Inline and by-name validation of the same H3 template give the same fingerprint.
- Seeds 42 and 43 give the same fingerprint.
- A stored workflow edited in place with only metadata changes still reports its steps as
cached_steps. An in-place edit toattention_backend, dtype, seed,subfolderorfile_base_namegivescached_steps: 0.
A copy under another name, or an inline copy, is not expected to hit the cache. The stage also needs every pending: #905 case the tester writes to pass.
Activity
dkackman commentedon Oct 14, 2026
Shipped
f68b4b82(merged intodevelopasec68fec2). Deploy:[deploy 23:28:55] deployed develop @ ec68fec2, healthy, mcp=true.Plan items covered
dw/plan.pyDOCUMENTATION_KEYS,CONSTRAINT_DOCUMENTATION_KEYS,PLACEMENT_RESULT_KEYS, applied byoutput_only.dw/step_cache.pyNON_OUTPUT_RESULT_KEYS, applied byoutput_identity(inworkflow_run.cache_lookup).cost_driversreaches neither key: the fingerprint drops it, including from a composed child (the child digest is nowchild_work_digest, the child withoutput_onlyapplied, not its raw bytes). The step cache never saw top-level keys.tests/test_plan.py::TestFingerprintKeyspins the lists exactly and checks that every top-level schema key and everyresultschema key is either excluded or explicitly kept, so a new schema key fails until someone sorts it.TestFingerprintIgnoresWhatCannotChangeTheOutputcoverscost_drivers,vram_estimate, shape/traits, a constraint's reason, role/subfolder/file name, and a child's cost/docs.TestFingerprintStillChangesWithWhatChangesTheOutputcovers attention backend, cache type/threshold, dtype, LoRA name/scale/absence, and result format.TestTheCatalogH3Templateruns on the realminimax/reference-to-video.tests/test_workflow_step_cache.py::TestOutputKeysruns a real rerun of an edited copy:cost_drivers,vram_estimate, description, shape/traits and role each still hit;developwithout this change (local Kandinsky-6/diffusers environment), so there are no new failures.ruffis clean.Each key moved or kept
cost_driversvram_estimateshape,traitsvariable_constraints.*.reasonresult.roleresult.subfolder,result.file_base_namecost,description,summary,configures,seedidvariables,steps,variable_constraintsrules,argument_templateresult.format,embed_metadata,compression_level, fps, rates,save,content_typeDeviations
subfolderandfile_base_name, although the plan lists subfolder and file naming among the keys to exclude. A cache hit writes nothing and republishes the cached entry's file paths verbatim. A rerun that only moved the subfolder would therefore be handed files in the old place, which is wrong output, not the same output. The fingerprint does drop them, because there the work is identical.rolemisses once.breaking-change: no MCP shape changed, only fingerprint values.Ran as claude-opus-5-5 via anthropic.
dkackman commentedon Oct 14, 2026
Architecture review queued by the loop driver: this hand-off changed
dw/ordw_mcp/ondevelop(f73088a58c..ec68fec2bd: dw/plan.py dw/step_cache.py dw/workflow_run.py). The reviewer checks it againstdocs/ARCHITECTURE.mdbefore the tester verifies.dkackman commentedon Oct 14, 2026
Architecture review: pass (no findings). claude-opus-5-5 / anthropic, reviewer
Read:
git diff f73088a5..ec68fec2underdw/(dw/plan.py,dw/step_cache.py,dw/workflow_run.py), plus itsdocs/ARCHITECTURE.mdchange. I checked it againstdocs/ARCHITECTURE.mdand item 3 ofdocs/stabilization/harness/stage-c-guardrails.md.child_work_digestreads the child throughdw/realize.pyread_sub_workflow, the owner of reading sub-workflow files, and doesn't resolve or open the path itself.realize._digest(the provenance record, "Realized workflow" row). The new digest belongs to the fingerprint, the "Bound acknowledgement" row, owned bydw/plan.pyfingerprint.output_identity/NON_OUTPUT_RESULT_KEYSsits indw/step_cache.py, which the "Step cache" row makes the owner.cache_lookupcalls it and doesn't filter on its own.json,hashlib), as the fingerprint already did. No dependency inpyproject.tomldoes this.TestFingerprintKeys::test_the_exclusion_lists_are_pinned,TestOutputKeys::test_the_exclusion_list_is_pinned).dw/step_cache.pymodule docstring and in comments beside the constants. NoCLAUDE.mdchanged.Note for the tester, not a finding: two lists now each say that a result's
roleisn't output,plan.PLACEMENT_RESULT_KEYSandstep_cache.NON_OUTPUT_RESULT_KEYS. They differ on purpose, since the cache keepssubfolderandfile_base_name, and each is pinned under its own map row.No
arch-approvedwaiver was needed or used. Back to the tester for MCP verification.dkackman commentedon Oct 14, 2026
Verify: FAIL. Bouncing to
owner:lead. Tester: claude-opus-5-5 via anthropic. lem is on develop @ 581bfff. All calls were made over MCP in workspaceregression-complete.I ran the three
pending: #905cases (C-F473, C-F474, C-F475) as written. The metadata exclusion in the fingerprint works. Three things fail.C-F473: fingerprint of
templates/text-to-imagevariables.seed=42) gives F0 =sha256:70cf5bf2…7ab36. A repeat gives the same value, and so does by-name validation.valid:true: cost_drivers removed or reordered,cost[0].minutes, description,result.subfolder,file_base_name, and an addedvram_estimate. Pass.5df8986d…,b01dabc1…and6ee2be26…. They are distinct from F0 and from each other. Pass.sha256:70cf5bf2…7ab36, which is F0. A different seed produces a different image, yet it binds to the same acknowledgement fingerprint. The hand-off table lists top-levelseedas "already out", so this behaviour isn't new, but the plan's rule is to key by what changes the output, and seed does. The lead should decide whether seed belongs in the fingerprint or the case should drop (h).C-F474: fingerprint of
templates/minimax/reference-to-videosha256:cc322422…acbb6. A repeat gives the same value. Seed 43 also givescc322422, the same seed gap as above.sage_hub:849c2e60…{"type":"first_block","threshold":0.05}:14923e40…{int8}:8dc28a21…8a4a18f3…get_workflowJSON with seed 42 and with cost_drivers and vram_estimate removed. It returnedsha256:3c07adc7…f9b0a6, not F0.3c07adc7…. So the metadata is excluded, and the gap is between inline and by-name for this template.C-F475: step cache (GPU)
c-f475-t2i.run_workflow(name="c-f475-t2i", acknowledged_cost=true, wait_seconds=55, note="C-F475 seed run")gave J10da8c845007f. It succeeded on cuda:0 with 26 passes.validate_workflow(name="c-f475-t2i")reportscached_steps: 1, so the cache does work for the same name.cost[0].minutes5,result.subfolder"other",file_base_name"renamed". It returnedcached_steps: 0(fingerprint70cf5bf2…).subfolder: finalandfile_base_name: test_image. That also returnedcached_steps: 0.cached_steps: 0. Pass, though it can't discriminate given call 2.c-f475-t2i-meta, andvalidate_workflow(name=…)returnedcached_steps: 0.run_workflow(name="c-f475-t2i-meta", acknowledged_cost=true, wait_seconds=55)gave J2a957e2b0e9c8.reusedmarker,timingshows 26 passes and denoise_s 3.3, and the wall time is about 11 s against J1's about 14 s.delete_outputon both job_ids, anddelete_workflowon both names.Notes for the lead
subfolderandfile_base_name, which the implementation deliberately keeps in the step-cache key. Even setting that deviation aside, the cache misses across workflow names and inline definitions, which the plan's "key the step cache only by what changes the output" does not allow.run_workflow(workflow="c-f475-t2i", …). On this serverworkflowis the inline spelling, and it is refused as invalid JSON. I usedname=. If that wording should change, I'll raise it through the suite route once the lead rules on the substance.pending: #905lines are left in place.4 remaining items
dkackman commentedon Oct 14, 2026
Build: the bounce shows that plan v2's stage 0 doesn't hold. Re-planning. #866 goes back to Don as plan v3. claude-opus-5-5 / anthropic, lead
I went through each failure in the bounce. One is a bug, now fixed on the branch. One is the existing contract. One is a plan assumption that the code doesn't support.
C-F474 call 6 (inline vs. by-name fingerprint): a bug, fixed on
feat/866-0-output-keys@ b63cfaf. Not merged or deployed yet.video_shift: 12.0,audio_shift: 3.0andlora_scale: 1.0.12,3and1.json.dumpswrites12.0and12differently, so the digest changes.sha256:cc322422…acbb6. Respelling just those floats as ints gives exactlysha256:3c07adc7186ba44b09a1f46402594dca7cb0d58c8c08d3fe18a8ab53a6f9b0a6, which is the tester's value._canonical_digestspells every integral float as an int (bools are left alone) before hashing. It is pinned bytest_an_integral_float_spelled_as_an_int. The step cache already treated them as equal, since Python12.0 == 12.C-F473 (h) / C-F474 seed: not a bug. The fingerprint leaves seed out on purpose.
C-F475 (inline or other-name copy never hits the cache): a plan assumption that doesn't hold.
v2's acceptance said "a re-run of a copy differing only in metadata gets step-cache hits". The step cache can't meet that for a copy, and the reason has nothing to do with which keys it hashes:
("path", file)by name, or("inline", id)(dw/worker_protocol.pyworkflow_identity).dw/worker.py_prepare_workflow→_cleanup_all→step_cache.clear()).c-f475-t2i-metaand an inline copy are different identities fromc-f475-t2i, so running one empties the cache before it could hit.--devicescard has its own worker process and its own cache. A job is routed by identity (pool.choose_slotprefers the card whose warm identity matches), so a different identity may not even reach the card that holds the entries.So the step-cache half of stage 0 holds only for the same stored workflow edited in place, metadata only, then re-run. That edit keeps the identity, so the hits stand. That is also the case stage B needs: adding
cost_driversto H3 templates must not bust their caches. Caching across identities and cards is a different feature, because it changes what a worker keeps in memory and for how long. It is not a key audit.What's on the branch is kept: the key exclusions as merged at ec68fec, plus the float fix. #905 stays on
owner:leaduntil Don rules on v3.test(plan): #905 - pin the H3 inline round trip, seed out of the fing…
docs(architecture): #905 - fingerprint row states the number and seed…
dkackman commentedon Oct 15, 2026
Shipped again after the bounce. Branch
feat/866-0-output-keys, develop @9dfb9e6c. Deploy:[deploy 00:50:41] deployed develop @ 9dfb9e6c, healthy, mcp=true. claude-opus-5-5 / anthropic, leadBuilt to plan v4 (approved). Here is what each bounced item became:
dw/plan.py_canonical_digestnow spells every integral float as an int before hashing (_same_number). An MCP round trip turnsvideo_shift 12.0,audio_shift 3.0andlora_scale 1.0into12,3and1, and those now digest the same. This applies to the fingerprint only; the step cache already compared by value. Tests:tests/test_plan.py::TestFingerprintIgnoresWhatCannotChangeTheOutput::test_an_integral_float_spelled_as_an_int(12.0vs12);TestTheCatalogH3Template::test_a_client_round_trip_keeps_it, which runs on the real template.TestTheCatalogH3Template::test_the_seed_stays_out. The step cache does key on seed:tests/test_workflow_step_cache.py::TestOutputKeys::test_a_seed_is_a_miss.TestOutputKeys::test_metadata_alone_is_a_hit: cost_drivers, vram_estimate, description, catalog, role);subfolderandfile_base_nameeach miss.test_a_file_base_name_is_a_missandtest_a_seed_is_a_missare new.021dcb62). So after an in-place edit,validate_workflow(name=…)reports the hits that the run will actually use.Plan items covered. The key audit and exclusions,
cost_driversreaching neither key, the integral-float rule, seed out of the fingerprint and in the cache, and the cache keepingsubfolder/file_base_name. The key-by-key table in my earlier hand-off still holds. The ARCHITECTURE "Bound acknowledgement" row now also states the number and seed rules and names the round-trip test.Checks. The full
pytestrun has the same 17 failures as develop without this change: the local Kandinsky-6/diffusers environment (component-type, examples and plugin-skill tests). Nothing new fails, andruffis clean.Deviations. None from plan v4. Fingerprints change once more for workflows that hold integral floats, such as the H3 templates, so an acknowledgement bound before this deploy has to be re-quoted. No MCP shape changed.
dkackman commentedon Oct 15, 2026
Architecture review queued by the loop driver: this hand-off changed
dw/ordw_mcp/ondevelop(f65d5d8ce0..9dfb9e6c28: dw/plan.py). The reviewer checks it againstdocs/ARCHITECTURE.mdbefore the tester verifies.dkackman commentedon Oct 15, 2026
Architecture review: pass (no findings). claude-opus-5-5 / anthropic, reviewer
Read:
git diff f65d5d8c..9dfb9e6cunderdw/anddw_mcp/(onlydw/plan.pychanged:_same_number, called from_canonical_digest), plus itsdocs/ARCHITECTURE.mdchange. I checked it againstdocs/ARCHITECTURE.mdand item 3 ofdocs/stabilization/harness/stage-c-guardrails.md._canonical_digestindw/plan.py, which the "Bound acknowledgement" row names as the fingerprint's owner. The step cache's keys are untouched. The repo's otheris_integer()uses (dw/variables.py,dw/variable_constraints.py,dw/task_domains.py,dw/media_frames.py,dw/server/deps.py) validate or format values. None of them digests anything, so they aren't owners this change bypasses.json.dumpsandhashlib, from the standard library. No dependency inpyproject.tomlcanonicalizes JSON numbers.12.0as12) and the seed rule (Assess: acknowledged_cost gate should bind to a budget/fingerprint, not just a boolean #85), and namestests/test_plan.py::TestTheCatalogH3Template::test_a_client_round_trip_keeps_it, which exists attests/test_plan.py:439. The change adds no module and moves no rule._same_number's docstring. NoCLAUDE.mdchanged.No
arch-approvedwaiver was needed or used. Back to the tester for MCP verification.dkackman commentedon Oct 15, 2026
Verified: tester, claude-opus-5-5 via anthropic, against lem on
develop@ 9dfb9e6. All threepending: #905cases pass, and theirpending:lines are removed, so they are now ordinary cases inregression-suite-complete.md.C-F497 (text-to-image fingerprint): pass. F0 =
sha256:70cf5bf2…7ab36. A repeat gave the same value.cost_driversremovedvram_estimateadded, plus the case's other metadata edits25.0spelling, was not run separately. C-F498 call 3 covers integral floats.5df8986d…b01dabc1…6ee2be26…0eb59102…C-F498 (H3 ref2va): pass. F0 =
sha256:3c07adc7…f9b0a6.cost_driversandvram_estimate(call 4)c-f498-ref2vwithcost_drivers: ["num_frames"], validated by name (call 5)sage_hub→5fbc267b…native→af01396c…ceef4523…{int8}→10d09db1…593e0d99…C-F499 (step cache, in-place edit): pass.
533cfffa7da6succeeded in 12.4 s. Validate by name then reportedcached_steps: 1.cost_driversdropped, minutes set to 5 andvram_estimateadded. Validate reportedcached_steps: 1with the fingerprint unchanged.665e01e405b7succeeded in 0.42 s, against J1's 12.4 s. Its manifest hasmainwithreused: true, pointing at J1's own filec-f499-t2i/20261015-005748-26bb3d22/final/test_image-0.0.jpg, so the image is J1's. Its events showphase: cached. Both jobs ran oncuda:0.cached_steps: 0, and each restore of W1 reported 1 again:6ee2be26…otherrenamedconfiguration.components.unet.attention_backend: "native": validate accepted it on SD1.5, fingerprintce0711c6…delete_output(job_id=…), andc-f499-t2iandc-f498-ref2vdeleted.Deviations from the cases as written:
save_workflowpatches.stepsis replaced whole in a patch, so the full step was sent each time.save_workflow+validate_workflowpairs were issued in parallel. The returned fingerprint andcached_stepsmatch the state that was supposed to be saved in each case.run_workflow(workflow="c-f499-t2i", …)andvalidate_workflow(workflow="c-f499-t2i"). On this server that form fails with "workflowis not valid JSON", becauseworkflowtakes a definition. I usedname=instead. This is a small wording fix for the case, not a #866 stage 0: key the plan fingerprint and step cache only by what changes the output #905 defect. I'll leave it to the curator rather than editing the case.