* Expect resolved STRING type for ModalMapInput boundaries
69a33e3 made _remote_output_io_type resolve wildcard "*" outputs through
their linked input, so a ModalMapInput boundary fed by a STRING list now
reports STRING, matching the mapped_input type the test already expects.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Add Remote Progressive Preview node
A local PreviewImage-style node that shows one empty slot per mapped item
when remote work starts and fills each slot as soon as that item's images
stream back, instead of waiting for ComfyUI to deliver the whole result.
- Planning records direct consumers as progressive_preview_target_node_ids
and exempts them from local bridge materializers.
- The remote shim streams boundary outputs for those targets; mapped item
payloads carry map_item_count and dispatch emits a start event.
- The host saves streamed images to temp, records them per prompt/node, and
sends modal_progressive_preview websocket events.
- The node reuses streamed slots on final execution and only materializes
bridge refs that were never streamed.
- web/progressive_preview.js renders the slot grid and hands off to the
native preview on the final executed event.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Execute every mapped item instead of reusing shared-session outputs
Mapped items share one remote session. The restored-session short-circuit,
meant for identical per-lane seed phases, looked outputs up by node id
only, so after the first item on a container every later item skipped
sampling and returned that earlier item's image under its own bridge key.
Payloads carrying map_item_index now always execute.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Keep finished progressive previews handed off to the native preview
Hide the panel with an inline display style so the frontend's DOM-widget
wrapper cannot override the hidden attribute, and ignore replayed events
for a prompt whose final images were already delivered.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Local mode runs rewritten components inside the local ComfyUI process and
exists only for developing the extension, yet it was the default, so users
who did not set the variable silently ran in it. settings now owns the
default (remote) through configured_execution_mode(), and every host-side
reader uses it. The Modal image environment pins local explicitly, as SSH
and Vast workers already do, so container code keeps its in-process
behaviour.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Share one Modal snapshot pool across lanes and log container placement
Each worker-pool slot key was a RemoteEngine class parameter, so Modal kept a
separate container pool and memory snapshot per lane. Restores only ever used
a same-slot snapshot, and every slot cold-booted on its own after each deploy.
Slot keys now collapse to one Comfy pool and one LLM pool. Cooldowns still
come from the full slot key, so the reserve lane stays a separate pool.
Containers now log one startup line with boot/restore kind, region, host, GPU
UUID, bound function id, and, for restores, where the snapshot was captured.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Classify snapshot-creating boots separately from real restores
Modal resumes a booting container in place after it captures a memory
snapshot, so the restore-state file exists for the snapshot's creator too.
A snapshot origin captured by the same task is now reported as
kind=snapshot-created instead of a restore.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
* Record the first controlled Modal snapshot restore test
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
The first fix listed modules from the entrypoint's own directory, but Modal
runs the deployed entrypoint from outside the repo copy, so no sibling module
logger was configured and durable-result timings were still dropped. List
modules beside cloud_runtime_logging.py instead, which is always imported
from the repo copy, and log the configured count and directory once.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Only the comfyui_modal_sync_cloud entrypoint logger had a handler in the
container. Flat-imported sibling modules (cloud_durable_invocation,
cloud_session_bridge, ...) fell through to the root WARNING handler, so
their INFO timing lines were dropped. That hid the durable-result step that
accounted for 28-90s per-item delays after execution finished.
Inside the container, give every shipped top-level module an INFO handler
that prints to the live sys.stdout at emit time (snapshot restores can swap
stdout), with propagation off so warnings are not printed twice.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Worker slots live in separate namespaces (mapped lanes use
worker-pool:slot:N, ordinary components worker-pool:comfy|llm:slot:N),
each its own Modal container. Matching only the trailing slot:N gave the
reserve cooldown to both the static comfy:slot:0 worker and mapped lane
slot:0 in hybrid prompts, so reserve_cooldown_count=1 kept two idle GPUs.
The planner now stamps one modal_reserve_cooldown_pool per Modal
environment (mapped lanes when the prompt has mapped work, else comfy,
else llm), and the lookup grants the reserve cooldown only to leading
slots of that pool; every other worker uses the global cooldown.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Current ComfyUI's PromptExecutor.execute_async indexes cache_args["ram"]
and cache_args["ram_inactive"] on every run. Local fallback execution built
its nested executor without cache_args, so every locally executed subgraph
failed with "'NoneType' object is not subscriptable".
Pass zeroed classic-cache args, and restore ComfyUI's module-global RAM
cache release state after the nested run so the host executor keeps its
RAM-pressure eviction.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Modal Configuration node gains three advanced widgets:
- global_cooldown (default 60s): idle scaledown for every Modal worker.
- reserve_cooldown_count (default 0): how many worker slots use the reserve
cooldown; must not exceed instance_count.
- reserve_cooldown (default 300s): idle scaledown for the reserved slots.
The policy travels on remote payloads and is applied per call with
Cls.with_options(scaledown_window=...), keyed by the worker-pool slot parsed
from the affinity key. Slots below reserve_cooldown_count stay warm longer;
other slots and unslotted keys use the global cooldown. Local-gap pools still
keep at least the local keepalive window. The fields do not shape the runtime
fingerprint, but derived payloads (warmups, prewarm targets, mapped phases)
inherit them so they land in the same container pool.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Hybrid mapped execution rebuilt its static, mapped, and cleanup phase payloads
from a fixed field list that dropped modal_gpu and modal_max_containers. Those
payloads resolved default settings and expected a different runtime fingerprint
for the same Modal app, so the sibling-validation guard refused them with
ModalRemoteAppOutOfDateError.
Define the fingerprint-shaping payload fields once in settings and copy them at
every derived-payload site (mapped phases, warmup requests, speculative
prewarm targets).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Warmup requests and speculative prewarm targets dropped modal_max_containers,
so they computed a different runtime fingerprint than the dispatch payload for
the same app name. The warmup then stopped and redeployed the app the dispatch
had just deployed, killing in-flight mapped generations with RemoteError.
Carry modal_max_containers into derived warmup payloads, and refuse to stop an
app whose fingerprint this process already deployed or validated.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>