Skip to content

Develop - #111

Merged
dkackman merged 41 commits into
masterfrom
develop
Sep 13, 2026
Merged

dkackman merged 41 commits into
masterfrom
develop

Conversation

@dkackman

Copy link
Copy Markdown
Owner

No description provided.

dkackman and others added 30 commits September 12, 2026 19:10
…de worktrees

Fingerprint leaves output:latest unpinned, drops iterations, adds
cached_steps and per-device estimate, nullable download sizes, and
notes the 409 must be server-side since dw_mcp is an httpx client.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… a sub-workflow is read by one resolver

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…or the caller's arguments

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…try when measured, plus each composed child

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…irst

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…sting's

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… than the listing

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#85 stage 1: POST /api/validate answers with the run's plan.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…rve, by the run's own preparation

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… asks without blocking behind a job

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ould serve

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…queued it

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…n's plan, and a changed plan is a 409

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… and a 409 re-quotes

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#85 stage 2: acknowledged_cost binds to the validated plan, a changed plan is a 409, jobs record the acknowledgement form, and the plan's cached_steps comes from the worker's step cache.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…the job tools name the acknowledgement field

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…not by probing the disk, and a composed child's digest is in the fingerprint

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…nd check, so a caller's 400 stays a 400

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ver read as the next probe's answer

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…a 409 without a measured estimate says so

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…d job says so

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
#85: the editor shows a validate answer's plan; a bound job says so.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
dkackman and others added 11 commits September 13, 2026 09:35
Setting a result's file_base_name glued it onto the front of the
<workflow id>-<step name>.<index> name it was meant to replace, with no
separator, so the one field that exists to make a file name predictable
produced a name the caller still had to read back out of the manifest.

It now replaces that name. The derived name is what made two steps' files
distinct, so a caller that sets the same one twice in a subfolder lands on
output_file_path's existing -2 counter rather than overwriting.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Measures tools/list, the server instructions, the plugin skills and the
fattest tool results against lem, and recommends where the tokens
actually are: a get_guide with no section costs more than the whole
55-tool surface does on connect.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The dw/path-injection scan flagged one stat in resolve_sub_workflow:
`os.path.isfile(candidate)` ran on `normpath(join(base_dir, name))` with
nothing checking that the result was inside anything. Every caller does
refuse the path afterwards - create_step_action validates the resolved
path against its confinement, and the run's own error for an escaping
reference is unchanged - so no caller read it. But resolution is not the
enforcement point, and that left a real hole one frame up:
realize.read_sub_workflow is handed the run's workflow_dir, which is None
for a bare CLI run, and validate_workflow_path(path, None) normalizes
without confining. An unconfined run therefore *read* a sub-workflow
outside its catalog (the run then refused it, and realization digested
it first).

Containment now happens before the stat, against the root the candidate
would be handed back with: the caller's confinement when it named one,
else the catalog root the run itself would confine to. That second rule
moved to workflow_sources.catalog_root so the resolver and
workflow.catalog_root_dir cannot drift apart - the latter now delegates.

Two details the surrounding design already insisted on, kept:

- normpath before the validator, because validate_path refuses a '..'
  outright - so a climb that stays inside the root is collapsed first and
  '../models/x.json', the form every template uses, still resolves.
- a climb out is a PathTraversalError, not a SubWorkflowNotFound: a
  refusal, not a name that was absent, which is the distinction the
  not-found message is careful about.

Tests: the resolver refuses a climb out (confined and unconfined) and
still resolves a climb to a sibling catalog folder; the unconfined read
now answers None rather than the file's bytes, while the sibling climb
stays readable.

Co-Authored-By: Claude <noreply@anthropic.com>
A `video` argument given a file path or an `asset:` was loaded by
fetch_video -> diffusers' load_video, which reads frames and drops the
rate. pair_audio then had nothing to carry, so a 24 fps shot paired with
a soundtrack was written at result.fps's default of 8: the same frames,
three times long, with the track that was cut to fit ending a third of
the way in, and nothing warned. `result.fps` rescued it for a caller who
knew to reach for it.

fetch_video now wraps the frames in a FrameList - a list subclass whose
only addition is `.fps`, read from the container without decoding - so
every consumer works unchanged while `getattr(video, "fps", None)`, the
question AudioVideo, concat_videos and interpolate_frames already ask,
gets a real answer. A file that states no rate stays a plain list; an
unopenable one is not an error, since the frames are already read.

This is the file-loading half of #84, which fixed the same silent
three-times-long failure for a join. interpolate_frames gets it too: a
`video` argument from a file now multiplies a real source rate rather
than reporting none.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…passed

Two failures from the verification of the acknowledged-cost work.

1. The estimate did not scale with a fan-out list. A `cost` figure is
measured against a workflow's stored defaults, and `per_entry` - the
measured per-entry rate that re-prices it - is set on no template yet, so
`templates/minimax/dialogue-short` quoted 42.0 minutes for 2, 5 and 10
shots alike. The plan knew it would run 13 steps over 10 shots and quoted
the 5-shot figure anyway, and a bound acknowledgement then confirmed the
wrong number because the fingerprint matched.

The catalog total is now extrapolated linearly over a list whose length
the caller changed, with a new `basis` value - `derived` - saying it is an
estimate rather than a measurement. `catalog` now means what it says: the
stored total for a run whose lists are the ones it was measured with. A
measured `per_entry` still beats the extrapolation, `other_device` is not
extrapolated (re-pricing a figure from another accelerator would dress a
guess as arithmetic), and when more than one list changed there is nothing
honest to extrapolate along, so the figure is withheld (`unknown`) rather
than quoted for one of them.

2. A bare `acknowledged_cost=true` recorded `acknowledged: "none"`. The
MCP layer treated the boolean gate as its own and sent nothing, so the
server had nothing to record and a job queued on an acknowledgement was
indistinguishable from one that never passed a gate. It now sends `true`.

Also the two nits from the same report: the 409 message ends its sentence,
and it now carries the `{fingerprint, minutes, downloads}` shape to resend
rather than only the new fingerprint.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The measured assessment found that the tool schemas are not what costs an
agent its context - single unbounded results are. One `get_guide` with no
section is ~19.6k tokens, more in one call than the entire 55-tool surface
costs to connect, and `get_schema` had no way to ask for less at all.
Approved items 1-4 of that assessment.

- `get_guide(name)` with no section answers the guide's index: its
  opening, its first section, and `sections`/`withheld` naming the rest,
  with a note saying how to fetch one. Nothing is unreachable, only
  unspent by accident.
- `get_schema(section=...)` takes `steps`, `pipelines`, `tasks`, `result`,
  `variables` or `configuration` and answers that fragment plus
  `elsewhere`, which names the section holding each definition it still
  `$ref`s. Additive - the no-argument call is the whole schema, which is
  what the web UI reads.
- `list_workflows()` with no filter cuts each entry to its summary and
  shape, because the whole catalog in full detail is ~6.8k tokens for a
  question that is really "which shape do I want". Asking with a `shape`
  returns the full entries.
- The two narrative moves: `wait_for_job`'s measured H3 lead-in figures go
  to the minimax-h3 skill, which loads exactly when H3 is in play, and
  `get_memory`'s pinned-host-cache explanation to a new ACCELERATION.md
  section. Every rule stays in both descriptions.

Result shapes change for the two no-argument calls - breaking-change for a
scripted consumer. Also carries the #85 409-message and docstring edits in
the files those two share.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ream

diffusers' _write_audio takes two channels and nothing else, so pairing a
mono voice track onto a picture died at the encode with a raw tensor shape
after the run had already been paid for. as_audio_track now duplicates a
single channel into two - lossless, and the ordinary case for dialogue -
and emits a warning saying it did, so the change to what was handed in
reaches the caller rather than only the log.

The pair_audio docstring carries both that and the frame-rate rule #104
added elsewhere, which is #107's second item: get_task reads the
implementation's own docstring, and that is where an author composing a
task step looks first.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nothing

Two of the three one-liners from verifying #85/#104 (the third, the
pair_audio docstring, is in the commit before this one):

- the re-acknowledge object in the cost gate's 409 was built field by
  field in an f-string, so the common case - an inline workflow, which
  never has a measured estimate - printed 'minutes': None, which is not
  JSON a caller could paste back. The whole object goes through
  json.dumps now.
- cached_steps: 0 cannot be told from 'probed and missed' out there, and
  the difference is that an unseeded workflow has no cache at all.
  validate now warns, once, at the point of use.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@dkackman
dkackman merged commit ecf3b45 into master Sep 13, 2026
8 of 9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant