You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Audited 2026-08-16 at f31653d: description updated to match HEAD. Every acceptance
criterion this issue was opened on is met in the shipped code; what keeps it open is the
scope appended on 2026-08-08, which the title and the criteria never described. The
decisions recorded in the comments below are untouched and remain the arbiter.
The decision this encodes
Recorded by Armando in the 2026-08-07 design session on the AI-assist substrate issue (#81),
which covered ModelProvider pre-annotation, the review loop in the UI, and importers:
ModelProvider is designed and declared now as a kernel port with no implementations,
shaped so that a local runner and a cloud service are both ordinary adapters later: MCP stays
a native first-class path; autolabeling will run locally and in the cloud eventually; nothing
ships yet, and nothing may be designed to preclude the cloud half. Local-first,
cloud-compatible after.
Status at HEAD: the port is designed, and the original deliverable is done
src/visionset/kernel/ports/model_provider.py is no longer the placeholder this issue was
written to replace. It answers all three of the design questions it posed, and argues each
against the dual test:
Invocation unit — per batch. PredictionRequest carries a tuple of targets; a local
provider loops, a hosted one does not pay a round trip per image.
Result shape — Iterator[AssetPrediction], not a materialised Sequence of Annotation. A provider answers in its own terms and stamps model_ref; the write gate
turns claims into labels, so no provider needs to know which schema version a batch pinned.
Progress without a transport — nothing in the port names a ProgressReporter, a queue, a
callback or a future. Yielding is the whole mechanism.
Discovery — settled as no entry-point group, and argued in the docstring: adapters are
instantiated from user-created InferenceConnection rows, so a provider found by entry point
would have nothing to be instantiated from, and a workspace could acquire the ability to
predict by an unrelated pip install.
The local adapter shipped alongside it as slice 2 (cf. #434), so the "zero implementations"
criterion is superseded rather than unmet. src/visionset/inference/ now holds sam_provider.py and transformers_provider.py behind that port.
What actually keeps this open
The hosted adapter and the HTTP endpoint contract — the cloud half of the dual test, which
the port was shaped for and nothing has exercised. That is the whole of the remaining scope, and
it is neither in this issue's title nor in the criteria below.
Two things to settle at pickup rather than in advance:
Overlap with the provider contract and plugin architecture (cf. Open providers over closed capabilities: the provider contract and plugin architecture #498). That issue designs
a provider toolkit above this port, with its own contract version, its own entry-point group
and its own refusal rules. Whether a hosted adapter is the first consumer of that layer, or
ships beneath it as an ordinary adapter of this port, decides whether this issue is work or a
pointer.
Original description, as filed 2026-08-08 (including the 2026-08-08 scope update)
The decision this encodes
Recorded by Armando in the 2026-08-07 design session on the AI-assist substrate issue (#81),
which covered ModelProvider pre-annotation, the review loop in the UI, and importers:
ModelProvider is designed and declared now as a kernel port with no implementations,
shaped so that a local runner and a cloud service are both ordinary adapters later: MCP stays
a native first-class path; autolabeling will run locally and in the cloud eventually; nothing
ships yet, and nothing may be designed to preclude the cloud half. Local-first,
cloud-compatible after.
Nothing in this issue ships an implementation. The deliverable is a port whose shape
survives both futures.
Correction to the premise: the port already exists, as a placeholder
src/visionset/kernel/ports/model_provider.py is at HEAD (904b37a) and is exported from kernel/ports/__init__.py:24. It is not a designed port and its own docstring says so — "Phase 3 — declared now to fix the port surface". Its entire surface is:
That shape fails the dual test in three ways, which is precisely the work:
Per-asset, so a provider that batches — which every hosted inference service does — pays
one round trip per image.
Synchronous, returning a materialised Sequence, so a remote provider that takes minutes
has nowhere to be except blocking its caller.
Discovered by nothing.Importer and Exporter name the visionset.formats entry-point
group; this port names no group at all, so there is no way to install a provider even in
principle.
So this issue re-shapes a declared placeholder. It does not add a new file to empty space,
and a PR that only creates one has not done the work.
The pattern to follow
kernel/ports/importer.py — a @runtime_checkable Protocol, discovered through an entry-point
group, zero implementations, documented as awaiting its producers. formats/registry.py
already filters one group by port (isinstance on an instance, because issubclass against a
protocol with data members raises), so reusing that group or declaring a new one both have
precedent — but the choice has to be made and argued rather than inherited.
Design questions the implementer answers in the PR, not in advance here
Every answer must hold for a hypothetical local ONNX runner and a hypothetical HTTP service
equally. That dual test is the acceptance criterion.
The invocation unit — asset, or batch? A per-asset call is simplest locally and worst
remotely, which is exactly the tension the port has to resolve rather than pick a side of by
accident.
The result shape — annotations carrying provenance='model', confidence, and model
identity/version. The domain already requires model_ref whenever provenance is model
(domain/annotation.py:69-71); the open question is whether that one string is the whole of
model identity or whether the port owes more. These fields align with the work that surfaces
provenance and confidence in the review loop (Model-produced annotations: provenance and confidence surfaced in the review loop #417), which is the rendering half of the same
split.
How a slow or asynchronous provider reports progress without the port assuming a
transport. The JobQueue port in kernel/ports/job_queue.py, which came out of the embedded
job system with its SQLite adapter and in-process dispatcher (Embedded job system: a JobQueue port, a SQLite adapter, and an in-process spawn dispatcher #328), already exists and is
explicitly not an executor — its separation of "where rows live" from "how the product asks
for work" is the shape to lean on, not to duplicate.
Out of scope
Scope updated 2026-08-08: the port and the local adapter shipped as slice 2 (#434),
including cross-box NMS, the fp16 shims, and the weight-download job. This issue remains open
for the hosted adapter and the HTTP endpoint contract. The original scope text is preserved in
this issue's edit history.
The decision this encodes
Recorded by Armando in the 2026-08-07 design session on the AI-assist substrate issue (#81),
which covered
ModelProviderpre-annotation, the review loop in the UI, and importers:Status at HEAD: the port is designed, and the original deliverable is done
src/visionset/kernel/ports/model_provider.pyis no longer the placeholder this issue waswritten to replace. It answers all three of the design questions it posed, and argues each
against the dual test:
PredictionRequestcarries a tuple of targets; a localprovider loops, a hosted one does not pay a round trip per image.
Iterator[AssetPrediction], not a materialisedSequenceofAnnotation. A provider answers in its own terms and stampsmodel_ref; the write gateturns claims into labels, so no provider needs to know which schema version a batch pinned.
ProgressReporter, a queue, acallback or a future. Yielding is the whole mechanism.
instantiated from user-created
InferenceConnectionrows, so a provider found by entry pointwould have nothing to be instantiated from, and a workspace could acquire the ability to
predict by an unrelated
pip install.The local adapter shipped alongside it as slice 2 (cf. #434), so the "zero implementations"
criterion is superseded rather than unmet.
src/visionset/inference/now holdssam_provider.pyandtransformers_provider.pybehind that port.What actually keeps this open
The hosted adapter and the HTTP endpoint contract — the cloud half of the dual test, which
the port was shaped for and nothing has exercised. That is the whole of the remaining scope, and
it is neither in this issue's title nor in the criteria below.
Two things to settle at pickup rather than in advance:
a provider toolkit above this port, with its own contract version, its own entry-point group
and its own refusal rules. Whether a hosted adapter is the first consumer of that layer, or
ships beneath it as an ordinary adapter of this port, decides whether this issue is work or a
pointer.
review_pending, never silentlyannotated, and schema validation is not relaxed forthem.
Out of scope
it rather than build it — that is a separate decision.
Acceptance criteria
ModelProvideris a designed port rather than a placeholder: every element of its shapejustified against both a local runner and a hosted service
docstring
Zero implementations ship— superseded 2026-08-08 by the local adapter (cf. feat(inference): ModelProvider port, local adapter, weight download (#418 slice 2) #434)importer.py's andjob_queue.py's registerOpen providers over closed capabilities: the provider contract and plugin architecture #498 instead
Original description, as filed 2026-08-08 (including the 2026-08-08 scope update)
The decision this encodes
Recorded by Armando in the 2026-08-07 design session on the AI-assist substrate issue (#81),
which covered
ModelProviderpre-annotation, the review loop in the UI, and importers:Nothing in this issue ships an implementation. The deliverable is a port whose shape
survives both futures.
Correction to the premise: the port already exists, as a placeholder
src/visionset/kernel/ports/model_provider.pyis at HEAD (904b37a) and is exported fromkernel/ports/__init__.py:24. It is not a designed port and its own docstring says so —"Phase 3 — declared now to fix the port surface". Its entire surface is:
That shape fails the dual test in three ways, which is precisely the work:
one round trip per image.
Sequence, so a remote provider that takes minuteshas nowhere to be except blocking its caller.
ImporterandExportername thevisionset.formatsentry-pointgroup; this port names no group at all, so there is no way to install a provider even in
principle.
So this issue re-shapes a declared placeholder. It does not add a new file to empty space,
and a PR that only creates one has not done the work.
The pattern to follow
kernel/ports/importer.py— a@runtime_checkableProtocol, discovered through an entry-pointgroup, zero implementations, documented as awaiting its producers.
formats/registry.pyalready filters one group by port (
isinstanceon an instance, becauseissubclassagainst aprotocol with data members raises), so reusing that group or declaring a new one both have
precedent — but the choice has to be made and argued rather than inherited.
Design questions the implementer answers in the PR, not in advance here
Every answer must hold for a hypothetical local ONNX runner and a hypothetical HTTP service
equally. That dual test is the acceptance criterion.
remotely, which is exactly the tension the port has to resolve rather than pick a side of by
accident.
provenance='model',confidence, and modelidentity/version. The domain already requires
model_refwhenever provenance ismodel(
domain/annotation.py:69-71); the open question is whether that one string is the whole ofmodel identity or whether the port owes more. These fields align with the work that surfaces
provenance and confidence in the review loop (Model-produced annotations: provenance and confidence surfaced in the review loop #417), which is the rendering half of the same
split.
transport. The
JobQueueport inkernel/ports/job_queue.py, which came out of the embeddedjob system with its SQLite adapter and in-process dispatcher (Embedded job system: a JobQueue port, a SQLite adapter, and an in-process spawn dispatcher #328), already exists and is
explicitly not an executor — its separation of "where rows live" from "how the product asks
for work" is the shape to lean on, not to duplicate.
Out of scope
Scope updated 2026-08-08: the port and the local adapter shipped as slice 2 (#434),
including cross-box NMS, the fp16 shims, and the weight-download job. This issue remains open
for the hosted adapter and the HTTP endpoint contract. The original scope text is preserved in
this issue's edit history.
recorded on exercise the AI-assist substrate — ModelProvider pre-annotation, review loop in the UI, importers #81 and preserved here so it does not die with that thread: predictions are
written into
review_pending, never silentlyannotated, and schema validation isnot relaxed for them.
it rather than build it — that is a separate decision.
Acceptance criteria
ModelProvideris a designed port rather than a placeholder: every element of its shapejustified against both a local runner and a hosted service
docstring
importer.py's andjob_queue.py's register