Repository navigation
feat: workflow controls (VLM opt-in per project, detection-only mode, clustering switch) - #329
Merged
Merged
Conversation
With OP_VLM_URL set, every project that never activated an endpoint labeled with the env endpoint, so a pure ingest-and-cluster workflow still woke the VLM worker. A project with no activation now has no VLM; the env endpoint is one it opts into like any other. New projects get an explicit off activation while still building. Projects created by an earlier release are pinned once at start (legacy repair) to what they used to inherit: the env endpoint when one is configured, else off, so their behaviour does not change and a later OP_VLM_URL grants nothing. PUT /settings defaults.vlm null now means off instead of "inherit env".
Adds check_cited_paths.py (pre-commit, shrink-only baseline for the frontend citations of design notes that left the tree) and rewords the 30 citations outside the frontend.
…bulk_ingest default, docs-site typecheck - capture_docs_screenshots.py: required public-project allowlist; listing pages refused while the backend holds a project outside it (#284) - check_docs_vs_code.py: CLI commands and flags, make targets, screenshot files, NAME= lines; version test scans frontend docs; docs.yml runs on docs/** and type-checks; weekly external link check (#287) - bulk_ingest.py: --api-base defaults to localhost:8000 inside the container (#288) - docs-site: drop deprecated baseUrl, add @types/react-dom, .nvmrc follows the Dockerfile (#293, #289)
… a subset of curation services, default the project name - profile: sets GPU_PROFILE and OP_INGEST_GPU_DECODE in an existing .env; the unread TRITON_WORKERS / MAX_BATCH_SIZE / SHM_SIZE keys and the never-loaded generated override are removed (#281) - ./openprocessor and make load an optional docker-compose.local.yml (#283) - curation up [service...] and make curation-up SERVICES=...; curation-evaluator gets its own evaluator profile (#285) - a checkout without COMPOSE_PROJECT_NAME uses the project openprocessor with a warning (#291)
…ering switch embedding.mode none stores boxes with no vector of any kind, the whole-frame vector included. clustering.automatic false keeps ingest and the embed scope from placing items in clusters and stops the cluster-refresh daemon and the auto-label worker's idle retrain for the project. The refresh daemon reads OP_CLUSTER_REFRESH_* as flag defaults, so no compose override is needed.
An unreachable VLM, or crops whose image the worker cannot read, left the auto-label job completed with predicted 0, the same as a run with nothing to do. The stage now reports failed and unreadable counts and fails the job when every selected crop failed.
…citly A project calls no VLM until it activates one.
… clustering switch
…ode, selective VLM recipe Pipeline modes gets a recipe run on public data (ingest without the VLM, cluster, name clusters by hand, run the VLM for one cluster or filter), the cluster-refresh variables and the per-project clustering switch. CHANGELOG entries record the behavior change and the one-time pin.
# Conflicts: # CHANGELOG.md
# Conflicts: # CHANGELOG.md # frontend/CHANGELOG.md
…at/workflow-controls # Conflicts: # CHANGELOG.md
…orkflow-controls # Conflicts: # CHANGELOG.md # docs-site/docs/guides/pipeline-modes.mdx
This was referenced Oct 15, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #282
Closes #279
Closes #292
The question this answers
"Is the VLM running on every ingest? If I just want to ingest images, detect cars and
people, cluster, and label clusters with custom names, do I have to wait for the VLM, or can
I use it selectively?"
Before: ingest never waited for the VLM, but with
OP_VLM_URLset every project that neveractivated an endpoint inherited the env VLM and
curation-vlm-workerlabeled every embeddeditem in the background (scope
all). A pure detection-only project was not selectable(lazy mode still stored the whole-frame vector; clustering had two triggers and no off
switch). After: a project calls no VLM until it activates one, detection-only is one policy,
clustering has a per-project switch, and the selective VLM run is a documented, checked
recipe.
Behavior change (CHANGELOG, Changed): a project calls no VLM until it activates one (#282)
Decision (the safest of the two options in the brief): explicit state in the activation
doc, with a one-time pin for old projects.
active_vlm_endpoint()returnsNonefor a project with no activation (it used to returnthe env endpoint).
GET .../vlm/endpoints/activeanswerssource: "off"; Cropwrightalready renders what the backend serves ("off: no VLM runs for this project"), so no
client re-derivation was added.
PUT /settingsdefaults.vlm: nullnow means off.lifecycle.createwrites an explicit off activation while the project isstill
building(src/services/projects/vlm_default.py).legacy_repair(runs at API start, under theper-project flock, active projects only) pins a project with no activation doc to what it
inherited:
envwhenOP_VLM_URLis set, elseoff. Their effective behavior does notchange, and a later
OP_VLM_URLgrants nothing. Idempotent: a project with anyactivation doc is never touched. The pin is a quiet write (no
config.changedevent).defaultproject of a fresh install is pinned by the samerule on first start (
envif the installer setOP_VLM_URL); every project created afterthat starts off.
projects_with_active_vlm,current_want) and the auto-labelVLM stage (
run_labeler/resolve_run_vlm) go throughactive_vlm_endpoint(), so theyrespect it. A per-run
vlm=<name>still works and does not change the project's setting.scripts/release/acceptance_run.shvlm_labelphase now activates the env VLM first.Detection-only mode and the clustering switch (#279)
Minimal shape, consistent with the existing
embedding.modevalues:embedding.mode: "none": store boxes with no vector of any kind (detectionsdeferred,no whole-frame vector, the GPU-decode path drops the whole-frame vector it computes in its
one pass). Labeled items from a dataset import still embed (the existing rule).
lazyisunchanged (the whole-frame vector is still stored).
clustering.automatic: false(newClusteringPolicy, default true): ingest and theembedreprocess scope place no item into a cluster,curation-cluster-refreshskipsthe project, and the auto-label worker's idle retrain skips it. Explicit clustering
(
/clusters/*, auto-label jobs) always runs. Later embed/cluster on demand:auto_label/start?embed_missing=true&train_clusters=true.nonemode and a "Cluster automatically"checkbox.
Cluster-refresh flags from the environment (#292)
OP_CLUSTER_REFRESH_INTERVAL_S,_GROWTH_THRESHOLD,_MAX_DEFERRAL_S,_RETRY_BACKOFF_S,_AUTO_LABELare the flag defaults; a flag still overrides. The service hasenv_file: .env, so no compose change was needed (cli-and-tooling's compose and CLIfiles are untouched). Documented in
env.template,configuration/advanced.mdxandpipeline-modes. No env var was renamed or removed, so
retired_env.pyis unchanged.Gap found by running the selective recipe, fixed
Running the recipe live, a VLM run against an unreachable endpoint finished
completedwithpredicted: 0: indistinguishable from "nothing to do". The auto-label VLM stage now reportsfailed(VLM request failures) andunreadable(crops whose image could not be read) andfails the job with those counts when every selected crop failed.
Live recipe (throwaway public project, deleted afterwards)
Run 2026-10-15 on the opmono API with 40 COCO images copied from
coco-bench-2kinto athrowaway
cwlife-recipeproject (deleted; the project list is back to the five originals).That API predates this change, so the first step deactivated the project's VLM to stand in
for the new default (it confirmed the old behavior first:
source: "env"for a brand-newproject). Outputs are in
docs-site/docs/guides/pipeline-modes.mdx, "Recipe". Ran: ingestpolicy (car, person), upload (40 images, 162 boxes kept, 155 dropped), cluster job (VLM stage
skipped),
clusters/refine(10 sub-clusters), create classspoiler,crops/batch_label(28 human-validated),
vlm/label_cluster/10000without an endpoint (409vlm_not_configured, fail closed), with?vlm=env(job selected the 134 unvalidated members,none of the 28). Not run live: anything that needs the new code (new-project default off,
embedding.mode none, the new failure report), because the opmono image does not contain thisbranch and I was told not to rebuild it; those are covered by the offline tests below. That
stack's VLM endpoint was unreachable, so no VLM labels were produced.
Tests written first and seen failing
(before the fix,
git stashofsrcandscripts)Frontend:
ingestPolicyPage.test.ts"saves detection-only mode with automatic clustering off"failed before the form change (
Cannot read properties of null (reading 'checked')).Test-suite note: many existing tests relied on "never activated means env". A
vlm_envfixture now only sets the env endpoint;
vlm_env_active(andcuration/vlm_opt_in.py)double a project that opted in, for tests whose fake storage cannot hold the activation doc.
test_wheel_example_e2eactivates the env VLM through the real route instead.Checks
-n 6),make contractsregenerated (contracts/openapi/curation.json,the
clusteringpolicy field andnonemode) andcontracts-check, mypy, pre-commit--all-files: results at the end of the final reply.legacy_repair.pybroad-except baseline tightened 2 to 1 (the repair steps are nowone loop) and the
index_itemscomplexity entry dropped (the whole-frame step is a helper).No new guard, so no plant-a-violation proof.
npm run check,lint,knip, vitest (451 files, 4759 tests),build, stubbede2e for the ingest-policy page. Page opened in Chromium on DISPLAY=:11 at 1600 and 430 px
and the screenshots viewed (stub-driven, not committed). Docs screenshots were not
refreshed: that needs the capture script against a live stack running this branch.
Decisions and things to know
defaults.vlm: nullonPUT /settingsused to mean "clear my choice, inherit env"; it nowmeans off (there is nothing to inherit).
GET /settingsdefaults.vlmis unchanged for a project with no activation (absent).read fails at start is retried at the next start (logged as
legacy_project_repairwith
vlm_pin_error), never guessed.label_cluster(uselabel_batchwith chosen crop ids);a "VLM: inherited from env" label was not needed because inheritance no longer exists.
Also in this PR (folded in to avoid repeated CI runs)
Closes #281
Closes #283
Closes #284
Closes #285
Closes #288
Closes #291
Closes #293