Add a sampler, scheduler, solver control, or annotator¶
Topics: generation
Each of these vocabularies is a fixed set defined in four repositories. This guide covers which repository owns which part, the order the changes have to land in, and what else needs updating alongside the value itself.
The samplers and schedulers reference covers what the existing values mean and cost. ADR 12 records why the SDK holds the single registry rather than each consumer keeping its own table.
Who owns what¶
| Repository | Question it answers | Where the vocabulary lives |
|---|---|---|
| hordelib | Can the backend render it? | hordelib/pipeline/constants.py: SAMPLERS_MAP, SCHEDULERS, SOLVER_TYPES, CONTROLNET_IMAGE_PREPROCESSOR_MAP |
| horde_sdk | What is it called, which settings apply, what does a step cost? | horde_sdk/generation_parameters/image/consts.py, constraints.py, sampler_work.py |
| AI-Horde (this repo) | Is the request valid, who may serve it, what does it charge? | horde/consts.py, horde/bridge_reference.py, horde/classes/stable/kudos.py |
| horde-worker-reGen | Which workers advertise it? | horde_worker_regen/consts.py, bridgeData_template.yaml |
Land the changes in that order. The service can accept a value before any worker renders it, which queues jobs nothing can pop, so a new value reaches dispatch only behind a bridge capability gate. The reverse is harmless: a worker able to render a value the service does not accept never receives one.
A bridge capability version is the reGen release major. horde_worker_regen.__version__ is reported in
bridge_agent, and check_bridge_capability compares it as semver against the keys of
BRIDGE_CAPABILITIES. Version 17 is the release that added extended ControlNet, the scheduler field,
extended samplers, solver options, sigma generators, and flow shift.
Add a sampler¶
- hordelib. Add the horde name to
SAMPLERS_MAPagainst its ComfyUI solver.SAMPLERS_MAPis the only place the horde spelling and the backend spelling meet. ComfyUI substitutes an unrecognised name rather than rejecting it, so the service gates on bridge version instead of relying on the worker to report the problem. - horde_sdk. Add the member to
KNOWN_IMAGE_SAMPLERS, then aSAMPLER_CONSTRAINTSrecord (its applicable numeric knobs, ranges, and solver types) and aSAMPLER_WORK_PROFILESrecord (marginal model evaluations per trajectory step, fixed-rate or adaptive). Both lookups raiseKeyErroron a member with no record, so an incomplete addition raises instead of falling back to a default. Optionally setSAMPLER_PRESENTATION_TIERS,RECOMMENDED_SAMPLERS, aSamplerRecommendation, and anyREJECTED_SAMPLER_SCHEDULER_PAIRINGS. Release the SDK. - AI-Horde. Add the name to
EXTENDED_SAMPLERSorSOLVER_KNOB_SAMPLERSinhorde/consts.py, whichever matches the oldest bridge release that maps it.KNOWN_SAMPLERSis derived from those sets, and the Swagger enum inhorde/apis/models/stable_v2.pyis derived fromKNOWN_SAMPLERS, so neither needs a separate edit. Add the name toBRIDGE_SAMPLERSinhorde/bridge_reference.pyunder the capability version that renders it. - AI-Horde kudos. Add an entry to
CANONICAL_KUDOS_SAMPLERSinhorde/classes/stable/kudos.pymapping the new name to the closest sampler already inKudosModel.KNOWN_SAMPLERS. That list is the frozen model's one-hot input and cannot grow without a retrain. A name in neither the map nor the frozen list is priced ask_euler, which underprices an expensive sampler. - reGen. Nothing per sampler beyond taking the SDK release, unless the sampler needs a bridge
release that predates it. In that case add the new major to
BRIDGE_CAPABILITIESandBRIDGE_SAMPLERSin this repository and gate dispatch on it.
Add a scheduler¶
Schedulers split by how the backend produces the sigmas, and the split decides the gating.
- hordelib. A schedule ComfyUI's
calculate_sigmasaccepts by name goes inSCHEDULERS. A schedule produced by a node goes inSigmaGeneratorScheduleand is carried byhordelib/execution/sigma_schedules.pyinstead, because the graphs take a schedule by name. - horde_sdk. Add it to
KNOWN_IMAGE_SCHEDULERS, and toSCHEDULER_BASELINE_APPLICABILITYwhen it only works for some model baselines. - AI-Horde. Add it to
EXTENDED_SCHEDULERSorSIGMA_GENERATOR_SCHEDULERSinhorde/consts.py. The pop query andWorker.can_generategate onSIGMA_GENERATOR_SCHEDULERS, so a sigma-generator schedule never reaches a bridge that would render a different schedule without reporting it.LEGACY_SCHEDULERSstays as it is: onlynormalandkarrasround-trip through the legacykarrasflag. - AI-Horde gating. If the new schedule needs a bridge newer than the ones already gated, extend the
conditions in
horde/database/functions.pyandhorde/classes/stable/worker.pyrather than adding a parallel check elsewhere. Both read the same constant sets.
Add a solver control¶
A solver control is a numeric setting on the sampler rather than a name in a vocabulary.
- hordelib. Add the option and its fallback bounds to
SOLVER_OPTION_FALLBACK_BOUNDS, or the dedicated bounds constant where one exists (FLOW_SHIFT_BOUNDS). - horde_sdk. Add the knob to
SAMPLER_SOLVER_KNOB, give it aNumericKnobRange, and list it on everySAMPLER_CONSTRAINTSrecord it applies to. Applicability is per sampler, so the empty case is the default and no edit is needed for samplers that ignore it. - AI-Horde. Add the request field to
horde/apis/models/stable_v2.pyand the parameter name toSOLVER_KNOB_PARAMSinhorde/consts.py. Validation inValidator.validate_sampler_constraintsreads the SDK registry, so range and applicability checks need no per-knob code. The pop query andWorker.can_generateiterateSOLVER_KNOB_PARAMS, so gating follows from the constant. - Return codes. A new rejection reason needs an entry in
horde/exceptions.pyandREADME_return_codes.md. ReuseSamplerKnobInapplicableorSamplerKnobOutOfRangewhere the failure is the same kind. A new code is worth adding only if a client would act on it differently.
Add an annotation control type¶
An annotation control type appears on two surfaces: the control_type of an image-generation request,
and the payload of the Alchemy annotation form.
- hordelib. Map the horde name to its preprocessor in
CONTROLNET_IMAGE_PREPROCESSOR_MAP. Add aCONTROLNET_MODEL_MAPentry if image generation can condition on it, aCONTROLNET_ANNOTATOR_DOWNLOAD_BYTESestimate for the worker's disk preview, and the detector to the prefetch sets inhordelib/preload.py. - horde_sdk. Add it to
KNOWN_IMAGE_CONTROLNETSandKNOWN_ANNOTATION_CONTROL_TYPES.AI_HORDE_EXTENDED_IMAGE_CONTROL_TYPESis derived by subtracting the legacy set, so an addition is automatically extended rather than classic. - AI-Horde. Add it to
KNOWN_CONTROL_TYPESinhorde/consts.py.IMAGE_CONTROL_TYPESand the Swagger enums follow. Do not add it toLEGACY_IMAGE_CONTROL_TYPES, which is the set old bridges can render; a new type there would be dispatched to workers that cannot produce it. - AI-Horde pricing. Add it to
ANNOTATION_DETECTOR_KUDOS_BUCKETSin the cost class that matches what the detector loads: weightless for pure OpenCV or numpy, weighted for a small checkpoint, hub for a large transformers-hub model. An unlisted type falls back to the weighted class, so a heavy detector left unlisted is underpriced. If image generation can condition on it, also map it onto its closest classic cost class inCANONICAL_KUDOS_CONTROL_TYPES, for the same frozen one-hot reason as samplers. An unmapped type prices as no ControlNet at all. - reGen.
CLASSIC_CONTROL_TYPESandEXTENDED_CONTROL_TYPESinhorde_worker_regen/consts.pyare derived from the SDK, so taking the SDK release is enough. Be aware thatallow_extended_controlnetis a single boolean covering the whole extended set: a worker advertises it only once its annotators cover every extended type, so adding a type raises what every opted-in image worker is expected to serve. Alchemists advertise per type throughannotation_types, so annotation work can be matched type by type where image generation cannot.
Update a published contract or schema¶
Four versions govern this surface. They move independently: bumping the wrong one either forces an unnecessary client update or lets an incompatible change through.
| Version | Where | Bump it when |
|---|---|---|
SAMPLER_CONSTRAINTS_DOCUMENT_SCHEMA_VERSION |
horde_sdk/generation_parameters/image/constraints_document.py |
The JSON shape of the published document changes in a way a client parsing the old shape cannot handle |
SamplerExecutionContractVersion |
horde_sdk/generation_parameters/image/sampler_work.py |
The behavior a worker guarantees about adaptive execution changes |
CAPABILITY_EXPANDED_REGEN_VERSION and BRIDGE_CAPABILITIES |
horde/bridge_reference.py |
A request field needs a bridge release that older workers do not have |
HORDE_VERSION and a new sql_statements/ file |
horde/consts.py |
The change adds or alters a database column |
Rules that follow from those being separate:
- Adding a sampler, scheduler, or control type to an existing document section is additive and does not bump the document schema version. Clients reject schema versions they do not understand, so a bump costs every client an update.
- A new execution contract version publishes its complete guarantee set in
execution_contracts. Workers advertise one version string and the server maps it to that set. A missing, malformed, or future version proves no ceiling and fails closed. The server does not infer a version from any other field. - New response fields go in
horde/apis/models/stable_v2.pywith an explicit default type. Swagger 2 translation is checked over the complete served document, including operation-id uniqueness, bytests/integration/test_swagger_contract.py. - A field a pre-17 bridge must not see is omitted from its pop payload rather than sent with a default,
which preserves the legacy payload shape for strict clients.
tests/integration/test_image_scheduler_gating.pycovers this. - Persisting a new worker capability requires a migration. Follow the existing pattern: idempotent,
rerunnable, with a
.licensefile beside it, andCREATE INDEX CONCURRENTLYkept out of any transaction wrapper.
Chores that go with the change¶
- Version pins. A new SDK symbol means bumping the
horde_sdkbound inpyproject.tomlandrequirements.txt, and regeneratinguv.lock. reGen pins the SDK separately, so a worker-visible addition needs both bumps before the capability gate can open. - Publishing the change. The sampler constraints endpoint compiles its document once per process and holds it, with no response cache in front, so a deployed change serves as soon as the process restarts. Nothing needs invalidating and no cache flush belongs in the deploy.
- Tests.
tests/unit/test_consts.pypins the vocabularies,tests/unit/test_sampler_constraints.pycompares the published document with the registry,tests/unit/test_kudos_pricing.pypins prices for pre-existing requests, andtests/unit/test_bridge_reference.pypins capability gating. The Locust sampler requester intests/stress/locustsuite/users/image.pyderives its cases from the SDK, so a new sampler enters load coverage without an edit. - Reference documentation.
docs/reference/samplers_and_schedulers.mdcarries the measured cost groupings. Where a sampler's marginal cost was not measured, record that rather than reusing a neighbouring sampler's number. - Frontends.
extended_image_frontend.mddescribes how a frontend builds controls from the published contract. An addition that fits the published shape needs no frontend change. One that does not fit is a sign the contract shape needs revisiting.