Blueprint: Decouple modeld versioning; resolve by protocol compatibility
Status: architecture blueprint. Scope is how the
contenoxCLI/runtime selects, installs, validates, and discovers a prebuiltmodelddaemon. It supersedes the version-selection model in modeld setup artifact detection — specifically its “exact-version only” matching, its “No latest channel” non-goal, and its open question on patch-release compatibility. Release production mechanics from modeld release artifacts still apply; only the version/keying/resolution contract changes.
Problem
modeld has no version identity of its own. The entire toolchain stamps a single
file:
runtime/version/version.txt # the only version file in the repo
-> Makefile MODELD_VERSION (modeld binary stamp + release artifact name)
-> contenoxcli.CLIVersion() (CLI version)
-> VSCODE_VERSION (extension version)
So a modeld build is labeled with the CLI’s release tag, and setup resolves
the artifact as modeld-<CLIVersion>-<platform>. This couples a heavy, separately
built CGO/GPU daemon (~254 MB, llama.cpp + OpenVINO + CUDA/HIP/Metal) to the CLI’s
release cadence, which is wrong:
- A CLI-only or docs-only release bumps the version, and setup then demands a
modeld-<new>artifact that nobody rebuilt — even thoughmodeldis byte-for-byte unchanged. - If that artifact is not republished, every fresh install breaks and falls back
to source-build, while a perfectly usable older
modeldsits published and is refused on a version-string mismatch.
This is not hypothetical: CLI v0.32.6 shipped via install.sh while only
modeld/v0.32.5/… exists in the bucket. The exact-version resolver looked for
modeld/v0.32.6/…, got an S3 403 (a missing key on a non-listable bucket reads as
403 AccessDenied, not 404), and dumped users to the source-build path.
There is also no compatibility contract. The only thing that must actually match
between the runtime and modeld is the transport/gRPC session protocol
(runtime/transport). Today nothing declares or checks it; the version string is a
proxy that does not mean what it pretends to.
Goals
- Give
modeldan independent version, bumped only whenmodeldchanges. - Define a transport protocol version as the real compatibility axis.
- Resolve
modeldby “newest published build that is protocol-compatible for this platform and has the needed backend” — never by version-string equality. - Replace exact-key probing with a single authoritative public index so
availability is unambiguous (no more
403-means-maybe). - Let the CLI and
modeldrelease on independent cadences; a CLI bump must never strand installs. - Install/validate/discover
modeldkeyed by its own version, surviving many CLI releases.
Non-Goals
- No change to how dependency bundles / final packages are produced (modeld release artifacts); only versioning, keying, the index, and resolution change.
- No semantic-version range negotiation beyond an integer protocol window.
- No runtime rebuild of native backends.
- No public bucket listing; the index is the only enumeration surface.
The Contract
Two independent version axes and one compatibility check:
modeld version independent release tag of the daemon (e.g. v0.32.5)
transport protocol integer; the gRPC/session wire+semantics contract
contenox build supports a protocol window [MinProtocol .. ProtocolVersion]
compatible(modeld) == MinProtocol <= modeld.protocol <= ProtocolVersion
select == newest modeld version that is compatible,
for this platform, containing the requested backend
Version strings are labels for humans and storage keys. They are never compared for compatibility.
modeld’s Independent Version
- New
cmd/modeld/version.txtholds modeld’s own version; bumped only when modeld changes (backends, llama.cpp commit, OpenVINO GenAI, native libs, or the protocol). Makefile:MODELD_VERSION ?= $(cmd/modeld/version.txt)(wasruntime/version/version.txt).runtime/version/version.txtcontinues to drive the CLI and the VS Code extension only.cmd/modeld/version.gokeeps stampingversionvia the existing-X 'main.version=$(MODELD_VERSION)'ldflag.
Transport Protocol Version (the real axis)
-
New
runtime/transport/protocol.go:// ProtocolVersion is the modeld gRPC/session wire+semantics contract this build // speaks. Bump on any breaking change to transport requests/responses or session // semantics. MinProtocol is the oldest daemon protocol this build still accepts. const ProtocolVersion = 1 const MinProtocol = 1 // Supported reports whether a daemon speaking protocol p is usable by this build. func Supported(p int) bool { return p >= MinProtocol && p <= ProtocolVersion } -
modeldand the runtime both compileruntime/transport, so each knows the protocol it was built with.modeld version --jsonreports it:{ "version": "v0.32.5", "protocol": 1, "backends": ["llama","openvino"], "backend_info": { "...": {} } }(
versionInfoincmd/modeld/version.gogains aProtocol intfield set fromtransport.ProtocolVersion.) -
Bump discipline: additive/compatible wire changes do not bump
ProtocolVersion; breaking ones do, andMinProtocoladvances only when old daemons are truly unsupportable.
The Public Index
One authoritative document enumerates every published build. The CLI fetches exactly
this — no per-key probing, so a missing build is a clear, single signal instead of an
ambiguous 403.
https://<base>/modeld/index.json # public-read
{
"schema": 1,
"builds": [
{
"version": "v0.32.5",
"platform": "linux-amd64",
"protocol": 1,
"backends": ["llama", "openvino"],
"channel": "stable",
"archive": "v0.32.5/modeld-v0.32.5-linux-amd64.tar.gz",
"sha256": "v0.32.5/modeld-v0.32.5-linux-amd64.tar.gz.sha256",
"size": 254622086
}
]
}
archive/sha256are paths relative to themodeld/base.channelallows a futurebeta; the default resolver usesstable.- The index is regenerated and uploaded last by the release tooling, so it never references a partially-uploaded artifact.
S3 Layout
Keyed by modeld version (not CLI version), plus the index:
modeld/index.json
modeld/<mver>/modeld-<mver>-<platform>.tar.gz
modeld/<mver>/modeld-<mver>-<platform>.tar.gz.sha256
The native-dependency prefix (modeld-deps/) stays private and unchanged.
Resolution Algorithm
In runtime/internal/modeldinstall (replacing the CLI-version exact-key logic):
1. GET modeld/index.json over HTTPS (public). Missing/!public -> ErrNoIndex -> fallback.
2. candidates = builds where
platform == runtime.GOOS+"-"+runtime.GOARCH
&& transport.Supported(build.protocol)
&& build.backends contains <selected provider> // llama | openvino
&& channel == "stable"
3. if none: ErrNoCompatibleArtifact -> source-build fallback (honest message).
4. pick = max(candidates, by version).
5. download pick.archive + pick.sha256 -> verify sha256 -> safe-extract.
6. install to ~/.contenox/modeld/<pick.version>/<platform>/ ; write the `current` pointer.
7. validate: run `modeld version --json`; assert transport.Supported(protocol)
&& backends contains <provider>. (Version string is NOT asserted.)
The download/verify/safe-extract code from the artifact-detection work is reused
unchanged. The exact-CLI-version gate (IsOfficialVersion(CLIVersion),
modeld-<CLIVersion> naming) and the ErrPublicAccess 403-misconfig branch are
removed; the index is the source of truth, and the artifact GET treats 403/404
identically as “absent → fallback.”
Install Location and Discovery
Decoupled from the CLI version:
~/.contenox/modeld/<mver>/<platform>/ modeld, modeld.bin, lib/, modeld-libs/, manifest.json
~/.contenox/modeld/current -> pointer to the active install (path or symlink)
runtime/internal/modeldprobeno longer derives the path fromCLIVersion. It resolves in order:CONTENOX_MODELD_BIN, thecurrentpointer, then a scan of~/.contenox/modeld/*/<platform>/choosing a build whoseversion --jsonreports atransport.Supportedprotocol, thenPATH.- One installed
modeldtherefore serves many CLI releases. A CLI upgrade does not re-trigger a download unless no compatible local build is found.
Validation
modeld version --json must:
- exit 0 and parse;
- report
transport.Supported(protocol) == true; - include the selected provider’s backend in
backends.
Version equality is not a validation input. A protocol mismatch is the only
hard “incompatible” verdict; a missing backend is ErrBackendMissing as before.
Build Changes
Makefile: sourceMODELD_VERSIONfromcmd/modeld/version.txt; release name andMODELD_RELEASE_S3_URI/<MODELD_VERSION>keying unchanged otherwise.cmd/modeld/version.go: addProtocoltoversionInfo.runtime/transport/protocol.go: new constants +Supported.scripts/modeld-package-release.sh: smoke gate additionally asserts the reportedprotocolis non-zero andSupported.
Release Process and Gating
modeldrepublishes only when it changes, on its own version. The CLI release no longer requires a same-namedmodeld.push-modeld-releaseuploads the archive +.sha256, then regenerates and uploadsindex.json(from an authenticated bucket listing or a tracked builds manifest) as the final step.- Release gate (runbook): after push, anonymously
GET index.jsonandHEADthe newest referenced archive +.sha256; fail the release if either is missing or not publicly readable. This is what would have caught thev0.32.6outage. - A CLI release gate asserts the index contains at least one
stable, protocol-compatible build per supported platform — otherwise the CLI ships knowing setup will fall back.
Setup UX
Local modeld provider selected: llama
Resolving a compatible modeld build (protocol 1, linux-amd64)...
Selected modeld v0.32.5 (protocol 1, backends: llama, openvino)
Downloading 243 MB...
Verified checksum e4fcaf5a...
Installed modeld to ~/.contenox/modeld/v0.32.5/linux-amd64/modeld
No compatible build:
No prebuilt modeld build is compatible with this contenox (protocol 1, darwin-arm64).
Use the source-build path for now:
...
Index unreachable:
Could not reach the modeld release index. Config is saved; rerun `contenox setup`,
or use the source-build path:
...
Migration / Backward Compatibility
- Existing installs under
~/.contenox/modeld/<cliversion>/<platform>/remain discoverable via the directory scan (validated byversion --json), so no forced reinstall. - The first
cmd/modeld/version.txtcan start at the currentmodeldversion (v0.32.5) so the existing published artifact is immediately the resolved build. - Until
index.jsonexists, the CLI cannot resolve; ship the index alongside the first decoupled release. (Optional transitional fallback: ifindex.jsonis absent, probemodeld/<MinProtocol-known-version>/…— but the index should land first.)
Corrections to the Artifact-Detection Blueprint
- “Version Selection … exact-version only” → replaced by protocol-compatible selection.
- Non-Goal “No ‘latest’ channel” → removed; a stable channel resolving newest compatible is the model.
- Open question “Whether CLI patch releases may use a compatible older
modeldpackage” → answered: yes, always, via protocol compatibility. .sha256404-vs-403 availability semantics → obsolete; availability comes from the index, and artifact403/404are treated identically as absent.
Tests
transport.Supportedboundary table (belowMinProtocol, in-window, aboveProtocolVersion).- Resolver picks newest compatible build; rejects wrong platform, wrong protocol, and missing-backend entries; ignores the version string entirely.
httptestindex + fake archive (built with the release-shaped top-level dir and a stubmodeldreporting{version, protocol, backends}) → installs the chosen build, validates protocol+backend.- Protocol-incompatible-only index →
ErrNoCompatibleArtifact→ source-build retained. - Missing/
403index →ErrNoIndex→ graceful fallback (no scary message). modeldprobediscovers an install via thecurrentpointer and via directory scan, independent ofCLIVersion.
Phased Plan
Phase 0: Versioning + protocol
- Add
cmd/modeld/version.txt; repointMODELD_VERSION. - Add
runtime/transport/protocol.go; reportprotocolinmodeld version --json; extend the release smoke gate. - Proof:
modeld version --jsonshows an independent version + a supported protocol.
Phase 1: Index + resolver
- Define
index.json; havepush-modeld-releaseregenerate it. - Rewrite
internal/modeldinstallto fetch the index and select by compatibility; drop the exact-version gate andErrPublicAccess. - Proof: v0.32.6 CLI installs the published v0.32.5 modeld with no republish.
Phase 2: Discovery + UX
currentpointer + version-independentmodeldproberesolution.- Setup messaging for selected/none/unreachable.
- Proof: a CLI upgrade reuses the existing install with no re-download.
Phase 3: Release gating
- Anonymous index/artifact verification in the runbook; CLI-release index assertion.
- Proof: a release that fails to publish a compatible modeld fails its own gate.
Open Questions
- Should the index be per-platform (
modeld/index-<platform>.json) to shrink the fetch, or one global document? - Should
beta/edgechannels exist at launch, or onlystable? - Should the
currentpointer be a symlink (POSIX) with a file fallback (Windows), or a file everywhere? - Where does the protocol version live so both the OSS runtime and modeld share it
without a heavier dependency than
runtime/transport? - Should the CLI cache the index briefly to avoid refetching on repeated setup runs?