Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,25 @@
# Changelog

## 0.44.0 (2026-07-21)

- **pgw#617: hierarchical slot bindings (th#980 companion).**
`RunJob.ModelBinding` gains `components` (field 5, component name ->
canonical tensorhub ref). The worker loads the base composition and
substitutes each named component from its OWN materialized snapshot via
the gw#479 `components=` from_pretrained injection (load-then-substitute).
The composition (base + sorted component refs) is the instance/residency
identity — a component-only rebind derives a new instance and reconcile
reloads it; flat bindings (empty map) are byte-identical to 0.43.x.
Unknown component names, non-CAS override refs, and overrides on
self-loading (str/Path) slots refuse typed (`ComponentSubstitutionError`)
at setup, never mid-denoise. Override refs join job pins, held refs/
digests, and compile-cell binding facts.
- **pgw#617: `selected_by=` slots may omit `default_checkpoint`.** Deploy-
time bindings (th#980) seed the hub mapping now, so the registry's
author-time requirement is dropped (mirror of tensorhub's relaxed
registration rule). Unblocks the ie#524 de-hardcode sweep of
request-branching endpoints (wan-2.2, sdxl slot-model, z-image).

## 0.43.1 (2026-07-21)

- **gw#608 CLOSED: self-mint arm is transactional over the process-global
Expand Down
1 change: 1 addition & 0 deletions proto/CONTRACT.md
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,7 @@ the endpoint's declared `Resources` + probed free VRAM, never the wire.
| `ref` | O resolver | W `ensure_local` + injection | canonical ref string |
| `loras` | O `_models` override gate (AllowLora) + BYOM ingest (th#585) | W per-request adapter overlay (gw#393) + adapter residency (gw#399) | LoRA overlays riding this slot's base model; attached as unfused named adapters that stay resident on the pipeline, ACTIVE only for jobs that name them (explicit activation — adapter-free jobs always run with adapters disabled). Purely W-side: O never routes on adapter residency. Empty for adapter-free jobs; workers predating the field ignore it |
| `inference_defaults` | O repo-metadata store (th#767c: PUT-time validated against the family's exported JSON Schema) | W `ctx.slots[slot].defaults` resolution chain (pgw#520) | JSON object, an instance of the slot's family vocabulary struct (`gen_worker.families.FamilyDefaults` subclass). Empty when the resolved repo carries no metadata — W merges by falling back to the endpoint's code-declared `Slot(fallback=...)` preset; empty AND no fallback ⇒ `ctx.slots[slot]` raises when the handler reads it. Additive: workers predating the field ignore it (Slot resolution falls back to the code preset unconditionally) |
| `components` | O deploy-time binding config (th#980: per-component validated against the pipeline family's component schema) | W load-then-substitute (pgw#617): base pipeline `from_pretrained` with each named component loaded from its OWN materialized snapshot and injected via the `components=` kwarg (gw#479 mechanism) | component name → canonical tensorhub ref (same grammar as `ref`); snapshots ride `RunJob.snapshots`/`DesiredResidency` exactly like base refs. The composition (base + sorted overrides) IS the instance/residency identity — a component-only rebind derives a new instance. Empty map = flat binding, byte-identical to pre-#617. Unknown component name = typed setup refusal (`ComponentSubstitutionError`). Additive: old workers ignore it |

### LoraOverlay (embedded in ModelBinding.loras)

Expand Down
7 changes: 7 additions & 0 deletions proto/worker_scheduler.proto
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,13 @@ message ModelBinding {
// (precedence: explicit payload value > this > endpoint fallback preset).
// Additive: old workers ignore this field.
string inference_defaults = 4;
// th#980 hierarchical slots: per-component repo substitutions on the base
// composition, component name -> canonical tensorhub ref (normal form,
// same grammar as `ref`). The worker loads the base composition then
// substitutes each named component from its materialized snapshot
// (pgw#617). Snapshots for these refs ride RunJob.snapshots /
// DesiredResidency exactly like base refs. Additive: old workers ignore.
map<string, string> components = 5;
}

// One LoRA adapter overlay: a resolved tensorhub ref + its adapter weight.
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "gen-worker"
version = "0.43.1"
version = "0.44.0"
description = "A library used to build custom functions in Cozy Creator's serverless function platform."
readme = "README.md"
license = "MIT"
Expand Down
15 changes: 15 additions & 0 deletions src/gen_worker/api/binding.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,12 @@ class ModelRef(msgspec.Struct, frozen=True):
version: str = ""
files: tuple[str, ...] = ()
components: tuple[str, ...] = ()
# pgw#617 hierarchical bindings (tensorhub only): sorted (component name,
# canonical ref) substitutions on the base composition — stamped from
# ModelBinding.components at dispatch, never declared by endpoint code.
# Part of the binding's identity: a component-only rebind derives a new
# instance/residency identity.
component_overrides: tuple[tuple[str, str], ...] = ()

def __post_init__(self) -> None:
# msgspec.structs.force_setattr, NOT object.__setattr__: the latter
Expand All @@ -103,8 +109,17 @@ def __post_init__(self) -> None:
force(self, "version", _clean(self.version))
force(self, "files", tuple(_clean(p) for p in self.files if _clean(p)))
force(self, "components", tuple(_clean(p) for p in self.components if _clean(p)))
force(self, "component_overrides", tuple(sorted(
(_clean(k), _clean(v))
for k, v in self.component_overrides if _clean(k) and _clean(v)
)))
force(self, "storage_dtype", _clean_storage_dtype(self.storage_dtype))

if self.component_overrides and self.source != "tensorhub":
raise ValueError(
"component_overrides= is tensorhub-only (pgw#617: refs are "
"tensorhub-CAS, mirror-first)"
)
if self.source == "tensorhub":
if not self.tag:
msgspec.structs.force_setattr(self, "tag", "latest")
Expand Down
24 changes: 24 additions & 0 deletions src/gen_worker/api/errors.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,30 @@ def __init__(
)


class ComponentSubstitutionError(WorkerError):
"""A dispatched component substitution cannot apply to the slot's base
composition (pgw#617/th#980 hierarchical bindings).

Named-axis refusal: the message carries function, slot, component name
and the base's known component vocabulary (or the failing override ref).
Raised at setup/injection time, never mid-denoise.
"""

def __init__(
self, function: str, slot: str, component: str, *, detail: str = "",
) -> None:
self.function = str(function or "")
self.slot = str(slot or "")
self.component = str(component or "")
msg = (
f"{self.function!r} slot {self.slot!r}: component substitution "
f"{self.component!r} cannot apply"
)
if detail:
msg = f"{msg}: {detail}"
super().__init__(msg)


class RefCompatibilitySurprise(ValidationError):
"""Post-download runtime mismatch on a caller-supplied PAYLOAD_REF.

Expand Down
Loading
Loading