Skip to content

#866 stage A: scale the catalog estimate along steps, canvas and frames #906

Closed

Description

@dkackman

Stage A of #866. See the plan's section "Stage A", and "The design → Scalable drivers / Tempering": #866 (comment). The plan comment (now v4) is authoritative, and this body restates it.

claude-opus-5-5 / anthropic, lead. Reshaped for plan v4: the acceptance uses a freshly saved copy instead of a fresh workspace, and the 30% accuracy bar is now a unit test on the scaling function.

Builds

  • When a declared scalable cost_driver differs from its default, _own_price and _child_catalog_price scale the curated figure. Today they reset to unknown. The scalable drivers are num_inference_steps, width, height and num_frames. A per-entry num_frames in a list such as shots counts as the sum over the list.
    • minutes = curated × (steps/steps₀) × (w·h / w₀·h₀) × (frames/frames₀). The step ratio is plain (Q2).
    • The quote is basis: "derived", and scaled names each axis that moved.
    • It multiplies with the existing list-length scaling (_repriced).
    • A non-numeric driver value still gives unknown.
  • _tempered blends toward the scaled figure when drivers moved. curated_minutes keeps its name.
  • The UI (ui/src/lib/plan.ts:83-90) shows scaled for derived.
  • The rule "a cost entry is measured at the workflow's variable defaults" is written in four places:
    • the schema cost description;
    • WORKFLOW_GUIDE, in the derived definition and the cost rule;
    • docs/SERVER.md;
    • the ARCHITECTURE estimate row.
  • dialogue-short's cost is re-priced to its 960x544 defaults, ≈21 min, and its reason prose is updated to match (Q3).
  • Out of scope: which price wins for a saved copy that carries its own cost (catalog vs. inherited). The current precedence stays.

Owes

  • The tests/test_plan.py tests that pin unknown after a scalar shift now expect derived.
  • New tests:
    • a steps ratio;
    • a canvas ratio;
    • per-entry frames summed;
    • the three combined;
    • a non-numeric value still gives unknown;
    • tempering toward the scaled figure.
  • The accuracy-bar unit test on the scaling function, using the issue's table. Each pair must land within 30% of the measured minutes:
    • B4→B1: 16.5 vs 15.1;
    • B4→B5: 13.0 vs 11.8;
    • B3→L5: 32.3 vs 29.5.
  • ui/src/lib/plan.test.ts.

Deploy

Server restart, UI build.

Passes when

The repro runs on a freshly saved copy. Run history is server-wide, so skip a row whose rung is already observed.

  • templates/minimax/reference-to-video at 30 steps gives basis: "derived" with minutes = catalog × 30/steps₀, and scaled names steps.
  • At its defaults it stays catalog.
  • dialogue-short at its defaults gives ≈21 min.
  • The accuracy-bar unit test passes.

The stage also needs every pending: #906 case the tester writes to pass.

Activity

  1. added
    featureWork bigger than a fix: designed with Don, built in stages (harnest R11)
    owner:leadFeature lead's turn (harnest R11)
    stageA child issue: one stage of a feature
    on Oct 14, 2026
  2. added 2 commits that reference this issue on Oct 15, 2026
  3. dkackman commented on Oct 15, 2026

    @dkackman
    OwnerAuthor

    Stage A built and deployed. Lead, claude-opus-5-5 via anthropic.

    Shipped: develop @ a5f56b3c (merge of feat/866-A-scale-catalog: ddd0970, 7f70be9, plus a docstring commit). Deploy line: [deploy 01:22:03] deployed develop @ a5f56b3c (healthy after 5s, mcp=true).

    Plan items covered

    • Scaling. _own_price and _child_catalog_price scale the curated figure. When a declared num_inference_steps, width/height or num_frames moves off its default, minutes = curated × steps/steps₀ × (w·h)/(w₀·h₀) × frames/frames₀. Per-entry num_frames in a list driver such as shots is summed. The result is basis derived, and scaled names each moved axis, e.g. scaled from the curated 7.9 min: steps 30 vs 9. The scaling is dw.plan.scale_curated. Tests: TestDriverScaling (steps, canvas, one side of the canvas, per-entry frames summed, all three combined).
    • Multiplies with _repriced. Test: test_it_multiplies_with_the_list_length (4 shots for 2, at 18 steps for 9, gives 40 from 10).
    • A non-numeric value stays unknown. Tests: test_a_non_numeric_value_stays_unknown, plus scale_curated returns None.
    • Tempering. A thin observed figure blends toward the scaled figure. curated_minutes is the scaled value and scaled is carried. Test: test_a_thin_observed_figure_tempers_toward_the_scaled_one. A settled observed figure (3+ runs) is not scaled.
    • Accuracy bar. TestScalingAccuracy, each pair within 30%: B4→B1 16.5 vs 15.1, B4→B5 13.0 vs 11.8, B3→L5 32.3 vs 29.5.
    • Real templates (TestTheCatalogTemplatesScale):
      • reference-to-video at its defaults: 7.9 catalog.
      • At 30 steps: 26.3 derived, with "steps 30 vs 9".
      • At 1344x768: 15.6 derived.
      • dialogue-short at its defaults: 21.2 catalog.
      • dialogue-short with shot 1 at 192 frames: 23.5 derived, frames 705 vs 637 over shots.
    • dialogue-short. Cost 42 → 21.2. The vram_estimate.reason prose now says the 42 was the 1344x768 field run, re-priced to the 960x544 defaults. tests/test_catalog_structure.py pins 21.2.
    • UI. ui/src/lib/plan.ts shows scaled on a derived figure, e.g. ~26.3 min on cuda - estimated (scaled from the curated 7.9 min: steps 30 vs 9), from a figure measured on RTX 3090, and on an other-device one. Tests are in plan.test.ts.
    • Docs. The rule "a cost entry is measured at the workflow's variable defaults" is now in four places:
      • the schema cost description, which replaces "Never derived";
      • WORKFLOW_GUIDE, in its derived definition and its cost rule;
      • SERVER.md, in its basis list;
      • the ARCHITECTURE estimate row.
        The PlanEstimate.scaled description now covers derived. The OpenAPI and TS types are regenerated.
    • Earlier unknown pins now expect scaled figures:

    Deviations and choices the tester should know

    1. Another device's figure stays other_device (with scaled set and minutes scaled), not derived. This matches validate_workflow: plan.estimate ignores for_each entry count and frame count (32-shot batch quoted 1.8 min, took 2 h 48 m) #589's rule that a resized list on another backend keeps other_device.
    2. A moved driver outside the four scalable names still gives unknown. An example is the unit fixture's bare frames variable. Stage B is meant to change that.
    3. Per-entry frames when the list's length is already priced (a for_each list, re-priced by _repriced). The frames factor is then the mean-frames-per-entry ratio, so length × frames equals the summed-frames ratio and nothing is counted twice. A longer list with the same frames per entry has no scaled. A list driver that no for_each runs over scales by sum/sum.
    4. Frames scale whenever the summed frames differ, even when every value is one the defaults already carried. Defaults [124,141] with a run at [124,124] give ×248/265.
    5. A catalog parent whose composed child was scaled reports derived overall. The child's scaled is prefixed with its path (child.json: scaled from …). Several are joined with ; .
    6. A non-numeric value only reaches the plan when the default is text. With an int default such as width: 960, the variable expansion casts the argument to int. If the cast fails (e.g. "wide"), it logs and keeps the default, so the plan quotes the catalog figure. Engine behavior is unchanged there. The unknown path is exercised with a text default.
    7. The response shape is unchanged. scaled was already an optional PlanEstimate key, so this stage doesn't carry breaking-change. Its behavior does change: moved steps or canvas now quote a number where they used to quote unknown.

    Local pytest: the same 17 failures as develop before this change (local mps and diffusers environment: Kandinsky6 pipelines and the mps VRAM ceiling), and none new. ruff is clean. UI vitest is 589/589, and check, lint and prettier are clean.

  4. added and removed
    owner:leadFeature lead's turn (harnest R11)
    on Oct 15, 2026
  5. dkackman commented on Oct 15, 2026

    @dkackman
    OwnerAuthor

    Architecture review queued by the loop driver: this hand-off changed dw/ or dw_mcp/ on develop (cf33dd1811..a5f56b3c4d: dw/plan.py dw/server/api_models.py dw/workflow_schema.json). The reviewer checks it against docs/ARCHITECTURE.md before the tester verifies.

  6. dkackman commented on Oct 15, 2026

    @dkackman
    OwnerAuthor

    Architecture review: pass. Reviewer, claude-opus-5-5 via anthropic.

    Read: git diff cf33dd18..a5f56b3c under dw/ and dw_mcp/ (dw/plan.py, dw/server/api_models.py, dw/workflow_schema.json), checked against docs/ARCHITECTURE.md and item 3 of stage-c-guardrails.md.

    • A second owner: none. The scaling (scale_curated, _driver_scaling, _list_frames_scaling, _scaled) lives in dw/plan.py, which is the module the map's The estimate row assigns. It replaces _scalar_driver_shifted there rather than adding a second check. It reuses _driver_comparable, _declared_drivers and _list_entry_field_shifted instead of re-deriving them. I looked at dw/server/deps.py:_scale_past_template, the Inherited cost row. It is a different rule: it scales a template's observed minutes for an inline copy's pipeline-step and frames-per-step count. This change does not touch it or re-implement it.
    • Build vs. buy: none. The new code is plain ratio arithmetic over workflow variables, and no pyproject.toml dependency does that. pyproject.toml is unchanged in the range.
    • The map: updated in the same change. The The estimate row now states the defaults rule, scale_curated, basis derived with scaled, the multiplication with the list-length re-price, and the tempering toward the scaled figure. It names TestDriverScaling and TestScalingAccuracy as evidence. No new module was added and no rule moved between modules.
    • Context: fine. The rationale is in dw/plan.py docstrings and comments (scale_curated, _list_frames_scaling, _driver_scaling, estimate). No CLAUDE.md changed in the range.

    There is no arch-approved on this issue and nothing was waived. This review does not cover the server behaviour or the Passes-when repro; those stay with the tester.

  7. dkackman commented on Oct 15, 2026

    @dkackman
    OwnerAuthor

    Verified by the tester (claude-opus-5-5 via anthropic) against lem on develop @ a5f56b3, over MCP only. Workspace: regression-complete.

    Stage repro: validate_workflow on templates/minimax/reference-to-video at num_inference_steps: 30 returns 26.3 min, basis: "derived", with scaled: "scaled from the curated 7.9 min: steps 30 vs 9". At its defaults the template itself is observed (33 runs, server-wide history), so I used fresh saved copies for the catalog-basis cases, as they specify.

    All five pending: #906 cases pass. I removed their pending: lines from regression-suite-complete.md.

    • C-F477 (refusals and edges):

      • width: 1000 is refused at arguments.width.
      • num_inference_steps: "thirty" is refused at arguments.num_inference_steps.
      • num_frames: 400 is refused at arguments.num_frames (at most 345).
      • width: 0 is valid with basis: "unknown" and minutes: null, and plan is present.
      • No numeric minutes came back on any of these.
    • C-F500 (10 calls on a copy of reference-to-video):

      Call Arguments minutes basis scaled
      0 defaults 7.9 catalog none
      1 steps 30 26.3 derived steps
      2 1344x768 15.6 derived canvas
      3 steps 30 + 1344x768 51.9 derived steps + canvas
      4 frames 192 12.2 derived frames
      5 steps 5 4.4 derived steps
      6 width 1344 11.0 derived canvas 1344x544
      7 frames 130 9.0 derived frames 141 vs 124, snap warning
      8 frames 345 21.9 derived frames
      9 defaults passed explicitly 7.9 catalog none

      No call reported low_confidence.

    • C-F501 (dialogue-short):

      • The template's cost is 21.2 and its vram reason now describes the re-pricing to 960x544.
      • A copy without observed returns 21.2 catalog.
      • With shot 0 at 192 frames (total 705) it returns 23.5 derived, scaled: "... frames 705 vs 637 over shots". Expected 21.2×705/637 = 23.46.
      • With 4 shots (total 496) it returns 16.5 derived. That is 0.78·D, the frames ratio alone, not 0.62·D.
      • Wording nit, not filed: the 4-shot scaled string says "scaled from the curated 17 min", but 16.5 = 21.2×496/637, not 17×496/637. It reads as though the per-shot share (21.2×4/5) is quoted as the base while the minutes use the full D.
    • C-F502 (accuracy ratios):

      • steps 12→30 gives r = 2.5; canvas 1344x768 at steps 30 gives r ≈ 1.975; canvas 1344x768 + int8 at steps 12→30 gives r = 2.5.
      • 6.6×2.5 = 16.5, inside B1's band of 10.6–19.6.
      • 6.6×1.975 = 13.0, inside B5's band of 8.3–15.3.
      • 12.9×2.5 = 32.3, inside L5's band of 20.7–38.4.
    • C-F480 (tempering, GPU):

      • Copy c-f480-t2i at num_inference_steps: 100 before any run: 0.8 derived, scaled: "steps 100 vs 25".
      • Run J1 0b2f1ccb0c49 succeeded in 21.3 s (T ≈ 0.36 min).
      • Re-validate: basis: "observed", runs: 1, tempered: true, observed_minutes: 0.4, curated_minutes: 0.8, minutes: 0.7. That is within [T, curated] and not the unscaled 0.2.

    Deviations from the cases as written (none of them touches a cost input):

    • The copies used shortened prompts, description and vram reason. cost, cost_drivers, variables, per-shot frames, constraints and steps were the template's.
    • C-F502's B4 and B3 were run by patching one copy in sequence rather than saving separate copies.
    • B4 left the LoRA variables in place. A merge-patch null deletes the key, and LoRA isn't a cost driver.

    Cleanup done: deleted c-f500-ref2v, c-f501-dialogue, c-f480-t2i, and J1's output.

  8. added
    status:verifiedTester confirmed the fix via a real MCP call
    and removed on Oct 15, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    featureWork bigger than a fix: designed with Don, built in stages (harnest R11)owner:testerTester's turn to actstageA child issue: one stage of a featurestatus:verifiedTester confirmed the fix via a real MCP calltarget:lem

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions