Repository navigation
Changelog
All notable changes to Atlas (formerly GenAI Vanilla) will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Generated by scripts/release_notes.py from Conventional Commits; do not edit by hand. Regenerate with uv run --project bootstrapper python -m scripts.release_notes --update-changelog.
Security
- security: except Blender's vendored Python findings in asset-baker (#1090)
- security: take the cryptography and MLflow fixes instead of excepting them (#1025)
- security: renew the expiring exception set and unblock the lock audit (#1023)
- security: patch a fresh advisory wave and time-box the one without a release (#1015)
- security: refresh runtime locks for newly published advisories (#913)
- supabase: enforce RLS on the spend ledger + stop exposing auth schema (9205105e)
Features
- support per-plugin Kong upstream timeouts (#980)
- bootstrapper: consumer-declarable managed host processes (#941)
- wizard: mark the Logs tab when it is holding an unseen error (#940)
- ollama: doctor check for host model-churn residency (#938)
- ollama: default content/vision model moves to qwen3.8 (#935)
- cache: bound the response cache, protect the queues, quantize KV (#933)
- ollama: doctor lint for host-daemon parallel serving (#849 part 2) (#923)
- tui: split the wizard into Setup and Logs tabs (#911)
- comfyui: consumer-declared custom nodes + managed-MPS install (#906)
- jupyterhub: FastMCP 3 toolkit + MCP notebook (#884)
- spark: #792 option-3 wrapper — REST driver-status confirmation via :6066 (#874)
- ollama: parallel-serving env on container sources — NUM_PARALLEL/MAX_LOADED_MODELS (#849, part 1) (#866)
- blender-mcp: managed host headless Blender + MCP bridge source (#772)
- ollama: pull declared models onto the host for ollama-localhost (#770)
- endpoints: export ATLAS_COMFYUI_INPUT_DIR + ATLAS_BLENDER_MCP_HOST_ENDPOINT (#768)
- comfyui: managed-host (MPS) model provisioning — complete #718 warn → provision (#766)
- bootstrapper: declarative deployment profiles that select sources and bundle an environment (#764)
-
bootstrapper: durable, platform-adaptive
<SVC>_SOURCE: auto(#762) - bootstrapper: durable BASE_PORT: auto in the consumer manifest (#751)
- n8n: activate consumer workflows without an API key (no consumer restart) (#747)
- endpoints: add 'endpoints assert' consumer contract drift gate (#739)
- consumer-manifest: key-gated env.values via enabled_if_env (#737)
- doctor: warn on declared-but-unpullable model provisioning (#733)
- bootstrapper: --base-port auto + doctor default-port-squat lint (#731)
- comfyui-mps: add COMFYUI_MPS_LISTEN for non-Docker-Desktop runtimes (#707)
- backend: durable, recoverable vector sync for memory + RAG ingestion (#674)
- endpoints: surface managed-localhost-mps ComfyUI output dir in consumer artifact (#634, #635)
- backend: evaluate answer relevancy without retrieved contexts (#625, #626)
- media-gateway: add ComfyUI-backed image provider (#616)
- backend: add media-gateway cancel endpoint with budget release (#557, #560)
- dashboard: redesign the Kong-root dashboard into themed category cards (#545, #546)
- wizard: prompt FAL with a masked API-token step after ComfyUI (#529, #530)
- litellm: expose TEI reranking via the Cohere-shaped /v1/rerank (#523, #527)
- litellm: route fal text→image through the native fal_ai provider (#520, #521)
- llm: add managed vLLM Metal Apple-silicon source (#507, #509)
- backend: LightRAG → TEI rerank adapter route (#500)
- comfyui: curate native Hunyuan3D-2 image→3D (MPS-runnable, shape-only) (#494)
- consumer: add LightRAG query profile registry (#491)
- media: spend ledger + budget/quota + per-provider kill-switch (#488)
- media: add hosted image→3D modality to the FAL gateway (#485)
- asset-baker: Blender headless HP→LP bake worker (#482)
- comfyui: add managed Apple-Silicon/Metal (MPS) source (#479)
- rag: generic RAG ingestion job contract for corpora, vectors, LightRAG (#476)
- consumer: seed + activate n8n workflows via atlas.consumer.yml (#473)
- consumer: add litellm_models manifest section for downstream model aliases (#470)
- backend: typed plugin.yml manifest — schema + validation + per-plugin Kong auth (#467)
- bootstrapper: add stable endpoints export contract for shared-network consumers (#463)
- minio: consumer object-storage contract with browser-safe presigning (#460)
- litellm: add versioned model capability metadata (#457)
- comfyui: add Krea 2 curated bundles (#454)
- add detached start mode (#438)
- add extensible MinIO consumer buckets (#435)
- backend: add Ragas evaluation surface (#429)
- backend: add Chonkie chunking surface (#425)
- backend: add Chonkie RAG chunking API (#375)
- comfyui: add diffusion_models + text_encoders categories (#348)
- data: add Spark Iceberg lakehouse runtime (#246)
- data: add Iceberg REST catalog service (#245)
- add Atlas root dashboard (#244)
- cli+wizard: make the project namespace overridable on start & stop (#165)
- backend: generic plugin seam for downstream route packages (#162)
- brand: make the block-art logo overridable via BRAND_LOGO_FILE (#161)
- models: declare embedding dim in YAML; picker auto-matches required dim (#153)
- move LLM + ComfyUI model source-of-truth out of Supabase into per-service YAML (ec927c51)
Also in this range: 160 fixes, 75 documentation, 4 build and ci, 42 maintenance, 67 promotions, 130 unclassified — counted here, not itemized; git log over the range lists them.
-
With the smokes gated to the native platform, the next
Final-image scancleared 38 images and then died inside Trivy on the Zeppelin image:context deadline exceededat the 10-minute--timeout, still in the analysis phase. The Spark images on the same hosted runner took about 6.5 minutes each; Zeppelin bundles thousands of interpreter jars plus two conda environments and is read back through the Docker daemon, so it needs more headroom. This was a scan-tool timeout, not a finding. -
Both the required
services-lintscan and the scheduledcontainer-securityscan now pass--timeout 30m(three invocations), with the reason in a comment next to the gating scan. The job-level limit (360 minutes) already covers the longer run.
1.3. Fixed — 2026-09-18 — Final-image scan runs the Spark and MLflow smokes only on the native platform (#1002)
-
The first
Final-image scanto get through every image's findings then failed with exit 124:run Spark S3A round trip timed out after 300 seconds. The amd64 Spark image had already passed the same round trip minutes earlier; the timeout came from the arm64 image, which the job builds under QEMU user emulation and then smoke-tested with a JVMspark-submitthat cannot finish inside the bound (natively the whole script takes about 12 seconds). The MLflow gateway-guard smoke, which starts a Python server, was next in line for the same treatment. -
Both functional smokes now run only when the platform is
linux/amd64. The arm64 images are still built and scanned for vulnerabilities exactly as before; only the emulated functional probes are skipped, and the workflow comment says why. The lakehouse test that pins the smoke wiring now also pins the platform guard.
1.4. Changed — 2026-09-18 — Reviewed nginx:alpine digest advanced after an attestation-only republish
-
The required
Build-validationcheck went red ondevelopa fourth time because Docker Hub movednginx:alpineagain (sha256:3a7edb15…→sha256:62ff2089…). Both reviewed platform manifests re-resolved unchanged (linux/amd64@sha256:7396be67…,linux/arm64/v8@sha256:03cceb15…; nginx 1.31.6 on Alpine 3.24.2), so the move was attestation-only and carries no new content; a pinned Trivy 0.74.0 scan of the new index still reports zero fixed HIGH or CRITICAL findings. The three review sites and the ledger advance together. - This is the fourth nginx:alpine move in three days, each one blocking every PR until refreshed. The probe pins a multi-platform index digest, which Docker Hub rewrites whenever it re-attests, even when no platform manifest changes; pinning the reviewed platform manifests instead (or moving the probe to the scheduled drift watch) would stop attestation-only republishes from failing the required check. Left for the owner's decision.
1.5. Security — 2026-09-18 — MLflow image takes Debian's published updates and moves four upstream-bundled Python packages (#1002)
-
The
Final-image scanreachesservices/mlflow/buildwith 46 fixed HIGH findings: 32 Debian 11 packages in the upstreamghcr.io/mlflow/mlflow:v3.15.1base, and 14 in that base's own site-packages (GitPython 3.1.57, sqlparse 0.5.5, setuptools 65.5.1, wheel 0.45.1). Atlas pins exactly MLflow 3.15.1 for the gateway SSRF review, and a pinned Trivy 0.74.0 scan of the newest v3.16.1 base still reports 45 findings, so no reviewed base refresh clears the gate. -
Both halves take the audited paths. The Dockerfile runs
apt-get upgradeafterFROMwith the rationale kept next to it (the accepted fallback); because Debian 11 left LTS on 2026-08-31 and deb.debian.org no longer serves the bullseye-security pool, the layer resolves against a date-pinned snapshot.debian.org (20260901T000000Z, the last LTS state), which also makes it reproducible. Moving off the EOL base needs the MLflow 3.16.x re-review. And the four Python packages move throughbuild/requirements.txtand the recompiledrequirements-locked.txtto GitPython 3.1.62, sqlparse 0.6.0, setuptools 84.0.0 and wheel 0.48.0, all inside MLflow's own declared ranges;pip checkin the build and the runtime-lock checker confirm the closure on both platforms.
1.6. Security — 2026-09-18 — LiteLLM and Open WebUI init images take Debian's published updates (#1002)
-
The
Final-image scanreachesservices/litellm/initandservices/open-webui/initwith the same three fixed HIGH findings inlibpcre2-8-010.42-1 (CVE-2026-86145, CVE-2026-89157, CVE-2026-89161; fixed in 10.42-1+deb12u1). Both pin the samepython:3.12.14-slim-bookwormdigest, and a pinned Trivy 0.74.0 scan of that tag's current index (sha256:782412e8…) still ships libpcre2 10.42-1, so no reviewed base refresh can clear the gate. -
Accepted fallback, and the last two reviewed-refresh-only Debian images move with it. Both Dockerfiles now run
apt-get upgradeafterFROMwith the rationale kept next to it. The test that required these two images to refuse mutable upgrades becomes a test that requires the upgrade and its rationale, matching the backend, JupyterHub and Neo4j tests; the contributor guide's list of images that take published updates gains neo4j, litellm-init and open-webui-init.
1.7. Security — 2026-09-18 — Zeppelin's five new upstream-bundled findings reviewed into its exception cluster (#1002)
-
The
Final-image scanreachesservices/zeppelin/buildwith nine fixed HIGH findings, all inside the upstreamapache/zeppelin:0.12.1image, which is still the newest published tag. Five jar findings sit in interpreters Atlas never binds (amqp-client 5.5.3 in the Alluxio interpreter; netty-handler 4.1.51/4.1.75/4.1.87 shaded into the Neo4j, Cassandra and R interpreters) or in Zeppelin's own interpreter RPC layer (libthrift 0.13.0, which cannot be swapped without rebuilding Zeppelin). Four Python findings sit in the image's conda Python 3.7 environment and package cache: grpcio 1.48.1 and tornado 6.2, whose fix (6.5.8) needs Python 3.9 or newer. -
Reviewed the same way as the existing 171-row Zeppelin cluster, not extended silently. Five purl-scoped rows are added to
.trivyignore.yamland the existing grpcio row gains the 1.48.1 purl, each with the concrete exposure (loopback-only port, no Kong route, unbound interpreters, container-internal RPC) and the same 2026-12-09 expiry as the rest of the cluster. A local amd64 build of the image scans clean with the updated file.
-
The
Final-image scanreachedservices/neo4j/buildwith 237 fixed HIGH findings: 223 Debian 13.5 packages and 14 jars in/var/lib/neo4j/lib(jackson-core/databind, netty-codec/-http/-handler, shiro-core, jetty-security). Neo4j published 5.26.30 on 2026-09-16; a pinned Trivy 0.74.0 scan of it reports no jar findings and only six OS findings (gzip, libpcre2, libsqlite3) that Debian has already fixed. The image pin moves 5.26.27 → 5.26.30 in every site that asserts it: the Dockerfile default,service.yml, the backup orchestrator and its offline dump/restore scripts (which verify the exact Neo4j version), the backup contract tests, the fragment-equivalence golden, the CI pull warm-up, and the service READMEs. -
The six residual OS findings take the accepted fallback. The Dockerfile now runs
apt-get upgradeafterFROMwith the rationale kept next to it, the same trade the backend, Airflow, Spark and JupyterHub images make. A local amd64 build of the refreshed image scans clean.
1.9. Security — 2026-09-18 — Local Deep Researcher image drops the unused system setuptools and moves uv to 0.11.33 (#1002)
-
The
Final-image scanstopped onservices/local-deep-researcher/buildwith four fixed HIGH findings, none in the OS layer. Two sat inside the base image's system setuptools 79.0.1 (_vendor/jaraco.context5.3.0, CVE-2026-23949;_vendor/wheel0.45.1, CVE-2026-24049) and two were the same quinn-proto advisory (GHSA-4w2j-m93h-cj5j) linked into the pinneduv0.11.19 binaries. -
Nothing in the image uses the system setuptools: the entrypoint builds the runtime venv with uv from the hash-pinned
runtime-requirements.lock, which carries its own setuptools 83.0.0 and wheel 0.47.0, and the init scripts import neither. The Dockerfile now uninstalls it after installing uv, matching thepython:3.12-slimimages that ship none. uv moves to 0.11.33, the last 0.11 release, which links quinn-proto 0.11.15; uv must stay in the image because the entrypoint runs it on first launch.
-
The
Final-image scanreached the JupyterHub image for the first time and stopped on seven fixed HIGH findings inlinux-libc-dev6.8.0-138 (Ubuntu 24.04 kernel headers pulled in by the base'slibc6-dev/gcc). Ubuntu published the fix (6.8.0-139) on 2026-09-03. The pinneddatascience-notebook:2026-08-24base predates it, and the newest upstream tag (2026-09-07) carries the fix but ships two new fixed findings (pyo3, quinn-proto) inside conda'srattler.abi3.so, which this Dockerfile never touches, so no reviewed base refresh alone could clear the gate. -
Same accepted fallback as the backend. The Dockerfile now runs
apt-get upgradein its existing apt layer with the rationale kept next to it;test_container_security.pymoves JupyterHub from the reviewed-refresh-only list to a test that requires the upgrade and its rationale, and the contributor guide's list of images that take published updates gains jupyterhub.
-
The required
Build-validationcheck went red ondevelopagain because Docker Hub movednginx:alpine. TheValidate pinned remote build contextsprobe pins the three floating bases the LLM Graph Builder upstream Dockerfiles use, and the index moved fromsha256:c8497b18…tosha256:3a7edb15…on 2026-09-17 UTC with genuinely new content: the same nginx 1.31.6 rebuilt on Alpine 3.24.2 instead of 3.24.1 (both platform manifests changed). -
Reviewed, not carried forward. Pinned Trivy 0.74.0 scans of the outgoing and incoming amd64 manifests both report zero fixed HIGH or CRITICAL findings, so the refresh neither adds nor removes exposure. The three review sites (
.container-scan-exclusions.yml, thetest_container_security.pyfixture and the external-contract ledger row) advance together with the evidence.
-
The JupyterHub image has been unbuildable since #1019. That Dependabot batch moved
msgpackto 1.2.2 in the notebook lock, but the Dockerfile's vendored-pip guard still asserted exactly 1.2.1 andrefresh_pip_vendor.pystill recorded 1.2.1 in pip's SBOM and vendor list, so the build died at the refresh step (AssertionError: (1, 2, 2)) and theFinal-image scanstopped there. The guard, the script'sNEW_MSGPACKconstant, and the refresh tests now agree on 1.2.2, so the copied wheel and the SBOM that describes it match again. The image is in the refresh-only policy and unchanged otherwise.
-
The
Final-image scanstopped on the Jenkins image with 44 findings, none in the OS layer. 39 sat in thegit-lfs3.7.1 binary the upstreamjenkins/jenkins:lts-jdk21image bundles (Go 1.25.3, x/crypto 0.36, x/net 0.38), 3 in jackson 2.20.1 inside/opt/jenkins-plugin-manager.jar, and 2 in jetty insidejenkins.war. The reviewed base refresh moves every pin fromsha256:f4f65e6c…(2.568.1) tosha256:c1e4c349…(2.568.3, re-resolved from thelts-jdk21channel), which clears the jetty findings; the Dockerfile now replaces the bundledgit-lfswith the checksum-verified 3.8.0 release the same way it already installsmc, which clears 38 of the 39 Go findings. -
Four reviewed exceptions, expiring 2026-12-09. Three for the plugin-manager jar, which Atlas runs only at image build time (
jenkins-plugin-cli) and the Jenkins runtime never loads, and one for x/crypto 0.54.0 in git-lfs 3.8.0 (an SSH server authentication bypass; git-lfs is only an SSH client). Jenkins stays disabled by default behind its own login. The external-contract ledger row records the re-resolved digest.
1.14. Security — 2026-09-17 — Iceberg REST catalog image takes Ubuntu's updates; fixture-jar findings reviewed (#1002)
-
The
Final-image scanstopped onservices/iceberg-rest/build. Three fixed HIGH findings were Ubuntu 22.04 packages the pinnedapache/iceberg-rest-fixture:1.10.1base lags (gpgv, libssl3); the Dockerfile now runsapt-get upgradebefore layering the PostgreSQL JDBC driver, the trade seven other images already make, and a local build scans clean at the OS level. -
Twenty-four advisories inside Apache's shaded
iceberg-rest-adapter.jarare reviewed, not fixed.1.10.1is the newest published fixture tag, so no refresh exists, and replacing shaded classes (jackson, netty, aircompressor, commons-beanutils, httpcore5) would create an unsupported catalog server. Each advisory has a purl-scoped.trivyignore.yamlexception expiring 2026-12-09 with the reachability boundary stated: the catalog is disabled by default, reachable only on the Compose network and a loopback-bound host port, and has no Kong route. The header of the exception file records the new cluster.
1.15. Security — 2026-09-17 — Docling adapter and MCP server images move to python:3.12.14-slim and take Debian's updates (#1002)
-
The
Final-image scanstopped on the Docling adapter image with 43 fixed findings (Debian 13.6, util-linux, gzip, …). Both the adapter and the MCP server image built frompython:3.12.13-slim; the newestpython:3.12.14-slimstill reports 13 fixed HIGH findings on its own (perl-base, libpcre2, sqlite, gzip), so a reviewed base refresh alone could not clear the gate. Both Dockerfiles now start from3.12.14-slimand runapt-get upgradeafterFROM, the trade the backend, Airflow, Spark, asset-baker, asset-worker, and backup-init images make; the compose fallbacks, manifest defaults,.env.example, the env-var reference, the fragment-equivalence baseline, and the MCP service tests move with the tag. Locallinux/amd64builds scanned with the pinned Trivy: adapter 43 → 0, MCP server 0 findings.
-
soupsieve2.8.4 → 2.9.2 inservices/docling/provider/localhost/uv.lock. CVE-2026-85999 and CVE-2026-86000 (GHSA-j934-xhv5-fg8f, GHSA-gjv8-xp57-g29c; hand-written selector regexes with pathological inputs) were published on 2026-09-17 and failed the required runtime-lock audit on develop; the fix is 2.9.0 and the lock now resolves the newest release. The airflow, docling GPU, jupyterhub, and local-deep-researcher locks already pin 2.9.1 or newer.uv lock --lockedstill passes;scripts.audit_runtime_locksreports PASS for every lock again.
-
Every service manifest now declares a
support:block —tier(stable,experimental,community,unsupported),evidence,evidence_revision(release tag or full commit), optionalownerandlimitations— validated by the schema and by a newsupport_evidencerule that refusesstableunless the evidence was gathered at a release tag; a repository test additionally requires that tag to appear in the release record, so a stale promotion is detectable. All 57 families startexperimentalwith the honest statement that no cited cold-start, workflow, or upgrade qualification run exists yet; nothing is calledstablewithout evidence. -
The tier renders on every surface from the same manifest. The generated service catalog gains a Support column, every README's Capabilities & limitations section opens with its support statement (per member for aggregate pages such as the STT provider), the manifest-fields reference documents the block, and the setup wizard shows a
[support: experimental]badge on the prompt of every non-stable family so a selectable-but-unqualified choice is marked before launch. No handwritten inventory was added.
-
The Unreleased section now opens with a generated summary.
scripts/release_notes.py --update-changelogrenders the block between theGENERATED RELEASE NOTESmarkers from the commits sincev0.1.0up to a recorded commit: breaking changes, security fixes, and features entry by entry, everything else as counts. The curated entries below it are untouched and remain the detailed history, which settles #838's changelog-split question by generation rather than by file.--check-changelogfails when the block differs from what its recorded range renders, so it cannot be hand-edited or go stale unnoticed; a test runs that check. -
Pull-request titles must be Conventional Commits. The required Manifest lint + unit tests job validates the title (
type(scope)!: summary) withscripts/release_notes.py --check-title. The generator reads the final squash subject, whose default depends on the repository's merge policy. History before the cut-off is left as it is; no release framework was installed and nothing was published.
1.19. Fixed — 2026-09-17 — Five init images build again after Alpine rotated their pinned packages (#1002)
-
apk addwith exact versions fails once Alpine's 3.24 repository rotates a package. The comfyui-init, hermes-init, lightrag-init, ollama-pull, and openclaw-init images pinnedca-certificates=20260611-r0,curl=8.21.0-r0, andjq=1.8.1-r0; the repository now serves 20260909-r0, 8.22.0-r0, and 1.8.2-r0 and no longer offers the old builds, sodocker compose buildand theFinal-image scanfailed at comfyui-init withapk add … did not complete successfully. The five Dockerfiles now pin the served versions (measured withapk policyagainst the pinnedalpine:3.24.1base on 2026-09-17); every other pin (bash,gettext,postgresql17-client,python3,wget,openssl) is unchanged and still served. Each image was rebuilt locally and scanned with the pinned Trivy: zero HIGH/CRITICAL findings.
-
The
Final-image scanstopped on the backend with 87 fixed HIGH findings, and no base refresh could clear them. The pinnedpython:3.12.14-bookwormdigest is the newest published build of that tag (and ofpython:3.12-bookworm), and the base image itself carries the findings (linux-libc-dev 64, libpcre2 15, libaom 4, libssh2 2, libexpat 1, …). The backend Dockerfile previously refused a mutable upgrade in favour of reviewed refreshes; it now runsapt-get upgradeafterFROM, the same trade the Airflow, Spark, asset-baker, asset-worker, and backup-init images make, with the rationale kept next to the instruction and the container-security tests updated to require it. The jupyterhub, litellm-init, and open-webui-init images keep the refresh-only policy.
- Navigation follows three reader journeys. Top-level sections now read Overview · Quick Start · Core Concepts · Tracks · Services · Configuration · Operations · Architecture · Development · Reference: Architecture moves from 6 to 8 so a local user reads 1–4, an application developer 5–7, and an operator or contributor 7–9. The documentation map opens with explicit entry points for each journey. Configuration becomes §6, Operations §7, Architecture §8; every affected canonical page's baked H1 and the generated site and wiki routes move with them.
-
docs/deployment/is nowdocs/operations/, and the operations overview is its index. The old folder fed two navigation sections; each section now owns one folder. Repository links in the README, the service READMEs, the generated ports reference, the drift audit, and the tests were updated together. -
Archived plans and specs state their status. Every file under
docs/superpowers/plans/anddocs/superpowers/specs/now carries a line under its title recording that it is an archived, dated point-in-time record whose landed outcome lives in the changelog. The changelog and roadmap split is deliberately not done: it depends on the #968 decision.
-
The Dependabot batch (#1092) moved
@gltf-transform/clito 4.5.0 but not the build guard. The asset-worker Dockerfile refuses to build when itsGLTF_TRANSFORM_VERSIONbuild arg (default 4.4.1, also the compose fallback and theASSET_WORKER_GLTF_TRANSFORM_VERSIONmanifest default) disagrees withpackage.json, sodocker compose buildand theFinal-image scanfailed at asset-worker while Build-validation stayed green: it runsnpm cion the host, not the Docker build. All three defaults, the fragment-equivalence baseline,.env.example, the env-var reference, and the README now say 4.5.0. -
A consistency test keeps the four sites in lockstep (
test_asset_worker_gltf_version_pins_agree_with_package_json), so the next Dependabot bump fails unit tests instead of the image build.
-
The
Final-image scanstopped onservices/backup/initafter asset-baker cleared. The digest-pinnedpostgres:17.10-alpinebase lags Alpine 3.24.1's security updates, so the image carried seven fixed HIGH util-linuxlibuuidadvisories (CVE-2026-53612, -53613, -53614, -76642, -78408, -78409, -78410). The Dockerfile now runsapk upgrade --no-cachebefore baking the exactopenssl=3.5.8-r0, the same trade the asset-baker, Spark, and Airflow images already make: byte-for-byte base reproducibility for a patched runtime. A locallinux/amd64build scanned with the pinned Trivy reports zero HIGH/CRITICAL findings; the OpenSSL pin and the entrypoint's built-image check are unchanged.
-
scripts/release_notes.pyrenders concise, grouped notes for any commit range. It walksmainfirst-parent, classifies each Conventional Commits subject into Breaking changes / Security / Features / Fixes / Documentation / Build and CI / Maintenance, expands develop-to-main promotion merges into the develop commits they carry, keeps promotion squashes as a single entry, de-duplicates by(#PR)so nothing is counted twice, and keeps non-conforming subjects verbatim under Unclassified.--format json,--since-tag, and an overrides file ({pr: {bucket, subject}}) cover automation and maintainer corrections; it refuses to writedocs/CHANGELOG.md. -
The numbering question is answered by construction. Generated sections are numbered contiguously and pass the same heading contract
make docs-checkenforces, so a generated changelog would not need an exemption; whetherdocs/CHANGELOG.mdbecomes generated stays a maintainer decision, documented with the tool indocs/deployment/releasing.md§6. No release framework was installed and nothing was published.
-
GitHub Actions —
actions/cache4.3.0 → 6.1.0 (55cc8345…),docker/setup-qemu-action3.7.0 → 4.3.0 (1f40c722…), andastral-sh/setup-uv10.0.1 → 10.1.0 (bec219d2…) acrossservices-lint,docs-pages,container-security, andupstream-drift-watch. Every commit pin was resolved from its release tag through the GitHub API before landing; the two majors only move the actions to the Node.js 24 runtime, which the hostedubuntu-24.04runners already provide, and they retire the "Node.js 20 is deprecated" warning the QEMU step printed on every run. -
npm minors —
@gltf-transform/cli4.4.1 → 4.5.0 in asset-worker (its lock drops thenode-fetchandlanguage-tagssubtrees the new release no longer needs) andn8n-workflow2.38.1 → 2.39.1 in the n8n init toolchain; thesharp0.35.4 override stays in force in both locks. - Batched from Dependabot #1071, #1072, #1073, and #1074 into one CI cycle because strict status checks would otherwise cost four sequential full runs; the pinned test and ledger sites move with the pins.
-
The
Final-image scanjob stopped at asset-baker since #1021. Blender 4.3.2 bundles its own CPython 3.11 with certifi 2021.10.8, setuptools 63.2.0, and urllib3 1.26.8 baked into the release tarball; nine HIGH findings (one certifi, three setuptools, five urllib3) are neither apt- nor pip-fixable, and the real fix is a Blender version move that also needs the blender-mcp bridge re-verified live. -
Nine reviewed, path-scoped, expiring exceptions. Each
.trivyignore.yamlrow is pinned to the exactdist-info/METADATApath under/opt/blender/4.3/python, so the same package anywhere else still fails the gate, carries a per-advisory reachability statement (the bundled interpreter runs onlybake.pyunderblender -b, never imports those packages, never opens a network connection, and never installs packages; the service's FastAPI layer uses the image's own patched Python), and expires 2026-12-09 with the rest of the reviewed set. The gate can now report on the contexts behind asset-baker for the first time; #1002 stays open for #996, #997, the remaining contexts, and the ruleset promotion.
-
GitHub Discussions is disabled on the repository, yet three reader-facing pages sent people there. The README Support section, the troubleshooting guide's Community Resources, and the roadmap's Contributing section now route questions to a GitHub issue with the
questionlabel and keep bug reports on GitHub Issues; the README and documentation map additionally direct security-sensitive findings to the private route in the security policy instead of a public issue. -
A guard prevents the mismatch recurring.
test_support_route.pyfails if any reader-facing page links Discussions while the feature is disabled (history and archived planning notes are exempt) and pins the bug / question / security routing on the three pages. A maintainer who enables and moderates Discussions deletes the guard in the same change that relinks it.
-
docs/critical-pages.yamldeclares the pages a reader cannot do without. Security (reporting route), prerequisites (wizard requirements), support (documentation-map Getting help), release (Cutting a release), and recovery (troubleshooting recovery and cold start, backup restore) are named by manifest id together with the section titles each must carry. The contract holds stable titles only, so it never duplicates the mutable policy prose that lives in the pages. -
make docs-checkenforces it on all three surfaces.scripts/docs/critical_pages.pyfails the gate when a critical page is missing from the manifest, unreachable from the documentation map, absent from the rendered site or wiki, or rendered as a bare title without a body under a required section. Reachability reuses the manifest-reachability walk, now exposed ascheck_docs.reachable_sources. -
External community references are permitted, not substitutes. The contract's
external_references(the issue tracker) must be absolute URLs outside the documentation surfaces; a URL or path can never satisfy a page role, and a site, wiki, or repository file URL is rejected as a reference. Mutation tests cover every failure mode plus the live contract.
-
SECURITY.mdis now a manifest page (§9.8 under Development). It was omitted fromdocs/manifest.yaml, so the site and wiki transforms stripped the documentation map's link to plain text and neither surface carried the reporting route. The policy now renders atsecurity-policy/on the site and9.8-Security-Policyon the wiki, appears in the site navigation and wiki sidebar, and the map links it on every surface. -
New §6 Automated Scanning states the live gate policy. It names the two
services-lint.ymlimage gates as they actually run —Build-validation(required) andFinal-image scan(reports on every run, not yet required, promotion tracked in #1002) — and the.trivyignore.yamlexception rules, so the policy text agrees with the gate rather than describing an aspiration. -
Publication triggers include the policy.
docs-pages.ymlnow rebuilds and republishes whenSECURITY.mdchanges. - Tests (
test_security_policy_surfaces.py) pin the manifest route, the baked H1, actionable reporting content on site and wiki, the map link on both generated surfaces, nav/sidebar reachability, the workflow trigger, and that the scan wording names the real job names.
-
The landing page's raw HTML actions now target files, not site directories.
docs/index.mdlinked its Quick Start, Service Catalog, and Architecture buttons toquick-start/,services/, andarchitecture/— MkDocs directory URLs that GitHub cannot open (docs/services/does not exist, and the other two show a bare tree listing). The canonical page now linksquick-start/index.md,services.md, andarchitecture/index.md, which GitHub renders directly. -
The site transform rewrites raw HTML anchors to MkDocs directory URLs. MkDocs rewrites Markdown links but leaves raw HTML alone, so
scripts/docs/transforms.pynow maps canonical file targets to the pretty URL each page is served from (quick-start/,services/,../setup/from a nested page), preserving fragments. The wiki surface already mapped them to numbered pages; unpublished targets stay untouched on both. -
scripts/check_doc_links.pyvalidates raw HTML targets.<a href>and<img src>are checked like Markdown links — file targets must exist, fragments must name a heading, and a directory target passes only when it carries aREADME.mdGitHub renders in place — with external, mailto, anchor, and code-quoted HTML skipped. A landing-page test pins the three canonical targets so the regression cannot recur. - One archival design spec quoted an
<img>inside inline code; the validator now ignores inline code spans in the HTML pass, so quoted markup is not treated as navigation.
-
A fresh advisory wave is patched, and one finding is time-boxed.
httpx2moves to 2.12.0 across the backend and asset-worker test locks, the Docling adapter, and the bootstrapper toolchain, closing five findings — an SSE line-buffering CPU exhaustion, multipart header injection, a Content-Length/Transfer-Encoding conflict,wss://traffic sent without TLS, and unbounded streaming decompression. Both npm projects already pinnedsharpthrough an override; that pin moves from 0.35.3 to 0.35.4, which is what actually carried the fix.accelerate1.14.0 keeps CVE-2026-69112 under review instead: it is the newest published release and the upstream fix exists only as an unreleased commit, while the path traversal needs a caller-supplied sharded checkpoint that Docling never accepts. -
endpoints exportrefuses to answer before ports exist. A consumer manifest'sBASE_PORT: autois allocated at bring-up, but the export emitted a complete, well-formed set of endpoints beforehand, every port drawn from the default block. Nothing marked them as provisional, and on a host running several Atlas projects that block plausibly belongs to another one — so the endpoints answered from the wrong stack rather than refusing. The command now exits 3 until a block is allocated, with--allow-unresolvedfor templating and sample files. -
An interrupted bring-up says its build may outlive it.
docker compose up --builddrives BuildKit inside the Docker daemon, so abortingstart.shstopped the supervisor while the build continued — finishing minutes later and creating containers for a run that had already been abandoned, including services a subsequent run had explicitly disabled. The resulting stack contained a container no single run explained, which reads exactly like a broken source toggle. Because the build belongs to the daemon rather than this process tree, an interrupt now says so — naming the project and the exactdocker psfilter to check — instead of leaving the operator to infer it from image and container timestamps. -
ComfyUI img2img inputs are decoded and fetched fail-closed. Data URIs require strict base64 PNG, JPEG, or WebP payloads; image bytes are decoder-verified before upload, decompression bombs and oversized inputs are rejected, and remote URLs are disabled unless their exact HTTPS origin appears in
COMFYUI_INIT_IMAGE_TRUSTED_ORIGINS. Remote fetches do not follow redirects or inherit proxy environment variables, and verified image content—not a URL suffix—selects the uploaded filename and media type. -
ComfyUI cancellation is targeted and retryable. Both Backend adapters use the pinned core's atomic
POST /api/jobs/{job_id}/cancelcontract and accept only an exact boolean result, eliminating the queue-snapshot race and bodyless global/interrupt. Interrupted history is terminalcancelled; ambiguous ComfyUI deliveries may be redelivered without changing FAL's stricter repeat semantics, while older localhost instances fail closed. -
RAG execution claims recover after ambiguous Redis writes. Every delivery gets a fresh owner, and only an atomic exact-owner compare-and-set may replace an ambiguous live claim. Redis outages retry independently and indefinitely with jittered backoff capped at 600 seconds, while upstream phase failures retain their existing three-retry terminal policy; execution leases, terminal idempotency, and heartbeat fencing are unchanged.
-
Asynchronous work handoffs retain definitive ownership through request cancellation. RAG ingestion and research creation reconcile their durable state, Ray submission requires a stable validated
submission_idand returns409for an existing ID, and async memory consolidation accepts a scopedidempotency_keybacked by a worker-side execution lease and bounded result marker. -
Required pull-request checks scan container changes before merge. Changed manifest images and built runtime images now receive pinned Trivy scans; generic exact-release checks also cover manifest defaults, Docker ARG defaults, and inline pip install operands. Local final-image scans fail every fixable High/Critical finding; vendor-unfixed findings remain visible in the scheduled complete scan and automatically become gating when a fix appears, while a fixable finding may be deferred only by an exact, expiring exception.
-
MLflow's tracking surface no longer exposes two SSRF paths. The server image moves from 3.14.0 to 3.15.1 to close CVE-2026-64849, and the Atlas entrypoint now accepts only that exact reviewed version. CVE-2026-71211 has no fixed release, so an outer ASGI guard returns 404 for every prefixed or unprefixed native, REST, and AJAX AI Gateway route family while preserving health, tracking, registry, and artifact APIs; direct unit coverage and a required pinned-image HTTP smoke prove the boundary. The exact 3.15.1 scanner exception now expires 2026-10-15 — deliberately short, because MLflow 3.16.0 is published and OSV records last_affected 3.15.2, so a fix appears to exist and the near deadline forces that upgrade decision.
-
The Alpine init-image OpenSSL advisory is narrowly time-boxed. CVE-2026-14456 affects OpenSSL QUIC listeners, but Atlas's Alpine images run finite init jobs and shell clients and never start one. Alpine has published the fixed package without a patched stable image tag, so the scan policy carries exact package-and-architecture exceptions through 2026-12-09 instead of either moving production init jobs to an alpha image or suppressing the CVE globally.
-
GitPython and Material for MkDocs are patched. The notebook image takes
gitpython3.1.59+ for PYSEC-2026-3785 through PYSEC-2026-3788, reached transitively through jupyterlab-git, and the docs toolchain takes Material for MkDocs 9.7.7 for CVE-2026-73295, a DOM cross-site-scripting hole in search suggestions. -
Weaviate moves to 1.38.13 to clear fifteen fixable findings. The pinned 1.38.2 image carried CVE-2026-56854 (CRITICAL,
golang.org/x/crypto) plus fourteen HIGH findings across the Go standard library, gRPC,x/net,x/text, and OpenSSL. They were never a regression — the image is only scanned when a pull request touches a service manifest or the exclusion policy, and the unauthenticated registry throttle had been aborting that step before it ran. Authenticated scanning surfaced them, and the pin advances across every asserted site inside the same minor line, clearing thirteen. The remaining two are time-boxed to 2026-11-30 because no released Weaviate fixes them: 1.38.13 is the newest release and still vendorsgolang.org/x/crypto0.53.0 andgoogle.golang.org/grpc1.82.1. CVE-2026-56854 affects that crypto library's SSH server, which Weaviate does not run, and CVE-2026-84304's gRPC memory-exhaustion path sits behind a port thatHOST_BIND_IPbinds to loopback by default. -
Dependency auditing survives transient registry weather.
npm auditresolves its advisory set over the network, and shared CI egress produced registry errors on two different projects across consecutive runs while both audited clean locally. That path now retries with backoff, and the audit carries a deadline sized to its real cost — the asset-worker graph takes just over four minutes, so the shared five-minute budget was failing on slower runners.pip-auditfails the same way from the other direction: a PyPI or OSV hiccup returns an ordinary exit code with an empty report, which surfaced as unparseable JSON blaming the lock — the Parakeet GPU lock failed that way on two consecutive runs while auditing clean locally — so an empty report is retried on the same backoff and named for what it is instead of accusing the lock. A real vulnerability count, a malformed response, and a genuine timeout all still fail on the first attempt. -
The final-image scan reports before it gates. Building and scanning every local Compose and init image is new, and the fleet still carries pre-existing findings — a stale Open WebUI base, Python packages vendored inside Blender's runtime, bundled JVM jars. Making it a required check on arrival would block every pull request on debt it did not introduce, including Dependabot's. It therefore runs as its own
Final-image scanjob, unchanged and reporting on every run, and is promoted to a required context once the tracked image work lands. -
Locally built images take published Debian security updates. Pinned upstream bases lag Debian, so the final-image gate reported fixable findings the pins alone could not clear — CVE-2026-56408 in
libexpat1on Airflow, and CVE-2026-31789 (OpenSSL) plus CVE-2026-33845 (GnuTLS) on the sharedpython:3.12.9-slimbase. Airflow, asset-worker, asset-baker, Jenkins, Local Deep Researcher, and Zeppelin now apply the published updates the same way the Spark image already did, trading byte-for-byte base reproducibility for a patched runtime. Three images are deliberately excluded: Neo4j has no live apt invocation, and JupyterHub is one of four images that policy requires to take security fixes through a reviewed base-image refresh rather than a mutable upgrade. One finding is time-boxed instead:libthrift0.16.0 arrives inside pyspark 4.1.2's bundled jars, and reaching the 0.24.0 fix requires moving the pyspark pin, theapache/spark:4.1.2cluster image, and the Iceberg runtime together, so it expires 2026-11-30 pending that coordinated upgrade. -
Required image scanning follows the images a change moves. A manifest edit that left every pin untouched previously re-scanned every image that manifest owns, and refreshing the reviewed digest baseline re-scanned all sixty-nine manifest-owned image and platform pairs. Both reported pre-existing findings that the change could neither introduce nor fix, so a routine upstream tag move could block an unrelated pull request. Selection now follows the image references a diff actually adds or removes, per file, and a rename or header-only entry still selects everything it owns because nothing there identifies which images moved. Built images remain covered by the separate final-image scan.
-
Remote image scanning can authenticate, and its structural exclusions are re-reviewed.
cr.weaviate.iodelegates authentication to Docker Hub's realm, so anonymous pulls from shared CI runners exhaust a per-IP quota that no retry budget can outlast — the scan step now forwards optional Docker Hub credentials, and treats an empty pair as absent so unauthenticated runs still work. The four build exclusions covering CUDA images that exceed the hosted runner profile and the two remote-context Dockerfiles move from 2026-09-15 to 2026-11-30; both rationales were re-verified rather than carried forward. -
Newly published advisories are patched or reviewed.
pypdfmoves to 6.16.2 across the backend runtime and test locks, closing three parser denial-of-service findings (CVE-2026-84309/84310/84311), and JupyterHub'stornadomoves to 6.5.8 for a request-parsing DoS pair and a cookie-attribute injection that reopened CVE-2026-35536. Two findings have no reachable fix and are time-boxed instead:nltk3.10.3 is the last affected release for PYSEC-2026-3740, andtransformers5.10.0 is unreachable whiledocling-corecaps it below 5.9.0 for the pinned docling family (CVE-2026-9856). Both advisories require a caller-supplied model path, which neither the notebook image nor the localhost document provider ever exposes; the exceptions carry rationales and expire 2026-11-27 and 2026-10-15 respectively. -
The notebook image no longer depends on a third-party wheel index.
pyg_libwas published only atdata.pyg.org, which became unresolvable on 2026-09-02 — its CNAME target carries no A record — and the package is absent from PyPI, so the sole source for it is gone. Both the compiled-lock gate and the image build failed closed on that CDN.torch_geometricstays, resolving from PyPI as a pure-Python wheel and running on its native fallbacks; the optional compiled accelerator is dropped, as no Atlas notebook imports it. The audited graph now resolves on Python 3.13 for both Linux architectures. -
JupyterHub's NLTK dependency is patched to 3.10.3. The compiled notebook runtime lock now excludes the eight newly published
PYSEC-2026-3733throughPYSEC-2026-3741advisories affecting NLTK 3.10.2; Atlas takes the upstream fix instead of adding advisory exceptions. Lock validation now also targets the image's actual Python 3.13 runtime on both Linux architectures, removing three stale Python 3.11-only backports from the audited graph. -
Root startup validation is failure-atomic and machine-readable. Invalid base ports, consumer manifests, and track synthesis now fail before mutating
.env;--jsonreserves stdout for exactly one terminal result even when Click rejects root arguments, while genuine subcommands retain their native output. Foreground log exit codes now propagate instead of being discarded. -
Managed host lifecycle actions require positive process identity. Generic, ComfyUI MPS, vLLM Metal, and Blender launchers require a start-time stamp before accepting a child as managed, and cross-process locks serialize install, start, stop, and removal for each managed state directory. Missing identity triggers bounded whole-process-group compensation; a failed compensation retains in-memory and on-disk PID recovery evidence and reports the surviving PID instead of claiming termination. Unknown, mismatched, permission-denied, and stale leaderless-group ownership never authorizes signalling, and control-flow interrupts are re-raised after visible cleanup diagnostics.
-
Managed host admission and readiness fail closed. Generic and Blender lifecycle locks now time out instead of blocking forever; platforms without the required POSIX lock/process-group primitives fail preflight and direct start before spawning. A port occupied by an unmanaged process is rejected under the lifecycle lock, and launch ownership remains in memory until readiness succeeds so a dead leader's surviving process group can still be swept or reported for rollback.
-
Shutdown reports what actually happened. Managed Blender teardown participates in the explicit managed-host stop path; manual stop, launch rollback, and state removal all verify raw PID/process-group liveness after signalling, preserve evidence for survivors, and return failure instead of trusting ownership-qualified status or a nominal stop result. State-directory deletion errors also propagate instead of reporting a successful removal. Project-name persistence is best-effort: a failure warns while the current teardown still uses the requested override. Hosts cleanup failures propagate, missing
sudois handled as an operational error, and help text now describes every managed runtime and migration. -
Required n8n webhook reactivation is part of launch success. A failed post-seed n8n restart now fails both linear and Textual launch paths, rolls back newly created managed hosts, and never commits or announces a successful stack. Default shutdown advisories also use fail-closed raw tracking evidence, warning when corrupt, unreadable, unstamped, or mismatched PID state means a host may still be alive even though ownership-qualified status is false.
-
MinIO bucket creation distinguishes absence from operational failure. Asset worker, asset baker, and the financial-research notebook create a bucket only for an explicit
404/NoSuchBucket/NotFound; authentication failures, timeouts, and other storage errors propagate instead of being misreported as a missing bucket. -
Back navigation restores the complete pending answer. Options, numbers, multiselects, inline secondary values, secrets, free text, and explicit clears survive a revisit. Pressing Enter on a visibly restored secret/text value now reconfirms the session answer instead of silently replacing it with the persisted-value KEEP sentinel; hidden-step secondary state is pruned rather than leaking into launch arguments.
-
Architecture and operations references match supported runtime paths. Generated SOURCE and LLM diagrams now include managed vLLM Metal, track overrides model both enabled and explicit-disabled outcomes, and the routing diagram labels inbound direct-port access as host NAT. Localhost port documentation separates Kong-routed HTTP, direct managed-host HTTP, and TCP/no-Kong endpoints; managed-host operations name Blender MCP from the outset, and CLI help distinguishes wizard tracks from deployment profiles while treating the linear stdout flow as a supported path.
-
The Airflow image clears its two remaining scan findings. The pinned
apache/airflow:3.3.1base ships GitPython 3.1.58, which carries CVE-2026-78675, CVE-2026-78676 (CRITICAL), and CVE-2026-78677; the overlay now raises the floor to 3.1.59 the same way it already raisessetuptools, resolving to 3.1.62. The other finding cannot be fixed by moving a pin:netty-handler4.1.115.Final is vendored inside the AWS SDK bundle jar that Airflow, Spark, and Zeppelin fetch for S3A, and Hadoop 3.4.2 qualifies AWS SDK 2.29.52 specifically — newer SDK releases break against third-party S3 stores, which is already why the CloudFront advisory carries an exception. CVE-2026-75595 is an SNI routing bypass that only affects a netty TLS server using per-SNIclientAuth=REQUIRE; Atlas runs the jar purely as an S3A client against private MinIO and starts no netty listener, so all three vendored copies are time-boxed to 2026-12-09. They are frozen for two different reasons: the 4.1.115.Final copy by Hadoop's SDK qualification, and the 4.2.13.Final and 4.2.7.Final copies — insideiceberg-aws-bundleand pyspark's spark-connect client — by the coordinated Spark family upgrade already tracked forlibthrift. -
A lost wheel fails the notebook build instead of stretching it. pip silently falls back to compiling an sdist when no wheel is available, and a source build is indistinguishable from a hung one — #994 watched the layer spend 44 minutes on
torch-clusterafter a third-party wheel index went unreachable. The install now passes--only-binary=:all:, including for the CPU torch trio where a source build would be catastrophic. Exactly two packages in the graph publish sdists alone,pyspark-clientandpython-louvain, both pure Python and both carved out by name; the set was established by resolving the locked graph with source builds disabled, not by guesswork. Any other package losing its wheel now stops the build with a clear error. -
Security exceptions are renewed on evidence, not extended silently. 259 of 262 Trivy exceptions were due to expire 2026-09-30, which would have failed CI outright —
container_securityrejects stale entries before a scan can run. Reviewing them showed they collapse into three clusters where no upstream fix is reachable: Zeppelin (171 entries) is already on 0.12.1, the newest published tag; Julia (40) is on 1.12.7, the newest 1.12.x; and Spark (40) cannot be fixed by any release — 4.2.0, the newest, still shipslibthrift0.16.0 and its netty 4.2.13 is short of the 4.2.17 that CVE-2026-75595 needs, which reframes #996 as blocked upstream rather than merely unscheduled. Renewed to 2026-12-09 with that verification recorded in the file — except MLflow's, held to 2026-10-15 because 3.16.0 now appears to fix CVE-2026-71211 and that decision should not wait a full cycle. OSV additionally reports 44 rows as no longer affected, but those were deliberately left alone: Trivy ships its own database, and deleting on OSV's word could turn a passing scan red. -
An advisory-ID surface change is absorbed without losing coverage. The lock audit began failing on develop with six "unreviewed" advisories and four "stale" allowlist entries. None of them were new vulnerabilities — pip-audit's database started surfacing PYSEC identifiers where it had reported CVEs, and every one is a straight alias: PYSEC-2026-3865 for CVE-2026-71211, PYSEC-2026-3850 for CVE-2026-68508, PYSEC-2026-3804 for CVE-2026-69112, PYSEC-2026-3929 for CVE-2026-9856. The exceptions are re-keyed to the identifiers the tool actually reports, with the original CVE named alongside so the rest of the tree still matches. One of the newly-surfaced findings turned out to be genuinely fixable:
pytorch-lightningis transitive under nemo-toolkit but is not covered by itslightning<=2.4.0cap, so PYSEC-2026-3967 is closed by flooring it at 2.6.6 rather than excepted — unlike its siblingshydra-coreandlightning, whose published fixes remain behind that cap. -
Two notebook advisory exceptions become real fixes. MLflow 3.16.0 widened its
cryptographyconstraint from<50to<51, and that single cap was holding two exceptions in place.cryptographymoves 49.0.0 → 50.0.1, taking the PYSEC-2026-3552 PKCS#7 EnvelopedData decrypt fix instead of arguing it was unreachable;mlflowmoves 3.15.1 → 3.16.0, which falls outside PYSEC-2026-3865 / CVE-2026-71211 — GHSA-h7x2-h6g9-p789 recordsintroduced 3.13.0, last_affected 3.15.2and was re-evaluated on 2026-09-10, after 3.16.0 shipped. Both exceptions are removed rather than renewed, and the lock audit confirms it by reporting them stale. This is the notebook's client pin and moves independently of the separately deployed MLflow service, whose image pin and AI Gateway guard are unchanged. ccxt stays at 4.5.64 deliberately: 4.5.77 is now resolvable, but it switched from version ranges to exact pins and would dragcertifibackwards from 2026.7.22 to 2026.6.17 while pinningtyping_extensions,orjson, anduvloopexactly — a routine bump with a real cost and no security benefit, since 4.5.64 carries no advisories. -
MinIO is pulled from quay.io, because Docker Hub stopped serving it.
minio/minioandminio/mcnow return401 UNAUTHORIZEDto an anonymous registry token, and 404 from the Docker Hub API — the repositories are no longer publicly pullable. This was not a rate limit: it failed identically on retry, and it breaks every fresh bring-up of theml-enganddata-engtracks, not just CI. Both references move toquay.io/minio/..., MinIO's own registry, which carries the exact same pinned tags (RELEASE.2025-09-07T16-13-09ZandRELEASE.2025-08-13T08-35-41Z) as multi-arch manifest lists — so this is a registry move, not a version bump. The fallback default insideservices/spark/compose.ymlmoved too, sincespark-initreuses the MinIO client image and would otherwise have kept a broken Docker Hub reference wheneverMINIO_INIT_IMAGEwas unset. Making the pull work also let the scanner reach these images for the first time, which surfaced 80 pre-existing findings across both MinIO images — 45 in the client (Go toolchain, vendored modules, and two Red Hat UBI 9.6 base packages) and 35 in the server, including three CRITICAL. The server set is bounded by deployment rather than by a fix: MinIO publishesRELEASE.2025-10-15T17-29-55Zon GitHub only — absent from Docker Hub and quay, verified — so even findings with a published upstream fix cannot be reached by moving the pin, and several have no fix at all. Both MinIO ports publish throughHOST_BIND_IP, which defaults to127.0.0.1:, so they are loopback-bound unless an operator deliberately widens them. That is a deployment mitigation, not a code fix — Go toolchain and vendored modules inside/usr/bin/mc, plus two Red Hat UBI 9.6 base packages — the image content is byte-identical to what Docker Hub served, so nothing regressed; the gate simply could not see it before. They are time-boxed to 2026-10-15 rather than the full horizon, becauseRELEASE.2025-08-13T08-35-41Zhas been the newestmcMinIO published for 13 months and no bump can reach the patched Go toolchain. The real question is whether Atlas should keep depending on an abandoned image at all, givenservices/jenkinsalready installsmcfrom its GitHub release binary instead.
-
plugin.ymlnow acceptsconnect_timeout,write_timeout, andread_timeoutin milliseconds. Each optional value is validated identically by the host bootstrapper and backend container against Kong's integer range (1..2147483646). Atlas copies only explicitly declared fields, so omissions retain Kong's 60,000 ms defaults. Since Kong scopes these settings to services, each timed plugin receives a deterministic dedicated backend service and path route; untimed plugins remain on the existing sharedbackend-apiservice, andinherit/open/key-authbehavior is preserved on both paths.
1.33. Changed — 2026-08-23 — Wizard TUI moved to Textual 8.2.8; log pane and command summary are now drag-selectable (#535 Pass 0/1)
-
Textual floor raised 6.2.1 → 8.2.8. The upgrade was needed for selection auto-scroll, cross-container selection, and the
TextSelectedevent (added in Textual 6.11.0), none of which the old floor carried. Textual 8.x in turn requiresrich>=14.2.0, sobootstrapper/pyproject.toml'srichfloor moves up to match (it had drifted to>=13.0.0, lettinguvresolve an undisclosed jump torich15.0.0). Follow-up (#535 followups review, finding R9): raising the floor's number alone reproduced the same unbounded shape that caused the original drift, andrichwas still resolving 15.0.0 — a major above the documented14.2.0requirement — undisclosed.richis now capped>=14.2.0,<16, pinning the ceiling to the major actually locked and tested, matching thetextual-image>=0.12,<0.13convention already used a few lines below it.textual's own floor is left unbounded — it was raised and reviewed this same pass rather than left to drift, and capping the framework owning the whole wizard surface is a bigger, separate call than this narrow fix. -
User-visible: the log pane and the command summary are now drag-selectable like any other text on screen — Textual 8.x dropped the restriction that kept scrolling containers (
RichLog, which the log pane subclasses) from supporting mouse selection. The existing keyboard-driven, filter-aware copy actions (which copy the whole matching scrollback, not just what's visible) are unchanged and still the recommended way to copy a filtered log. -
Internal: a new
wizard/model/layer (state.py,state_builder.py,service_discovery.py, plus extracted domain rules for tracks, cloud-provider resolution, and LLM source selection) is Pass 1 of an MVVM rebuild of the setup wizard (#535). A new AST-based test enforces the layer import direction (view → viewmodel → model) so the split can't silently erode.
-
apache/airflow3.3.0 → 3.3.1, and the locks that pin its provider graph. The 3.3.0 image and the Airflow runtime lock carried advisories whose fix is a patch release:apache-airflow/-core/-task-sdk3.3.0→3.3.1,providers-common-compat1.17.0→1.18.0,providers-common-sql2.0.3→2.1.0,providers-amazon9.33.0→9.34.0, andsqlparse0.5.5→0.6.0. The JupyterHub lock sharessqlparseand takes the same bump; it resolves byte-identically on both required platforms, so the lock stays single-file. Both locks were recompiled against themselves as a constraint with only the advisory packages released, so nothing else in either graph moved. The image pin moves in twenty places —.env.example, the service manifest, fourcompose.ymlbuild blocks, the DockerfileARG, the CI build-validation invocation, three test files, the rendered-config golden ×4, and both READMEs.The final Airflow 3.3.1 image runs Python 3.13, so its provider lock is now compiled byte-identically for amd64 and arm64 against Python 3.13 rather than the former Python 3.12 approximation. Compatibility with packages already present in the upstream image pins
botocore 1.43.56,importlib-metadata 8.9.0,protobuf 6.33.6, andwebsockets 16.1.1; the corrected graph also adds its resolvedpyyaml-ft 8.0.0dependency. Image construction runspip checkso future overlays cannot silently leave the combined environment inconsistent.Per the #850 lesson — CI-green is not functional correctness for a pinned-upstream bump — the external-contract ledger's two Airflow rows were re-verified against the pulled
3.3.1image rather than carried forward: the local image ID is re-recorded,amd64/arm64manifests confirmed present,ENTRYPOINTconfirmed still/entrypoint, and all three init CLI contracts re-executed (db migrate,users createwith its six flags,connections addwith its eight typed flags). -
One exception instead of a bump, for a capped transitive.
CVE-2026-68508is RCE inhydra-coreviahydra.utils.instantiate()resolving_target_out of a config the caller does not control. 1.3.4 ships the fix, butnemo-toolkitcapshydra-core<=1.3.2in every extra the Parakeet GPU lock pulls — including the newest 3.0.0 — so there is no reachable upgrade target. Atlas never imports hydra; the provider's only NeMo entry point isASRModel.from_pretrainedon the operator-pinnedPARAKEET_MODEL, which no request payload can select. Recorded as a fail-closed reviewed exception with a 2026-11-27 re-review, alongside thePYSEC-2026-3624exception already carried on that lock.
-
A loop variable shadowed the active filter chip.
_rebuild_visiblebindstagto the selected filter chip and re-reads it, per option, in the badge test at the top of its loop. Two inner loops that splice in host-pulled variants reusedtagas their own loop target, so after the first expanded family with anything pulled,tagheld a variant string like"8b"— and every remaining family failed the badge match and disappeared, while the chip row still readALL. Reachable on anyollama-*source, and_sort_keyfloats pulled families to the top, so the first family a user expands is exactly the one that triggers it. Renaming the loop target then exposed that its body used the same shadowed name for the leaf's ownvariant=, correct only by accident of the two meanings coinciding inside the loop.
-
check_docspassed without checking either generated surface.check()callsbuild(..., check=True), which renders into a temporary directory and returns without writinggenerated/— which is gitignored and absent on a clean checkout. Both surface validators then globbed zero files and the gate printed PASS having examined neither the site nor the wiki; with a stale tree left by an earlier branch it validated the old output. CI escaped it only because the Makefile happens to run a full build immediately before, an ordering dependency expressed nowhere in the gate and not honoured bycheck_site.py.check()now produces the tree it validates, usingbuild()rather than the bare renderers so the diagram assets exist too — without them the wiki-link check reports every embedded image as a missing target.
-
Two seed slices created
publictables with no RLS. Slice 06 grantsanonSELECT acrosspublic, PostgREST publishes that schema withPGRST_DB_ANON_ROLE: anon, andHOST_BIND_IPships empty so it binds0.0.0.0— leaving RLS as the only control. Verified against a running stack: an unauthenticated GET returned every row ofcomfyui_workflows, andanoncould DELETE. One of the two is GoTrue's migration tracker, whose four rows exist so GoTrue skips a migration PostgreSQL 17 rejects — a single unauthenticated DELETE returns the auth service to a crash-loop. Separately,anonheld SELECT onstorage.objects/buckets, where RLS is disabled by design so the grant is the only gate; that grant was being reissued by slice 06 after slice 04 dropped it, so the fix and its explicit REVOKEs live at the authoritative site.
-
key-authwas enforced with no consumer holding a key. Five services (auth-v1,rest-v1,graphql-v1,realtime-v1-rest,storage-v1) enablekey-authwith noanonymousfallthrough, and DB-less Kong has no admin API — so the declarative file is the only place a credential can come from, and it carried none. A/B against the pinnedkong:3.9.3: the correctSUPABASE_ANON_KEYwas indistinguishable from a wrong one, both401 Unauthorized. The state was test-locked by an assertion that the consumer list had exactly one entry. Also fixed: the Atlas root-dashboard route matched host + path prefix/, and Kong ranks by number of matching criteria rather than prefix length, so it outranked every Supabase path route —/rest/v1/…onlocalhostreturned 200 with dashboard HTML.
1.39. Fixed — 2026-08-20 — A consumer's managed host process could SIGKILL an unrelated process group
-
The PID-reuse guard never reached the generic manager. #647 and #947 hardened
blender_mcp_manager,comfyui_mps_managerandvllm_metal_manageragainst a recycled pid, butManagedHostManager— the consumer-declarable path added by #795/#941 — was written without_pid_is_stranger. A crashed consumer process leaves its pid file behind; the OS recycles that pid onto a process owned by the same user, so_pid_alive'sPermissionErrorarm never fires and the pid reads as live../start.sh managed-host stop <name>then signalled it. The blast radius was larger than in the built-ins because_signal()escalates throughos.killpg, so the SIGTERM/SIGKILL pair hit the stranger's whole process group.status()had the mirror-image bug: it reported the stranger as running, soensure_running_with_ownershipskipped the real launch and the stack came up against a process that did not exist.The guard now runs in both
stop()andstatus()and identifies the process by(pid, start time), which is unique on POSIX:start()stamps the start time into the pid file and the guard compares it against the live process. Two earlier attempts matched the process argv instead and each failed in a different direction — seeding markers from the whole argv cleared 87 unrelated processes on one machine (-m,127.0.0.1, the port number all match half a process table), while curating the markers left specs whose tokens are all generic with no evidence that appears in a command line at all, so the guard disowned its own live process andstop()deleted the pid file while the service kept running. An argv is not an identity: a wrapper script,exec,setproctitleor a gunicorn master rewrites it. Thepsprobe is pinned toTZ=UTCandLC_ALL=C, becauselstartotherwise renders through the ambient timezone and locale — a service started from an interactive shell and stopped from launchd or CI (both UTC) would not have matched its own record.
-
.envline injection through a consumer value or key..envis line-oriented andparse_env_fileresolves it last-wins, and consumer entries were emitted asKEY=VALUEwith no sanitization. A YAML block scalar is the natural way to write a newline by accident, and it landed a second assignment below the generated one —SUPABASE_SERVICE_KEY,SUPABASE_JWT_SECRET,N8N_ENCRYPTION_KEY, the MinIO secrets orBASE_PORT— which then won. It was permanent, too: the rewrite pattern^KEY=.*$is MULTILINE but not DOTALL, so later runs rewrote only the first line and stepped over the injected remainder. The same worked through a key carrying a newline, since the emitted line isKEY=VALUEeither way, and a key containing=additionally defeated the duplicate-key conflict check. This matters because a consumer manifest can come from a third-party repo that vendors Atlas as a submodule. Keys are now validated as shell identifiers and values rejected if multi-line, at both the manifest parse boundary and the.envwrite boundary — several override paths (the env-user overlay, storage exports, auto-source and auto-BASE_PORT resolution, profile apply) do not pass through the parser, so the writer has to be safe on its own.
-
A blank multi-select is an answer, not a missing value.
backfill_missing_env_varsrefills blank.envkeys from.env.example, which is right for a value the user never set. But the wizard's multi-selects write exactly that shape: deselecting every Ollama model wroteOLLAMA_USER_MODELS=, backfill runs last in the pipeline, and the same run restored the default trio — soollama-pulldownloaded several GB the user had just declined, with "Filled 4 blank value(s)" as the only trace.WEAVIATE_ENABLE_MODULESandN8N_INIT_NODESbehaved the same way. Those lists are now excluded from the blank-fill. Single-valued settings deliberately are not: a blankLITELLM_DEFAULT_MODELis an error state, not a choice.
-
active: falseon an n8n workflow meant the opposite. Unquoted it is a YAML boolean, andFalse or "fromJson"is"fromJson"— the policy that honours the workflow file's own"active": true. So the one value meaning "keep this workflow off" seeded a live webhook. The asymmetry gave it away:active: truerendered"True", which is not a policy and was loudly rejected. -
dev:anddefault:profile overrides raced instead of conflicting.devaliasesdefault, but overrides were bucketed by the raw name, so two blocks targeting one profile landed in separate buckets, the conflict detector never fired, and YAML key order decided the winner.
-
The pruning glob was unescaped while its regex was escaped.
ATLAS_ENV_FILEis operator-supplied and documented as such, so a basename containing a bracket turned the glob into a character class matching nothing and pruning never ran — retaining every secret snapshot, the one outcome the retention cap exists to prevent. A related path-form bug counted the just-written snapshot as prunable when the source path was relative, retaining one fewer than asked and, at a cap of 1, deleting the file being returned.
1.44. Fixed — 2026-08-20 — Subprocess output decoded with the ambient locale, crashing on one non-ASCII byte
-
15 call sites passed
text=Truewithoutencoding=. Thirteen sibling sites already paired the two, so this was drift rather than a decision. Withoutencoding=, decoding followslocale.getpreferredencoding()— ASCII underLC_ALL=C, which is what CI runners, launchd and minimal containers actually set. A single non-ASCII byte frompip,git,ollamaorpsthen raisesUnicodeDecodeError, aValueErrorthat none of the surroundingexcept (OSError, SubprocessError, TimeoutExpired)handlers catch, so a clean error became a raw traceback.blender_mcp_manager._pid_is_strangerwas itself exposed, since it readsps -o command=output carrying user paths. All 15 now passencoding="utf-8", errors="replace".
-
Every rewrite of
.envtook a backup and nothing ever removed one. A base-port change, Supabase key generation or an env migration each drop a timestamped0600copy of.envnext to it, andcreate_private_backuphad no retention at all — six production call sites, zero pruning anywhere in the tree. Each copy holds the Supabase JWT signing keys plus whatever cloud-provider keys are configured, so after a key rotation the superseded secret stayed readable on disk indefinitely, in a growing set of files nothing tracked.create_private_backupnow keeps the five most recent snapshots per source-and-version prefix and prunes the rest, after the new snapshot is durable and never touching the one it is about to return..env.backup.*remains gitignored.
-
The default-model move was never swept past
docs/. #935 madeqwen3.8:latestthe default-active content and vision model and updatedservices/ollama/models.yaml,.env.exampleand the docs tree, but notservices/*/. Five of the survivors were live runtime fallbacks —LITELLM_DEFAULT_MODEL: ${LITELLM_DEFAULT_MODEL:-ollama/qwen3.6:latest}in the n8n (x2) and local-deep-researcher compose fragments, the JupyterHub startup script's generated.env, and the example ETL DAG — so any environment where that variable is empty asked LiteLLM for a model the catalog no longer registers and gotInvalid model nameback. The rest were READMEs, docstrings and notebooks naming the retired tag as current, including two that contradicted the file they cited:services/ollama/README.mdclaimedqwen3.6was thedefault: trueentry inmodels.yaml, andservices/hermes/README.mddescribed a priority list #935 had already changed. The one remaining mention inservices/*/runtime configuration, inmodels.yaml, is a deliberate historical comparison. Illustrative uses elsewhere are intentionally left: the TUI widget docstrings render sample option rows as ASCII art,docs/superpowers/is an archive of past plans, and the backend test fixtures use the tag as an arbitrary model id.
-
15_mcp_clients.ipynbusedClientin four cells and imported onlyFastMCP. The in-memory transport demo in the last cell is not gated onMCP_SERVERS_URL, so running the notebook top-to-bottom failed withNameError: name 'Client' is not definedwhether or not MCP was enabled. The invocation cell was separately unusable: it called a literal"tool_name"placeholder that can never resolve. It now uses a boundedsearxng_web_searchsmoke with fixed input and a one-result limit; live result content still depends on SearXNG. An unusedimport tracebackwent with it.
1.48. Documentation — 2026-08-20 — The changelog's [Unreleased] section read oldest-first at the bottom
-
Entries had been appended rather than prepended. The file declares Keep a Changelog, which is newest-first, but roughly a month of work — the wizard Setup/Logs tab split, the bounded response cache, the
qwen3.8default move, both blender-mcp process-lifecycle fixes, the landing-page grounding pass — sat ~2,600 lines below the top while a reader landed on 2026-07-19 entries. The[Unreleased]section is now strictly newest-first (verified: zero ascending violations across every dated entry (115 of them at the time of writing)), with every entry and line preserved. Three ungrounded claims inAGENTS.mdwere corrected in the same pass: aservices/litellm/catalog-init/path that does not exist, a virtual-manifest roster listing 5 of the 7 manifests that declarevirtual: true, and a test count of 1,300 against an actual 3,659.
1.49. Fixed — 2026-08-16 — The documentation landing page overstated the platform and under-rendered the tracks
-
"60 service families" counted directories, not families. A service family is a
service.ymlowner (manifests.py::_is_service_dirrequires it), so the three doc-only folders —stt-provider,doc-processor,multi2vec-clip— inflated the total by exactly three. The real count is 57.docs/ROADMAP.mdalready stated the policy ("the generated documentation home … derive their current counts directly fromservices/*/service.yml, avoiding hand-maintained totals"), but the home was the one page carrying a hand-maintained total, and nothing enforced the policy. -
The page claimed 7 tracks above 6 cards.
All / Customhad no card at all, so the assertion and the evidence directly beneath it disagreed — on the.iosite and the wikiHomeas well, since both derive from the same landing source. -
Track names now match
tracks.ymldisplay names verbatim on both hand-authored surfaces. The README wroteTrading/Financial ResearchandAll/Customwhere the source (and the landing page) sayTrading / Financial ResearchandAll / Custom— the two surfaces disagreed with the source and with each other. -
Five tests now ground the opener (
test_docs_landing_grounding.py): the service-family count againstservices/*/service.yml, the track count and one card per track againsttracks.yml, canonical display names verbatim on both surfaces, and the always-on core against the locked tier. Each is mutation-checked against the exact defect it replaces. The opener is the fastest-decaying region in the repo — it asserts counts and names the generator does not derive — so the fix that matters is the enforcement, not the numbers.
-
A recycled pid is a stranger, and the pid file outlives the crash. When the managed bridge dies without cleanup, its pid file remains; the OS is then free to hand that pid to something else entirely.
blender_mcp_manager.stop()signalled it blind — SIGTERM, then SIGKILL — so a stale pid file could take out someone's editor or build. Confirmed by a test that spawns a real unrelated process and asserts it survives; without the guard, it does not. -
_pid_is_strangernow gates the signal, using the process start time recorded alongside the PID. If that identity is absent, mismatched, or cannot be probed, ownership is unknown: teardown refuses to signal, preserves the PID file for inspection, and reports failure instead of risking an unrelated process group. -
Command-line substring ownership was retired in all three managers. Generic markers such as
Blender,main.py, orvllmcan also appear in unrelated processes and are not identities. The shared(pid, start time)comparison now provides the only authorization to signal. -
The
blender-mcp stopzombie-reap test was re-armed in the same change. Adding this guard silently defanged it: its child also read as a stranger, sostop()short-circuited and the test passed without ever reaching the reap it exists to cover. The test now stamps the child PID with its real start time, with an explicit ownership assertion, and removing the reap makes it fail again.
-
The failure had no cause attached, which is why it kept recurring.
make docs-checkends inmkdocs build --strict, and Material'sprivacyplugin self-hosts external assets by downloading them at build time — ~20 files (Google Fonts, plus mermaid from unpkg)..cacheis gitignored, so CI refetched every one on every run, and under--stricta single transient failure is a hard error. This is how #934 and #941 failed on diffs that touched no external URL. -
The docs jobs now cache
.cache(actions/cache, keyed onscripts/docs/build_docs.py+docs/assets/stylesheets/**), so the steady state performs no third-party fetches at all and a font or theme change re-primes it. Theprivacyplugin stays enabled — it is what keeps the published site from calling third parties, so disabling it would trade a CI annoyance for a real privacy regression. -
--forward-stderrnow forwards output on failure too. The flag already existed, documented as "use only for non-secret build logs", and everydocs-checkstep passes it — butbounded_subprocesshonoured it only on success, returning early on a non-zero exit. So the one flag that exists to surface build logs did nothing in the only case anyone needs it: CI printedsubprocess output redactedand nothing else. Steps that do not opt in stay fully redacted, which is the entire safety boundary and now has its own test. -
The cache key hashes tracked sources, and a test enforces that. The first attempt keyed on
mkdocs.yml+docs/stylesheets/**— butmkdocs.ymlis generated (absent when the cache step runs) and the stylesheets live underdocs/assets/stylesheets. Both patterns matched nothing,hashFiles()returned an empty string, and CI loggedCache saved with key: mkdocs-privacy-with no hash at all: a constant key that would never re-prime when the fonts changed. It now hashesscripts/docs/build_docs.py(which declares the theme fonts and generatesmkdocs.yml) plusdocs/assets/stylesheets/**, and a test asserts everyhashFilespattern matches at least one tracked file — the untracked half is the dangerous one, since it is green locally and empty in CI. - The forwarded streams are flushed in order (stdout is block-buffered when piped, stderr is not) so the detail cannot land above the line naming what failed, and the header reads
failed (exit N); output follows:rather than claiming redaction while printing the output.
-
A zombie still answers
kill(0). A child of the current process that has exited but has not been waited on stays in the process table until reaped.blender_mcp_manager._pid_aliveprobed liveness withos.kill(pid, 0)alone, so after SIGTERM actually killed the bridge,stop()kept polling for its full 10-second grace window, escalated to SIGKILL, polled again, then returnedFalse— and, per its own "a failed stop keeps the pid file" rule, left a stale pid file behind. -
comfyui_mpsandvllm_metalwere never affected: both already reap via a_reap_childhelper.blender_mcpwas the one manager missing it, and it now uses the same approach rather than a third variant. - Surfaced while building the consumer-declared managed-host framework (#795), whose tests spawn a real process instead of mocking
Popen. A mocked process cannot exhibit this — the bug lives precisely in the interaction betweenPopenchild ownership andos.kill(pid, 0)— so the regression test spawns a real one too and asserts all three symptoms: the return value, the elapsed time, and the stale pid file.
-
Three hand-built managers were converging on one shape. ComfyUI-MPS, vLLM-Metal and Blender-MCP each implement
preflight → install → start → status → health → stop → removeover a~/.atlas/<name>state dir. A consumer that needed a fourth — an MLX segmentation service, a Metal-only inference server — had to either land a bespoke manager upstream or hand-roll a lifecycle outside Atlas. -
managed_host_services:inatlas.consumer.ymldeclares one: name, command, port, optional venv (with an optionalmetal:guard), install steps, and a health probe../start.sh managed-host list|preflight|install|start|stop|status|health|remove <name>runs the same lifecycle the built-ins get,doctorreports amanaged-host-servicesrow, and the endpoint contract gainsATLAS_<NAME>_HOST_ENDPOINT. -
Named for what it is. The abstraction is a managed host process; a Metal venv is one optional flavor.
blender-mcpis a host process with no venv at all, so calling the capability "managed MPS" would have baked the exception into the interface. -
Three constraints are load-bearing, not stylistic. A declared command is argv handed straight to
subprocessand never a shell, so a semicolon in a value stays a string. A non-loopbackbindis refused withoutallow_remote: true, because these processes are unauthenticated by construction.workdirandvenv.requirementsmust resolve inside the declaring consumer's root — stricter than the sibling manifest blocks, because this one declares things Atlas executes. -
The endpoint scheme follows the probe. An
httpprobe exportshttp://, atcpprobe exportstcp://— the same trapATLAS_BLENDER_MCP_HOST_ENDPOINTalready avoids, where advertising a raw-socket bridge ashttp://hands a consumer a URL no client can use. -
The three built-ins now share the framework's
PreflightResultinstead of carrying three near-identical copies, so the count of duplicated verdict types went from three to zero rather than to four.ProcessStatusstays per-manager: each reports genuinely different fields (device, served model ids, port-open), and unifying it would produce a union type nobody reads. -
A zombie no longer reads as running. The generic manager reaps its own exited children before probing liveness: a process that has exited but not been waited on still answers
kill(0), which would makestop()poll its whole grace window and then report failure for a process it had just killed.
- A toast is the wrong medium for a failure you were not there to see. The launch-phase error toast announces itself once and evaporates. If the operator is on the Setup tab — or away from the keyboard entirely — the only remaining trace is a log line behind a hidden tab, and the run looks fine.
-
The Logs tab label now carries a red
!—[ Logs! ]— from the moment an error is written while the tab is hidden until the tab is actually visited. Visiting it is the acknowledgement, so there is no dismiss key to learn. - Errors only. Warnings were deliberately excluded: a normal launch emits enough of them that the marker would be lit from the first minute onward, which conveys exactly as much as no marker at all.
- The glyph is measured as part of the tab's plain-text width, not painted over it, so the click targets and border padding stay correct while it is lit — a regression test asserts the
Logslabel still falls inside its own recorded span after the marker appears. - The Setup/Logs tabs themselves are now documented in the wizard guide (tab semantics,
1/2/Shift+Tabnavigation, per-tab shortcut bar, and this marker), which the original Setup/Logs tab split never covered.
1.55. Added — 2026-08-11 — Host-Ollama residency: stop a multi-model run evicting its own working set (#798)
-
The failure has no error message, which is why it needs a check. A pipeline touching several models in sequence — a LightRAG ingest with separate extract, embed and keyword models — evicts its own working set when the daemon's
OLLAMA_MAX_LOADED_MODELSis below that count: Ollama unloads one model to load the next and reloads it moments later. The run simply crawls, and onlyollama pscycling throughStopping…reveals it. -
New
ollama-residencydoctor check. DeclareOLLAMA_MODELS_RESIDENT_MIN(the number of distinct models one run touches) and./start.sh doctorreads the host daemon's realOLLAMA_MAX_LOADED_MODELSandOLLAMA_KEEP_ALIVE, failing before a long run rather than after it, with thelaunchctlcommand to fix it. Advisory by construction, like its parallel-serving doctor sibling: an unreadable host config reportsskipped, neverfail, because warning about a value that could not be read trains people to ignore doctor output. -
OLLAMA_KEEP_ALIVEis now a first-class variable on the container sources, where Atlas does own the daemon. It is not defaulted to-1: that pins every loaded model in RAM until reverted and the daemon restarted — tens of gigabytes on a large model-set, competing directly with ComfyUI and vLLM-Metal on a unified-memory host. Operator opt-in, with the cost stated wherever it is documented. -
Two levers, deliberately distinguished in the docs, because conflating them is why this bites:
OLLAMA_MAX_LOADED_MODELSis how many models fit resident;OLLAMA_KEEP_ALIVEis how long each stays after last use. Ollama's 5m default evicts even when there are enough slots. -
reusing-atlas.mddocuments host sizing for multi-model ingest, including the full set-for-the-run-then-revertlaunchctlsequence and its RAM cost.keep_aliveis read as a duration string (5m,1h,-1) through its own probe rather than the int-coercing parallel reader, which would have reported it as permanently unknown.
-
qwen3.8:latestreplacesqwen3.6:latestas the default-active Ollama content and vision model. Verified against the live library before pinning:qwen3.8:latestand the27bfamily (27b,27b-mlx,27b-bf16,27b-mxfp8,27b-nvfp4,27b-q4_*,27b-q8_0,27b-mtp-*) all resolve. 27B parameters, 18 GB at:latest, 256K context, natively multimodal across image and video. -
think: falseis now load-bearing rather than incidental. Qwen3.8 ships with thinking mode on by default (disableable per request), where the previous default did not. Without that request default every extract/keyword call would pay for reasoning tokens it does not need and return prose wrapped around the answer. Roles that genuinely want deliberation can re-enable it per request. -
Two upstream knobs are deliberately not wired:
reasoning_effort(depth) andpreserve_thinking(carry reasoning across turns).bootstrapper/schemas/models.schema.jsonpinsrequest_defaultstothinkonly, so exposing them is a schema change rather than a catalog edit — recorded here instead of silently widened. -
The Hermes fallback moved too.
services/hermes/init/scripts/init-hermes.shcarries the only copy of the default model outside the catalog, used whenHERMES_DEFAULT_MODELis blank. It is not derived frommodels.yaml, so a rename that missed it would silently fall through to the next candidate — a cloud model — changing cost and locality with no error. The script now says so. - Everything else propagates:
.env.example'sOLLAMA_USER_MODELS,LITELLM_DEFAULT_MODELandLITELLM_VISION_MODELare generated from the catalog's default-active set byenv_assembler, not hand-maintained. -
Note on size:
:latestis 18 GB. On a unified-memory host this is the single largest resident default, and it multiplies withOLLAMA_NUM_PARALLEL; theOLLAMA_KV_CACHE_TYPE=q8_0change exists to offset exactly that.
1.57. Changed — 2026-08-10 — Bounded the response cache, protected the Redis queues, quantized Ollama's KV cache
-
The LiteLLM response cache now has a useful lifetime and a namespace. Gateway caching has always been on, but with no explicit
ttlLiteLLM fell back toBaseCache's 60 seconds — bounded, but far too short to pay off on anything except rapid-fire identical prompts.LITELLM_CACHE_TTLnow defaults to an hour, andLITELLM_CACHE_NAMESPACE(litellm.cache) makes gateway keys scannable and separately droppable on a Redis db 0 that it shares with four other consumers. Both are baked as literals at render time rather thanos.environ/…references, because that form resolves to a string andttlmust be numeric; an unparseable value falls back rather than failing the render. -
Redis bounds memory with TTL-based eviction.
volatile-lruevicts TTL-bearing keys, including LiteLLM cache and ordinary Backend media records. Persistent keys survive; if no TTL key remains, writes fail with OOM.REDIS_MAXMEMORYdefaults to0(unlimited). Without a cap, the container can still be OOM-killed. Media retention and ledger exceptions are documented in Redis tuning. -
Ollama quantizes its KV cache by default.
OLLAMA_KV_CACHE_TYPE=q8_0roughly halves the attention KV cache for a negligible quality cost. This is the dominant per-slot memory cost andOLLAMA_NUM_PARALLELmultiplies it, so it pairs directly with the parallel-serving work.OLLAMA_FLASH_ATTENTION=1is pinned alongside because the quantization is a no-op without it — a silently-inactive memory setting is worse than an absent one, since it reads as configured. Both apply to theollama container-*sources; the host daemon owns them forollama-localhost. - Documented explicitly, because the question comes up: Redis cannot cache an attention KV cache. Those are per-sequence tensors touched on every generated token; Redis is a network hop. Ollama has no external cache backend of any kind. What Redis caches for LLM traffic is whole responses, one layer up at the gateway.
1.58. Fixed — 2026-08-10 — Langfuse tracing was shipping to the public cloud; langfuse-web never went healthy (#928, #929)
-
Gateway tracing recorded nothing, silently (#929). LiteLLM was given
LANGFUSE_BASE_URL, but the langfuse-python v2 SDK bundled in the pinned LiteLLM image reads onlyLANGFUSE_HOST— confirmed in the v2.57.13 source,os.environ.get("LANGFUSE_HOST", "https://cloud.langfuse.com"). With it unset, every trace was shipped to the public Langfuse cloud using locally-generated keys, rejected there, and dropped. LiteLLM still loggedInitialized Success Callbacks - ['langfuse'], every call still succeeded, andGET /api/public/tracesreturned a clean, empty HTTP 200 — so nothing anywhere indicated a problem. Both host variables are now set to the same endpoint:LANGFUSE_HOSTfor the current pin,LANGFUSE_BASE_URLbecause v4 prefers it and keeps the old name only as a deprecated alias. -
langfuse-webwas permanently(unhealthy)(#928). Next.js standalone bindsprocess.env.HOSTNAME, and Docker sets that to the container ID, so the server listened on the container's eth0 IP only while the healthcheck probed loopback. The app was fully functional throughout, but./start.shexited non-zero, which breaks scripted bring-up for any consumer enabling Langfuse. PinningHOSTNAME=0.0.0.0is the standard Next.js-in-Docker fix. Upstream's own compose does not hit this because it ships no healthcheck onlangfuse-webat all. -
Coverage is now documented honestly. Langfuse tracing in Atlas is gateway-level: it captures exactly what passes through LiteLLM, which covers Open WebUI, the backend, and LightRAG's default binding. The documented exception is LightRAG's per-role
*_LLM_BINDING_HOSToverrides, which can point a role straight at a native provider and bypass the gateway — those calls produce no traces and nothing warns about it. Noted in both the Langfuse and LightRAG READMEs, and the "no traces appear" troubleshooting now leads with the one-command check that would have caught #929.
-
The track matrix had two byte-identical generated homes — nav §4 (
docs/tracks.md) and §10.5 (docs/reference/tracks.md), both rendered from the samemodel.tracks. Because they were generated from one source they could never drift, which is exactly why the duplication survived unnoticed. The reference copy is removed and the nav-section page — the one users actually browse — survives;docs/reference/index.mdand the six landing-page cards now point at it. The renumber ripple this deferral warned about turned out to be contained: §10.6 → §10.5 and §10.7 → §10.6, with no hand-authored page affected. - Guarded against silent return — a test asserts the matrix has exactly one generated home, rendering the real page set and matching on the table header rather than on a filename. Mutation-proven: restore the second generator and it fails.
- Two of #838's three items remain open by decision, not oversight: the CHANGELOG/ROADMAP split is deferred until the current issue queue stops appending to them, and the nav reordering plus
docs/deployment/rename is blocked on a target order that no document specifies.
-
./start.sh doctornow catches an under-provisioned host Ollama. Onollama-localhostAtlas cannot set the daemon's environment — the host-prereq doctrine means the operator owns it. Ollama defaults to one parallel slot and silently serializes concurrent requests rather than rejecting them, so a consumer needing eight gets correct-but-slow behaviour with nothing in any log to explain it. DeclareOLLAMA_PARALLEL_MINand the newollama-parallelcheck reads the daemon's actualOLLAMA_NUM_PARALLELback, failing with the exactlaunchctl setenvcommand when the host is below it. -
Deliberately narrow, and advisory by construction. The probe reads the host config only where that is genuinely verifiable — macOS, where the daemon inherits
launchctl setenv— and reports unknown everywhere else, because a daemon's environment otherwise depends on how it was started (systemd drop-in, shell export, container) with no single readable source. An unknown reportsskippedand never warns: a warning about a value that could not be read would train people to ignore doctor output. That scoping is what makes the check safe to ship without the live per-platform validation CI cannot provide, which is why part 2 of #849 had been deferred. - Part 1 of #849 (the
OLLAMA_NUM_PARALLEL/OLLAMA_MAX_LOADED_MODELScompose env for the container sources) already shipped earlier; this completes the ticket.
-
The command summary no longer vanishes on short terminals —
#lower-paneclips, andPromptPanel's1frclaimed the leftover rows first, so the summary was laid out past the clip and rendered zero visible rows below ~32 terminal rows while its own CSS comment claimed it "yields on short terminals". It is now docked, which makes that claim true down to the point where the pane itself has fewer than three rows — genuine space exhaustion rather than a layout bug. Guarded at 44/38/34/32/30 rows. - A failed launch no longer contradicts itself — the footer said "ctrl+q detach" while the log pane directly above it still said "ctrl+c to cancel", because only the success path updated the pane's border subtitle. Both keys worked, so nothing broke, but the two pieces of chrome disagreed. A setup-phase worker error still shows "cancel", which is the truthful hint for a launch that never started.
-
Clearing a cloud provider round-trips again — an empty selection disables the provider at launch, but the command summary emitted no flag for it, so a pasted command fell back to
.envand silently re-enabled what had just been cleared. It now emits--cloud-<provider>-source disabled, matching what--ollama-custom-modelsalready did for the same situation. -
Log-copy failures are no longer effectively silent — the deliberately broad
exceptthat stops a clipboard error from setting the CLI exit code showed only the exception type in a transient toast; it now also writes to the log pane and session-log tee. -
Tab cycling derives its order from
BrandPanel._TAB_LABELSinstead of a duplicated list that would desync silently the day a third tab is added. -
Dead
_log_pane/_log_chipsNone-checks removed — both are built in__init__and typed non-Optional, so every check contradicted its own declared type. -
Test gaps closed — click routing now covers the Setup tab, a border click while tabs are disabled, and the widget-relative coordinate basis; the summary's height cap is asserted against
MAX_BODY_ROWSrather than a substring that passed withmax-height: 0; and the documented drag-to-select on the summary is finally pinned. -
Busy-wait polls bounded — three
while …: await asyncio.sleep(0)loops in the provider-boundary, parakeet-startup and docling-cleanup tests hot-spun with no deadline, so a condition that never came true hung with no diagnostic. They now use a real deadline and report what they last observed. The single-turnawait asyncio.sleep(0)yields elsewhere are a correct idiom and were left alone.
-
The Identity Edit LoRA is now a catalog entry —
comfyui-krea2editexists to runkrea2_identity_edit_v1_2.safetensors, and its own README lists that LoRA as a hard requirement. Without it the nodes register and nothing edits, whileprovision-nodesreports success anddoctorreports the node present — installed-but-inert, a worse failure shape than a clean error because every signal Atlas emits says it is installed.krea2-identity-edit-v1-2(1.83 GB, categorylora, pinned by revision + sha256) now ships in the curated catalog and declaresrequires_custom_node: [comfyui-krea2edit], so the linkage is expressed in the schema rather than in prose nothing enforces. -
Licensing adds no new surface — HuggingFace has no SPDX entry for the Krea 2 Community License, so
otheris what every Krea 2 artifact carries, including the base weights Atlas already ships. The LoRA'sNOTICEplaces it under that same agreement as a Derivative Model of Krea 2 Raw, so selecting it accepts exactly whatkrea2-raw-bf16/krea2-turbo-bf16already accept. It is an unofficial community fine-tune and says so; a test pins its licence fields equal to the base weights'. -
Raw-vs-Turbo caveat documented — the LoRA's
base_modelis Krea 2 Raw. The node advertises both, but identity fidelity may differ on Turbo, which is the cheaper and more common pick — worth knowing before paying for a 35 GB Raw download to find out. -
Dead node URL corrected — the manifest fixtures pinned
krea-ai/comfyui-krea2edit, which does not exist; the real pack islbouaraba/comfyui-krea2edit. It lived only in tests, but those were the sole place in the tree naming a URL for this node and it reads as canonical, so a consumer copying it got a clone failure at provision time rather than an obviously-wrong value. A guard now fails if the dead URL reappears.
-
The tabs now look like tabs — they shipped inheriting the border colour, so neither read as selected and nothing suggested the other was clickable. The active tab is bold accent, the inactive one muted, and hovering an inactive tab brightens it; the active tab keeps its accent when hovered.
_tab_segmentnow returns the styled markup and its rendered width separately, so the byline and the click targets are measured from plain text — previously a single string was measured with an ad-hoc escaped-bracket correction that would have silently shifted every click target the moment styling was added. The byline-only border (no tabs, during the wizard) is untouched and still byte-identical at 60/90/140/200 columns. -
The answered prompt no longer lingers on the Setup tab — a regression from the tab split. Before it,
_transition_to_launchcalledremove_children(), which took the prompt panel and command summary down as a side effect; the tab swap replaced that teardown to keep the stack overview live, and nothing assumed the retiring job, so the Setup tab kept showing the last answered question (67/67) for the whole launch. They are now hidden, not unmounted — other paths still hold references. -
You can stop the stack from the TUI — nothing could before:
ctrl+csets exit 130 and exits,ctrl+qdetaches, and both leave containers running, so stopping meant leaving and running./stop.sh.ctrl+sstops (volumes kept) andctrl+xcold-stops (volumes removed). Neither acts on a single press: each arms and only a second press of the same key commits, so arming a stop then pressing cold re-arms rather than deleting volumes uncommitted. Managed ComfyUI-MPS, vLLM-Metal, and Blender MCP runtimes are host-global singletons shared across consumers, so they are deliberately left running — and the completion notice says so. Offered only after a successful launch:_launch_detach_readyis also set on failure, and teardown-on-failure is a separate decision (tracked in #912). -
The base-port step accepts
auto—autowas already a first-class value on the CLI (--base-port auto) and in consumer manifests (BASE_PORT: auto), but the wizard step iskind="number"and silently replaced any non-numeric entry with the default, so the literal was unenterable. It now resolves to a concrete free block at selection time (three downstream consumersint()this value), which also lets the stack overview preview the ports the run will really bind, while the command summary still shows--base-port auto. - Long option hints stay inside their indent — line 2 of an option row started with the label-column spaces but was one logical line to Rich, so only the first visual row was indented and every wrapped continuation restarted at column 0. Most visible on the wizard's first prompt, whose track hints enumerate a whole track's service list.
-
"Profile" now means one thing — the track picker was titled
Track · pick your profileand asked "Which profile fits what you're building?", spending the word one step before the real dev/prod profile step, which made prod/dev read as missing. It is not missing: it is step 2, it is never skipped, and its selection is honoured. The track step now asks about workloads. Separately,--trackand--profilenow reach the command summary — neither did, so a summary advertised as copy-pasteable was dropping the two flags that decide which services run and how they are hardened.
-
Patched two runtime graphs —
pypdf6.14.2 → 6.15.0 in the backend runtime and test locks (CVE-2026-71852, CVE-2026-71870), andgitpython3.1.57 → 3.1.58 in the JupyterHub lock (GHSA-4gmw-gg2m-w46p, GHSA-9rj7-rf2p-w77r, GHSA-hh9p-6wh2-4mfc, GHSA-jm78-9fvv-mhgr, GHSA-wvpp-8hx9-p66j). Both are patch-level bumps within the pinned major. These advisories were published after the last green run and were failingAudit compiled service runtime lockson every open PR, not just one. -
Reviewed exception for PyTorch Lightning — PYSEC-2026-3624 / CVE-2026-58659 is an RCE in
_load_state, reachable only throughLightningModule.load_from_checkpointon an attacker-supplied checkpoint. No released 2.x carries the fix (upstream landed it in commitd710d68; OSV reports "fixed in 2022.6.15", a CalVer artifact of the pre-1.x line that is unusable as an upgrade target). Atlas never callsload_from_checkpoint—lightningis transitive vianemo-toolkit, and the Parakeet GPU provider loads throughnemo_asr.models.ASRModel.from_pretrainedon the operator-pinnedPARAKEET_MODELrepo. Recorded as a fail-closed exception inscripts/audit_runtime_locks.pywith a documented drop trigger, matching the existing MLflow/cryptography precedent in the same file.
- The launch logs get their own tab — Atlas now ships 61 source-configurable services, and the stack overview grew with them: on a 44-row terminal the fixed chrome over-subscribed the screen by 6 rows, squeezing the log pane to nothing exactly when it matters. The wizard now has two tabs. Setup keeps the stack overview, the step prompt, and the command summary; Logs shows the filter chips and the log pane. The logo pane and the shortcuts bar stay on both, and the shortcuts contents swap per tab. On a 44-row terminal the log pane goes from effectively zero rows to 20+.
-
Tabs cost no vertical space — they render on the logo pane's bottom border, to the left of the existing author/license/version/repo byline, which keeps its place and elides on narrow terminals. Switch with
1/2,shift+tab, or by clicking a tab label. Launch switches to Logs automatically; Setup stays reachable, and because both bodies stay mounted the stack overview keeps updating live while you read logs and the log stream keeps appending while you are on Setup. -
Copying — the command summary and stack overview are selectable with a normal drag. The log pane is a scrolling
RichLog, which Textual will not drag-select, so it gainsy(copy the log buffer) andY(copy the full session log); terminal-native Shift-drag also works.
-
Command summary no longer grows one line per selected source — the live
./start.shpreview rendered each flag on its own continuation line, so with the number of source-configurable services Atlas now ships it reached ~30 rows by the end of the wizard and overflowed its slot in the pane it shares with the prompt. The command now renders as one flowing line that soft-wraps to the panel width, so its height tracks the command's total width rather than the selection count, and the panel caps at four content rows withoverflow-y: auto— a very long command scrolls inside the panel instead of squeezing the prompt/service area. A full 29-flag selection now occupies 4 rows instead of ~30. Flags, values, styling, border, and position are unchanged. -
Command summary emits pasteable flag values — model multiselect steps rendered
--ollama-models 3 selected (nomic-embed-text,…)and--openai-models 2 selected (…). Those flags take a comma-separated string, so the count-and-parenthesis description was not a valid invocation (Click would read the trailing names as stray positional arguments). The summary now emits the CSV itself, shell-quoted because it contains commas; an explicitly cleared custom-model list renders as"". API keys still render as the<set>placeholder — the raw secret is never printed. -
"Generate encryption keys failed" on a warm start with existing DB volumes —
generate_missing_keysasserted thatGRAPH_DB_AUTHhad been de-placeholdered, but the rotator that writes it (generate_and_update_graph_db_password) deliberately skips when this project's Neo4j data volume already exists — the password is baked in at first boot and rewriting.envcannot change it. So on every warm start with an existing graph volume the composite legitimately stayed atneo4j/neo4j_password, that singleFalsefailedall(results.values()), and the pipeline reportedGenerate encryption keys failedeven though every rotator had succeeded and only advisory "rotation is skipped" warnings were printed. The assertion is now gated on the same existing-volume guard as the rotator; a genuine fresh install (no volume) and a cold start (volumes wiped,force_regenerate=True) still enforce it, so the placeholder check is not weakened. -
Wizard panels are separated by a real gutter, and the gap above the shortcuts bar is gone — the brand, stack-overview, prompt, and command-summary panels all had
margin: 0, so their rounded borders butted directly together and the stack read as one fused block rather than discrete panes. Each panel below the first now carries a one-row top margin so the screen background shows through between them. Separately,#lower-paneclaimed all remaining height and the leftover collected between the command summary and the shortcuts bar; the prompt panel now absorbs that slack, so the summary sits directly above the footer while the footer stays pinned to the bottom.
-
Consumer-declared custom nodes — a consumer can now declare
custom_nodes.comfyui(a path or path-list to a consumer-authored pinned-node YAML) inatlas.consumer.yml; the paths merge intoCOMFYUI_CUSTOM_NODES_FILE(os.pathsep-joined). The Atlas-shippedservices/comfyui/custom-nodes.yamlis always present in the merged allowlist (catalog models reference its nodes) and wins on name collision; consumer-declared nodes are active unconditionally (a model need notrequires_custom_nodethem — they are workflow nodes, e.g. an edit-workflow node). The pinned-SHA + GitHub-HTTPS discipline is enforced at consumer-manifest load (fail-loud) and again at provision; cross-source name collisions reject asConsumerManifestError. Completes the container↔managed-MPS parity arc (#754 models, #757 ollama, custom nodes). -
Managed-MPS custom-node install —
./start.sh comfyui-mps provision-nodesclones the resolved custom nodes (Atlas + consumer) into the host ComfyUIcustom_nodes/at their pinned SHAs and pip-installs their requirements into the host venv — idempotent (git rev-parse), per-node non-fatal, mirroring #754. Hardened against the shared-MPS-venv pollution risk: apip freezebefore/after warns loudly on anytorch/torchvision/torchaudiodrift and points atcomfyui-mps install --update(which re-pins); an optionalmps_unsafe: truefield pre-skips CUDA/x86-only nodes on Apple Silicon. Adoctorcheck reports declared-but-missing nodes. (End-to-end Apple-Silicon validation ofcomfyui-krea2edit— the driving tableau#318 use case — is deferred to a live run; the node is deps-free. It also needs the Identity Edit LoRA catalog entry from #909.)
-
Failed launches no longer lock the user in the TUI —
_launch_detach_readywas set only on the success path, so after any launch failure (build, init, profile validation, project-name persist) the user could not Ctrl+Q to detach and instead saw a misleading "Startup is still running; Ctrl+C cancels it" toast._mark_launch_failednow flips the detach flag, so a failed launch frees Ctrl+Q while the failure stays visible in the log pane.
-
Memory facts default to vector-sync-pending until embedded — facts were inserted with
vector_sync_pending=false, and the per-fact embedding-writeback loop ran outside any guard, so a transient DB blip on a later fact left the un-reached facts atweaviate_id=NULLwithvector_sync_pending=false— permanently invisible to Weaviate semantic recall (the reconciler only retriespending=true). Facts now insertpending=trueand clear it only once the embedding is durably stored, so any interruption is recoverable. Covered by a new extraction-contract test. -
Research startup sweep is best-effort —
start_maintenance's initialrecover_stale_sessionsran unguarded at FastAPI lifespan startup, so a DB still initializing when the backend boots prevented startup. It is now best-effort (the maintenance loop recovers stale sessions on later sweeps), consistent with the lazy-pool philosophy. -
GraphQL string escaping covers backspace/form-feed —
memory_store's GraphQL string escaper handled\\,\",\n,\r,\tbut omitted\band\f, so a fact containing those bytes produced invalid GraphQL and failed the recall. -
Consolidation index coercion — LLM-returned merge/supersede indices are now coerced to int before validation; a float index (e.g.
0.0) previously raisedTypeErrorand aborted consolidation for the current and all subsequent users. -
Research source url null-safety — a source dict carrying
url: Noneno longer trips theresearch_sources.urlNOT NULL constraint and rolled back the entire result transaction; it coerces to"".
-
Index on
research_sources.result_id— theresult_idforeign key cascades onresearch_resultsdeletion, but had no index, so deleting a research result (directly or via thesessions -> results -> sourcescascade) forced a sequential scan ofresearch_sourcesto locate orphaned children. An index onresult_idlets Postgres find the children directly; the seed-schema golden was regenerated to match.
-
Nullglob-safe sample-notebook copy — the JupyterHub startup script copied
/home/jovyan/notebooks/*intowork/examples/underset -e. With bash's defaultnullgloboff, a present-but-empty notebooks directory (a custom image or empty bind-mount) left the glob literal, socperrored and aborted startup, crash-looping the container. The copy is now guarded withcompgen -Gso an empty directory is skipped instead of fatal. The default image, which bakes the sample notebooks in, was unaffected.
-
Parakeet startup supervised-restart on transient loader failure —
ModelStartupnow terminates the provider process on a generic load failure, not only on the startup deadline. Previously a transient HuggingFace rate-limit, network blip during weight download, or CUDA OOM left the container alive with/healthreturning503indefinitely, because Docker'srestart: unless-stoppedrestarts on container exit, not on a failed healthcheck. Mirroring the deadline branch, any load exception now drives the process down for supervised restart; a deterministic misconfig (bad model name) crash-loops under Docker's restart backoff, which is visible and bounded. -
Bounded-subprocess deadline and parent-loss hardening — the reaper's final
wait()afterSIGKILLis now bounded, so a leader wedged in uninterruptible sleep (frozen mount, NFS, kernel lock) can no longer block the deadline/SIGTERMpath indefinitely; and the main-thread cleanup contextmanager now also interceptsSIGHUP(terminal close / SSH disconnect), the most common way a long-running bounded command loses its parent. Children launched withstart_new_session=Truedo not inherit the parent'sSIGHUP, so without the handler they were re-parented to init and kept running — the escaped-subprocess leak the subsystem exists to prevent. -
Compose-config error redaction —
validate_compose_configno longer echoes the resolved compose command line (project name,--env-filepath, and every-foverlay) into its error message on timeout, matching the redaction the rest of the bounded-subprocess wrapper already enforces. -
TEI reranker amd64/GPU and Jenkins image digest pins — the TEI amd64 (
cpu-1.9) and GPU (1.9) image defaults and the Jenkins compose build-arg fallback are now bound to an immutable index digest, matching the existing arm64 TEI pin; the movingcpu-1.9family tag and bare1.9minor tag were republished per patch. The channel-tag pin test now covers both TEI architectures.
-
BLENDER_MCP_SOURCE=managed-localhost(#759) — Atlas now provisions the pinnedahujasid/blender-mcpadd-on (sha256-verified) and runs headlessblender --backgroundas a managed host process, via a generated launcher that shims the add-on's timer registration into a main-thread queue so the same execution contract holds without a GUI event loop. The bridge binds loopback-only unlessBLENDER_MCP_ALLOW_REMOTE=true(a deliberate double opt-in), and the lifecycle mirrors the ComfyUI MPS surface:./start.sh blender-mcp preflight|install|start|stop|status|health|remove. -
Content-quality lint gate (
scripts/docs/content_quality.py) — enforced in the docs-drift audit; flags diagram-narration prose, production/style narration, marketing adjectives in service READMEs, and copy-pasted blocks across architecture pages, plus a one-time content pass removing existing instances.
-
Declared models provisioned onto the host daemon (#757) —
ollama-localhostnow pulls the declared union (OLLAMA_USER_MODELS∪OLLAMA_CUSTOM_MODELS) onto the host Ollama daemon at every start: present tags skip via/api/tags, missing ones stream throughPOST /api/pull, and per-tag failures warn without aborting the stack. Theunpullable-modelsdoctor check names any declared-but-missing tag; manualollama pullis now an optional pre-warm rather than a required setup step.
-
ATLAS_COMFYUI_INPUT_DIR+ATLAS_BLENDER_MCP_HOST_ENDPOINT(#758) —endpoints exportnow emits the managed-MPS ComfyUI input directory (the image-staging twin ofATLAS_COMFYUI_OUTPUT_DIR, emitted only underCOMFYUI_SOURCE=managed-localhost-mps) and a Blender MCP host endpoint under either Blender host source. The latter carries atcp://scheme — the add-on serves a raw TCP socket, not HTTP, so consumers must dial it with a socket client.
-
Declared weights provisioned at start (#754) —
COMFYUI_SOURCE=managed-localhost-mpsnow downloads the resolvedCOMFYUI_USER_MODELSset intoCOMFYUI_MPS_MODELS_PATHat every start — idempotent and sha-verified, completing the #718 warn → provision arc — with a standalonecomfyui-mps provisionlifecycle command for out-of-band runs. No manual weight staging remains for the managed host source.
-
bootstrapper/profiles.yml(#755) — the deployment profile is now a declarative bundle: platform-defineddefault/prodmaps (devaliasesdefault) of per-servicesources(a concrete option id orauto), unless-operator-setenvvalues, andhost_bind_ip. Consumers pinprofile:and override individual fields viaprofile_overrides:inatlas.consumer.yml; switches are tracked through theATLAS_PROFILE_APPLIEDmarker and reset the prior profile's asserted sources first, while an explicit--<svc>-sourceCLI flag on the current run always wins.
-
autosource sentinel (#753) — services can declare an orderedauto_preferpreference list in their manifest; setting<SVC>_SOURCE: auto(typically committed inatlas.consumer.yml) resolves the first entry whose host capability (apple_silicon/nvidia_gpu/host_ollama) holds and whose option the active profile offers. The resolution is durable across restarts and cold.envregens, and explicit non-default operator overrides are honored.
-
Provider authentication migration — Atlas now generates and preserves
DOCLING_API_TOKENandPARAKEET_API_TOKEN; every Docling and Atlas-managed Parakeet route except/healthrequires the matching bearer by default. Backend, n8n web/worker, JupyterHub, Open WebUI, and Hermes receive only the token required by their trusted server-side path. Existing local-only deployments can temporarily set the corresponding*_AUTH_MODE=disabledas an explicit rollback while clients are migrated. -
Bounded provider lifecycle — Docling conversion and Parakeet startup/inference reserve capacity before multipart parsing, cap upload bytes, enforce a finite total 120-second upload deadline, and enforce a finite 900-second model-work deadline. Slow uploads return
408and release admission; fatal native/model timeouts return504before the provider process exits for supervised restart. Their published container ports and native bind hosts default to loopback. -
Isolated LightRAG parser adapter — In-stack LightRAG v1.5.4 now reaches Docling through an unexposed compatibility adapter on
docling-lightrag-network; LightRAG never receivesDOCLING_API_TOKEN. The adapter implementsPOST /v1/convert/file/async(multipart fieldfiles),GET /v1/status/poll/{task_id},GET /v1/result/{task_id}, andGET /health, with pre-parser request-body limits, bounded jobs, attempts, streamed result size, artifact TTL, and a validated configurable temporary-storage budget. Completed results retain their job slot through a full one-shot download and ignore and do not advertise byte ranges. Transmission completion or timeout starts verified cleanup; an unlink failure is logged and retried while the slot remains occupied until deletion succeeds. -
Research admission bulkhead — each Backend process admits at most
RESEARCH_MAX_CONCURRENTresearch sessions (default4); excess starts return retryable HTTP429before database or background work. - Durable RAG definitions and synchronized memory vectors — queued ingestions now persist and expose the effective corpus and normalized profile snapshot so registry edits cannot change work after submission. Confirmed Weaviate conflicts replace current objects, while profile reconciliation removes chunks left by shortened or deleted sources. LangMem uses memory UUIDs as vector IDs and a durable Postgres marker to reconcile activation, deactivation, edits, soft deletes, consolidation, and retention across deterministic and legacy objects. GraphQL errors preserve pending work, and optimistic timestamps reject consolidation decisions made stale by concurrent edits.
- Deferred media cancellation and strict adapter boundaries — media cancellation now remains nonterminal and retains its spend reservation until provider polling confirms the outcome. Hosted-media model validation completes before state, accounting, storage, or provider work; LightRAG reranking rejects malformed indexes and non-finite scores; and reranker/Tika timeouts fail startup unless finite, positive, and no greater than one hour.
- Fenced workers and exact provider schemas — RAG ingestion now acquires an owner-fenced renewable execution lease before phase side effects, cancels its active async phase on lease loss, and keeps transient retry accounting independent of lease-contention retries, while Celery limits fail closed on malformed or contradictory values. Media operation writes compare their observed status so stale polling cannot overwrite cancellation; unpersisted paid submissions retain spend even when FAL accepts a cancellation request; verified image-to-3D models receive their exact vendor image fields; Docling success and TEI rerank cardinality are validated; and public extraction failures no longer expose provider bodies or transport details.
- Runnable asynchronous RAG ingestion — Backend and Celery now share ingestion state, generated profiles, upstream settings, limits, and manifest-bound MinIO credentials. Lease loss reschedules work, final transient exhaustion terminalizes the job, and deterministic LightRAG document identities make retry replay and equal basenames safe against the pinned server's duplicate-source contract.
-
Hosted-media provider contracts — FAL configuration now fails closed on malformed booleans, non-finite or excessive timeouts, and unsupported output formats. The two default FLUX endpoints receive only schema-supported fields; malformed image sizes and seeds fail before state, budget, or provider work; compatibility-route validation returns
400; custom endpoints require explicit provider-native arguments; FAL security/persistence documentation matches Redis-backed owner-scoped operation state; and the ineffective Parakeet GPU compute-type setting andint8guidance are removed. - Canonical documentation headings and links — all 102 manifest-owned pages now carry their hierarchical number in the canonical H1, and the generator rejects heading drift instead of silently rewriting it. Atlas repository blob links map to their generated site/wiki pages, and top-level architecture views use explicit labelled relationships rather than generic sequential arrows.
-
Asset processor capacity and telemetry — Asset Worker now honors multipart
up_axis, bounds concurrent transformations with429admission control, and exposes the same Prometheus HTTP counters and duration histograms as Asset Baker. Both processors emit structured lifecycle logs and are present in the bundled Prometheus scrape configuration. - Asset build integrity — Asset Baker verifies the published SHA-256 checksum for its pinned Blender 4.3.2 archive before extraction.
- Zeppelin network and storage boundary — the unauthenticated Zeppelin UI is loopback-only and no longer receives a Kong alias. Its Spark interpreter uses a generated MinIO service account scoped to event-log and lakehouse workflow buckets instead of MinIO root credentials.
- Research failure redaction — detailed Local Deep Researcher errors remain in Backend logs while persisted status and research-log responses expose a stable non-sensitive failure message.
-
Project-scoped cold cleanup —
start.sh --coldandstop.sh --coldnow remove only the active Atlas Compose project's containers, orphans, and named volumes. Cleanup failures stop startup before secret rotation and propagate a nonzero exit status instead of falling through to a partial launch. -
Managed-host lifecycle rollback — native ComfyUI MPS and vLLM Metal processes now start at the launch boundary under an ownership lock, roll back only when the current invocation created them and the stack fails to converge, and remain discoverable by
stop.shafter their SOURCE selection changes. Native teardown failures propagate a nonzero stop status. -
Readiness and private runtime files — Neo4j now publishes a real Cypher health check for dependents that require
service_healthy; generated.envand endpoint-export files are written atomically with owner-only permissions. -
Ray job API authorization — every Backend
/api/rayroute now requires the auto-generatedRAY_JOB_API_TOKENas an HTTP bearer token, including calls made through the direct Backend port. Ray's native unauthenticated dashboard, GCS, and client ports are bound to loopback by default. - Non-destructive permission recovery — an unwritable bind-mount directory is never deleted as a repair strategy; Atlas preserves its contents and reports the ownership command needed before retrying.
-
Truthful privileged hosts cleanup —
stop.sh --clean-hostselevates only the hosts-file mutation and exits nonzero when the requested cleanup fails, while the repository workflow continues to reject whole-process sudo execution. -
Pinned Local Deep Researcher runtime — Local Deep Researcher now materializes a manifest-owned full upstream commit SHA, verifies the upstream
uv.lockdigest, and synchronizes a private environment from Atlas' committed hash-pinned runtime lock. The pin includes upstream's currentlanggraph-checkpoint4.1.1 andlanggraph-sdk0.3.15 lock update. Restarts reuse the detached checkout without pulling a mutable branch or resolving either project or LangGraph CLI dependencies again. -
Locked CI environments — all three
services-lintjobs now synchronize bootstrapper dependencies with--locked, so tests and audit scripts fail on lock drift instead of silently resolving or modifying a different dependency graph. -
JupyterHub ML stack refresh — the notebook image now ships PyTorch 2.13.0, torchvision 0.28.0,
pyg_lib/PyG 0.8, and the matching 2.13 CPU wheel index. MLflow 3.15.1 permits cryptography 49.0.0, closing the current certificate-verification advisories while an exact non-reachable PKCS#7 exception remains owned and time-bounded until MLflow supports cryptography 50. -
JupyterHub NLP asset integrity (Issue #64) — the ml-eng-lab spaCy model wheel and NLTK VADER ZIP are now installed from byte-exact projected manifests; the VADER archive is verified at build time against SHA-256
8adba4294eef3964d820bf655e37e61bdc3a341994356af59b74fb3b4a36ce5cand reuses/home/jovyan/nltk_datawithout a runtime downloader. - Backend dependency ownership — production images now install only Backend runtime requirements; pytest, async-test support, and TestClient compatibility tooling live in a separate CI-owned development requirements file. At an intermediate maintenance checkpoint, Dependabot's coordinated PyTorch/PyG hold list matched the 2.11 CPU wheel family; the JupyterHub ML stack refresh above supersedes that baseline with 2.13/PyG 0.8.
- Current n8n configuration — Atlas no longer advertises or injects four retired environment variables that n8n 2.28.2 does not consume; owner setup and pinned community-node loading continue through n8n's native flow and the Atlas init container.
-
Release metadata consistency — product metadata now describes
0.1.0as the current tagged pre-1.0 version while retaining post-tag work under the changelog's Unreleased section. -
CLI/manifest parity —
--comfyui-sourcenow accepts the manifest-supportedmanaged-localhost-mpsmode, Prometheus retention enforces the same 1–365 day range as the wizard, and--trackhelp includes the shipped trading profile. -
Effective optional-service gates — Cloudflare Tunnel container mode now fails validation before startup when its named-tunnel token is blank, and the on-demand backup entrypoint rejects backup/restore runs while
BACKUP_SOURCE=disabledinstead of treating enabled and disabled identically. - Graph Builder optional configuration — Diffbot and Google Cloud options are now manifest-owned and namespaced; GCS caching has project/bucket wiring, ADC credentials mount read-only into the backend, and incomplete enabled GCP configurations fail validation before Compose.
-
Collision-safe launch logs — concurrent wizard sessions now create distinct owner-only
/tmp/atlas-launch-<timestamp>-<unique>.logfiles atomically instead of truncating the same second-level path. - Backend application identity boundary — protected Backend routes now require a verified Supabase user JWT or a generated service credential on direct and Kong paths. User-owned memory, research, media operations, and spend reads are subject-bound; operator routes require the trusted internal token; JupyterHub receives a separate stateless-only token; Ray and LightRAG retain their dedicated tokens.
-
First-party caller authentication — bundled Open WebUI tools and seeded n8n workflows use separate route-family-scoped bearers, while Chonkie and Ragas notebooks attach the stateless-only notebook bearer. Plugin
inherit/manifest-less routes enforce Backend identity, declared routers cannot escape their route prefix,key-authis validated in-app as well as at Kong, and only explicitopenplugins remain public. -
Backend owner synchronization — idempotent trigger/backfill paths synchronize both
auth.usersand valid Open WebUI user UUIDs intopublic.users, making authenticated subjects valid research and memory foreign-key owners without overwriting Auth-owned profiles. Supabase Auth deletion removes its owner and cascades owned records; Open WebUI deletion preserves an owner that remains backed by Supabase Auth. - Atomic RAG ingestion state — concurrent submissions now atomically claim a content-sensitive corpus fingerprint, cancellation and terminal states survive stale worker writes, Redis drops expired list members, and a broker dispatch failure records a retryable failed job instead of leaving a permanently deduplicating pending record.
- Durable media and research operations — hosted-media polling and cancellation state now uses Redis with atomic first-terminal-writer semantics across Backend replicas and restarts. Research releases database connections before remote waits, conditionally claims pending sessions, and transactionally stores results only while the session remains running, so cancellation cannot be overwritten by late work.
- Bounded provider inputs — Asset Worker, Asset Baker, Docling, and Parakeet now stream multipart inputs to bounded temporary files, enforce the same limits on MinIO references, and keep blocking conversion/inference work off async API loops. Asset Worker subprocesses have explicit timeouts, and Docling conversion failures return errors instead of successful Markdown that downstream ingestion could index.
-
Durable provider and worker retries — every FAL SDK operation now honors
FAL_TIMEOUT_SECONDS; RAG Redis access is offloaded from async routes; and transient RAG-ingestion or memory-consolidation failures reach Celery's bounded retry policy without leaving terminal or misleading job state. - Executable examples and documentation hygiene — bundled n8n research workflows now use the pinned node-field contract, preserve item context without execution-global mutation, and connect terminal/error branches. Notebook inventory, Python syntax, and direct-import declarations are gated; Python and Scala Spark examples both verify MinIO; public documentation no longer exposes publication mechanics, stale static service counts, or confirmed dead external references.
- Truthful environment notebook — JupyterHub's environment check now distinguishes its bounded core-integration probes from the full Atlas service catalog, hides credential-bearing URLs, checks every advertised HTTP target, and applies finite database connection timeouts.
- Security dependency baselines — patched Python multipart, Requests, Pillow, Click, PyArrow, NLTK, Pydantic Settings, SoupSieve, FastAPI, Starlette, Uvicorn, NeMo, ONNX, Protobuf, and related transitive floors are now explicit at each owning runtime. Regression tests prevent the service manifests and lockfiles from returning to vulnerable ranges.
-
Airflow 3.3 security release — the Airflow family now uses
apache/airflow:3.3.0on both architectures. Provider installation follows upstream's supported two-stage model instead of reapplying the core constraints file, allowing Spark 4.1.2 and patched provider transitives to resolve together. - Ray 2.56 runtime alignment — Ray server images and Backend/Jupyter clients now move together on the 2.56 line, closing unsafe WebDataset deserialization paths without introducing client/server skew.
-
Backend JWT verification — Backend identity validation now uses PyJWT and requires
subplusexp; the verification-only HS256 path no longer pulls the signing-side-channel dependency inherited throughpython-jose. - Notebook and speech ML baselines — an intermediate JupyterHub baseline installed the PyTorch 2.11 CPU family and matching PyG wheels; the later JupyterHub ML stack refresh above supersedes it with 2.13/PyG 0.8. Parakeet GPU uses NeMo 2.7.3 with patched ONNX and Protobuf floors. Residual upstream model-loader advisories are limited to operator-selected trusted model artifacts and are documented beside the compatibility pins.
-
Truthful operational readiness — Backend now separates cheap
/healthliveness from/readyprobes for PostgreSQL, Redis, and LiteLLM, and its container health check uses readiness. Asset Worker, Asset Baker, Docling, and Parakeet no longer report healthy before their required executable, processor, or model is available. The OpenTelemetry Collector validates its mounted config on every health probe, while explicitly enabled but incomplete Backend tracing fails startup instead of silently disabling spans. -
Current Backend test contract — Backend upload rejections now use Starlette's current
HTTP_413_CONTENT_TOO_LARGEsymbol while preserving HTTP 413 behavior. The required CI job installs the Backend's owned requirements instead of a stale inline subset, so identity and async tests collect with PyJWT,pytest-asyncio, and the current FastAPIhttpx2test client. - Bounded RAG corpus discovery — mounted and MinIO-backed RAG ingestion now enforce manifest-owned per-file, aggregate-byte, and file-count limits while reading in bounded chunks. Oversize metadata is rejected before download and unknown-size streams remain bounded, preventing corpus discovery from allocating arbitrary Backend memory.
-
Correct Development documentation navigation — the generated site now publishes the Diagram Catalog as the explicit 9.4 child page instead of allowing MkDocs to mistake its canonical
README.mdsource for the Development section index. - RAG parser and runtime boundaries — ingestion now calls the real keyword-only document-extraction contract, honors each declared Docling/Tika parser slot exactly, offloads synchronous corpus and Chonkie work from the API event loop, and applies finite Redis connect/read deadlines to shared RAG and media operation state.
- Transactional memory extraction — LangMem extraction now releases PostgreSQL during LiteLLM calls, validates model output shape, commits facts and session completion atomically under a per-user quota lock, and records terminal failures. Recall and summarization no longer pin database connections while awaiting model responses.
- Truthful startup controls and verification — the Textual launch UI now permits detachment only after Compose startup and required init jobs finish, verifies Redpanda and Zeppelin one-shot initialization, uses readable secondary text contrast, and keeps Airflow's opt-in build-validation target aligned with the runtime 3.3.0 image.
- Complete optional operations controls — Backup and Cloudflare Tunnel now have matching CLI and wizard SOURCE controls, and backup/restore package, database, archive, and S3 commands enforce a configurable positive deadline instead of waiting forever.
- Bounded hosted-media inputs — the Backend rejects oversized media request streams before route parsing, rejects oversized inline images before base64 decoding, and checks source and conditioned image dimensions before Pillow allocation. Media budget configuration now fails closed for malformed booleans, stores, caps, retention, and missing durable-store credentials.
- Compensated media submission — hosted-media submission now preflights shared operation storage before paid provider work, retries post-submit persistence, and attempts provider cancellation when durable state cannot be recorded. Recovery responses preserve the provider operation id and retain spend reservations for uncancelled work requiring manual reconciliation.
- Leased research execution — research session creation and its first log are atomic, running work maintains database heartbeats, and every Backend replica terminalizes abandoned pending/running rows after a configurable lease. Graceful shutdown records interrupted local tasks as failed before process exit.
- Authenticated n8n bootstrap removal — required community packages now install from an exact committed npm lock into n8n's shared user folder before the web/worker runtime starts. Initialization no longer calls an authenticated internal REST route without credentials or treats authorization failures as successful installation.
- Truthful memory degradation — Weaviate schema failures now activate the pgvector fallback instead of marking an unusable vector store initialized. Memory persistence, extraction, health, and operational logs return stable diagnostics without exposing infrastructure exception text.
- Current document and speech provider contracts — Docling now applies request OCR/table settings plus device/formula/code environment controls through its pinned pipeline API and rejects unsupported output formats. Parakeet MLX serializes the pinned aligned sentence/token result shape and reports timestamp availability only when aligned data exists. Both provider families return stable public failures.
-
Provider request validation — FAL image submission validates prompts and endpoint-specific numeric controls before provider work, and init-image requests use and persist the dedicated
fal-ai/flux/dev/image-to-imagequeue endpoint. Docling limits overlap to half the chunk size and caps responses at 10,000 chunks. Bundled n8n image/research workflows consume real webhook-body envelopes, require a bound Header Auth credential, cap batch fan-out, and reject malformed request controls before invoking Backend with their workflow bearer. -
Bounded consumer workflow seeding — generated n8n consumer seed overlays enforce finite HTTP and child-process deadlines, and detached launch verification includes the
n8n-seedone-shot container. - Public edge routing and security — Cloudflare Tunnel guidance now requires an origin Host override for each Kong alias and a least-privilege Cloudflare Access policy for every public hostname; the security posture accounts for the optional public edge.
- Documentation hierarchy and completeness — all tracked reader documentation now uses deterministic hierarchical numbering and professional text, nested provider and migration guides participate in the three-surface manifest, stale placeholder READMEs are removed, and generated surfaces reject structural drift.
-
Reproducible dependency corrections — Atlas now enforces Docker Compose 2.20.3 for modular
includesupport and the Local Deep Researcher lock carries audited security floors for Click, langchain-classic, LangSmith, and Soup Sieve.
-
Media gateway multi-provider (#519) —
POST /media/generatenow acceptsprovider=comfyui, modality=image, routing the same submit/poll/cancel seam to the managed/localhost ComfyUI host (COMFYUI_ENDPOINT) that #335 ships. Local generation is genuinely free (cost_usd=0.0, still ledger-recorded for provenance); theartifact_urlis a backend/comfyui/image/{filename}proxy path. Text2img (CheckpointLoaderSimple for SD1.5/SDXL; the split UNETLoader/CLIPLoader(krea2)/VAELoader graph for Krea 2) and img2img (init image +strength, #453 contract parity) are both supported, as isPOST /media/operations/{id}/cancel(#518 parity: queue-delete + interrupt). Closes the multi-provider half of #339's charter; unblocks the local-ComfyUI side of the Tableau #26 E2E.
-
Canonical publication pipeline (#606 / #607) — repository Markdown, the MkDocs
.iosite, and the native GitHub wiki now derive from one ordered manifest with strict drift, self-containment, local-link, diagram, and notebook-source checks. At this historical release point, the public hierarchy covered 102 pages, 59 service guides, and 71 synchronized architecture diagrams. - Native wiki HTML links (#613) — HTML hero actions are now rewritten to their manifest-derived numbered wiki pages while the MkDocs site retains its pretty URLs. The shared link model audits Markdown and HTML links and images, and the docs gate rejects missing wiki-local targets, including dotted hierarchical slugs.
-
Promotion sync (#615) —
developrecords the protectedmaindocumentation promotion as an ancestor before the wiki-link correction is promoted, preserving strict GitFlow ordering without changing the verified documentation content.
-
Consumer manifest (#399) — parent repositories can now register Atlas integrations with
atlas.consumer.ymlvia./start.sh --consumer <path>orATLAS_CONSUMER_MANIFEST, declaring project branding/env, external Compose overlays, backend plugin roots, and ComfyUI/Ollama model sidecars without symlinking intoservices/_user/. Compose validation, the consumer doctor, launch summaries, and docs now understand the manifest contract; list-valued model entries merge by ordered union while scalar conflicts fail validation. -
Promotion sync:
developwas merge-synced with the currentmaintip before promoting the consumer manifest work, preserving strict-mode branch ordering while keeping the documentation record current.
-
Consumer doctor (#401) —
./start.sh doctornow runs headless consumer preflight checks without starting containers, with text output for local debugging and--format jsonfor CI. The first registry includes Compose validation,_useroverlay env-reference validation, plugin directory sanity, model sidecar parsing, endpoint reporting, and tracked-file cleanliness. -
Promotion sync:
developwas merge-synced with the currentmaintip before promoting the consumer doctor work, preserving strict-mode branch ordering while keeping the documentation record current.
-
Headless env and compose checks (#397) —
./start.sh env backfillnow exposes the additive.envbackfill path without entering the startup flow, and./start.sh compose validateruns the assembled Compose config validation includingservices/_useroverlays. Both commands are non-interactive and documented for parent-repo submodule upgrade scripts. -
Promotion sync:
developwas merge-synced with the currentmaintip before promoting the headless bootstrapper command work, preserving strict-mode branch ordering while keeping the documentation record current.
-
Detached start mode (#398) —
./start.sh --no-tui --detach(alias--no-follow) now runs the normal linear start pipeline, waits for Compose health gates, prints a final per-service status summary, and exits instead of tailingdocker compose logs -f.--jsonemits the detached status summary in machine-readable form for parent-repo wrappers and CI. -
Promotion sync:
developwas merge-synced with the currentmaintip before promoting the detached start work, preserving strict-mode branch ordering while keeping the documentation record current.
-
Extensible MinIO bucket provisioning (#409) —
minio-initnow acceptsMINIO_EXTRA_CONSUMERS, a parent-ownedCONSUMER:BUCKET_VAR:ACCESS_VAR:SECRET_VAR[:EXTRA_BUCKET_VAR,...]declaration that lets_useroverlays provision their own buckets and scoped service-account credentials without forking Atlas's init script. The reuse, submodule, service,.io, and wiki docs now show the DayDreams-style overlay pattern. -
Promotion sync:
developwas merge-synced with the currentmaintip before promoting the MinIO extra-consumer bucket work, preserving strict-mode branch ordering while keeping the documentation record current.
-
Parent-repo consumer layout (#421) — documented the RAG-showcase/DayDreams-style submodule pattern: parent-owned Compose overlays symlinked into
services/_user/, force-set project SOURCE/branding wrappers, explicit track override behavior, what-lives-where ownership, and a validation checklist for clean consumer repos. -
Promotion sync:
developwas merge-synced with the currentmaintip before promoting the consumer-layout documentation work, preserving strict-mode branch ordering while keeping the documentation record current.
-
Backend Ragas evaluation API (#378) —
POST /api/rag/evaluatenow exposes Atlas-owned Ragas evaluation for supplied question, answer, retrieved-context, and optional reference records. The endpoint supports faithfulness, answer relevancy, context precision, and context recall metrics while routing evaluator calls through the existing LiteLLM gateway. -
JupyterHub exploratory Ragas surface (#378) — the JupyterHub image now installs Ragas and ships
14_ragas_evaluation.ipynbfor opt-in local metric experiments plus calls to the Backend/api/rag/evaluateruntime contract. -
Promotion sync:
developwas merge-synced with the currentmaintip before promoting the Ragas evaluation work, preserving strict-mode branch ordering while keeping the documentation record current.
-
Backend Chonkie chunking API (#375) —
POST /api/chunknow exposes Atlas-owned token, recursive, and semantic text splitting backed by Chonkie. Responses include ordered chunks, stable character offsets, optional token counts, and strategy metadata so n8n and downstream ingestion workflows can call the Backend instead of importing Chonkie directly. -
JupyterHub exploratory Chonkie surface (#375) — the JupyterHub image now installs Chonkie and ships
13_chonkie_chunking.ipynbfor comparing token, recursive, optional semantic chunking, and the Backend/api/chunkruntime contract. -
Promotion sync:
developwas merge-synced with the currentmaintip before promoting the Chonkie chunking work, preserving strict-mode branch ordering while keeping the documentation record current. -
Ancestry sync: the final
main→developsynchronization for this promotion is merged with merge-commit semantics sodeveloprecords the protectedmaintip as an ancestor before the develop-to-main PR is merged.
-
External env overlay for submodule consumers (#396) —
ATLAS_ENV_USER_FILEnow points Atlas at a parent-owned.envoverlay outside the checkout. Atlas applies sibling.env.userfirst, then the external overlay, then CLI flags, on every start including--cold; missing or unreadable external files warn and continue so wrapper scripts can fail gracefully. -
Promotion sync:
developwas merge-synced with the currentmaintip before promoting the external-overlay work, preserving strict-mode branch ordering while keeping the documentation record current.
-
Asset worker glTF/GLB post-processing (#343) — new disabled-by-default
asset-workermedia service for upload- or MinIO-reference-based model post-processing. The service normalizes uploaded assets upright to the ground plane, scales by requested height or width target, runs the pinned glTF-Transform CLI with Draco/Meshopt/texture options, persists artifacts to MinIO when configured, and exposes deterministic SHA256 artifact metadata plus a local fallback download route. -
Promotion sync:
developwas merge-synced with the currentmaintip before promoting the asset-worker work, preserving strict-mode branch ordering while keeping the documentation record current.
-
Media gateway operation model (#339) — the backend now exposes
POST /media/generateandGET /media/operations/{operation_id}as the provider-neutral hosted-media surface. The first registry entry supports FAL image generation, keeps provider keys backend-only, returns normalized provider/model/modality/artifact/cost/license/provenance fields, and preservesPOST /comfyui/generateas the simple-image compatibility route. -
Promotion sync:
developwas merge-synced with the currentmaintip before promoting the media gateway work, preserving strict-mode branch ordering while keeping the documentation record current.
-
ComfyUI runtime foundation (#334) — the ai-dock runtime now exposes a pinned upstream
COMFYUI_REF=v0.9.2withCOMFYUI_AUTO_UPDATE=true, keeping the ComfyUI core version explicit without changing container families. The bootstrapper also writesactive-custom-nodes.tsvfrom selected models'requires_custom_nodevalues, but only after mapping them throughservices/comfyui/custom-nodes.yaml; the AI-Dock provisioning hook clones those allowlisted GitHub repos into thecomfyui-custom-nodesvolume at full commit SHAs and installs declared requirements through the ComfyUI Python environment. -
Custom-node safety contract: node auto-install is allowlist-only, repo URLs must be GitHub HTTPS
.gitURLs, refs must be full 40-character commit SHAs, and unknown catalog requirements warn instead of cloning arbitrary code. -
Promotion sync:
developwas merge-synced with the currentmaintip before promoting the ComfyUI runtime work, preserving strict-mode branch ordering while keeping the documentation record current.
-
ComfyUI multi-file model bundles (#336) — catalog entries can now declare
files:so one wizard selection expands into multiple download rows with per-file category, filename, SHA256, target directory, precision, and variant metadata. The generated manifest recordsbundle_id/bundle_file_role, the init downloader honors explicit target directories while preserving backward-compatible TSV rows, and custom sidecar YAML supports the same bundle shape. -
Promotion sync:
developwas ancestry-synced with the currentmaintip before promoting the ComfyUI bundle work, preserving strict-mode branch ordering while keeping the documentation record current.
1.95. Fixed — 2026-07-07 — overnight maintenance: init quoting, bootstrapper/backend hardening, latent SQL abort
Broad verify-and-fix pass. Headline fixes:
-
psql init quoting (Critical):
init-label-studio.sh,init-mlflow.sh(copy-pasted from each other), andinit-langfuse.shused psql:'var'inside-c/-tAcstrings — but psql only interpolates:'var'in SCRIPT input (stdin /-f), never inside-c. The literal:'var'shipped to the server raised a syntax error and (underset -eu) aborted the init container, so label-studio and mlflow could never provision their Postgres role/database, and langfuse's database-existence check never matched (createdb ran unconditionally and failed on any restart with a persistent volume). Converted to the stdin/printfpattern already used ininit-airflow.sh/init-iceberg-rest.sh. Addedtest_init_scripts_psql_quoting.py— a regression guard, sincebash -n/shellcheckcannot catch this psql-semantic class. -
Bootstrapper error handling:
main()'s catch-all over a ~620-line try body now prints the traceback to stderr (was a barestr(e), making incidents undebuggable); the prod profile no longer silently resets operator-customizedLOG_MAX_SIZE/LOG_MAX_FILE(now warns when resetting a divergent value); the no-TUI--trackpath warns when track-contract synthesis fails instead of swallowing. -
Backend resilience:
plugin_seam's import-time pip-install is now bounded byBACKEND_PLUGINS_PIP_TIMEOUT_SECONDS(was the sole unbounded external call in the backend); the staleray_clientauth comment is refreshed (BACKEND_KONG_AUTH=key-authnow coversbackend-api); deadexcepthandlers inresearch_client.cancel_researchremoved. -
SQL: removed
ALTER SYSTEM SET wal_levelwrapped in aDOblock in07-functions.sql— illegal inside a transaction block, a latent abort (only dead today because the supabase image pre-setswal_level=logical). -
Dead code: dropped a redundant guard in
topology._allocate_slotsand a no-op.split("?")incomfyui_resolver._filename_from_url.
Deferred (need a product/architecture decision; rationale in the run report): the public.users FK is unsatisfiable (no population path — breaks memory/research inserts for any real user_id); the wiki↔docs-site prose templates have diverged; backend requirements.txt has no lockfile (posture decision); several asyncpg connection-held-across-LLM-call sites in the backend.
1.96. Added — 2026-07-05 — FAL cloud media provider + backend Kong auth + user Supabase migration slot
Backfilling [Unreleased] for the 2026-07-05 merges that were missing from this log:
-
FAL cloud media provider (#331) — new
falvirtual manifest +fal_media_clientexposing image generation through the FAL cloud API. -
Optional backend Kong auth (#330 / #322) —
BACKEND_KONG_AUTH=key-auth(opt-in, default disabled) adds key-auth + ACL to thebackend-apigateway route, gating the Ray job-submission and storage-upload surfaces. -
User Supabase migration slot (#329) —
db-init-runnerapplies a_userSQL mount alphabetically after the Atlas scripts, so downstream forks can ship custom migrations without forking the init image.
1.97. Fixed — 2026-07-05 — backend plugin requirements + install-error surfacing + env-overlay cold-start preservation
-
Backend plugin requirements loading (#326) and fail-clearly on plugin dependency install errors (#327) — the plugin seam loads shared + per-plugin
requirements.txtand raises a typedPluginRequirementsInstallErrorinstead of crashing opaquely at import time. -
Preserve user env overlays across cold start (#328) — the
.env.useroverlay survivesstop.sh --cold/ fresh bootstrapper regeneration.
-
Kong root now serves Atlas instead of Supabase Studio. The bare gateway root (
http://localhost:${KONG_HTTP_PORT}) returns a generated Atlas service directory with SOURCE state, track context, direct/Kong links, auth notes, warnings, and browser-side reachability probes. Supabase Studio remains available through its explicitsupabase-studio.localhostroute and keeps the existing Kong basic-auth gate.
-
Startup and init resilience:
./start.sh --setup-hostsnow requests privilege only for the hosts-file write instead of asking operators to run the whole launcher undersudo,local-deep-researcherbounds each LiteLLM health-poll attempt withcurl --max-time,lightrag-initfails fast when no chat model can be resolved, and the launcher waits for one-shot init containers so failures surface before the stack is reported ready. -
Credential and configuration hygiene: Neo4j and Supabase volume-baked password rotation stays guarded after volumes exist, secret manifest rows now carry descriptions in
.env.example, and LightRAG role/base model resolution remains documented and tested. - Docs and examples: MinIO release-pin notes now distinguish the Docker-published pin from GitHub-only upstream releases, duplicate roadmap headings were removed, and all bundled Jupyter notebooks now declare nbformat 4.5+ cell IDs with a regression test.
1.100. Changed — 2026-06-29 — SearXNG: thin use_default_settings override instead of a forked settings.yml
- Replaced the ~2800-line forked
services/searxng/config/settings.yml(a full copy of SearXNG's defaults incl. the ~250-engine list) with a ~40-line thin override built onuse_default_settings. The fork drifted from the pinned image on every bump — engines lost their modules (Cannot load engine …: FileNotFoundError) or changed config APIs (Engine setup was not successful), spamming startup errors (this is the durable fix for the searxng follow-up from the #176 cleanup, which had removed 8 dead-module engines but left 7 config-drift ones). The override now carries only what the stack needs:search.formats: [html, json](JSON is off in the default; the backend/n8n/LDR/Hermes all use the JSON API) anduse_default_settings.engines.remove: [ahmia, torch](Tor-only engines that need a Tor proxy we don't wire). Everything else — full engine list with correct per-engine config,server.limiter: false,valkey.url: false,enable_metrics: true, and thesecret_keyplaceholder — is inherited from the image and stays version-matched on future bumps.SEARXNG_SECRETis applied via the SearXNG-native env var (compose), not written into the file (the README's old "bootstrapper writes SEARXNG_SECRET into settings.yml" note was wrong and is corrected). Verified live: SearXNG boots with 0 engine-load errors (was 21+) and the JSON search API returns results. README updated.
1.101. Fixed — 2026-06-28 — post-launch service error-log cleanup (realtime crash + 3 noisy services)
A full audit of a freshly-launched stack surfaced one crash-loop and three sources of recurring error logs; all fixed and verified live:
-
supabase-realtime crash-loop (the headline). The supabase set bump (#170) moved
supabase/realtimev2.33.72 → v2.112.0, and the newer image does an unconditionalSystem.fetch_env!("METRICS_JWT_SECRET")at boot (runtime.exs— it signs the JWT guarding the Prometheus/metricsendpoint). The var wasn't in our compose, so realtime crash-looped with(System.EnvError) ... "METRICS_JWT_SECRET" ... is not set. Fix:METRICS_JWT_SECRET: ${SUPABASE_JWT_SECRET}(same posture asJWT_SECRET/SECRET_KEY_BASE; no new.envvar). The newGEN_RPC_*cert vars are only fetched when SSL inter-node RPC is enabled (GEN_RPC_SSL_SERVER_PORT), which we don't set, so they're not required. This was the runtime-compat risk flagged when #170 merged; storage-api/gotrue/postgres-meta/studio came up healthy on their bumps. -
redis-exporter false "unhealthy" —
oliver006/redis_exporteris a distroless image (no shell / nowget/curl), so thewget --spidercontainer healthcheck could never run (exec: "wget": not found) → permanent "unhealthy" (a pre-existing bug, not introduced by the version bump). Removed the unusable healthcheck; Prometheus scrapes:9121/metricsdirectly as the real liveness signal, and nothingdepends_onits health. -
postgres-exporter scrape-error spam (~every 5s) — on
supabase/postgres17.6 a GUC row has a NULLshort_desc, and the exporter'spg_settingscollector can't map NULL→string (error scraping dsn … converting NULL to string is unsupported) while still serving all otherpg_*metrics. SetPG_EXPORTER_DISABLE_SETTINGS_METRICS: "true"(the low-value settings-as-metrics export) to silence it. -
searxng dead-engine load failures at startup — our forked
settings.ymllisted 8 engines whose modules were dropped from the pinned image (svgrepo,ask,cppreference,livespace,mullvad_leta,podcastindex,searchcode_code,seekr,stract→FileNotFoundError: …/engines/<name>.py, "set engine to inactive"). Removed those 11 dead entries (some modules had multiple entries). Known remaining (separate follow-up): 7 more engines (wikicommons×4,piped.music,torch,ahmia) still error at startup because their config API changed in the pinned version (e.g.wikicommonsnow needswc_search_type,piped.musicneedsfrontend_url) — searxng self-disables them and keeps working. The durable fix is to stop shipping a full forkedsettings.ymland switch touse_default_settings: true+ a thin override (inherits correct per-engine config from the image, ends the recurring drift); tracked as a focused follow-up rather than piecemeal engine edits.
(Benign, left as-is: cadvisor/node-exporter /etc/machine-id + udev notices on macOS Docker, SLF4J StaticLoggerBinder warnings, uvicorn's uvicorn.error logger name, lightrag's startup-only "relation does not exist" lazy check-then-create probes, grafana's xychart already registered notice, zeppelin's default-XML notice.)
1.102. Fixed — 2026-06-28 — lightrag-init password authentication failed — pipeline ordering (the real "stale volume" cause)
-
Root cause of the recurring
FATAL: password authentication failed for user "supabase_admin"(lightrag-init exit 1), which the launcher mis-reported as a "stale supabase-db volume".generate_service_configurationbakesSUPABASE_DB_PASSWORD(andGRAPH_DB_PASSWORD/REDIS_PASSWORD) into the derivedLIGHTRAG_PG_URI/LIGHTRAG_NEO4J_PASSWORD/LIGHTRAG_REDIS_URIconnection values — but in both the Textual and linear launch flows it ran beforegenerate_encryption_keysrotated those passwords. So on a cold start (or any first-run placeholder→real upgrade) the URI carried the stale password while the Postgres volume was freshlyinitdb'd with the new one → auth failed against a perfectly fresh volume. This was NOT a stale volume; wiping/--colddidn't help because the ordering re-created the mismatch every run. -
Fix: moved
Validate Supabase keys+Generate encryption keysahead ofGenerate service configuration(and the Kong/LiteLLM config steps) in both flows, so every secret is finalized before any step derives a connection string from it. Added a source-order regression guard (test_secret_gen_before_config_gen.py) for both flows. Recovery after pulling this fix is a normal (non-cold)./start.sh—generate_service_configurationrewritesLIGHTRAG_PG_URIto match the existing volume's password; no wipe required.
- On a cold start the data volumes are wiped, but
generate_missing_keysrotated the three volume-baked DB passwords —SUPABASE_DB_PASSWORD,SUPABASE_DB_APP_PASSWORD,GRAPH_DB_PASSWORD(neo4j) — with a hardcodedforce=False, ignoring the cold flag that every other regenerated secret honors. The cold path only re-randomized them indirectly (viasetup_env_fileresetting.envto the placeholder), so a.envthat retained a real-but-stale value could survive a cold start and then drift from the freshlyinitdb'd volume →FATAL: password authentication failed for user "supabase_admin"(which cascades tolightrag-initand every other DB client). Fix: passforce=force_regeneratefor those three, coupling the password regen to the cold volume wipe so a cold start always yields a consistent fresh (password, volume) pair. Non-cold behavior is unchanged — real values still stick and the existing-volume guard still applies. (Recovering an already-drifted volume still needs a one-time./start.sh --coldordocker volume rm <project>-supabase-db-data, since a baked password can't be changed by rewriting.env.) The wizard's Cold start · rebuild step now spells this out — its subtitle and the per-option hints note that a cold start regenerates the Supabase + Neo4j DB passwords to match the fresh data.
1.104. Fixed — 2026-06-28 — Launch pipeline crash on the embedding dimension warning ('function' object has no attribute 'print')
- The Textual launch flow swaps
starter.bannerfor a_NullBannerto suppress stdout while inside the app. Its__getattr__returned a barelambdafor undefined attributes, sostarter.banner.console.print(...)raised'function' object has no attribute 'print'. This crashed the "Apply user model selections" pipeline step whenever a non-768-dim embedding default triggered the dimension warning (start.pyapply_user_model_selections). Fix:_NullBanner.__getattr__now returns a chain-swallowing_NullSink, sobanner.console.print(...)— and anybanner.<attr>.<method>(...)chain reached under the NullBanner (17 suchself.banner.console.*call sites exist) — is a safe no-op. (The dimension warning is still surfaced at selection time in the embedding step's heading/subtitle.)
- The LLM-defaults chat / content picker (
LITELLM_DEFAULT_MODEL) was offering embedding models (e.g.nomic-embed-text:latest,mxbai-embed-large:latest) as chat-default candidates. Two root causes: the catalog capability lookup was tag-sensitive (the curated catalog storesnomic-embed-textbare but the wizard selectsnomic-embed-text:latest, so the tagged form missed the catalog and fell through to a content-only default), and non-catalog models (mxbai-embed-largeisn't in the curated catalog) were synthesized as content-only. Fix:_classify/_descriptionnow match the catalog tag-insensitively (exact name OR family root on both sides), and a sharedmodel_resolver.looks_like_embedding()heuristic classifies catalog-unknown models so embedding models are routed to the embedding picker and kept out of the chat/vision pickers.model_resolver._synthesizeuses the same heuristic, so the runtime default-model resolver agrees with the wizard (it previously synthesized any catalog-unknown embedding model ascontent=8). A regression test reproduces the exact reported selection set.
1.106. Changed — 2026-06-28 — Image-pin sweep: kill moving tags, refresh stale pins (#168 / #169 / #170)
-
Moving
:latest/ floating tags pinned to concrete versions (#168). Closed the reproducibility hole behind the ollama drift incident (a volume-preserving reset reused a months-old cachedollama/ollama:latestthat could no longer runqwen35moeor pull the 1536-dim embedder):ollama/ollama→0.30.11,n8nio/n8n→2.28.2,dyrnq/open-webui→v0.6.32,searxng/searxng→2026.6.28-357662d86,nousresearch/hermes-agent→v2026.6.19,ghcr.io/openclaw/openclaw→2026.6.10, the sevenalpine:latestinit images →3.24.1, plus the floating partialspython:3.12→3.12.13,python:3.11-slim→3.11.15-slim,redis:7.2-alpine→7.2.14-alpine,postgres:17/15-alpine→17.10/15.18-alpine, andmulti2vec-clip→ the…ViT-B-32-1.5.1build. Each pin equals what a freshlatestpull returned that day — reproducibility, not a behavioral upgrade. (CHATTERBOX_IMAGE:gpuandTEI_RERANKER_CPU_ARM64_IMAGE:cpu-arm64-lateststay floating — upstream publishes no version-pinnable variant.) -
Stale concrete pins refreshed + jupyter base migrated off Docker Hub (#169). Same-major / patch-minor bumps: weaviate
1.27.5→1.38.2(aligns with the mergedweaviate-client>=4.22), neo4j5.19.0→5.26.27(5.26 LTS), kong3.9.0→3.9.3, zeppelin0.12.0→0.12.1, cloudflared2025.8.1→2026.6.1, node-exporterv1.8.2→v1.11.1, cadvisorv0.49.1→v0.55.1, redis-exporterv1.62.0→v1.86.0, postgres-exporterv0.18.1→v0.19.1, lightragv1.5.0→v1.5.4. The two GPU base tags upstream no longer builds were moved forward: doclingpytorch/pytorch:2.5.1-cuda12.4-…→2.12.1-cuda12.6-cudnn9-runtime, parakeetnvcr.io/nvidia/pytorch:25.01-py3→26.06-py3. The jupyterhub base migrated from the Docker-Hubjupyter/datascience-notebook:python-3.11(frozen since 2023) to the maintainedquay.io/jupyter/datascience-notebook:python-3.11.10— same Python 3.11 line, maintained registry. -
Supabase tier bumped as one coordinated set (#170). supabase/postgres
17.4.1.016→17.6.1.139, postgres-metav0.88.9→v0.96.6, storage-apiv1.22.7→v1.61.5, gotruev2.171.0→v2.191.0, realtimev2.33.72→v2.112.0, studio off the moving:latestto2026.06.22-sha-2207d7f. postgrest (v12→v14) was intentionally held (breaking major). The dockerized real-Postgres seed harness was bumped to 17.6.1.139 and re-verified: seed-rows golden byte-unchanged, schema golden regenerated (the only delta is base-image evolution — 17.6 no longer pre-creates the still-installablepg_graphqlin the default DB; some role ownership moved tosupabase_admin);seed_harness._normalizenow strips pg_dump ≥ 17.5's randomized\restrict/\unrestrictnonce lines. Runtime caveat: the harness validatessupabase/postgres+ our SQL only, not the storage-api/gotrue/realtime boot-against-17.6 path — smoke-test the stack before production reliance. -
Held by decision (not regressions): breaking majors postgrest v14, prometheus v3, grafana 13, redis 8; and litellm (no newer
-stablebuild thanv1.83.14-stable.patch.2).
- Combined six non-breaking backend/jupyterhub version-floor bumps into one PR to avoid the strict-mode merge cascade across the same-file Dependabot PRs: backend
asyncpg>=0.31.0,weaviate-client>=4.22.0,hiredis>=3.4.0,pytest>=9.1.1,pytest-asyncio>=1.4.0, and jupyterhubweaviate-client>=4.22.0. The major-bump Dependabot PRs (langchain 0.x→1.x, numpy 1.x→2.x) and the docling lock-regeneration (#151) were left open as deliberate, validated follow-ups.
- The ASCII block-art lockup (wizard brand panel +
--no-tuibanner) is now part of theBRAND_*rebranding contract: setBRAND_LOGO_FILEto a text file (wide rows, optional---separator, then a compact fallback) to override the hardcoded "ATLAS" art. Empty = built-in ATLAS, byte-for-byte. Both render surfaces resolve through the newbootstrapper/utils/brand_logo.py, so a custom lockup stays in parity across the TUI and the linear banner (the parity test now covers a custom lockup on both paths). The image-derived globe splash (atlas_hero.py) is out of scope and stays the Atlas hero. Docs:docs/quick-start/interactive-setup-wizard.md§15.1.
- The LLM and ComfyUI model catalogs moved out of the
public.llms/public.comfyui_modelsPostgres tables (both dropped via guarded decommission migrations15-decommission-llms.sql/16-decommission-comfyui-models.sql) into per-serviceservices/{ollama,litellm,comfyui}/models.yaml. DB-free resolvers (bootstrapper/utils/model_resolver.py/comfyui_resolver.py) compute the active set at startup; thelitellm-catalog-initandcomfyui-catalog-initsidecars were removed. - New wizard step picks the default model per role (content / embeddings / vision). Embedding entries declare their output dimension via
dim:; the picker auto-selects the model whosedimmatches the backendmemory_facts vector(768)column (MEMORY_FACTS_EMBEDDING_DIM) and warns on a mismatch. The Supabase seed scripts were repartitioned per-service (services/supabase/db/scripts/).
-
ComfyUI custom-models sidecar read again: the host-side resolver falls back to the repo path
services/comfyui/custom-models.yamlinstead of the dead/custom-models.yamlcontainer path, so operator-authored custom models are no longer silently dropped. -
docker compose upno longer aborts on Docker 29.x / macOS:litellm-initmounts the model YAMLs into/atlas-models(viaATLAS_MODELS_DIR) instead of nesting them under the read-only/catalogbind mount (runc could not create a mountpoint inside a:romount). -
Supabase password drift guard:
SUPABASE_DB_PASSWORDrotation is skipped (with a warning) when the project'ssupabase-db-datavolume already exists, preventing.envfrom drifting out of sync with the initdb-bakedsupabase_adminrole password (password authentication failed for user "supabase_admin"). -
lightrag-init Postgres race:
lightrag-initpolls the Postgres endpoint until it accepts connections before running its pgvector migration — the SOURCE-safe readiness gate (supabase-db is source-replaceable, so a hard composedepends_onis intentionally avoided) — instead of racing supabase-db readiness. -
litellm-initimport regression:init.pynow adds the/catalogdir tosys.pathwhen exec-loading the catalog modules, somodel_resolver's container-context loose imports (import llm_catalog,from cloud_providers import …) resolve. Previouslylitellm-initaborted the whole stack withModuleNotFoundError: No module named 'llm_catalog'(the bootstrapper venv hasutilsas a package, so the loose fallback — and the bug — only triggered inside the container). Covered by a new subprocess regression test that exercises the loose-import path. -
local-deep-researchervestigial DB wiring removed: thepublic.llmsquery was deleted in the model-SoT move, but the dead supabase wiring it left behind (DATABASE_URL,psycopg2-binary/postgresql-client,depends_on: supabase-db-init,data_flow.calls: supabase) remained and crash-looped stale images. LDR is now correctly DB-free — theresearch_*tables are persisted by the backend, which calls the LangGraph server over HTTP. -
ollama-pullretries transient pull failures: each model pull now retries up to 3 times with linear backoff before logging the terminal (non-fatal) ERROR, so a transient registry/network blip on a default model (e.g.qwen3-embedding:0.6b) self-heals instead of leaving the model unpulled until the nextcompose up.
Atlas intentionally began its public semantic-version tag line at v0.1.0.
This checkpoint establishes the repository-reuse contract and includes the
Phase 0 production-hardening work plus the Phase 1 user-overlay mechanics.
-
services/_user/overlay services now launch. The bootstrapper discovers everyservices/_user/<name>/compose.ymland merges it into thedocker composeinvocation (DockerManager._compose_file_args:-f docker-compose.yml -f services/_user/<name>/compose.yml …), so a downstream consumer's co-located services come up/down with the stack. When no overlay exists the invocation is unchanged — default file auto-discovery preserved, byte-equivalence baseline unaffected. Overlay services are self-contained Compose fragments (own image/ports/env, joined to the shared network); they are intentionally not wired into the wizard/topology/.env.example. -
Release/version-tag convention for pinning a vendored Atlas: semver
vMAJOR.MINOR.PATCHdocumented indocs/deployment/releasing.md, with the first tagv0.1.0. - Docs: new
docs/deployment/releasing.md;reusing-atlas.mdreadiness rows flipped to Ready + a §6.1 extension walkthrough;CONTRIBUTING-services.md§21 updated. Phase 1 design:docs/superpowers/specs/2026-06-21-phase1-reuse-mechanics-design.md.
- New
services/cloudflared/service, disabled by default (CLOUDFLARED_SOURCE=disabled). SetCLOUDFLARED_SOURCE=container+ provideCLOUDFLARE_TUNNEL_TOKENto run an outbound Cloudflare Tunnel daemon that terminates TLS at the Cloudflare edge and proxies to Kong — no inbound ports opened. Egress-only (no Kong route); requires a named tunnel configured in the Cloudflare Zero Trust dashboard.
- New
services/backup/one-shot runner, disabled by default (BACKUP_SOURCE=disabled; never long-running). Invoke withdocker compose run --rm backup. Dumps the Supabase Postgres database (pg_dump) and snapshots the critical named volumes (supabase-storage, graph-db, weaviate) to S3-compatible storage — on-box MinIO by default,BACKUP_S3_ALIAS_URLfor offsite. Shipsrestore-postgres.shfor recovery drills.
-
Placeholder-secret coverage guard — a test (
test_no_unrotated_nonempty_secret_defaults) now asserts everysecret: truemanifest default is either empty (generated-when-absent) or a registeredKeyGenerator.PLACEHOLDER_DEFAULTSliteral (rotated-when-placeholder). It caught and registered a previously-unregistered composite,GRAPH_DB_AUTH. -
Prod-launch secrets gate —
KeyGenerator.assert_no_placeholders_remaining()refuses to launch under--profile prodif any managed secret in.envstill equals its shipped placeholder. It runs after key generation (so a fresh./start.sh --profile prodauto-rotates first and passes) and fails loud if.envis unreadable. A rotation-consistency test proves every gated placeholder is actually rotated bygenerate_missing_keys(). -
Cross-OS doc accuracy — corrected the README's overstated "works on all OS" claim: Atlas runs natively on Linux and macOS (Intel/Apple Silicon) and on Windows via WSL2 / Git Bash (the
start.sh/stop.shentrypoints are POSIX shell; there is no native PowerShell/cmd wrapper).
A new deployment profile consolidates all production-hardening behaviors under a single flag and a matching wizard step:
-
--profile prodCLI flag / wizard profile step — selects the prod profile for the current run. The wizard surfaces this as a dedicated step; the CLI flag skips the wizard entirely for that choice. -
HOST_BIND_IPlocalhost binding — all published ports are prefixed with${HOST_BIND_IP:-}, which resolves to the empty string in dev (no-op, byte- identical compose output) and to127.0.0.1under prod, so no service socket is reachable from outside the host. -
Per-service resource limits — the heavy compute + data services (Airflow,
ComfyUI, Hermes, LightRAG, Neo4j, Ray, Spark, TEI Reranker, Weaviate, Zeppelin)
declare
*_MEMORY_LIMITand*_CPU_LIMITvalues as always-on.envdefaults wired directly into theircompose.ymldeploy.resources.limits(applied unconditionally, independent of--profile prod). These are OOM fences, not reservations; the GPU/compute services default to scale 0 and are enabled per track, so the sum of all default limits intentionally exceeds a 32 GB host — size and enable per your track. Lighter always-on services (Backend, n8n, LiteLLM, etc.) currently run unbounded. -
JSON-file log rotation —
LOG_MAX_SIZEandLOG_MAX_FILEcontrol the Docker json-file log driver'smax-sizeandmax-fileoptions, wired into every service'scompose.ymlfragment as always-on.envdefaults (independent of--profile prod). - Observability defaulted on — Prometheus and Grafana are promoted from their default-disabled state to default-on when the prod profile is active.
-
Declarative
profiles:source metadata — each source option inservice.ymlnow carries an optionalprofiles:list. The wizard filters out sources not listed for the active profile (hiding dev-only localhost sources in prod), and the CLI validator rejects them with an explicit error.
-
feature_flags.splash_enabled()is a new single source of truth that gates ALL Atlas startup block-art: the TUI opening splash overlay (AtlasSplash) and the linear /--no-tuiprinted hero banner (BannerDisplay.show_hero). Both call sites check it first. -
Disabled by default — the block-art reproduction read too coarse at real
terminal sizes. Nothing was removed: flip
_SPLASH_DEFAULTtoTrue(or setATLAS_SPLASH=1at runtime) to re-enable both paths. The existing suppressors (--no-splash/ATLAS_NO_SPLASH, terminal-width, artwork-present) still apply on top when enabled. - The
atlas_poster_*.jsongrids remain committed (latent) so re-enabling is a one-line change.
Building on the rename, the project gained a visual identity (PRs #116–#119):
-
Atlas logo + brand assets under
assets/(atlas-poster.png,atlas-profile.png,atlas-social-preview.png,atlas-source.png), used for the README hero poster and the GitHub social/profile images. -
Wizard splash — a centered poster over a navy overlay shown on
./start.sh, rendered viatextual-imagewith anAtlasHeroblock-art fallback on terminals that can't display inline images. -
Terminal hero art for the linear (
--no-tui) path. -
Splash suppression — the new
--no-splashflag andATLAS_NO_SPLASHenvironment variable skip the splash for non-interactive / fast launches.
This release renames the project end-to-end. Every variant of the old
name (GenAI Vanilla, GenAI Vanilla Stack, genai-vanilla,
genai-* container prefix, GENAI_ENV_FILE env var, GenAIStack*
Python classes) flips to Atlas / atlas. The hand-drawn block-
art title in the Textual TUI and the linear (--no-tui) banner was
also hand-redrawn as ATLAS while preserving the existing color
gradients (#74A6F4 → #0A1A55 vertical on the TUI; 15-color
color(17) → color(195) horizontal on the linear full banner).
User-visible changes that need action:
-
Container prefix flipped
genai-* → atlas-*.PROJECT_NAMEdefault is nowatlas, so every${PROJECT_NAME}-<svc>template across 33 services renders asatlas-<svc>. Old containers and volumes namedgenai-*are orphaned on the next pull. To migrate:- Easiest path — accept fresh state:
./stop.sh --coldthen./start.sh. - Preserve existing data — rename volumes per service:
for svc in supabase-db n8n-data jupyterhub-data weaviate-data redis-data ...; do docker volume create atlas-$svc docker run --rm \ -v genai-$svc:/from \ -v atlas-$svc:/to \ alpine cp -a /from/. /to/ done
- Then
./start.shboots against the new atlas-prefixed volumes.
- Easiest path — accept fresh state:
-
GENAI_ENV_FILEenv var renamed toATLAS_ENV_FILE. The old name is still honored as a deprecated alias with a one-shot stderr warning per process; it will be removed in a future major release. Submodule users should flip the variable name in their wrapper scripts. -
Grafana folder name changed
'genai-vanilla' / 'GenAI Vanilla' → 'atlas' / 'Atlas'. Existing Grafana volumes will see the 7 starter dashboards appear under a NEW empty "Atlas" folder, while the old "GenAI Vanilla" folder is left behind with the previous dashboards inside (until you manually move or delete them). -
GitHub repo URL flipped to
github.com/thekaveh/atlasin 18 places (BRAND_REPO_URL default, README clone instruction, Open WebUI tool author_urls, etc.). Until the GitHub repo is renamed on the remote, these links 404; GitHub auto-forwards old→new for many months after a rename, so timing is non-urgent. -
Python class + method names:
GenAIStackStarter → AtlasStarter,GenAIStackStopper → AtlasStopper,HostsManager.get_genai_hosts → get_atlas_hosts(and the private_genai_hosts_from_topologyvariant). Anyone importing these from a parent project must flip the identifier. -
Python package names flipped:
bootstrapper/pyproject.tomlgenai-bootstrapper → atlas-bootstrapper;services/docling/provider/localhost/pyproject.tomlgenai-doc-processor-localhost → atlas-doc-processor-localhost. Bothuv.lockfiles regenerated. -
Launch log path template:
/tmp/genai-vanilla-launch-*.log → /tmp/atlas-launch-*.log. Old log files survive but new launches write to the new path. -
JupyterHub Neo4j MERGE node: notebook
03_neo4j_graphs.ipynbcreates a graph node named'Atlas'on next run. Existing graphs from prior runs still carry the'GenAI Vanilla Stack'node; merge with a one-shot cypher:MATCH (s:System {name: 'GenAI Vanilla Stack'}) SET s.name = 'Atlas'
Internals (no operator action required):
- Block art
_LOGO_ROWSinblock_logo.pyredrawn;_GRADIENTand render pipeline unchanged. Linearget_ascii_art_full()is now a single 6-row ATLAS strip; the 15-color gradient applies per-character. -
BRAND_NAMEdefault inservices/globals/service.yml→"Atlas";.env.exampleregenerated. - Architecture HTML template
<title>flips(genai-vanilla)→(atlas); 33services/*/architecture.htmlregenerated. - JSON Schema
$idURLshttps://genai-vanilla/schemas/...→https://atlas/schemas/...(internal identifiers; never resolved over HTTP). - Test baseline
bootstrapper/tests/fixtures/rendered_config_baseline.ymlregenerated to reflect the newatlas-*container/volume/network names. - Historical CHANGELOG entries below this section deliberately preserved (they describe the project as it was named at the time).
-
./start.shusage errors now exit with click's conventional code. The--spark-workersrange check raisedclick.UsageErrorfrom inside the broad startup try-block, so the catch-allexcept Exceptionswallowed it:./start.sh --spark-workers 99printed "Unexpected error during startup" and exited 1 instead of click's usage error with exit 2.click.ClickExceptionis now re-raised ahead of the generic handler (covers any future inlineUsageError, not just--spark-workers). -
JupyterHub starter-notebook cross-references.
00_environment_check.ipynb"Next Steps" pointed at the renamed01_ollama_basics.ipynb(now01_litellm_basics.ipynb— all LLM access goes through the LiteLLM gateway) and stopped at notebook 06, omitting the shipped07_ray_clusterand08_scala_basics. Thestartup.shwelcome README listed only 00-06. Both now match the actualnotebooks/directory. -
Backend resilience. File uploads no longer run storage3's blocking I/O on
the event loop (offloaded via
asyncio.to_thread), so a slow/large upload can't stall every other in-flight request; the n8n HTTP client is closed on app shutdown; failed Weaviate vector deletes are logged instead of silently swallowed; and/research/startvalidatesuser_idlike its sibling routes (clean 400 instead of an opaque 500). -
CHANGELOG accuracy. Corrected the rename-sweep tautology "(formerly Atlas)"
→ "(formerly GenAI Vanilla)", and the upload-size note that implied
MAX_UPLOAD_BYTESis.env-overridable (it is a code default, not wired into the backend's compose/.env.example).
-
supabase-db-init:
storage.objects.path_tokensbackfill (CRITICAL, PR #105):services/supabase/db/scripts/04-storage.sqlswitched toCREATE TABLE IF NOT EXISTS storage.objects (…, path_tokens text[] GENERATED ALWAYS AS …)to stop wiping data on every boot, but theIF NOT EXISTSguard meant volumes created before thepath_tokenscolumn was added kept their old shape — the new column never landed and the next-line GIN-indexCREATE INDEX … (path_tokens)crashedsupabase-db-initwith exit 3, taking the whole stack down at./start.sh. Now pairs the CREATE with an idempotentALTER TABLE … ADD COLUMN IF NOT EXISTS path_tokens text[] GENERATED ALWAYS AS …; no-op on fresh installs, backfills the column on existing volumes before the index attempt. -
supabase-db-init:
public.llmsschema-drift backfill: same class as PR #105. The CREATE statement in05-public-tables.sqlaccreted 5 new columns (description,size_gb,context_window,api_key,api_endpoint) across 2025-07-06 and 2025-08-24, plus type-changed 4 others (vision/content/structured_content/embeddings: boolean → integer for the confidence-gradient rework). Pre-mid-2025 volumes never received any of it.05a-public-tables-migrations.sqlnow appends idempotentADD COLUMN IF NOT EXISTSfor the 5 new columns and aninformation_schema-guardedALTER COLUMN TYPE integer USING (CASE WHEN col THEN 1 ELSE 0 END)for the 4 boolean columns (legacy true→1, false→0). Without this,llm-catalog-init's UPSERT writes (e.g.content=8, structured_content=5) hard-failed on every old volume. -
supabase-db-init:
public.memory_facts/memory_sessions/memory_consolidation_loguser_idschema-drift backfill: commit 6e33a48 (2026-04-27) changeduser_idfromVARCHAR(255) REFERENCES public."user"(id)→UUID REFERENCES public.users(id). The old Open WebUI legacy tablepublic."user"no longer exists in supabase-db, leaving the FK dangling on volumes created in the 12-day window before that commit. Every memory write frommemory_store.py::_to_uuidfailed silently. New10a-langmem-migrations.sqldetects the legacy VARCHAR shape viainformation_schema, drops the dangling FK, ALTERs the column touuid, re-attaches the FK topublic.users(id); idempotent on already-migrated installs. -
SupabaseKeyGenerator.update_env_fileatomic write: the method used a non-atomicopen('w') + write()for the most security-critical secrets in the stack (SUPABASE_JWT_SECRET,ANON_KEY,SERVICE_KEY). A crash or SIGTERM mid-write truncated.envand there was no recovery path short of regeneration. Switched to the tmp +os.replace+chmodpattern already used byKeyGenerator.update_env_keyandSourceOverrideManager. -
/storage/uploadbounded buffering: the backend handler calledawait file.read()with no size limit, buffering arbitrarily large uploads into RAM and OOMing the worker on a single multi-GB POST. Now reads in 1 MiB chunks bounded byMAX_UPLOAD_BYTES(a 100 MiB code default); fails cleanly with HTTP 413 when exceeded. -
Local Deep Researcher fallback hardening:
services/local-deep-researcher/build/scripts/init-config.pywrotelocal_llm = "ollama/qwen3.6:latest"whenpublic.llmshad no active content row — unroutable in cloud-only setups (Ollama disabled) AND not a real Ollama model id. PR #88 had already removed the same anti-pattern frommemory_service; this was the second site. Now mirrors the memory_service resolution order:LITELLM_DEFAULT_MODELif set, else exit non-zero with a clear pointer telling the operator to activate a content row or set the env var. Surfaces misconfiguration at compose-up instead of at first /research request.
-
Backend hot-reload via
uvicorn[standard] --reload(PR #104): the backendDockerfileCMD now passes--reload;services/backend/app/app/ requirements.txtswaps bareuvicorn>=0.27.0→uvicorn[standard]>=0.27.0sowatchfiles(reload-watcher),uvloop,httptools, andwebsocketsship by default. The compose fragment already bind-mounted./app/app:/app, so host-side edits to backend code are picked up without adocker compose build backend— onlyrequirements.txtchanges need a full rebuild now. -
JupyterHub
thekaveh-nnx[lm]==0.2.0restored (PR #104): thennx-pytorchpackage was renamed tothekaveh-nnx; the old distribution is deprecated. The[lm]extra pulls the language-model deps the 28-of-29 ml-lab notebooks thatimport nnxneed. Pin verified upstream on PyPI. -
README + CONTRIBUTING test-count refresh: README's project-structure
comment and
docs/CONTRIBUTING-services.md's CI-table row both bumped to900+(suite is at 907 + 3 skipped). -
services/backend/README.mddev-loop note: §1 now reflects PR #104's hot-reload behavior (edit-in-place via the bind-mount; rebuild only forrequirements.txtchanges) instead of the stale "edit + force-recreate" guidance.
-
Dependabot ignore:
groq(HIGH): theservices/backend/app/app/requirements.txtpingroq>=0.30.0,<1keeps groq inside thelangchain-groq>=0.1.5window — Dependabot's previous group bump to groq 1.4.0 silently broke the backend docker build (langchain-groq couldn't resolve). Without an ignore entry the doomed bump retries every weekly cadence; PR #87 only fixed the pin, not the retry loop. -
Dependabot ignore: 7 Airflow providers: PR #87 relaxed
apache-airflow-providers-{amazon,postgres,redis,common-sql,neo4j,openai,fab}back to the floors listed in upstream Airflowconstraints-3.2.2/constraints-3.12.txt. Each weekly Dependabot bump above those floors produces a PR that can't be installed against the constraints file — same shape as PR #47 (spark provider). Lift each entry when the Airflow version itself bumps. -
stop.shsudo guard:stop.shalso shells throughbootstrapper/_run.sh, sosudo ./stop.shwould write root-owned files into the same.venv/__pycache__paths PR #87'sstart.shguard was added to prevent. Mirror guard now refuses to run as root with the same exit-2 message and pointer atdocs/TROUBLESHOOTING.mdfor recovery. -
Tracks seam-parity test:
test_tracks.py::test_every_track_service_resolves_via_source_override_managerasserts every service listed inbootstrapper/tracks.ymlhas a matching entry inSourceOverrideManager.source_mapping(afternormalize_service_keyfolding for family aliases / runtime_sc divergences). Without this guard a future tracks.yml edit could add a service without a CLI seam, silently leaking it into every restricted track. Same shipping-class risk asproject_post_merge_env_staleness.mdclass A, one rung up the tree. -
Typing hygiene:
compute_always_on(config_parser)inbootstrapper/tracks.pynow annotatesconfig_parser: Any(was a bare untyped param on a public API, inconsistent with the module's otherwise-strict typing). Import added; no behavior change. -
Duplicate-track-key guard: the runtime reject at
tracks.py:205-206was untested.test_load_tracks_duplicate_key_raiseslocks it. The schema (bootstrapper/schemas/tracks.schema.json) also gainsuniqueItems: trueon the tracks array — catches exact-copy-paste duplicates one rung earlier, before the runtime guard runs. -
_run.shmissing-Python error: the dispatcher used to print "Using system Python (install uv for better dependency management)" and thenexec python3 …, so an environment withoutpython3got an unhelpful shell-level "command not found" right after a message that implieduvwould fix things. New branch explicitlycommand -v python3checks before the exec, exits 127 with install pointers for bothuvandpython3. -
open-webui/initpython pin alignment: the init-tier Dockerfiles forlitellm/initandcomfyui/catalog-initboth pin topython:3.12.7-slimfor reproducibility, butopen-webui/initwas on the floating3.12-slimtag and silently rolled forward. Pin aligned, comment refreshed (it previously claimed parity that wasn't actually there). -
Init-script stdout is now line-buffered (HIGH):
open-webui/init'sregister-tools.py(20 print sites) andregister-functions.py(13),lightrag/init'sresolve-models.py(6), andlocal-deep-researcher/build'sinit-config.py(16) all printed withoutflush=True. In init containers stdout is pipe-attached and block-buffered by default — a script that crashes or is killed mid-run drops its progress trail silently, the same blind-spot class as the PR #67register-tools.pySyntaxError that hid for 24 hours. Each script now runssys.stdout.reconfigure(line_buffering=True)at the top (mirrors theflush=True-everywhere pattern litellm-init already used).lightrag/init'sresolve-models.pywas the most load-bearing — its KEY=VALUE output is sourced by the shell, and a silently-truncated emit produced an emptyEMBEDDING_MODELat runtime. -
New buffering guard:
test_init_script_stdout_is_line_bufferedwalks everyservices/*/init/scripts/*.pyAST and asserts eithersys.stdout.reconfigure(line_buffering=True)at module top orflush=Trueon everyprint()call. Test suite grew 902 → 906. -
Doc-only folder skip guard:
test_doc_only_folders_are_skipped_by_real_manifest_loadpinsservices/{stt-provider, doc-processor, multi2vec-clip}/— the three aggregator folders that ship README + diagrams but noservice.yml— against the realload_manifests()output. A future_is_service_dirrefactor that started loading them as manifests would break this real-repo test instead of slipping through (the existing synthetic-folder tests cover the predicate in isolation but don't pin it to the actual on-disk layout).
Stopped at 10 consecutive zero-issue passes (passes 33-42); no MAX_PASSES cap hit. Test suite at 907 passed + 3 skipped; all 6 audit scripts and the docs-drift gate exit 0.
-
Hermes capability wiring (HIGH):
service_config.pynow emitsTTS_INTERNAL_URL,STT_INTERNAL_URL,COMFYUI_INTERNAL_URL, andSEARXNG_INTERNAL_URLwhenever Hermes is in container mode and the respective provider source is enabled. Until now onlyLIGHTRAG_INTERNAL_URLwas wired; the other four fell through the compose${VAR:-}fallback to empty, andinit-hermes.sh'sstrip_blocksilently omitted the TTS / STT / image-generation / web-search capability blocks fromconfig.yaml. Hermes ran without those capabilities even when their upstream providers were on. Five new regression tests intest_hermes_n8n_backend_adapts_to_lightrag.py(4 enabled-emit + 1 disabled-blank) lock the contract in. - README localhost-source docs surface the Hermes dashboard port
(
HERMES_LOCALHOST_DASHBOARD_PORT=63029) alongside the API port — previously only the API was documented despite Kong'shermes.localhostroute fronting the dashboard. - Dependabot ignore list adds
apache-airflow-providers-apache-spark(PR #47 was closed unmerged because the 6.0.0 bump dropped the bundledpysparkdependency). Without the ignore entry the doomed bump retries on every weekly cadence. - Pydantic hygiene:
MemoryFact.metadatanow usesField(default_factory=dict)instead of the bare{}literal, matching the project's preferred convention. -
CONTRIBUTING-services.mdTL;DR said "four-command regen + lint chain" but §12 lists five; LICENSE copyright year extended2025→2025-2026. README test-count claim refreshed from800+to840+. - README §4.1 service table inflated TEI Reranker's default model id
to the namespaced upstream form
mixedbread-ai/mxbai-rerank-base-v1(baremxbai-rerank-base-v1wouldn't pull from HuggingFace). - README + 9 service READMEs + quick-start troubleshooting + the
expected-startup-warnings table aligned to the
${PROJECT_NAME}-<svc>container-name convention. Non-default-PROJECT_NAMEdeployments now have working cut-paste docs throughout. The warning table keeps the literalatlas-prefix (it documents actual log output) but the new note at §1 tells readers to substitute their ownPROJECT_NAME. - README §6
./stop.sh --coldblock now warns that thedocker system prune -f --volumesstep is host-wide — it also prunes unused images / volumes belonging to OTHER docker projects on the same host. - Textual wizard's first paint now mirrors
state_builder.build_app_state()whenKONG_HTTP_PORTis blank in.env:_build_steps_and_rowsnow uses'63000'as the alias-port fallback instead of empty string, so the TUI and--no-tuipaths render the same alias_port for every Kong-aliased service. Regression test intest_blank_base_port.py. - Test hygiene:
test_adapts_to_includes_lightragnow declares parametrizeids=so pytest failures point at the failing (service, container, env-var) tuple by name instead ofsvc0-container0-expected_env_var0. - Prometheus README §1 reworded "All three lifecycle together" (verbed noun) to "All three share a single lifecycle" — same scaling semantic, cleaner prose.
- n8n queue mode: the worker now exports metrics too (5 mirrored
N8N_METRICS*vars + a dedicatedn8n-worker:5678scrape job) — execution-data counters are written worker-side, so the Grafana panels were blind to them; scrape-target counts updated to 13 across docs. - Supabase: documented the auth-less pg-meta host publish
(
SUPABASE_META_PORT, SQL assupabase_admin) with a firewall/remove guidance note; submodule guide'sPROJECT_NAME=… ./start.shexample replaced with the working.env-based flow (a shell prefix splits the compose project name from fragment interpolation). - README: project tree gained redis/lightrag/tei-reranker entries; the architecture-diagram summary now names the full optional set.
- Port rewrite:
VAR=63002(trailing whitespace, no comment) was silently skipped by the rewrite regex — fixed + regression test. - Dropped the dead
GRAPH_DB_HOSTdeclaration (zero consumers and itsgraph-dbdefault never matched the realneo4j-graph-dbhostname). -
--setup-hostscycles no longer accumulate blank lines in /etc/hosts (boundary normalized; a read failure now aborts instead of risking a truncated hosts file); backend README lists the full route surface (/workflows,/comfyui/*,/api/ray/*were undocumented). - Stale-comment sweep: ollama wizard-step docstrings (three-step era), Kong "10 localhost routes" count, and the MinIO console-port docstring (63019, not 63018) refreshed.
- Backend:
cancel_researchnow accepts PENDING sessions (the insert→RUNNING race window previously left a live background task uncancellable); memory-service initialization is lock-guarded so the Weaviate delete-and-recreate collection heal can never race itself. - Textual wizard: worker failures during the setup phase surface as
error toasts (previously
_write_statusno-op'd with no log pane and the error vanished); the confirm-step launch worker gained the sameexit_on_error=Falsehandling as the auto-launch path. - New validator rule
runtime_sc_missing_variant: a main runtime_sc slice missing a declared source option is now a CI-blocking lint (previouslyget_service_config()silently returned{}and consumers fell back to hardcoded defaults). -
stop.sh: the--clean-hostsbanner no longer claims success when the hosts-file edit failed (e.g. without sudo);--cold's globaldocker system prune -f --volumesstep is now disclosed in--helpand the README (scoping it is a tracked follow-up). - n8n:
N8N_METRICS_INCLUDE_EXECUTION_DATA_METRICS=truewired (compose + runtime_sc + baseline) — the Grafana execution-data panel was permanently "No data" because the flag is default-off upstream. - Zeppelin: the seeded note and README used
%jdbc(postgres)prefix syntax that Zeppelin 0.12 removed; flow rewritten around a dedicated%postgresinterpreter. OPENCLAW_ENDPOINT documented as a forward-looking hook (no consumer wired today).
-
Backfill vs migrations (HIGH):
backfill_missing_env_vars()no longer pre-seeds keys the migration chain owns. Previously it splicedBOOTSTRAPPER_PORT_LAYOUT_VERSION(and migration v2's*_LOCALHOST_PORTtargets / v3's COMFYUI model vars) from.env.exampleinto legacy.envfiles beforerun_port_migration()inspected them — stamping the file as already-migrated and silently dropping the user's legacy port and model-set customizations. Six regression tests including an end-to-end legacy-env chain. - Dockerfile hygiene: jupyterhub/backend ARG defaults now match the manifests' image pins (CI build-validation previously built a different base than production); coursier pinned to v2.1.24; LDR's psycopg2-binary pinned to the repo-wide 2.9.9.
- Blank
BOOTSTRAPPER_PORT_LAYOUT_VERSION=lines now count as unmigrated and are stamped in place (previously: skipped by the digit-only regex and duplicated on stamp). - Docs: weaviate module-list truth in source-configuration.md,
CONTRIBUTING category table (5/6 rows were missing services) + manifest
counts,
ray.localhostrestored to the alias list, openclaw opt-in framing in the redis research row. -
Localhost-port collision class (MED): five
*_LOCALHOST_PORTdefaults were stale pre-migration literals sitting on ports OTHER services now publish (docling/63021=Neo4j Browser, parakeet/63022=Redis, openclaw/63024=Spark UI, whisper-cpp/63025=Spark History, chatterbox/63027=Weaviate gRPC). Re-defaulted onto each family's freed slot (63040/63042/63042/63044/63065) across manifests, wizard wiring, Kong generator, localhost validator, service_config, tests, and docs. Existing.envfiles keep their old values (migration-v4 candidate — see Known follow-ups). - Test suite: default-assertions made hermetic (no longer read the live
repo
.env); stale_KNOWN_NO_CONSUMERexclusions dropped; new cross-seam guard pins every hardcoded localhost-port fallback literal to.env.example. Suite grew 826 → 839.
- Neo4j backup tooling:
auto_restore.sh/restore.shno longer print "restored successfully" whenneo4j-admin database loadfails — failures now exit non-zero with a partial-state warning (container startup behavior unchanged);ollamapull.sh addsPGPORTto its required-env check. - Audit scripts:
validate_research_schema.py --alland the docs-drift SOURCE-matrix check fail loudly when their target globs match nothing instead of passing vacuously. - Manifests: dropped Spark's unwired
supabasedata-flow edge (no JDBC driver or env wiring ships — same user-driven rule as Zeppelin's litellm exclusion) and its deadprometheusoptional dep; documented MinIO'ssupabaseentry as ordering-only slot pinning. - Textual UI:
ctrl+qhint relabeled "quit" (setup-phase quit never saved); removed the deadon_completehook and_refresh_topologyshim; refreshed stale PresentationApp/palette/wizard comments. The--no-tuilinear flow now clears stale shell-exported*_PORTvars before port configuration, matching the TUI pipeline. - Backend research surface: documented the
research_client.py↔ LangGraph protocol mismatch (client targets bespoke/research/*+/health;langgraph devserves/ok+/threads+/runs) as a known gap in all three claim sites; porting the client is a tracked follow-up. - Env migrations: fresh
.envfiles now ship sentinelBOOTSTRAPPER_PORT_LAYOUT_VERSION=3(no spurious v2+v3 first-run pass with backup-file noise), migration v3 honors its append-if-absent contract forCOMFYUI_CUSTOM_MODELS_FILE, and migration v2 recognizes quoted URL values.
- Redis README cluster closure: the per-database index map now matches
compose reality everywhere (n8n BullMQ on db
/0, LightRAG + Open WebUI sharing/2, JupyterHub on/3), Local Deep Researcher research routes documented as existing, and the backend's injected-but-unreadREDIS_URLframed truthfully across all four cross-referencing READMEs. - Backend README intro rewritten from the aspirational "fans out to every
data-tier" hub claim to the actual runtime call list (Supabase, Weaviate,
LiteLLM, ComfyUI, n8n, Ray, Local Deep Researcher);
adapts_tolist completed withray+lightrag. - Open WebUI README: removed the false "Weaviate is wired" premise from the hybrid-search future item and corrected the consumer boilerplate (only Kong + its init container consume Open WebUI).
- Hermes README: example pin
nousresearch/hermes-agent:0.13.0(a tag that does not exist upstream — the registry publishes onlylatest/sha-*) corrected tolatest; n8n README's bidirectional "agent can call a workflow" claim reduced to the one wired direction. - LiteLLM README: "Prompt caching (Redis)" no longer listed as an unused feature (response caching is already enabled stack-wide); future item now points at the genuinely-unused semantic cache + per-key TTL controls.
- All
redis-clidoc examples now carry-a "$REDIS_PASSWORD"(requirepassis on) and use--scaninstead ofKEYS.
- README long-tail factual sweep (the last seven never-audited service docs): weaviate's ports corrected (63026/63027 — it listed Redis's 63022/63023) along with its Kong route and module lists; local-deep-researcher's "no Kong route / no backend API" claims inverted (both exist); redis's database map matched to reality (n8n queue on db 0, JupyterHub on db 3, Kong on default 0); backend's required deps now include litellm and the right Hermes port (8642); ray's shm_size 8gb; spark documents its local S3A-enabled build; openclaw's deep-health command uses the real entrypoint. The full code remainder pool (12 widgets, utils/core internals, wizard sections) was read end-to-end the same pass — clean.
- The dead
WEAVIATE_LITELLM_BASE_URLchain removed (generated into.envwith the same wrong/v1suffix, consumed by nothing, described falsely); stacks deployed before the/v1/v1fix get their broken Memory collection deleted and recreated at backend startup (Weaviate 1.27.5 forbids vectorizer-config updates, and the class could never store vectors anyway); reorg_user_env's backup-safety check honorsGENAI_ENV_FILE; a dead langgraph.json that contradicted the runtime graph registration removed; linear banner taglines now honorBRAND_TAGLINE.
-
Weaviate-backed memory inserts/searches 404'd on every call — the
collection's
text2vec-openai.baseURLcarried a/v1suffix that Weaviate's openai module joins/v1/embeddingsonto (/v1/v1/embeddings); suffix dropped, and weaviate-init's deadDEFAULT_OPENAI_BASE_URLexport (same wrong suffix, read by nothing) removed. - Parakeet-GPU timestamps read NeMo's real
.timestampfield (the pass-26 fix used the nonexistent.timestep); docling upload handlers gained the same filename guards. - /etc/hosts handling is now comment-aware and address-anchored in BOTH
directions — a commented-out
# 127.0.0.1 aliasno longer counts as present, and removal spares commented lines and the user's hyphenated lookalikes (regression tests cover both paths). - Submodule-usage examples now show Kong's REAL routing (Supabase REST
path-routed on the gateway root; everything else host-routed) and
the right
SUPABASE_API_PORT;.envrewrites in key_generator and migration_v3 are atomic + mode-preserving (backups included). - searxng's trusted-proxy claim now matches limiter.toml; hosts-check and kong-consumer test nits.
-
Every parakeet-GPU transcription request 500'd — NeMo's RNNT/TDT
decoder returns
List[Hypothesis]even withoutreturn_hypotheses, and the handler passed the dataclass tolen(); text is now extracted defensively (mirroring the MLX sibling) and timestamps are actually requested attranscribe()time and read from NeMo's.timestampfield, so the advanced endpoint returns real timing data. - Docling chunking clamps caller-supplied
chunk_size/chunk_overlap(an overlap ≥ size made the chunk loop never advance — unbounded memory growth from one bad form value), in both the shared and localhost copies.
-
Neo4j backup/restore never worked on the shipped 5.19 image —
database dump --output-namedoesn't exist (every backup failed ANDset -eaborted beforeneo4j start, leaving the DB stopped), and community editions have nodatabase restoresubcommand at all. The scripts now dump via--to-path+ rename, restore viadatabase load --from-stdin, and restart Neo4j through an EXIT trap even on failure; the README's 4.x-isms (NEO4J_dbms_memory_*,db.indexes(), "incremental backups", wrong volume name, phantom APOC) corrected. - node-exporter now passes
--path.rootfs=/rootfs— without it the filesystem metrics the containers-and-host dashboard graphs described the exporter's own overlay mount, not the host disks the bind exists to expose. - The ollama.com variant scraper accepts
M-suffixed context windows (10M context windowrows — the llama4 class — were silently dropped to coarse sizes). - Earlier in this span: a health-probe DB connection leak closed (close-in-finally), the silently-no-op'd keys help-text edit landed for real, and the whole resource-close pattern class was exhaustively swept (23 sites verified safe).
- The regen tool's doc-only boilerplate variant now also covers
AGGREGATE folders: stt-provider / doc-processor READMEs stopped
citing a
service.ymlthey don't have (they now point at the member manifests that actually carry the edges);services/comfyui/empty/is committed so container-mode runs stop creating it root-owned at runtime; an airflow troubleshooting bullet stopped referencing theOpenAIOperatorclass the same README explains doesn't exist.
- The host-run Docling localhost server loaded
.envfrom the wrong directory (three parents instead of five — the load silently no-op'd) and bound the container-modeDOC_PROCESSOR_PORTinstead of the stack'sDOCLING_LOCALHOST_PORTcontract (it only worked because the fallback happened to match); its README taught the wrong var. - multi2vec-clip README: module lists now include the
text2vec-ollama/generative-ollamapair (following the old disable snippet verbatim would have dropped them) and the env story correctly credits compose interpolation, not weaviate-init; the regen tool gained a doc-only boilerplate variant so pointer docs stop citing aservice.ymlthey themselves say doesn't exist.
- Hermes context-window guidance replaced a fabricated
ollama --ctx-sizeflag (no such flag upstream) with the real paths:OLLAMA_CONTEXT_LENGTHon the server or/set parameter num_ctx+/save <model>in the REPL; the "defaults to 4096" claim updated to current upstream behavior (VRAM-dependent 4k/32k/256k). Test-suite hygiene: validator tests moved onto the shared env fixture; passes 14-15 were zero-finding verification sweeps.
- The speaches GPU-image rewrite now honors a shell-exported
SPEACHES_GPU_IMAGE(the pin refresher's documented override path) — pass 12's version consulted only.env, losing exported pins and, in the no-.env-line case, silently falling back to the CPU image again.
- The image-pin refresher now also covers pins declared as plain env
vars (
SPEACHES_GPU_IMAGEwould otherwise go stale in user.envs on every cuda bump — and the pass-11 fix had duplicated its literal); the two LightRAG/TEI wizard-port test rows that pass 11's docstring bump promised are actually in the table now; a swept-in.pycis untracked and the over-broad!*in the bundled-data gitignore scoped; a non-integerLIGHTRAG_EMBEDDING_DIMnow warns before falling back to auto-probe.
-
speaches-container-gpuactually runs the CUDA image now — the compose fragment interpolates${SPEACHES_IMAGE}under both profiles, and nothing ever wiredSPEACHES_GPU_IMAGEin despite three docs claiming "the speaches-gpu profile selects it" (the manifest's own description admitted "not yet wired"). The generator now resolves the winning profile's image; gpu→cpu switches self-heal via the pin refresher. -
LIGHTRAG_LLM_MODEL/LIGHTRAG_EMBEDDING_MODEL/LIGHTRAG_EMBEDDING_DIMare honored — the README told users to set them, but lightrag-init never received nor read them (and its WARN advised overriding via a var nothing consumed). The dim override now defaults to empty = auto-probe (a hardcoded 768 default would have silently bypassed the probe for non-768 models). - LightRAG and TEI Reranker
localhostoptions gained the inline port widget every other localhost-capable service already had; the cloud-providers registry docstring stopped overclaiming start.py's imports; three never-consumedCOMFYUI_*_PATHvars removed from the manifest/.env.example.
- Clearing log filters while the source popup is open no longer gets
silently reverted by the popup's stale snapshot on dismiss; one
garbled docstring from pass 8 rewritten whole; hermes config template
comment now cites the
ollama/-prefixed id LiteLLM actually publishes.
- The atomic
.envwrite clamps the tmp file's mode BEFORE secrets are written (no umask-default window beside a 0600.env); two LiteLLM docstrings corrected to match actual behavior (a missing config stub is always written; non-container custom Ollama models are registered with a warning, not ignored).
- The
--no-tuibanner now honors theBRAND_*rebranding knobs (it hardcoded the upstream credits while the Textual wizard rebranded); the atomic.envwrite preserves the file's mode, cleans up its tmp sibling on failure, and.env.tmpis gitignored; the launch-time skip-prune moved to a module-level helper so its regression test binds to production code instead of an inline replica.
- Pre-launch command summary no longer shows flags from steps the user
later hid via Back-navigation (matches what launch actually
persists); the SOURCE-override
.envrewrite is now atomic (tmp +os.replace— a crash mid-write used to truncate.env); launch-time prune gains a regression test; a migration docstring stopped overstating its STT involvement.
-
Both seeded n8n research workflows were broken end-to-end: the
weekly scheduler sent
user_id: "system_scheduler"(backend casts to UUID → 500 on every run, forever); the SearXNG research workflow's model-lookup compared the integercontentcolumn to a boolean (no such Postgres operator → node always errored) and its AI-summary node read$env.LITELLM_BASE_URL/LITELLM_API_KEY, which were never in the n8n containers' env (URL rendered "undefined/…"). All three fixed; the LiteLLM vars are now injected into n8n AND n8n-worker (queue mode) with the runtime_sc dual-write. - Curated OpenRouter id corrected to
anthropic/claude-sonnet-4.6(OpenRouter serves the dot form; the hyphen form is Anthropic-direct only — verified against the live models API). - A stale picker commit no longer persists after Back-navigating and disabling the owning service (skip-hidden steps are pruned at launch).
-
services/docling/provider/localhost/requirements.txtremoved — it duplicated pyproject+uv.lock (the actual install path), and Dependabot bumps to it alone would silently re-drift the pins the lock-gate can't see. - redis fragment header no longer claims the fragment is unreferenced.
-
--base-portruns now persistBASE_PORTitself — the port rewriter updated every*_PORTbut never the anchor, so the very next flagless run (which preserves.env'sBASE_PORTsince pass 1) read the stale 63000 and silently reverted the whole custom layout. -
Upgrading an old
.envwithCOMFYUI_MODEL_SETnow activates real models — migration_v3 translated to catalog-phantom names (sd15-pruned-emaonly/sdxl-base-1.0exist nowhere), so catalog-init activated only the VAEs and every seeded workflow failed at render. The SD1.5/SDXL-base checkpoints now live in the curated catalog layer (present regardless of scrape outcome) and the translation emits their real names. - ComfyUI
[pulled]badges now also match the catalogfilenamecolumn (civitai/sidecar downloads were never recognized on re-runs); an explicit deselect-all in the ComfyUI picker now clearsCOMFYUI_USER_MODELSlike the Ollama picker; the consolidation-log tense map accepts both tense forms; the dead per-sourceCOMFYUI_ARGS/AUTO_UPDATEkeys left inruntime_scare gone;DASHBOARD_PASSWORD's.env.exampledescription now documents the auto-rotation; Studio auth nuance documented (Kong route gated, direct port open).
-
CRITICAL (self-caught): the pass-2 airflow quote-safety fix broke
airflow-init on every boot — psql performs
:'var'interpolation only in script input, never inside-cstrings, so both role statements errored underset -e. Statements now pipe via stdin (quote-safe AND functional; verified empirically against a live Postgres). -
Ollama model picker no longer duplicates every pulled model: the
"pulled-but-not-in-library" bucket compared tagged names
(
qwen3.6:latest) against bare library families (qwen3.6), so each normal pull also surfaced as a bogus "(local model, not in public library)" row. The picker's/api/tagsprobe also now honorsOLLAMA_LOCALHOST_PORT(the 5th consumer site of the localhost-port symmetry rule). -
./stop.sh --clean-hostsno longer deletes the user's own /etc/hosts entries — removal matched substrings, so a personal127.0.0.1 my-n8n.localhostline vanished because it contains a stack alias. Now whole-token comparison. - Memory consolidation log recorded every merge as "superseded" — the action guard compared past-tense values against the LLM contract's present-tense vocabulary.
- Smaller correctness: migration_v3 no longer drops the user's
COMFYUI model-set translation when the old line carried an inline
comment; a list-rooted ComfyUI sidecar YAML no longer crashes the
wizard/catalog-init (warn + ignore per its never-raises contract);
http.client.HTTPException(IncompleteRead etc.) is now caught at all six catalog/scrape fetchers; the service.yml schema rejects typo'd keys insideruntime_sc.<container>.<source>blocks (previously silently dropped). - Refactors (output-verified): the five uniform SPA Kong routes
(prometheus/spark-master/spark-history/airflow/zeppelin) collapsed
into one data-driven table — generated config byte-identical across
4 SOURCE permutations; capture-free helpers hoisted out of the
285-line
build_ollama_steps; four duplicated test env-splice loops replaced by a sharedenv_with_overridesconftest factory. - Docs/CI: CONTRIBUTING's CI-gates section now documents all four jobs
+ the four-seam picker-flag rule; minio image note drops a
placeholder CVE id;
.gitignoresheds two dead personal-scratch entries; kong/comfyui READMEs lose claims invalidated this run.
-
storage.objectswas dropped and recreated on EVERYdocker compose up(04-storage.sql) — all Supabase Storage object metadata (ComfyUI uploads included) silently vanished on each restart, and storage-api's own migration ledger stayed marked applied so its later columns never came back. NowCREATE TABLE IF NOT EXISTSlike every sibling table. -
Grafana dashboards re-verified against the PINNED upstream versions
(the previous fix validated against upstream master): kong.json's four
panels all used Kong 2.x metric names that don't exist in kong:3.9.0
(
kong_http_requests_total/kong_request_latency_ms_bucket/kong_bandwidth_bytes{direction}now); both Weaviate app-tier panels used master-onlyweaviate_module_*metrics absent from 1.27.5 (unprefixedrequests_total{api}/queries_durations_ms_bucket); the n8n uptime fix had replaced a correct prefixed name with an unprefixed one (n8n_process_start_time_secondsis right — prom-client default metrics ARE prefixed); litellm failed-requests grouped by labels that don't exist (requested_model/exception_classnow). Datasource provisioning gains an explicituid: Prometheusmatching every panel ref;GF_SERVER_ROOT_URLnow carries the Kong port. -
Supabase Studio is now actually behind the documented credential
gate: the Kong dashboard route shipped with only CORS — no
basic-auth, no ACL — while README/.env promised
DASHBOARD_USERNAME/DASHBOARD_PASSWORDprotection (the consumer + auto-rotated password existed; the route just never used them). -
Pass-1 regressions caught by an adversarial diff review and fixed:
the localhost-validator port conversion fed a string port into
socket.connect_ex(Neo4j probe always failed even with a live listener) and used blank-value-unsafedict.get;GET /workflowswould have flipped its wire format to camelCase (validation-only aliases now); a degraded model-fetch's KEEP sentinel leaked into the command summary and could wrongly flip a cloud provider's overview state. -
local-deep-researcher could be configured against model ids LiteLLM
never serves — its init prefixed every provider (
openai/gpt-…,openrouter/openrouter/…); only Ollama rows carry a prefixed alias. Same family: LightRAG's default-chat fallback picked the first /v1/models entry, which is typically an embeddings-only route — now filters out embedding/agent/self entries. -
Six seeded workflows/tools referenced checkpoint filenames the
download pipeline never produces (
sd_v1-5_pruned_emaonlyvs the catalog'sv1-5-pruned-emaonly,sdxl_base_1.0vssd_xl_base_1.0) — every seeded ComfyUI workflow failed at render even with the model installed. Civitai catalog entries also gain a realfilename(their download URLs have none, so files landed extension-less where ComfyUI never lists them). -
LightRAG's Neo4j migration never applied while logging OK — the
whole multi-statement cypher file went up as a single tx statement
(guaranteed syntax error) and Neo4j reports errors inside an HTTP 200
body the script never read. Now split per-statement + errors[] gate.
Its pgvector meta table also gains the PK that made
ON CONFLICT DO NOTHINGa no-op (one new row per boot, with self-heal for existing installs). -
CI hardening: backend's pytest suite now runs in the required
Manifest lint + unit testscheck (it previously ran nowhere); the docling localhost provider gets auv lock --lockedgate (no Dockerfile → build-validation can't see its pins); all GitHub Actions are SHA-pinned;check_doc_links.pynow validates#anchorfragments against GitHub heading slugs (and immediately caught a dead{#launch-log}kramdown anchor GitHub never supported);check-kong-routes.pynow covers all 17 default-emitted hosts (was 9). hermes-init's model dedup no longer hides direct-API cloud entries when OpenRouter twins exist; ollama-pull's wait is bounded and pull errors inside HTTP-200 NDJSON are surfaced; n8n community-package checks parse n8n's{"data": …}envelope; memory-table RLS policies now actually scope toservice_role(USING (true)+ default-privilege grants had left authenticated PostgREST callers full CRUD on all memories). -
Init hardening + dead-chain removals (same commit as the CI
gates): airflow-init's role statements switched to quote-safe psql
:'pw'interpolation (NOTE: this introduced the regression pass 3's first bullet fixes —-cstrings don't interpolate); openclaw's inline config patcher gotset -e+ tmp-file writes (a missing jq used to truncate openclaw.json to 0 bytes); db-init-runner's DB wait is bounded (300s); minio-init now refreshes service-account secrets + policies on re-runs (rotations used to silently never propagate); the deadIS_LOCAL_COMFYUIchain, unreadWEBUI_ADMIN_*container env, and two never-called legacy methods in the research streaming tool were removed. - Docs: zeppelin README no longer claims
%sparkworks without the Spark-Connect setup (the image ships no Spark distro) and its starter notebook usesspark.version(noscunder Connect); comfyui README stops claiming the bootstrapper injects--force-fp16/AUTO_UPDATEper source (all static via.env); searxng's "Redis is wired" claims corrected everywhere (valkey.url: false, dependency is slot-pinning only); n8n README's Hermes→n8n inverse path is webhook-based (no execute endpoint exists); ROADMAP counts corrected to 32 families / 62 containers.
-
N8N_SOURCE=disablednever disabled n8n.N8N_SCALEwas read from.envwith the manifest value as a mere dict-default; the key always exists, so the source was never consulted and n8n/n8n-worker/n8n-init all started anyway. Scale now derives from the manifest per source, and the dependency manager's auto-disable now zeroes worker/init scales too (it previously left both running against a dead main) and no longer sticks after the violated dependency is re-enabled. -
DOC_PROCESSOR_SOURCE=docling-container-gpuwiped the speaches/parakeet/chatterbox compose profiles — the doc-processor generator rebuiltCOMPOSE_PROFILESfrom a dict that never contains that key instead of stacking onto the shared tally, so enabling Docling-GPU silently excluded the active STT/TTS containers. The pipeline now ownsCOMPOSE_PROFILESend-to-end (seeded empty each run, so stale profiles from since-disabled sources also clear) and the var is declared auto-managed in the globals manifest. -
GENAI_ENV_FILEwas half-wired: all fourdocker composeargv builders hardcoded--env-file=.env(compose silently ran against the wrong file),KeyGeneratorwrote generated secrets to the repo-root.env, and a relative path resolved against CWD (differs between the uv launcher and the system-python fallback). All seams now honor the resolved path. -
Multiple invalid
*_SOURCEvalues reported "success All SOURCE values are valid" while exiting 1 — the per-value validator reset the shared error list on every call, so only the last variable's errors survived. -
Kong's n8n route emitted a literal
${KONG_HTTP_PORT}intoX-Forwarded-Host(Kong DB-less config does no env interpolation), so n8n baked the unexpanded token into webhook/editor URLs served vian8n.localhost. The port is now resolved at generation time. -
./start.sh --no-tui(and any non-TTY run) silently reset a custom port layout — the linear flow fell straight to base port 63000 instead of preserving theBASE_PORTalready configured in.env, rewriting every*_PORTand leaving.envself-inconsistent. It now mirrors the TUI's read-from-.env fallback. -
Wizard (TUI) fixes: the ComfyUI model picker now honors the source
you just selected instead of the stale pre-wizard
.envvalue (it used to hide after enabling ComfyUI, and show for a just-disabled one); "No — exit without starting" on the final confirm actually exits (was a silent no-op); launch-phase crashes surface in the log pane instead of freezing the UI silently; a failed model-catalog fetch no longer lets a single Enter wipe your savedOLLAMA_USER_MODELSCSV; ComfyUI filter chips no longer desync from the row filter onf-cycling; the[pulled]badge scan now resolves the real<project>-comfyui-modelsvolume mountpoint viadocker volume inspectinstead of scanning a host path that never exists. -
Backend API:
/storage/uploadcalled storage3 methods that don't exist (every upload 500'd — now uses the per-bucketfrom_()API);GET /workflowsreturned n8n's{data: …}envelope raw (failed response validation — now unwrapped, with camelCase timestamp aliases);POST /workflows/{id}/executeremoved (n8n's public API v1 has no such endpoint — the route could never succeed);/comfyui/cancel/{id}now deletes queued prompts viaPOST /queueand only interrupts when the prompt is actually running (it used to abort whatever was running); plus 400/404 correctness on research-session listing, ComfyUI model CRUD, and image fetch, and JSONB metadata decoding on memory update.N8N_API_KEYis now a declared (empty-by-default) env var passed to the backend — n8n CE only issues keys via its UI, and the/workflowsendpoints 401 without one. -
RAY_ADDRESSnever reached any container — declared only inruntime_adaptive(which writes.env, not container env), so every/api/ray/*backend route 503'd and notebook 07 reported "Ray is disabled" even with Ray enabled. Now injected via the backend and jupyterhub compose environment blocks (+RAY_DASHBOARD_URLdeclared). -
n8n queue-mode workers were missing every workflow-facing env var
(
STT_ENDPOINT,TTS_ENDPOINT,DOCLING_ENDPOINT,WEAVIATE_URL, Hermes/LightRAG endpoints,GENERIC_TIMEZONE) — and the stack defaults to queue mode, so$env.*resolved empty exactly where workflows actually execute. Mirrored into the worker block. -
JupyterHub notebooks: 02_langchain_rag crashed at cell 1
(
langchain-openaiwas never installed — now pinned, plus an explicitopenaipin that was previously only transitive); 00_environment_check's PostgreSQL probe always printed error under SQLAlchemy 2.x (raw-stringexecute— nowtext()), its "Ollama" probe actually hit LiteLLM with an endpoint LiteLLM doesn't serve, and its HTTP checker treated 404/500 responses as success. -
stop.shalways exited 0 even whendocker compose downfailed (undetectable to scripts/CI) and told users to restart with a nonexistent./start.py. -
Security: docling 2.93.0 → 2.94.0 (CVE-2026-47214, 3 high
alerts) + starlette 1.0.0 → 1.2.1 in the docling localhost-provider
lock; 3 phantom Dependabot alerts on the retired
tts-provider/localhost/path dismissed asnot_used. -
.envparsing is now quote-aware:PASSWORD="ab#cd"was silently read asab(any#truncated the value); quoted hashes are data, and unquoted hashes only start a comment after whitespace. -
Localhost-port override symmetry completed: the localhost
validator now reads
OLLAMA_LOCALHOST_PORT/COMFYUI_LOCALHOST_PORT/WEAVIATE_LOCALHOST_PORT/NEO4J_LOCALHOST_BOLT_PORTlike every other consumer, instead of probing hardcoded ports and warning falsely on overridden setups. - searxng's compose no longer gates startup on redis (
valkey.url: false— pure coupling; the manifest keeps a slot-pinning entry).
-
data_flow.callscorrected across five manifests (and all per-service README §Deps tables + diagrams regenerated): local-deep-researcher +supabase (its init readspublic.llmsover psycopg2); jupyterhub now mirrors the env surface its notebooks actually use (+comfyui, +n8n, +backend, +searxng; −minio which had no env and no notebook); open-webui now models its real edges (+supabase app DB, +redis websocket manager, +backend extras tools; −weaviate and −searxng, which have no wiring today and stay documented as Future pairs); backend −lightrag (env passed but unread); prometheus +grafana (the scrape job existed, the mirror didn't). - Documentation: hierarchical numbered headings enforced across 10
guides (CONTRIBUTING-services, troubleshooting, the four deployment
docs, diagrams/research/services READMEs, SECURITY) with anchors
rewritten; fabricated
admin@example.com / changeme123credentials replaced with the real auth story (Kong basic-auth + auto-rotatedDASHBOARD_PASSWORD; n8n first-visit owner setup);.env.exampleprovenance corrected everywhere (it is generated from manifests — never hand-edit); supabase README per-service ports fixed (5 off-by-one entries); troubleshooting volume names fixed (atlas-supabase-db-data, notatlas_supabase_db_data); source-matrix rows added forRAY/AIRFLOW/SPARK/ZEPPELIN_SOURCE; wizard-guide "5a" heading renumbered into a clean 1–18 sequence; docs hub now links the research-corpus guide and superpowers plans/specs; test counts updated to 800+.
-
services/open-webui/init/scripts/register-tools.py:create_admin_usershipped with a duplicatetimeout=30keyword argument, raisingSyntaxError: keyword argument repeated: timeoutat module-import time on every open-webui-init container boot since PR #67. The function's broadexcept Exceptionswallowed the SyntaxError as a generic "Signup request failed", so the admin user silently never got created — open-webui-init's 60-attempt retry loop then exited 1 with "No admin user found". Fix removes the duplicate kwarg. -
services/grafana/config/provisioning/dashboards/*.jsonshipped with 12 metric names that don't exist in upstream LiteLLM / n8n / postgres-exporter / Weaviate / Prometheus. Every affected panel rendered "No data" indefinitely. Verified firsthand against canonical source files (LiteLLMprometheus.py, n8nprometheus-metrics.service.ts, postgres-exporterpg_stat_user_tables.go, Weaviate monitoring docs, Prometheus config docs):- litellm.json: 4 panels —
litellm_requests_total,litellm_total_tokens,litellm_request_latency_bucket,litellm_failed_requests_metriccorrected to the upstream_metric/litellm_proxy_*/litellm_request_total_latency_metric_bucketnames. - n8n.json: complete rewrite (5 panels) — upstream emits
n8n_workflow_execution_duration_seconds,n8n_active_workflow_count,n8n_execution_data_writes_total, notn8n_workflow_executions_total/n8n_active_workflows/n8n_total_workflows/n8n_process_start_time_seconds. - postgres-redis.json:
pg_relation_size_bytes→pg_stat_user_tables_table_size_bytes. - app-tier.json:
weaviate_queries_total+weaviate_objects_total→weaviate_module_requests_total+weaviate_module_request_duration_seconds_bucket;minio_bucket_usage_total_bytes(only at/metrics/bucketwhich we don't scrape) →minio_cluster_usage_total_bytes. - stack-overview.json:
up{stack="atlas"}→up. The Prometheus docs explicitly noteglobal.external_labelsonly apply toremote_write/federation/Alertmanager, NEVER to locally-scraped TSDB series — the selector matched zero series. Panel title also updated to drop "Hermes" (Hermes ships no/metrics).
- litellm.json: 4 panels —
-
services/lightrag/service.yml::runtime_adaptive.lightrag-init.failure_modecontract was wrong — declared "lightrag-init exits non-zero; LightRAG container does not start" when LiteLLM is unreachable, butresolve-models.py:42catches URLError + JSONDecodeError and returns[], thenmain()falls back to env-var defaults / hardcodedollama/nomic-embed-text+ dim=768 and exits 0. Realigned to "lightrag-init logs warning, falls back to env-var defaults; LightRAG starts but every chat/embed call 502s until LiteLLM becomes reachable".
-
services/weaviate/init/scripts/init-weaviate.sh:18—until psql ... do sleep 5; donehad no upper bound; a persistently unreachable Supabase DB would hang weaviate-init forever. Bounded to 300s (mirrors n8n / minio patterns). -
services/hermes/init/scripts/init-hermes.sh:100— curl to LiteLLM/v1/modelsgained--max-time 15. Previously a LiteLLM-side stall blocked hermes-init for the OS default TCP timeout (~75s). -
services/comfyui/init/scripts/download_models.sh:88— wget gained--timeout=30 --tries=3so a stalled HF/civitai mirror doesn't hang a multi-GB download. -
services/n8n/init/scripts/install-nodes.sh— 4 curl sites missing--max-time(readiness probes capped at 5s, GET community-packages at 15s, POST install at 120s). -
bootstrapper/utils/system.py— 3subprocess.runsites (docker version,docker network inspect,docker run --rm alpine) + the genericrun_command()helper gained explicittimeout=(10s / 60s) withsubprocess.TimeoutExpiredadded to the except clauses.
-
services/minio/README.md:12-13+docs/ROADMAP.md:63— both files advertised the MinIO admin console on63018and S3 API on63017, contradicting.env.example'sMINIO_PORT=63018(S3 API) andMINIO_CONSOLE_PORT=63019(console). User-facing instructions now match. -
services/tei-reranker/README.md:98— CPU memory guidance still quoted BGE-reranker-v2-m3 needing ~3 GB; updated to mxbai-rerank-base-v1 (~1.5 GB) which has been the default since 2026-06-07.
-
services/{litellm/init,litellm/catalog-init,comfyui/catalog-init}/Dockerfile— patch-version pinnedFROM python:3.12-slim → python:3.12.7-slim(floating tags admit moving targets without operator visibility). comfyui/catalog-init also gained pinnedrequests==2.32.3andPyYAML==6.0.2for the same reason. -
bootstrapper/pyproject.toml— migrated[tool.uv].dev-dependencies→ PEP 735[dependency-groups].dev. The old table is deprecated and uv warns on every invocation. CI workflow updated touv sync --group dev. -
.github/dependabot.yml— addedtorchaoto the torch+PyG ignore list. torchao tracks torch's minor version (PyTorch ecosystem); an auto-bump would silently break against the currenttorch==2.4.1pin. -
bootstrapper/services/dependency_manager.py:245— narrow secondexcept Exceptionon .env-rewrite path →except OSError. PR #67 narrowed line 221 but missed this parallel block.
-
bootstrapper/tests/test_init_scripts_compile.py— parametrisedpy_compileover everyservices/*/init/scripts/*.py+ parametrisedbash -nover every*.sh+ AST-walk for duplicate kwargs. Closes the gap that let the open-webui-init SyntaxError ship. -
bootstrapper/tests/test_dockerfile_pins.py— everyservices/**/Dockerfile's non-ARG FROM must use a digest or a patch-version-pinned tag (major.minor.patch prefix). Locks the Pass 1 pin posture in CI. -
bootstrapper/tests/test_pyproject_dependency_groups.py— guards PEP 735[dependency-groups].devcontract; fails if a future edit re-introduces deprecated[tool.uv].dev-dependencies.
docs/diagrams/architecture.svg (and its architecture.html standalone
view) refreshed to reflect the current 33-service stack. Eight services
shipped since the diagram was last hand-authored were absent:
LightRAG + TEI Reranker (2026-06-05, PR #62), Apache Airflow
- Apache Spark + Apache Zeppelin (2026-06-05, PR #35), and Ray + Prometheus + Grafana (earlier in 2026).
Layout additions: Zeppelin joins APPS (5 cards), LightRAG + Airflow join AGENTS (5), TEI Reranker joins LLM CORE (4), Spark sits beside Ray in DISTRIBUTED COMPUTE (2), and a new OBSERVABILITY band carries Prometheus + Grafana. ViewBox grew from 1400×1100 to 1400×1240 to host the new bands without compressing the existing topology.
The README's embedded diagram updates transparently (GitHub renders the SVG inline). The corresponding "Known follow-up" entry under [Unreleased] is removed.
.env.example shipped publicly-known defaults for 8 credential vars
that survived a clean cp .env.example .env && ./start.sh boot
unchanged. The worst was N8N_ENCRYPTION_KEY=your-random-encryption-key
(n8n AES-encrypts every saved workflow credential under it, so saved
API keys / OAuth tokens were recoverable from the on-disk SQLite blob
by anyone reading the public repo). Others: SUPABASE_DB_PASSWORD=password,
SUPABASE_DB_APP_PASSWORD=app_password, GRAPH_DB_PASSWORD=neo4j_password
(Neo4j; also rewrites the composite GRAPH_DB_AUTH=neo4j/<password>),
REDIS_PASSWORD=redis_password, DASHBOARD_PASSWORD=kong_password
(Kong admin), OPEN_WEB_UI_ADMIN_PASSWORD=admin, OPEN_WEB_UI_SECRET_KEY=secret.
bootstrapper/utils/key_generator.py now carries a PLACEHOLDER_DEFAULTS
dict and a _is_placeholder_or_empty() helper; per-rotator
generate_and_update_* methods upgrade the placeholder on first launch
and preserve any operator-supplied real value (rotating mid-run would
lock out the existing database/role/user — destructive). The aggregator
in generate_missing_keys() wires all 8 rotators alongside the existing
LiteLLM / Hermes / Airflow / Grafana / MinIO / SearxNG generators.
Operator action: hand-edited .env files with custom values are left
alone. Fresh installs (or any .env still carrying a placeholder)
will rotate to a random value on the next ./start.sh.
Two latent bootstrapper holes surfaced by the overnight audit loop:
-
unset_port_environment_variableswas missing 9 port slots added by PR #29 / PR #35 (RAY_DASHBOARD_PORT,RAY_CLIENT_PORT,RAY_GCS_PORT,SPARK_MASTER_UI_PORT,SPARK_HISTORY_PORT,AIRFLOW_PORT,ZEPPELIN_PORT,PROMETHEUS_PORT,GRAFANA_PORT). Cold-start with a custom--base-portwould have shell-export-shadowed the freshly-computed slot for any of these services with a stale value. -
The TUI-launch flow's
stack_optionscarriedcloud_user_modelsandollama_user_modelsfilters but had no catch-all bucket for scalar env-write flags (COMFYUI_CUSTOM_MODELS_FILE,RAY_WORKER_COUNT,PROMETHEUS_RETENTION_DAYS,SPARK_WORKER_COUNT). On the./start.sh --flag <value>path under a TUI-capable terminal, all four flags were silently dropped (they only worked under--no-tui). Newuser_env_writesbucket carries the residual unfiltered keys through to the sameapply_user_model_selectionspipeline.
-
services/lightrag/init/scripts/resolve-models.pyembed-dim probe no longer swallowsException— narrowed to(URLError, JSONDecodeError, KeyError, IndexError). The wide swallow silently fell back todim=768against a 1024-dim store on transient failures, then every runtime insert failed with "dimension mismatch" with no log trail. -
services/lightrag/init/scripts/init-lightrag.shwritesresolve-models.pyoutput to/app/data/.env.tmpthenmvatomically — the plain>redirect truncated the destination BEFORE python ran, so a script crash left the file empty and lightrag booted with noLLM_MODEL/EMBEDDING_MODEL/EMBEDDING_DIM. -
services/open-webui/init/scripts/register-{tools,functions}.pypicked up missingrequests.get/requests.posttimeouts (10s/30s),psycopg2.connect(connect_timeout=5)to bound the TCP-handshake worst case, and atry/finallypattern around DB cursor+conn so a restart loop doesn't leak one connection per attempt.
After PR #29/PR #35/PR #47 port reshuffles, ~25 stale port literals
remained scattered across READMEs (root README, services/n8n/README.md,
services/openclaw/README.md, services/neo4j/README.md, services/redis/README.md,
services/supabase/README.md, docs/deployment/submodule-usage.md,
docs/quick-start/troubleshooting.md, docs/deployment/source-configuration.md)
and ROADMAP.md carried wrong Kong-route shape + ports for the shipped
LightRAG + TEI Reranker entries. Stale external/api source-variant
references in README, source-configuration.md, and wizard-guide were
also scrubbed.
services/kong/service.yml::data_flow.calls was missing lightrag +
tei-reranker despite live Kong routes; services/hermes/service.yml,
services/n8n/service.yml, services/backend/service.yml each had
runtime_adaptive.adapts_to lightrag with compose passing the env
vars, but the manifest's data_flow.calls had no matching row — so
the auto-generated §5.2 / §6.2 tables and per-service architecture
diagrams omitted the edge. Filled all four gaps + regenerated docs
and the hermes byte-equivalence golden fixtures.
-
LIGHTRAG_RERANK_BINDING_HOSTmanifest declaration aligned withservice_config.py's imperative/rerankappend (the two sources of truth had drifted). -
LIGHTRAG_DOC_STATUS_STORAGEdefault unified toRedisDocStatusStorageacrossservice.yml/compose.ymlfallback / README (three-way split was usingRedisKVStoragein two of them). -
services/hermes/service.yml::runtime_adaptive.hermes-init.environment_adaptationwas missingLIGHTRAG_API_KEY— the compose env block + init script + template all read it, but the manifest under-specified the cross-service contract.
bootstrapper/utils/hosts_manager.py (6 sites),
bootstrapper/core/docker_manager.py (4 sites), and
bootstrapper/services/dependency_manager.py (2 sites) all carried
bare except Exception blocks that silently absorbed real bugs
(malformed regex, attribute typos, KeyError) alongside the intended
OS-level failures. Narrowed each to its actual failure surface
(OSError, UnicodeDecodeError, subprocess.SubprocessError,
psycopg2.Error) so future regressions in these modules surface
loudly instead of being silently absorbed into safe-default returns.
Behavioral diff: previously-masked TypeError / AttributeError / etc.
now propagate.
-
tests/test_lightrag_manifest_imperative_parity.py(new): asserts both ends of theLIGHTRAG_RERANK_BINDING_HOSTcontract end in/rerankso a future manifest edit can't silently drift from the imperative emitter inbootstrapper/services/service_config.py. -
tests/test_user_model_selections_seam_parity.py::test_tui_launch_carries_user_env_writes_buckettightened: was a loose AST walk accepting any Dict literal with auser_env_writeskey; now requires the key live on the specificAssign(targets=[Name('stack_options')])Dict AND its value be aDictCompoveruser_model_selections.items(). Stub assignments now fail loudly. -
tests/test_lightrag_litellm_registration.py: stubpsycopg2/psycopg2.extrasinsys.modulesbefore exec_module so the 3 lightrag_model_entry tests run in any bootstrapper venv (matched the established pattern fromtest_catalog_init_auto_import.py). Previously these tests silently failed locally — CI's resolved dep tree pulled psycopg2 transitively, masking the breakage.
- Top-level
permissions: contents: readon.github/workflows/services-lint.yml(no job needs write scopes; principle-of-least-privilege). - Path-filter expanded with
LICENSEand.gitattributesto prevent required-checks deadlock on a config-only PR (the same class of bug PR #48 hit on.github/dependabot.yml). -
services/open-webui/init/Dockerfilebumpedpython:3.11-slim→python:3.12-slim(psycopg2-binary 2.9.9 ships cp312 wheels; the 3.11 pin was no longer load-bearing) and pinnedrequests==2.32.3/psycopg2-binary==2.9.9/PyJWT==2.10.1against upstream-regression surprise. -
services/docling/provider/gpu/DockerfileARG default aligned topytorch/pytorch:2.5.1-cuda12.4-cudnn9-runtime(the manifest pin).
- New
services/lightrag/manifest: graph-augmented RAG server pinned toghcr.io/hkuds/lightrag:v1.5.0. Defaultdisabled. - Storage adapts to Supabase pgvector, Neo4j, Redis with in-process fallback when any backend source is
disabled. - Registered with LiteLLM as the
lightragmodel (Ollama-shim served via OpenAI adapter); reachable transitively by open-webui, openclaw, n8n, hermes, backend, local-deep-researcher, jupyterhub. - Wired into
runtime_adaptiveof hermes/n8n/backend for direct calls. - Kong route
lightrag.localhostwithpreserve_host: True(WebUI SPA). - Init container resolves LLM/embedding model + dim from LiteLLM
/v1/modelsat boot. - RAG-Anything is NOT added — subsumed by LightRAG v1.5.0's multimodal pipeline.
- New
services/tei-reranker/manifest: HF text-embeddings-inference running BAAI/bge-reranker-v2-m3. Defaultdisabled. Four source variants:container-cpu,container-gpu,localhost,disabled. - Reusable by compatible consumers through its REST endpoint. Direct LightRAG-to-TEI reranking is disabled by default because their rerank payload shapes are incompatible without an adapter.
- Kong route
rerank.localhost.
Three new services added in a single coordinated landing as the stack's
compute / orchestration tier. Spec at
docs/superpowers/specs/2026-06-04-airflow-spark-zeppelin-design.md;
plan at
docs/superpowers/plans/2026-06-04-airflow-spark-zeppelin.md.
-
Spark cluster (
SPARK_SOURCE=disabled|container) — Apache Spark 4.1.2 in standalone mode, 5-container family: 1 master + N workers (default 2, range 1-8 via the new--spark-workersflag mirroring Ray's--ray-worker-count) + history server + dedicatedspark-connectgRPC sidecar (runsstart-connect-server.shagainst the master — the upstream-supported path for binding Spark Connect on port 15002) + one-shotspark-initthat creates thespark-historyMinIO bucket using theminio/mcimage. Web UI atspark.localhost, history atspark-history.localhost. Clients reach Spark Connect atsc://spark-connect:15002(backend-network only). -
Zeppelin notebook (
ZEPPELIN_SOURCE=disabled|container) — Apache Zeppelin 0.12.0 with pre-configured Spark / SQL (JDBC to Supabase Postgres) / Shell / Markdown interpreters. Hard-gated onSPARK_SOURCE != disabled— Zeppelin without Spark refuses to start with an actionable error from_generate_zeppelin_config. Web UI atzeppelin.localhost. Starter notebook ships atservices/zeppelin/notebooks/spark_basics.zplnexercising Spark + S3A + JDBC. -
Apache Airflow (
AIRFLOW_SOURCE=disabled|container) — Apache Airflow 3.2.2 (LocalExecutor), 4-container family:airflow-webserver(api-server: UI + REST API),airflow-scheduler,airflow-dag-processor(REQUIRED standalone service in Airflow 3.x — the scheduler no longer parses DAG files in-process; without it, no DAGs ever load), and the one-shotairflow-init. Wired withapache-airflow-providers-openaifor LiteLLM integration. Bundled providers: apache-spark, amazon (MinIO via custom endpoint), postgres, redis, common-sql, weaviate, neo4j, openai, fab. (LangChain chains run via PythonOperator + langchain-openai; there is no published apache-airflow-providers- langchain package.) Metadata DB lives in a newairflowdatabase on Supabase Postgres (created idempotently byairflow-init). 7 Airflow Connections seeded — 3 unconditional (postgres_supabase,litellm_default,redis_default— all 3 sibling services are always-on or locked source) and 4 gated on the matching sibling source:spark_default(SPARK_SOURCE=container),minio_default(MINIO_SOURCE=container),weaviate_default(WEAVIATE_SOURCE=container),neo4j_default(NEO4J_GRAPH_DB_SOURCE=container). Sampleexample_etl_with_llmDAG ships inservices/airflow/dags/. Web UI + REST API atairflow.localhost. Hermes → Airflow integration via the REST API is documented in the per-service README §6.
Cross-stack integration coverage (per the spec's integration matrix):
- Spark: MinIO (s3a), Supabase Postgres (JDBC), Kong (preserve_host on both Web UI + History UI).
- Zeppelin: Spark (interpreter), MinIO (via Spark), Supabase Postgres (JDBC), Kong.
- Airflow: Supabase Postgres (metadata + user conn), Spark (sample DAG
uses
PythonOperator+ Spark Connect atsc://spark-connect:15002;SparkSubmitOperatoravailable via the bundled provider for user DAGs), MinIO (S3Hook), LiteLLM (LangChain/OpenAI operators), Redis (RedisHook), Weaviate, Neo4j. Hermes → Airflow REST trigger pattern documented.
Wizard additions: 3 new source steps in the appropriate category
bands (data / apps / agents). Spark's source step carries a
SecondaryNumberInput widget for SPARK_WORKER_COUNT (1-8) mirroring
Ray's worker-count widget. New CLI flags: --spark-source,
--spark-workers N, --zeppelin-source, --airflow-source.
Defaults: all three services default to disabled matching the
heavyweight-services convention (Ray, Prometheus, Grafana). Opt in via
wizard or CLI flag. Estimated memory footprint with all three enabled:
~7-9 GB additional RAM.
4 new bootstrapper-generated secrets for Airflow:
AIRFLOW_FERNET_KEY (Connection-password encryption),
AIRFLOW_SECRET_KEY (Airflow 3.x AIRFLOW__API__SECRET_KEY — inter-process payload signing), AIRFLOW_ADMIN_PASSWORD,
AIRFLOW_DB_PASSWORD. All force=False in generate_missing_keys()
because rotating any of them mid-run breaks something.
Known follow-ups (deferred from this PR):
-
Spark × Prometheus + Grafana — spec §5.1 marked this CRITICAL-opt-in
(JMX exporter sidecar + scrape job + a starter
spark.jsonGrafana dashboard) but the wiring did not ship in this PR. cAdvisor's container-level metrics cover the gap in the existing dashboards until the JMX integration lands. Tracked separately. Seeservices/spark/README.md§4. -
Spark × Supabase Postgres JDBC pre-wiring — spec §5.1 listed
spark.jdbc.postgres.urlenv-var pre-config on the master as CRITICAL (config only). Users wire JDBC manually today via--jars postgresql.jar+ ajdbc:postgresql://supabase-db:5432/...URL per job. Seeservices/spark/README.md§4. -
Zeppelin JDBC interpreter auto-binding — the
ZEPPELIN_JDBC_POSTGRES_*env vars are injected but Zeppelin doesn't auto-bind them to a JDBC interpreter profile. Users do a one-time UI setup (Interpreter → JDBC →+ Create→postgresgroup). Seeservices/zeppelin/README.md§4. -
Airflow
postgres_supabaseConnection uses admin credentials — intentionally seeds withSUPABASE_DB_USER/SUPABASE_DB_PASSWORD(superuser) until the prerequisiteSUPABASE_DB_APP_USERPostgres role is actually created bysupabase-db-init(it's declared in.env.examplebut the create-role script is missing). Least-privilege migration tracked separately. User DAGs that need fine-grained access should create their own Connection objects. -
Airflow × Prometheus + Grafana — Airflow 3.x has no built-in
/metricsendpoint; the canonical path is StatsD → statsd_exporter → Prometheus. The PR ships none of the three (no statsd_exporter sidecar, noAIRFLOW__METRICS__STATSD_*env vars on webserver/scheduler/dag-processor, no scrape job inservices/prometheus/config/prometheus.yml).airflow'sdepends_on.optionalwas scrubbed of the dead-promiseprometheusentry to avoid auto-generated diagrams showing an edge that doesn't exist.
Three follow-ups to the original Scala-kernels + VS Code wiring (PR #30):
-
services/jupyterhub/build/notebooks/08_scala_basics.ipynb— a new Scala 3 sample notebook (10 cells) showing basic syntax,import $ivydependency loading via Almond, ajava.net.http.HttpClientcall into the LiteLLM gateway, and Scala-3-only features (enums + extension methods). Demonstrates that the kernels actually work end-to-end and gives users a template to crib from. -
JupyterHub README §11 verification steps — added a
jupyter kernelspec listsmoke-test, an explicit rebuild recipe (docker compose up jupyterhub --build --no-deps -d) for users whose running container predates the Almond layer, and a one-linerjupyter run --kernel=scala3smoke-test that confirms the kernel is actually reachable without opening JupyterLab. -
JupyterHub README §10.5 / §10.6 troubleshooting + screenshots
scaffolding — added three new troubleshooting entries (Scala
kernels missing from picker → rebuild; no kernels listed → token
suffix missing; output in wrong notebook → restart kernel),
a new §10.6 listing four reference screenshots
(
services/jupyterhub/docs/screenshots/{01..04}-vscode-*.png), and a §10.7 with capture instructions for first-time setup. Screenshots directory ships with aREADME.mdexplaining the layout but no PNGs yet — users capture them on their own machines per §10.7.
docs/research/candidates/omnivoice.md (new) records a feasibility
assessment of omnivoice.app +
k2-fsa/OmniVoice as a potential
fifth TTS engine alongside Speaches's Kokoro+Piper and Chatterbox. The
hosted SaaS has no public developer API; the OSS reference
implementation is CLI/Python only with no FastAPI wrapper or published
Docker image and would make this repo the upstream wrapper maintainer
against a fast-moving 0.1.x library. The only genuine differentiator
over Chatterbox is OmniVoice's 600+ language coverage (vs Kokoro 8,
Piper 30+, Chatterbox 23). Recorded as deferred under
services/tts-provider/README.md §9.5 with three concrete re-evaluate
triggers (SaaS API published / community wrapper appears / Speaches
adds OmniVoice as a backend).
config/prometheus.yml shipped scrape jobs targeting jupyterhub:8000
and hermes:8000. Both were broken:
-
JupyterHub — the container EXPOSEs
8888, not8000. Even with the right port, the image we ship is single-userjupyter/datascience-notebook(not real multi-user JupyterHub), which has no built-in/metricsendpoint. The historical/hub/metricspath only works on real multi-user JupyterHub. -
Hermes — listens on
8642(API) /9119(dashboard), not 8000. Also a third-partynousresearch/hermes-agentimage with no/metricsendpoint; instrumenting it would require forking upstream.
Removed both scrape jobs from config/prometheus.yml with inline
comments documenting why they're deferred. Removed the three JupyterHub
panels (active users, running servers, spawn-duration p95) from the
app-tier Grafana dashboard since they could never have data; retitled
the dashboard to "App tier (Weaviate + MinIO)" and dropped the
jupyterhub tag. Updated services/prometheus/README.md §4 (14 → 12
targets, with a Deferred note) and the top-level README §3.4 narrative.
JupyterHub metrics return when the multi-user spec ships; Hermes metrics return when upstream instrumentation lands.
Five URLs in README.md's Quick Start access block and §4.1 Service
Overview table quoted the wrong host port — readers would 404 or hit a
sibling service. .env.example is the canonical source for all five:
- Supabase Studio:
localhost:63016→localhost:63017(63016 isSUPABASE_REALTIME_PORT) - MinIO Console:
localhost:63018→localhost:63019(63018 isMINIO_PORT, the S3 API) - Neo4j Browser:
localhost:63020→localhost:63021(63020 isGRAPH_DB_PORT, the bolt protocol port) - MinIO Console narrative also referenced the S3 API as
:63017; corrected to:63018.
Each wrong value appeared in BOTH the Quick Start block and the §4.1
table; this commit aligns both with .env.example's pins.
Four startup-noise / functional cleanups against the observability bundle (PR #29), surfaced once the bind-mount fix from PR #31 let the stack actually launch:
-
cAdvisor lost Docker-socket access on Docker Desktop. The compose fragment mounted
/var/run:/var/run:ro(whole directory). On Docker Desktop,/var/run/docker.sockon the host is a symlink to/Users/<you>/.docker/run/docker.sock; the symlink survives the bind but its target isn't reachable inside the container, so cAdvisor loggedCannot connect to the Docker daemon at unix:///var/run/docker.sockand silently dropped all per-container metrics. Replaced the whole- directory mount with the canonical/var/run/docker.sock:/var/run/docker.sock:ro— Docker Desktop's daemon resolves the symlink at mount time, and the same form is portable to Linux Docker where the path is the real socket. -
Grafana
provisioning/plugins/directory was missing. Grafana scans all four standard provisioning subdirs (datasources/,dashboards/,alerting/,plugins/) at startup and errors loudly on any that are absent. Added an emptyplugins/.gitkeepso the dir exists in git. -
Grafana
provisioning/alerting/.gitkeepproduced a warn every startup. Grafana enumerates files with.yaml/.yml/.jsonsuffixes in each provisioning dir and warns about anything else. Replaced.gitkeepwithplaceholder.ymlcontaining the minimalapiVersion: 1stub so the dir stays non-empty without tripping the scanner. Real alert provisioning can later replace the placeholder. -
Grafana bumped 11.3.0 → 11.4.3. 11.3.x has a known bug where
autoMigrateXYChartPanel(a feature flag Grafana itself enables by default) collides with the bundledxychartcore panel — logs every startup asCould not register plugin pluginId=xychart error="plugin xychart is already registered". Upstream fixed it in grafana/grafana#93540, shipping in 11.4. Bump the default inservices/grafana/service.yml(and the rendered.env.example) to the latest 11.4 patch.
services/prometheus/compose.yml and services/grafana/compose.yml (both
added by the PR #29 observability bundle) declared their config bind-mount
sources as ./services/<svc>/config/... — written as if Compose would
resolve them from the repo root. Compose v2's include: directive
resolves relative paths in an included fragment from the fragment's own
directory, so the actual resolved path was
services/<svc>/services/<svc>/config/... (the path doubled). On the
first launch Docker auto-created the missing source as a directory; the
second launch then failed with not a directory because the mount
target expects a file.
Rewrote the four affected volume entries (two in each fragment) to
./config/..., removed the stray services/prometheus/services/ tree
that Docker had auto-created, and regenerated the byte-equivalence
baseline in bootstrapper/tests/fixtures/rendered_config_baseline.yml.
Prevention: added bootstrapper/tests/test_fragment_bind_sources.py
— a static check that walks every services/*/compose.yml, resolves
each relative bind-mount source against the fragment's directory, and
fails if the resolved path contains the literal doubled marker
services/<X>/services/<X>/ (where <X> is the fragment's own folder
name). This is the exact PR #29 regression class and the structural
pattern can never be correct, so the check has zero false-positive
surface against fragments that legitimately mount runtime-generated
paths (litellm config, neo4j/supabase snapshot dirs, kong dynamic
config). Runs in the existing "Manifest lint + unit tests" CI job with
no Docker daemon. Verified to fail on the buggy form during development
(prometheus fragment temporarily reverted) and emits an actionable
error naming the fragment, offending raw source, and resolved path.
The JupyterHub container now ships three kernels and is configured for remote-kernel access from VS Code on the developer's host machine.
Scala kernels (Almond):
- Two new kernels installed at image build time via Coursier —
scala213(Scala 2.13.16) andscala3(Scala 3.4.3), both running on Almond 0.14.5 over OpenJDK 17. Pick from JupyterLab's launcher or VS Code's kernel-picker. Toolchain footprint ≈ 600 MB; drop the relevant Dockerfile blocks if you don't need Scala. - Pinned via
ALMOND_VERSION/ALMOND_SCALA_2_VERSION/ALMOND_SCALA_3_VERSIONDockerfile build args so future bumps are explicit and rebuild predictably.
VS Code remote-Jupyter:
-
services/jupyterhub/compose.ymladds three--ServerApp.*flags to the container command —allow_origin=*,allow_remote_access=True,disable_check_xsrf=False. Token auth still gates every request; the origin allowlist can be tightened via the newJUPYTER_ALLOW_ORIGINenv var. - Full operator walkthrough lives at
services/jupyterhub/README.md§ 10 (Connecting from VS Code). The flow: install Microsoft's Jupyter extension, copyJUPYTERHUB_TOKENfrom.env, pastehttp://localhost:63081/?token=<TOKEN>into the "Existing Jupyter Server" prompt. VS Code then offers the new kernels via its kernel-picker.
-
Removed
nnx-pytorchfrom the ml-lab support block. The 28-of-29 ml-lab notebooks thatimport nnxwill not run until the package is restored. Supporting libraries (python-louvain,nltk,spacy,torchao,prettytable) stay so non-nnx notebooks keep working.
New paired bundle in the infra band giving full-stack metrics observability
out of the box. Both services default to disabled — opt in with
--prometheus-source container --grafana-source container or the wizard.
New services:
-
services/prometheus/— metrics scraper + TSDB with bundlednode-exporter(host metrics) andcAdvisor(container metrics) as co-lifecycled containers. Default retention: 7 days, user-configurable at wizard time via the new inlinesecondary_numberrow schema. Static scrape config shipped with 14 targets initially; later trimmed to 12 (see the JupyterHub + Hermes Fixed entry above). -
services/grafana/— observability UI + unified alerting. Pre-provisions the Prometheus datasource and 7 starter dashboards: Stack Overview, LiteLLM (per-model tokens / spend / latency), Kong (per-route req rate / latency / bandwidth), Postgres + Redis, Containers + Host, n8n (workflow executions), and App tier (Weaviate + MinIO; JupyterHub panels dropped in a follow-up alongside the unreachable scrape jobs). Admin password (GRAFANA_ADMIN_PASSWORD) auto-generated on first run viagenerate_grafana_admin_password()— same posture as LiteLLM's master key.
Sidecar exporters (embedded in existing manifest families, scale 1↔0 with
PROMETHEUS_SOURCE):
-
postgres-exporter(inservices/supabase/) — readspg_stat_*views, auto-discovers every Supabase database. -
redis-exporter(inservices/redis/) — Redis memory, ops/sec, hit ratio.
Cross-stack /metrics enablement (always on; sit unscraped when
PROMETHEUS_SOURCE=disabled):
-
Kong — global Prometheus plugin via
kong_config_generator, Status API on:8100(internal-only),KONG_STATUS_LISTEN=0.0.0.0:8100. -
LiteLLM —
'prometheus'added tolitellm_settings.callbacks(shared viabootstrapper/utils/litellm_settings.pyso both the host stub and the init-script render).PROMETHEUS_MULTIPROC_DIR=/tmp/litellm_metrics+ tmpfs required for the multi-worker (4 uvicorn) layout. -
Weaviate —
PROMETHEUS_MONITORING_ENABLED=true; metrics on port 2112 (internal-onlyexpose). -
n8n —
N8N_METRICS=true+ prefix / workflow-id labels. -
MinIO —
MINIO_PROMETHEUS_AUTH_TYPE=public(no JWT needed). -
Backend —
prometheus-fastapi-instrumentator>=7.0.0middleware; emits standardhttp_request_duration_seconds/http_requests_totalseries. -
JupyterHub — originally shipped expecting built-in
/hub/metrics, but the image is single-userjupyter/datascience-notebook(not multi-user JupyterHub) and has no built-in metrics surface; the scrape job was dropped in the follow-up Fixed entry above. Returns when the multi-user spec ships.
Deliberate exclusions: Ollama (LiteLLM gateway already emits per-call
request/token/cost — direct scraping would duplicate); Neo4j Community
(metrics are Enterprise-only); ComfyUI, SearXNG, OpenClaw (no native
/metrics today). cAdvisor covers container-level resources for all of these.
Hermes is a third-party container without a /metrics endpoint; its scrape
job was dropped in the follow-up Fixed entry above and returns when upstream
instrumentation lands.
Bootstrapper plumbing:
-
PROMETHEUS_SOURCE/GRAFANA_SOURCECLI flags +source_mappingentries. -
--prometheus-retention-daysCLI flag (default 7; wizard prompts via the newsecondary_numberrow-schema field). -
_generate_prometheus_config()— cross-manifest scale arithmetic hook that writesPROMETHEUS_SCALE,NODE_EXPORTER_SCALE,CADVISOR_SCALE,POSTGRES_EXPORTER_SCALE, andREDIS_EXPORTER_SCALEfrom a single SOURCE value (matches the_generate_stt_provider_configpattern). -
_generate_grafana_config()— scale + endpoint resolution. -
generate_prometheus_service()/generate_grafana_service()in the Kong route generator. Both routes usepreserve_host: True(Grafana is an SPA that builds redirects from the Host header). - TUI
_TAG_BY_KEYentries forprometheus,node-exporter,cadvisor,grafana,postgres-exporter,redis-exporter. -
services.schema.jsonextended with optionalsecondary_numberon rows.
Audit + tests:
-
check-compose-source-deps.py::REQUIRED_DEPENDS_ONadds(postgres-exporter, supabase-db),(redis-exporter, redis),(grafana, prometheus). -
test_wizard_app_discovery::EXPECTED_DISCOVEREDaddsPrometheusandGrafana; thesource_mappingflag assertion adds the matching CLI keys. -
test_deps_resolver::test_kong_fronted_services_in_upstreamupdated — Kong's downstream is now{prometheus}because Prom scrapes Kong's Status API. -
rendered_config_baseline.ymlregenerated for the new compose shape (~825 lines.env.example; +275 lines baseline). - Per-service docs (READMEs + architecture diagrams) regenerated via
bootstrapper.docs.regen --all.
Source variants external (ComfyUI), ollama-external (Ollama), and
ray-external (Ray) and their associated env vars COMFYUI_EXTERNAL_URL,
LLM_PROVIDER_EXTERNAL_URL, and RAY_EXTERNAL_ADDRESS are removed pending
a stack-wide authenticated-remote design. Each external variant today is
just a URL with no associated authentication design (API keys, bearer
tokens, mTLS); shipping more external slots in new manifests would
compound that gap. A future spec will reintroduce authenticated remote
endpoints across the stack with a coherent auth model.
User-side migration. Users with RAY_SOURCE=ray-external,
COMFYUI_SOURCE=external, or LLM_PROVIDER_SOURCE=ollama-external in
their .env must switch to container (or disabled, or none for
the LLM provider). On bootstrap, start.py now detects these legacy
values, prints a pointer to this entry, and exits with status 2 — no
silent fallback to a different source.
Plumbing impact. Removed: --ray-external-address CLI flag,
COMFYUI_EXTERNAL_URL/LLM_PROVIDER_EXTERNAL_URL/RAY_EXTERNAL_ADDRESS
env vars, the ray-external branch in _generate_ray_config,
external-related code paths in kong_config_generator.generate_comfyui_service,
the RAY_EXTERNAL_ADDRESS_TITLE wizard step, and the external-flavored
test fixtures in tests/conftest.py. services/litellm/catalog-init's
host-side auto-import path now applies only to ollama-localhost.
The "ComfyUI · models" wizard step previously only fired for
container-cpu / container-gpu, contradicting the Ollama-mirror
design that was the goal of PR #17. The fix lets the step show for
every non-disabled source (container-cpu / container-gpu / localhost
/ external) — exactly matching how Ollama's picker shows for any
ollama-* source. For localhost / external, comfyui-init
(the wget container) now scales to 0 so the picker's selection is
DB-only — the user populates their host ComfyUI install's models
directory themselves, same as ollama pull <name> for Ollama
localhost. comfyui-catalog-init still scales to 1 for all
non-disabled sources so public.comfyui_models (the table the
backend /comfyui/db/models endpoint reads, consumed by Open WebUI +
n8n) gets the active set populated regardless of where ComfyUI is
actually running. Six parametrized regression tests pin the new
predicate.
Added a new wizard step ("ComfyUI · models") that lets users pick
from a curated catalog of popular models across Image, Image-edit,
Video, Audio, and 3D categories, sourced live from Hugging Face +
civitai with a bundled fallback for offline. The wizard UI mirrors
the Ollama picker: filter chips (f), name search (/ or Tab),
space-to-toggle, enter-to-confirm, and green [pulled] badges for
models already on disk. Selection persists as COMFYUI_USER_MODELS
(comma-separated catalog names) in .env; CLI flag
--comfyui-models accepts the same CSV. A
--comfyui-custom-models-file flag (default
services/comfyui/custom-models.yaml) allows sidecar YAML additions
that surface with a [Custom] family badge and are ingested into the
DB on the next start.
The init pipeline was rewritten to mirror the Ollama pattern. A new
comfyui-catalog-init container UPSERTs the curated allowlist +
sidecar YAML into public.comfyui_models on every docker compose up
and flips active = true for names in COMFYUI_USER_MODELS /
custom-models.yaml. comfyui-init now queries
SELECT … FROM public.comfyui_models WHERE active = true via psql and
downloads each active model via wget (with optional SHA256
verification), replacing the previous COMFYUI_MODEL_SET-based
bucket-selector + wget-by-set logic. The public.comfyui_models schema
was extended additively (family, target_dir, sha256,
min_vram_gb, cpu_supported, requires_custom_node, popularity,
source — all ADD COLUMN IF NOT EXISTS); backend
/comfyui/db/models routes continue to work. (The notes field on
sidecar entries is a wizard-side display field, surfaced in the wizard
subtitle from custom-models.yaml; not persisted to the DB.) Migration script at
services/supabase/db/scripts/12-extend-comfyui-models.sql.
COMFYUI_MODEL_SET is retired. The bootstrapper's migration v3
auto-translates existing values on first run (minimal/sd15 →
SD 1.5 + VAE; sdxl → SDXL base + VAE; full → all four), takes a
.env.backup.<timestamp> before any rewrite, and bumps
BOOTSTRAPPER_PORT_LAYOUT_VERSION to 3. The four hardcoded ComfyUI
model rows previously in services/supabase/db/scripts/08-seed-data.sql
are removed; those models now arrive via comfyui-catalog-init's
curated allowlist.
Wizard selection persistence. wizard_screen.py's "Apply user
model selections" step now unpacks comfyui_user_models from
stack_options alongside cloud_user_models / ollama_user_models —
before this fix the dict-merge silently dropped wizard-driven ComfyUI
selections on confirm. A new seam-parity test
(test_wizard_screen_consumes_comfyui_user_models) guards against
this regression class.
Known follow-ups:
-
Custom-node auto-install. Required nodes are surfaced as
required node: <node>warning badges only; users install manually. A future ticket will integrate ComfyUI-Manager'scm-clifor one-click install. -
Disk pre-flight hard block. The status header turns yellow/red on
projected fill but does not block confirm. A future ticket will gate
the wizard on
dfchecks.
Architecture note (not a follow-up — this PR is what closed it): Both
pickers now share the same public.{llms,comfyui_models} +
*-catalog-init + DB-backed pull architecture. Custom-model surface
differs (Ollama: CSV in .env; ComfyUI: sidecar YAML) because ComfyUI
lacks an upstream registry that resolves models by name. The earlier
"pipeline divergence" concern is resolved.
A third convergence audit ran the night PR #13 merged. 16 verification iterations dispatched 3 parallel-domain audit subagents on iter-1 + single-agent narrow probes on subsequent iters, surfacing ~32 new findings on top of the ~280 from PR #11 and ~80 from PR #12. The fix pass landed in this PR; the residual deferrals are unchanged from the PR #12 Known-follow-ups block beneath this entry.
Highlights:
-
Correctness:
dependency_managernow exposes both_SCALE_VAR_MAPPINGand_SOURCE_VAR_MAPPINGas class-level constants (read + write paths read from the same source — a latent hermes / openclaw-gateway auto-resolve gap is closed); env-file rewriters insource_override_manager.py+service_config.pyswitched to alambda _m, r=replacement: rform sore.subno longer interprets\1/\g<name>in the replacement (silent corruption if an env value ever contained a literal backslash);update_memory's embedding-update branch stopped opening a redundant second asyncpg connection. Four more init scripts had the PR #12-classset -e+var=\$(psql/grep …)anti-pattern (init-weaviate.sh× 2 sites,ollama-pull/pull.sh,comfyui-init/download_models.sh,local-deep-researcher/docker-entrypoint.sh) — fallback branches unreachable; each gets the canonical|| var=""suffix. -
Brand customization parity:
services/globals/service.ymlnow declaresBRAND_AUTHOR_EMAIL(was consumed at the call site but missing from the manifest) and correctsBRAND_LICENSEfromMITtoApache License 2.0.bootstrapper/ui/state.py::AppStatealigned 4 stale brand-field fallbacks (brand_name/tagline/version/repo_url) with the manifest defaults so a user blanking aBRAND_*value hits the same string the manifest ships. A new drift-gate test (test_appstate_brand_defaults_match_globals_manifest) catches future drift between the two layers at CI time. -
Dead deps + dead code: dropped
dspy>=2.4.6+dspy-ai>=2.4.6+aioredis>=2.0.1fromservices/backend/app/app/requirements.txt(none imported anywhere); cleared 5 unused imports acrossmain.py/research_service.py/deps_section_writer.py/regen.py/research_subagent_prompt.py; updated one stale code-reference comment ingenerate_readme_topology.py(pointed at the retiredgenerate_architecture_diagram.py). -
Documentation hygiene: main
README.md+docs/diagrams/architecture.htmlalt text harmonized so both surfaces describe the SVG identically;docs/CHANGELOG.md[Unreleased]Known-follow-ups preamble bumped from "four classes" to "three classes" (architecture-diagram skill rewrite closed by PR #13); 8 staledocs/scripts/check-…references in descriptive bullets flipped toscripts/check-…;docs/README.mdgained Contributors / Architecture-diagrams / Cross-service-research sub-sections so the docs hub mirrors the project-root README §9 hub;docs/quick-start/interactive-setup-wizard.mdBRAND example block synced to match.env.example(was missingBRAND_AUTHOR_EMAILand diverging from the canonical defaults on 4 other lines);services/tts-provider/provider/localhost/README.mdoverride example port63041(collided with COMFYUI_PORT) replaced with9000. -
Audit-script hygiene:
scripts/check-compose-source-deps.py+scripts/check-docs-drift.pygainedExit codes:paragraphs in their module docstrings, matching the convention already established in the other three scripts..gitignore.audit/rule deduplicated (the three audit-pass PRs each appended their own copy).
The top-level architecture diagram (docs/diagrams/architecture.svg) is now
hand-authored through the architecture-diagram skill — JetBrains Mono on a slate-950
background, category palette of cyan / emerald / violet / amber / rose /
orange / slate, layered topological flow from external clients down
through Kong → Apps → Agents → LLM Core → Media → Data → Ray. The
previous Graphviz pipeline (docs/diagrams/architecture.dot +
bootstrapper/tools/generate_architecture_diagram.py) is retired
alongside the Graphviz prerequisite from the contributor docs.
Per-service diagrams (services/<name>/architecture.{svg,html}) keep
their auto-regenerated workflow via bootstrapper.docs.regen, but their
renderer migrated to the same design system:
bootstrapper/services/topology.py::CATEGORY_COLORS now exposes the
skill palette (#fb7185 rose / #a78bfa violet / #fbbf24 amber /
#fb923c orange / #34d399 emerald / #22d3ee cyan) and a sibling
CATEGORY_FILLS dict carries the matching rgba(..., 0.3–0.4)
semi-transparent fills the skill uses for component boxes.
bootstrapper/docs/diagram_renderer.py now stamps font-family on the
root SVG, paints a #020617 background before the grid, and renders
both pills and the focus box with the two-rect (opaque backdrop +
themed fill) pattern.
All 21 per-service SVG + HTML files were regenerated against the new
renderer; the hermes golden snapshot under
bootstrapper/tests/fixtures/hermes.architecture.svg was refreshed.
This closes the Architecture-diagram skill rewrite item that was
deferred in the 2026-05-27 audit's Known follow-ups block.
A second convergence audit ran the night PR #11 merged. 14 verification iterations dispatched ~14 parallel domain audits and surfaced ~80 genuine findings on top of the ~280 from PR #11. The fix pass landed in this PR; the residual ~16 follow-up findings (smaller-scope ergonomic / refactor / wizard-info items that risked behaviour changes outside this PR's scope) are recorded in the Known follow-ups block beneath this entry.
Highlights of what landed:
-
Correctness: asyncpg JSONB strings now decoded in
research_service.get_research_result/get_research_logs(used to 500 on real data — fixed via the samejson.loads-if-strpattern memory_service already had). The two duplicateexecute_workflowFastAPI handlers inbackend/app/main.pywere shadowing each other at module scope; renamed toexecute_n8n_workflow/execute_comfyui_workflow. Sync Ray-SDK calls in the async/api/ray/*handlers were blocking the event loop; wrapped viaasyncio.to_thread. UUID validation added to the four/research/{session_id}/*endpoints. Init-scriptset -ewas silently aborting on the first failedcurlinollama-pullandn8n-init(the explicit "continue on failure" branches were unreachable); wrapped each curl with|| curl_exit_code=$?. -
Data-flow.calls schema alignment:
services/ollama/service.ymlandservices/neo4j/service.ymlcarried init-time bootstrap edges (supabase + litellm) indata_flow.calls, contradicting the schema's "runtime in the request path; init-time excluded" description. Dropped them.services/open-webui/service.ymlwas understating its surface area; addedcomfyui,stt-provider,tts-provider,doc-processor,local-deep-researcher,weaviateto match whatruntime_deps.optionalalready listed. -
Stale port literals + fallbacks: TTS/STT aggregator READMEs and the deeper provider/* sub-READMEs cited 63022/63023/63026/63027 defaults; the post-port-layout-v1 values are 63042/63044/63046/63045. Compose-fragment
${X_PORT:-NNNNN}fallbacks had drifted in speaches, parakeet, parakeet/mlx api_server, chatterbox, hermes, and openclaw. The JupyterHub welcome README hardcodedlocalhost:63009for Supabase Studio (now 63016). -
Documentation hub: README.md §9 was rebuilt as a four-tier index (First-time users / Operators / Contributors / Release history) so SECURITY.md, CONTRIBUTING-services.md, the deployment/* + quick-start/* guides, and the research integration matrix are discoverable from the main README.
docs/README.mdgained the missing Ray service entry. README §2.1 stopped framing Ray as "always-on" (it defaults toRAY_SOURCE=disabled). -
Naming normalization: 20+ sites across 10 files normalized from
OpenWebUI/Open-WebUIto canonicalOpen WebUI(HTTPUser-Agentvalues left as-is). 5litellm-init/init.pyfile-path comments updated to the post-modularization path; the_LITELLM_INIT_SENTINELstring was left intact for upgrade-detection compatibility. -
Audit-script + CI hardening:
scripts/check-compose-source-deps.pynow falls back to.env.examplewhen.envis absent and exits 2 with stderr surface whendocker compose configfails (previously silently produced wrong-answer output).scripts/check_doc_links.pynow scansservices/<name>/README.mdby default (the primary doc location since the 2026-05-22 retirement ofdocs/services/)..github/workflows/services-lint.ymlpull_request.paths:now includes root-level README.md / SECURITY.md / start.sh / stop.sh / .gitignore. -
CHANGELOG hygiene: Reordered so
[Unreleased]sits above[3.0.0]per Keep-a-Changelog convention. Corrected the "engine READMEs removed" claim (they exist as pointer stubs); the god-class-refactor figures (LOC + method counts) were stale. Pointed retired remediation reports' history-only location explicitly viagit show <SHA>:docs/security/<file>commands in SECURITY.md.
The cleanup PR documented at the top of this section deliberately defers three classes of work — each large enough to deserve its own plan rather than a drive-by fix:
-
Backend test coverage.
services/backend/app/app/has ~3,700 LOC of production Python acrossmain.py(33 FastAPI endpoints),memory_service.py,research_service.py,comfyui_client.py,n8n_client.py,memory_store.py,research_client.py. Only theray_routes/ray_clientsurfaces have tests. Smoke-level TestClient suites for the memory / research / comfyui / workflow endpoint families are tracked for a follow-up. -
Bootstrapper utility test gaps.
bootstrapper/utils/{localhost_validator,key_generator,llm_catalog,cloud_models,supabase_keys}.py,bootstrapper/core/docker_manager.py, andbootstrapper/services/source_validator.pyhave zero unit tests. The drift gates + integration tests cover them transitively, but no isolated unit coverage exists. Adding targeted tests is tracked separately. -
Bootstrapper god-class refactors.
bootstrapper/start.py::GenAIStackStarter(~1,800 LOC, 31 methods), the 14 near-identical_generate_<svc>_configmethods inbootstrapper/services/service_config.py, and the 10generate_<svc>_servicemethods inbootstrapper/utils/kong_config_generator.pyare flagged for table-driven consolidation in a separate refactor plan. The current code paths are all tested and correct; these are maintenance-debt items, not bugs.
- New
services/ray/family with head + worker containers, dashboard atray.localhost, RAY_SOURCE source-variant pattern. - Wizard wires Ray worker count inline via the SecondaryNumberInput widget on the source step.
- Backend
/api/ray/*endpoints (submit/status/stop/cluster-status) gated on RAY_ADDRESS — return 503 when Ray is disabled. - JupyterHub picks up
ray[client]dep + seeded07_ray_cluster.ipynbnotebook. - Hermes Agent + Backend agents can dispatch compute jobs to the cluster (future integration; Ray exposes only via Backend REST today).
- Replaced the 7 per-service
<SVC>_LOCALHOST_URLenv vars with<SVC>_LOCALHOST_PORTinteger vars; the URL is derived at compose-render time ashttp://host.docker.internal:${<SVC>_LOCALHOST_PORT:-<default>}. - 3 newly-overridable services (Ollama, Neo4j HTTP + Bolt, Weaviate) gain dedicated LOCALHOST_PORT env vars.
- Wizard adds an inline integer textbox per localhost source row using the SecondaryNumberInput widget; the override propagates symmetrically through
.env, runtime_sc, Kong routes, and the wizard's service-table. - New migration
bootstrapper/services/migrations/migration_v2.pyrewrites users' existing.envfiles (gated byBOOTSTRAPPER_PORT_LAYOUT_VERSION1→2). - Pre-launch summary surfaces port collisions as warnings (warn-don't-block).
Path-reference note: entries written before the per-service configuration-modularization change below reference top-level directory names (
hermes-init/,litellm-init/,llm-catalog-init/,comfyui-init/,n8n-init/,weaviate-init/,minio-init/,ollama-pull/,open-webui-init/,searxng/,stt-provider/,tts-provider/,doc-processor/,graph-db/,backend/,local-deep-researcher/,jupyterhub/,n8n/,open-webui/,supabase/). After this refactor they live under their owning service's manifest folder, e.g.services/litellm/init/scripts/init.py,services/n8n/init/,services/supabase/db/. The original wording is preserved to keep the historical record honest; usegit log --followon the new path to trace the move.Naming: the doc-processing surface uses three names that all refer to the same thing — the retired top-level dir was
doc-processor/, the post-refactor folder isservices/docling/, the env-var selector isDOC_PROCESSOR_SOURCE(chooses betweendocling-container-gpu/docling-localhost), and the human-facing virtual-service docs live atservices/doc-processor/README.md. Thedoc-processorname is the stable public API;doclingis the single engine implementing it.
2.76. Changed (Documentation consolidation — service docs live with their services, hierarchical numbering, Phase C Future content)
-
Service docs moved alongside their services. Every per-service README, architecture SVG, and architecture HTML moved from
docs/services/<name>/toservices/<name>/. Thedocs/services/directory is retired entirely. Each service folder is now the single source of truth for that service: manifest (service.yml), compose fragment (compose.yml), anyinit/scaffolding, and the human-facingREADME.md+ diagrams sit in one place. -
Three doc-only folders introduced for the aggregate doc-folders without a single-manifest owner:
services/stt-provider/,services/doc-processor/,services/multi2vec-clip/. The manifest loader skips dirs withoutservice.yml(_is_service_dirnow requires the file), so these doc-only folders are invisible to the bootstrapper. -
Constituent engine READMEs reduced to pointer stubs.
services/parakeet/README.md,services/speaches/README.md,services/chatterbox/README.md,services/docling/README.mdpreviously duplicated the user-facing description from their aggregator (STT-provider / TTS-provider / doc-processor). They now each contain a single "Engine quick reference" section + a pointer link to the aggregator + the auto-regenerated Dependencies & Integrations block — kept around because each owns aservice.ymland is in scope of the drift gate, but no longer authoritative for user-facing prose. -
Hierarchical section numbering. Every service README uses
## N. <Title>for top-level sections and### N.M <Title>for subsections. The## Dependencies & Integrationssection keeps its position-driven numbering — the regen tool detects whatever number the section sits at (5 in the canonical 6-section layout, but READMEs with more pre-deps content can have it at 7, 9, 14, etc.) and emits matching### N.1…### N.6subsections. -
bootstrapper/docs/regen.pylearned to preserve Phase C Future content. The auto-block (## N. Dependencies & Integrations+### N.1Current Upstream +### N.2Current Downstream +### N.3Architecture diagram +### N.4-6Future placeholders) is regenerated from manifests on every run, BUT any user-authored content under### N.4 Future — Missing pair integrations,### N.5 Future — Candidate new services, and### N.6 Future — Unused features in this serviceis preserved across regen passes. New helper_render_section_with_futureextracts the existing Future bodies before re-rendering and splices them back in. -
Phase C content populated in all 21 service READMEs. Each of the three Future-* subsections in every service doc now lists concrete bullets (pair integrations to wire, candidate new services to add, unused upstream features to pursue) sourced from the Phase B research artifacts under
docs/research/rows/<svc>.mdanddocs/research/candidates/<slug>.md. The seven previously-thin docs (backend, comfyui, local-deep-researcher, multi2vec-clip, n8n, redis, searxng) were rewritten to Hermes-grade depth (≥150 lines each, all canonical sections present). -
docs/aggressively trimmed. Removed entirely:docs/services/(moved intoservices/),docs/superpowers/(planning-history artefacts whose value lives in git log — note: later reinstated as the active feature-track spec/plan home, see docs/superpowers/README.md),docs/security/(completed Dependabot remediation reports — paper trail preserved in git history). Moved:docs/scripts/*.py→scripts/(these are operational scripts, not docs). What remains underdocs/: CHANGELOG, ROADMAP, CONTRIBUTING-services, top-level README, the canonical deployment/ and quick-start/ subdirs, the stack-wide diagrams/, images/, and the Phase B research/ corpus referenced from every service doc. -
Path rewrites. All cross-doc references to
docs/services/<X>.mdordocs/services/<X>/README.mdrepointed toservices/<X>/README.md. The_AGGREGATE_DOC_FOLDERSmapping inbootstrapper/docs/deps_resolver.pyis unchanged — it's still the source of truth for doc-folder ↔ manifest aggregation. Service manifests'docs:fields updated to point at the newservices/<X>/README.mdlocation. Constituent engine manifests (parakeet, speaches, chatterbox, docling) point to their aggregate doc folder. -
Migration tooling retired.
scripts/migrate_docs_to_folders.py(the one-shotdocs/services/<X>.md→docs/services/<X>/README.mdmigration helper from a previous restructure) and its testbootstrapper/tests/test_doc_migration.pyare removed — both were one-shot artefacts of completed migrations.
- Architecture diagrams under
services/<name>/now render the data-flow model (runtime "X calls Y" edges) instead of the bootstrap-dep model. Source of truth is a new optionaldata_flow.callsfield perservices/<name>/service.yml. - Diagram layout redesigned: services in the upstream and downstream lanes group by category (infra / data / llm / media / agents / apps) into mini-clusters; one edge per cluster (not per pill); focus box gains a category-colored glow; legend bar + 3 summary cards below.
- Deps-section tables in each README simplified to
Service | Category(the old Type / Mechanism / Failure mode columns no longer have data in the data-flow model). -
depends_on.required,runtime_adaptive.adapts_to,runtime_deps.optional, anddoc_extras.diagram.extra_consumersremain in manifests (still used by the compose layer) but the diagram resolver no longer reads them. - Spec: diagram-refresh design (2026-05-22) —
docs/superpowers/was retired; see git log for the design doc and the commits around that date.
- Added 21 per-service integration-research files under
docs/research/rows/<service>.md(missing-pair integrations, candidate new services, per-service feature gaps). - Added 32 candidate one-pagers under
docs/research/candidates/<slug>.md. - Added generated master index at
docs/research/integration-matrix.md(re-build withpython -m bootstrapper.docs.merge_research). - New tooling:
scripts/validate_research_schema.py(schema validator),bootstrapper/docs/merge_research.py(merge + index generator),bootstrapper/docs/research_subagent_prompt.py(programmatic Phase B subagent prompt builder). - Phase C (content authoring) is next — see the cross-service deps + diagrams design (2026-05-16);
docs/superpowers/was retired, consult git log for the doc.
- Migrated
services/<name>.md→services/<name>/README.md(per-service folders). - Added standardized Dependencies & Integrations subsection to every service README, with Current (manifest-derived) tables and Future (placeholder) subsections.
- Added per-service architecture diagrams (
architecture.html+architecture.svg) under each service folder, generated from manifests viapython -m bootstrapper.docs.regen. - Added CI drift gate (
bootstrapper/tests/test_docs_drift.py) that fails when committed deps sections or diagrams diverge from manifest state. - Added internal-link validator (
scripts/check_doc_links.py) covering README, CHANGELOG, and the wholedocs/tree. - New optional manifest fields:
runtime_adaptive.<container>.failure_mode(string) anddoc_extras.diagram.extra_consumers(list of service names). - Cross-service deps + diagrams research/authoring (Phases B & C) deferred — see the cross-service deps + diagrams design (2026-05-16);
docs/superpowers/was retired, consult git log for the doc.
-
.github/dependabot.yml— weekly pip + GitHub Actions scans on every active manifest (bootstrapper/,services/backend/app/,services/jupyterhub/build/,services/docling/provider/{gpu,localhost}/,services/parakeet/provider/{gpu,mlx}/). Alerts grouped by ecosystem to reduce PR noise.directories:deliberately enumerates ALL active manifests so an omission doesn't silently drop coverage from the scan. -
SECURITY.mdthreat model — published threat tiers, supported versions, and the responsible-disclosure address. Aligns with the dependabot scan-coverage list. -
Bulk-dismiss tooling — operators triaging stale alerts on deleted/moved manifests can use the GitHub REST API with
reason=not_used; thedocs/security/2026-05-14-dependabot-remediation-report.mdcaptures the playbook from the May 2026 cleanup (77 alerts triaged, 62 phantom dismissals).
-
Kong route
litellm.localhost→http://litellm:4000/— added tobootstrapper/utils/kong_config_generator.py::generate_litellm_service()and wired intoget_adaptive_services(). Always-on (LiteLLM is mandatory; no SOURCE variation, no dashboard-disable toggle). The same alias exposes/ui/(admin dashboard with per-model spend, key/team management, request logs),/v1/*(proxy API), and/spend/*(raw usage telemetry rollups) — Kong routes the entire LiteLLM surface, not just the dashboard path. -
litellm.localhostadded tobootstrapper/utils/hosts_manager.py::GENAI_HOSTSso./start.sh --setup-hostswrites the/etc/hostsentry. -
Wizard service box now shows
http://litellm.localhost:${KONG_HTTP_PORT}in the URL column on the LiteLLM row (was—before). Wired via a single"LiteLLM": "litellm.localhost"line inbootstrapper/ui/state_builder.py::_HOST_ALIAS; downstream rendering (integration.py→service_table.py) picks it up automatically. -
Auto-redirect
/→/ui/on the LiteLLM alias — LiteLLM serves Swagger UI at its root and the admin dashboard at/ui/. A bare visit tohttp://litellm.localhost:${KONG_HTTP_PORT}/would otherwise land on Swagger, which is not what operators reaching for the alias expect. Apre-functionLua snippet on the Kong route short-circuits the request with a 302 to/ui/only when the path is exactly/;/v1/*,/spend/*, and/openapi.jsonfall through to the upstream unchanged. Requirespre-functioninKONG_PLUGINS(already allowlisted inservices/kong/compose.yml). Operators who want Swagger UI directly can still reach it at the direct porthttp://localhost:${LITELLM_PORT}/. -
preserve_host: Trueon the LiteLLM Kong route — without this, Kong rewrites theHostheader fromlitellm.localhost:${KONG_HTTP_PORT}(the browser's URL) tolitellm:4000(the internal upstream). LiteLLM's SPA reads theHostheader when constructing the SSO login-redirect URL, so it embedded the internal Docker hostname, producing aLocation: http://litellm:4000/ui/login/...that the browser cannot resolve. Settingpreserve_host: Truemakes LiteLLM see the real browser-facing hostname and build correct redirects. Same pattern n8n's route uses. -
Admin-dashboard login credentials made explicit — modern LiteLLM versions retired the "master key alone authenticates the UI" fallback; without explicit
UI_USERNAME+UI_PASSWORD,/v2/loginraisesProxyException. Compose now setsUI_USERNAME: ${LITELLM_UI_USERNAME:-admin}andUI_PASSWORD: ${LITELLM_MASTER_KEY}(reusing the auto-generated master key so operators don't have to remember a second secret). New envLITELLM_UI_USERNAMEadded to.env.exampleandservices/litellm/service.yml. Login isadmin/${LITELLM_MASTER_KEY}by default; override the username via.env.
-
Kong route
minio.localhost→http://minio:9001/— added tobootstrapper/utils/kong_config_generator.py::generate_minio_service()and wired into the route orchestrator alongside the other host-aliased services. Gated onMINIO_SOURCE != disabled. Usespreserve_host: Trueso the MinIO console SPA constructs login/session URLs against the browser's real hostname instead of the internalminio:9001(same pattern n8n / Hermes / LiteLLM use). The S3 API at port 9000 is deliberately NOT aliased — S3 clients use full URLs with explicit ports anyway, and Kong proxying introduces unhelpful preserve-host complications for the S3-signature workflow. -
minio.localhostadded tobootstrapper/utils/hosts_manager.py::GENAI_HOSTS(so./start.sh --setup-hostswrites the/etc/hostsentry) andbootstrapper/ui/state_builder.py::_HOST_ALIAS(so the wizard service-box showshttp://minio.localhost:${KONG_HTTP_PORT}on the MinIO row alongside the direct port). The cross-surface agreement test intest_kong_and_hosts_wiring.pyenforces the parity automatically. -
scripts/check-kong-routes.py::EXPECTED_HOST_ROUTESgained the new entry so the audit script enforces the route's continued presence. -
docs:
services/minio/README.mdgot an expanded "Endpoints" table covering the new alias + the preserve-host plumbing rationale;docs/deployment/ports-and-routes.mdgained the Kong column on the MinIO Console row;services/kong/README.mdadded the dynamic-route bullet + curl example;services/minio/README.mdgot a new## Accesssection; rootREADME.mdgot the alias row in the service table and a quick-start hint.
-
4 new test files / 19 regression tests that codify the recent Ollama-discovery bugs as a permanent guard:
-
bootstrapper/tests/test_wizard_ollama_options.py(5 tests) — exercises the wizard's_merged_ollama_optionsclosure with mocked/api/tags+ library scrape. Asserts: (a) every host-pulled tag lands in the family'spulled_variants; (b) family parent's[pulled]badge fires when ANY tag is on host (the "bare family name in pulled_set" bug); (c) bucket-1 fallback for tags whose family isn't inollama.com/libraryat all; (d) options carry enough info for pre-check seeding. -
bootstrapper/tests/test_prompt_panel_leaf_badges.py(4 tests) — exercisesPromptPanel._leaf_render_dataon a stub. Asserts: per-leaf[pulled]/[library]reflectsopt.pulled_variantsindependently of the parent's status; mixed-status leaves within one family render correctly; emptypulled_variantsleaves the leaf status-less (fallback to parent). -
bootstrapper/tests/test_live_catalog_sync.py(3 tests, skip-aware) — integration: queries the live host Ollama/api/tagsand the live LiteLLM/v1/models, asserts every host model is published by LiteLLM (auto-import fix), no phantom Ollama models in LiteLLM that aren't on the host or declared inOLLAMA_USER_MODELS/OLLAMA_CUSTOM_MODELS, AND runs the wizard's actualoptions_provideragainst the live host to confirmpulled_variantsmatches/api/tagsreality. Skips cleanly when the stack isn't up. -
bootstrapper/tests/test_catalog_init_auto_import.py(7 tests) — unit tests forservices/litellm/catalog-init/scripts/sync-catalog.py::_fetch_ollama_tags. Loads the script viaimportlib.util.spec_from_file_locationwithpsycopg2stubbed insys.modulesso the test runs without the catalog-init container's deps. Covers: happy path, alternatemodelfield name, empty upstream, unreachable upstream, malformed JSON, empty URL short-circuit, garbage-entry tolerance.
-
- Tests are wired into the existing pytest infrastructure; total suite count grew from 126 to 145 (all passing).
-
Per-variant
[pulled]badge under a [library] parent — the wizard's Ollama-models step computed leaf badges via_inherited_leaf_badges, which strips status tags (pulled,library,legacy) under the assumption "every leaf of a [library] parent is library; the user already sees that on the parent right above". That assumption fails when only some specific tags of a family are pulled — e.g. a host withqwen3.6:35b-a3b-coding-mxfp8pulled but notqwen3.6:27b/35b/etc. The family's parent gets[library]but the one pulled tag should render[pulled]to match reality. Fix: addedpulled_variants: frozenset[str]toPromptOption(populated by the wizard's_merged_ollama_optionsfrom/api/tags), and made_leaf_render_dataemit per-leaf status ([pulled]whentag in opt.pulled_variants, else[library]). Family parents now show[pulled]whenever ANY tag of that family is on the host (was: only when the bare family name itself appeared in/api/tags, which it never does). -
Wizard auto-pre-checks every pulled host model —
PromptPanel._load_stepfor multiselect now seeds_checked_valuesfrom each option'spulled_variantsin addition to the staticdefault_values. Mirrors the runtimeOLLAMA_AUTO_IMPORT_LOCAL_MODELSbehaviour so the wizard UI tells the same story aspublic.llmswill after confirmation. The post-confirm CSV is still the final word — operators who want a model hidden can uncheck it before pressing Enter.
-
llm-catalog-initauto-imports every model on the host's Ollama whenLLM_PROVIDER_SOURCE=ollama-localhostorollama-external. The catalog-init container queries the upstream's/api/tagsat boot and unions the result withOLLAMA_USER_MODELS, so anyollama pull <name>you do on the host propagates topublic.llms→ LiteLLM → every consumer on the next./start.sh— no wizard re-run required. This makes the host's Ollama instance the authoritative source for which models the stack exposes, instead of relying on the wizard's multiselect to be re-run every time the host catalog changes. Container sources (ollama-container-*) skip auto-import because their upstream is populated FROMOLLAMA_USER_MODELSbyollama-pull(querying it would be circular). -
OLLAMA_AUTO_IMPORT_LOCAL_MODELSenv var (default:true) added toservices/ollama/service.yml+.env.example. Set tofalseto keep strict wizard-only control of which models are exposed — useful when you have private fine-tunes on the host that shouldn't be exposed across every stack consumer. -
llm-catalog-initnow reacheshost.docker.internal— addedextra_hosts: ["host.docker.internal:${HOST_GATEWAY_IP}"]to the catalog-init container so the new/api/tagsquery works forollama-localhost. The container also receivesLITELLM_OLLAMA_UPSTREAM(same env-var litellm-init consumes for its rendering), soollama-externalis supported through the same code path. -
_fetch_ollama_tags()helper inservices/litellm/catalog-init/scripts/sync-catalog.pymirrorsbootstrapper/utils/ollama_discovery.py::list_pulled_modelsin shape and failure mode (empty list on any error), so the two sites — the wizard's option list and the catalog's auto-import — fail consistently against the same/api/tagsendpoint.
-
Kong DNS error noise during stack restart — Kong's default
KONG_DNS_NOT_FOUND_TTL=30smade it cache "name not found" verdicts for half a minute, so an active websocket retry loop (e.g. an Open WebUI tab reconnecting during./start.sh) flooded the Kong log with DNS errors until the cache expired.services/kong/compose.ymlnow setsKONG_DNS_NOT_FOUND_TTL=1andKONG_DNS_STALE_TTL=4, so Kong picks up newly-registered service containers within ~1s instead of ~30s. Error window dropped from 37 seconds / ~18 entries to single-digit retries. -
Searxng
missing config file: /etc/searxng/limiter.toml— Searxng's bot-detection module wants an explicitlimiter.tomlnext tosettings.yml. Without it, Searxng logs the warning on every boot. Addedservices/searxng/config/limiter.tomlusing the current upstream schema ([botdetection]/botdetection.trusted_proxies, not the deprecated[real_ip]form). Trusted proxies set to Docker bridge subnets only (172.16/12, 192.168/16, 10.0/8); deliberately NOT including 127.0.0.0/8 so Searxng's own loopback healthcheck doesn't trip the X-Forwarded-For warning every check. -
Searxng Wikidata 403 spam at boot — Wikidata's SPARQL endpoint rate-limits aggressively and returns 403 with 24-hour suspension on initial engine probe from a new IP. The Searxng wikidata engine eagerly probes the endpoint at
init(), which fired before anydisabled: trueflag was honored (the disable check is for query-time, not init-time). Removed the engine block entirely fromservices/searxng/config/settings.yml. DuckDuckGo's infobox covers the same UX role. Block-removal commentary inline so operators can restore the engine if they have a dedicated Wikidata arrangement. -
Searxng X-Forwarded-For "fires once per boot" log line — this is not fixable from outside Searxng. The
log_error_only_once()call in/usr/local/searxng/searx/botdetection/trusted_proxies.py:141is gated to fire exactly once per worker lifetime on the first request that lacks bothX-Forwarded-ForandX-Real-IP. Searxng's own internal startup probe (granian's warm-up) hits the worker with no headers before the user's first browser request arrives, so the error always fires once at boot regardless of upstream proxy config. The logger then silences itself for the rest of the container's lifetime. Documented as expected boot-noise; no functional impact. -
scripts/check-kong-routes.py::EXPECTED_HOST_ROUTESgained the new entry so the audit script enforces the route's continued presence. -
docs:
services/litellm/README.mdgot a new## Accesstable;docs/deployment/ports-and-routes.mdgained the Kong column on the LiteLLM row;services/kong/README.mdadded the dynamic-route bullet + curl example;services/litellm/README.mdgot a matching## Accesstable for the service-folder reader; rootREADME.mdgot the alias row in the service table.
-
Ollama chat completions returned empty
content— every Ollama model was registered in LiteLLM'smodel_listasmodel: ollama/<name>, which makes LiteLLM hit Ollama's/api/generateendpoint. That endpoint (a) does not support tool calls, (b) flattens multi-turn message history into a single prompt, and (c) silently drops the Ollama-nativethinkparameter. So any thinking-capable model (qwen3, gpt-oss, deepseek-r1) got cut off mid-<think>block and returned emptycontent. Hermes Agent, Open WebUI's chat surface, n8n's LLM nodes, and the backend's agentic paths were all affected. Fix:services/litellm/init/scripts/init.py::render_model_listnow writesmodel: ollama_chat/<name>for chat models (uses/api/chat, which supports tool calls, multi-turn, vision payloads, and thethinkparam) and keepsmodel: ollama/<name>only for embedding models (the/v1/embeddingsroute refuses theollama_chat/adapter). Detection is name-based: any catalog model with"embed"in its name is an embedding model. Additionally,think: falseis set on every chat entry so thinking models always populatecontentrather than the side-channelreasoningfield; consumers that want the trace can opt back in per-request with"think": true. Seeservices/litellm/README.md→ "Ollama adapter choice" and "Thinking models". -
Hermes Agent registered LiteLLM twice in its provider picker —
services/hermes/init/templates/config.yaml.tmpldeclared the gateway via bothmodel.provider: custom+base_url: http://litellm:4000/v1AND a namedcustom_providers[] = {name: litellm, base_url: http://litellm:4000/v1}entry. Hermes'sget_compatible_custom_providers()dedupe path did not collapse the inline anonymous entry against the named one, so the provider picker showed twolitellmrows — one with the default model bound, the second orphaned at "0 models". Fix: kept the inlinemodel.provider: customblock (Hermes's documented enum isauto | openrouter | nous | codex | custom— there's nolitellmenum value) and emptiedcustom_providers. Future skills that need to address LiteLLM by an explicit named alias can add it back under a non-colliding name (e.g.litellm-aux).
-
Monolithic
docker-compose.ymlretired — the 1,425-line file split into per-service fragments underservices/<name>/compose.ymlmerged at the top level via native Docker Composeinclude:directive. The new rootdocker-compose.ymlis a 55-line shell. Requires Compose v2.20+ (v2.26+ recommended). Byte-equivalent rendering preserved across the full 36-container stack via the golden baseline atbootstrapper/tests/fixtures/rendered_config_baseline.yml. -
bootstrapper/service-configs.ymldeleted — each service's runtime data (source variants, adaptive bindings, dependency declarations) now lives in its manifest atservices/<name>/service.ymlunderruntime_sc:,runtime_adaptive:,runtime_deps:blocks; the stack-wide tier ordering moved toservices/globals/service.ymlunderruntime_dependency_tiers:. A newbootstrapper/services/sc_synthesizer.pyconcatenates these slices into the dict shape consumers (service_config.py,source_validator.py,dependency_manager.py,ui/state_builder.py,wizard/llm_steps.py) used to load from YAML.ConfigParser.load_yaml_config()now calls the synthesizer. -
Each service is now a folder (
services/<name>/) containingservice.yml(manifest — env vars, source variants, image refs, dependencies, plus per-source bootstrapper runtime data underruntime_sc:) andcompose.yml(Compose fragment). 24 manifests total — 21 container-backed + 3 virtual (cloud-providers, tts-provider, globals). Schema-validated againstbootstrapper/schemas/service.schema.json. -
docs/CONTRIBUTING-services.mddocuments how to add a new service.
-
bootstrapper/services/manifest_validator.py— 8 cross-manifest checks (duplicate env vars, duplicate containers, dangling dependencies, undeclared exports/effects, source-var consistency, unknown consumer references). Runs in CI. -
bootstrapper/services/env_assembler.py— pure-function .env.example assembler from manifests (library-only). -
bootstrapper/tools/validate_fragments.py—python -m tools.validate_fragmentsCLI entry. -
bootstrapper/tests/— 110+ tests: loader, cross-manifest validator, env assembler, validate_fragments CLI, fragment-equivalence (byte-equiv vs golden baseline), source-permutation matrix, env-example consistency (manifest ↔ .env.example parity), backfill interplay (manifest change → backfill → user .env propagation). -
.github/workflows/services-lint.yml— three CI jobs:lint(manifest validator + unit tests),compose-equivalence(rendered byte-equiv + source-permutation matrix), andaudit-scripts(docs drift + doc-links + compose-source-deps + Kong routes + research-schema). -
scripts/check-compose-source-deps.pyupdated to render compose viadocker compose configso it sees the merged shape rather than only the thin include shell.
2.90. Added (Hermes Agent — auto-pick default model, embedded Chat tab, dual Ollama aliases in LiteLLM)
-
hermes-initauto-picksHERMES_DEFAULT_MODELwhen blank — without a default, Hermes's renderedconfig.yamlhadmodel.default: null, every dispatch 500'd, and Open WebUI'shermes-agentproxy route returned errors that looked like "Hermes can't see any models". The init script now querieshttp://litellm:4000/v1/modelsand picks the first match from a priority list (ollama/qwen3.6:latest→claude-sonnet-4-6→claude-opus-4-7→gpt-5→gpt-5-codex→gpt-5-mini→ first-non-hermes-agentfallback). Cheapest-local-first, then big-context-cloud. Choice is logged in the init log for traceability. Operator override viaHERMES_DEFAULT_MODELin.envis preserved verbatim (auto-pick only fires when blank). -
Ollama models now registered under bare model_name in LiteLLM (in addition to the prefixed form) —
litellm-init/scripts/init.py:render_model_listwas emittingmodel_name: ollama/{name}for Ollama rows while cloud providers used bare names (gpt-5, notopenai/gpt-5). When a client like Hermes Agent strips theollama/prefix on outbound (treating it as a provider hint) and forwardsqwen3.6:latestto LiteLLM, the gateway 400'd withInvalid model name. Each Ollama row now emits twomodel_listentries pointing at the same upstream —ollama/{name}(kept for backwards compat:backend'sLITELLM_EMBEDDING_MODEL=ollama/nomic-embed-text,weaviate-init's/shared/weaviate-config.env) plus bare{name}(for prefix-stripping clients). Both names route through the samelitellm_params, so latency/spend tracking stays single-counted. -
Embedded Chat tab in the Hermes dashboard — set
HERMES_DASHBOARD_TUI=1(now the default) to expose the upstream-supported/chatroute +/ws/chatWebSocket inside the dashboard, with a PTY-backedhermes --tuisession as the backing terminal. Users can talk to the agent directly from the web UI without round-tripping through Open WebUI / curl. Documented in upstream docs; theptyprocessextra ships innousresearch/hermes-agent:latest. Flip to0in.envfor a read-only dashboard.
-
Inline search box above the filter-chip row on the Ollama models step: a 1-cell
Input(placeholderTab or / to filter models by name…) that narrows the visible list by case-insensitive substring match against the model name.Tab, mouse click, and/all focus it;Tab,Enter, orEscreturn focus to the option list. The substring filter STACKS with the chip filter — both must match for a row to render. Lives as a persistentInputonPromptPanel(mounted once, display toggled) to dodge aDuplicateIdsrace on the splash → real-options re-render that the lazy-fetch flow triggers foroptions_providersteps. Focus is explicitly parked on the option list immediately after mount so a freshly-mounted Input never swallows a Space/j/kthe user thought was driving the cursor; the input lights up in bold cyan-on-tinted-bg when it actually has focus. -
Keystroke routing while search is focused —
WizardScreen.check_actionwhitelistsback(Esc),quit_wizard(Ctrl+Q),move(arrow keys), andtoggle_search_focus(Tab); every other priority-binding (f,a,e,w,i,space, vim-stylej/k) is suppressed so the keystrokes land in the Input as text. Thej/kbindings were split off into a newvim_moveaction specifically so they can be suppressed independently of the arrow-keymoveaction. Enter on the focused search input unfocuses (viaPromptPanel.on_input_submitted) instead of confirming the whole step. -
[mlx]capability badge — Apple-Silicon-optimised MLX variants are now flagged per-leaf in the variant tree. New parser regex_VARIANT_MLX_REinbootstrapper/utils/ollama_library.pydetects the upstreamborder-neutral-600 … MLXchip on each variant block ofollama.com/library/{model};OllamaVariant.mlx: boolplus the existingcapabilitiesproperty surface the tag.mlxis added to_PER_VARIANT_CAPSinprompt_panel.pyso it stays per-variant (NOT inherited from parent to all leaves) since only specific quant tags carry it. -
Capability column alignment — capability tags now render in a fixed canonical column order (
embedding · thinking · vision · tools · audio · mlx) with reserved per-slot widths; absent tags reserve their column so the same capability lands at the same horizontal position across every visible row. Status tags (pulled/library/legacy/default) follow with variable width. Start column is computed dynamically byPromptPanel._mount_visible_rows— it walks the visible row set, finds the longest prefix+label content, and passes that column to everyOptionRowso even outlier-length variants likeqwen3.6:35b-a3b-coding-mxfp8 (38GB · 256K ctx)keep the tag block flush with shorter siblings. Narrow terminals (< 100 cells for parents, < 130 for leaves) fall back to inline variable-width tags to avoid pushing the pull-count column off-screen. -
Ollama Cloud-exclusive models filtered out — the live listing-page scrape now flags entries that carry the
cloudchip AND publish nox-test-sizevariants (e.g.glm-5,minimax-m2,kimi-k2,deepseek-v4-pro, …). These cannot beollama pull-ed, so the wizard drops them from the multiselect before render and writes[info/ollama-fetch] excluded N cloud-only Ollama Cloud model(s) — not pullable: …to the session log. Hybrid models that publish both cloud and pullable local variants (gemma3,gpt-oss,qwen3-coder,deepseek-v3.1, …) keepcloud_only=Falseand remain in the list with their local variants intact. New fieldOllamaLibraryEntry.cloud_only: bool; new regex_CLOUD_BADGE_RE; filter applied inbootstrapper/wizard/llm_steps.py:_fetch_ollama_options.
-
backfill_missing_env_vars()onGenAIStackStarter— appends keys present in.env.examplebut missing from the user's.env, preserving every existing value. Catches the upstream-merge case where new services land in.env.example(MinIO, Hermes, Speaches, Chatterbox, Whisper.cpp) but the user's pre-existing.envpredates the merge; without backfill,docker compose upfailed withservice "minio" has neither an image nor a build context specifiedbecause${MINIO_IMAGE}was empty. Preserves the source file's section organisation — missing vars are emitted under their original# === SECTION ===heading with their immediate context comments intact, ordered by where they appear in.env.example. Idempotent; called four times (once at every entry to thesetup_env_filepipeline + a final defensive call beforedocker compose upso any intermediate write that drops keys is recovered).
-
speachesrestart loop — thePRELOAD_MODELSenv indocker-compose.ymlwas a comma-separated CSV (hexgrad/Kokoro-82M,Systran/faster-distil-whisper-large-v3), but Speaches types the field aslist[str]on apydantic_settings.BaseSettingsmodel whoseEnvSettingsSourcedecodes complex fields viajson.loads. The CSV blew up withJSONDecodeError: Expecting value. Switched to a JSON-array literal'[]'(empty) — the names Speaches expects inPRELOAD_MODELSare internalexecutor_registryids (e.g.kokoro), not the HuggingFace ids we keep inSPEACHES_TTS_MODEL/SPEACHES_STT_MODEL(those go on the request, not the preload). Empty preload matches the existing "lazy-loads on first /v1/audio/* request" comment; users wanting preload can edit the line directly with registry ids. -
hermeshealthcheck failing — the historicwget -q -O- http://127.0.0.1:8642/v1/models …probe exitedwget: not found, and the obvious python fallback hitpython: not found(the image only shipspython3). Verified the image actually does shipcurl(the previous compose comment was wrong on both counts). Switched tocurl --fail --silent --show-error -H "Authorization: Bearer $$API_SERVER_KEY" http://127.0.0.1:8642/v1/models. Container now(healthy). -
hermes-initwas a no-op — the compose block mounted./hermes-init/scripts:/scripts:ro+./hermes-init/templates:/templates:roagainst analpine:latestimage but had noentrypointorcommand— the container started, found nothing to run, exited 0 in ~150ms, anddocker compose psreportedExited (0)looking exactly like a successful init./opt/data/config.yamlwas never rendered;hermesthen fell back to its image's bundled default (provider: openrouter, default: anthropic/claude-opus-4.7) and 401-spammed the log indefinitely becauseOPENROUTER_API_KEYwas empty. Addedentrypoint: ["/scripts/init-hermes.sh"]matching the existingweaviate-init/openclaw-initpattern. The script now actually runs,/opt/data/config.yamlrenders against the LiteLLM-routed template, no more 401s. -
local-deep-researcherflapping unhealthy on first launch — the Dockerfile'sHEALTHCHECK --start-period=60sexpired while the entrypoint was still cloning the upstream repo anduv pip install-ing 72 packages (numpy, lxml, langchain, langgraph, openai, …) — routinely 2-5 minutes on a clean machine. Bumped to--start-period=300s; subsequent restarts hit the cached venv and pass in <10s, so the higher ceiling costs nothing in steady state. -
supabase-realtimelibcluster spam every 5 seconds — the upstreamsupabase/realtime:v2.33.72image hardcodesCluster.Strategy.DNSPoll(thefly6pntopology) and ignored ourLIBCLUSTER_STRATEGY/LIBCLUSTER_TOPOLOGIESoverrides. With noDNS_NODESenv set, libcluster loggedquery or basename param is invalid: query: nilon a 5-second cadence. PointingDNS_NODESat the container's own hostname created a different warning (unable to connect to :realtime@<container-ip>— Erlang node mismatch). Final fix:DNS_NODES: supabase-realtime-noop.invalid— the.invalidTLD (RFC 6761) returns NXDOMAIN, libcluster's empty-peer-list path is silent, the env var is set so libcluster considers itself "configured". Dropped the two ineffectiveLIBCLUSTER_*vars. -
n8n/n8n-workermigration race — both containers were depending onsupabase-db-init: service_completed_successfullybut not on each other, so both started concurrently and both ran TypeORM migrations against the sharedn8nschema. One container would lose onCreateWorkflowHistoryTable1692967111175withduplicate key value violates unique constraint "pg_type_typname_nsp_index", retry, succeed; while n8n recovered automatically the boot logs printed scaryerror running database migrationslines on every cold start. Added a healthcheck ton8n(wget -qO- http://127.0.0.1:5678/healthz, 15s interval / 90s start_period) and changedn8n-worker.depends_on.n8nfromservice_startedtoservice_healthyso the worker waits for n8n's migration phase to finish before starting its own.
-
MinIO object storage: S3-compatible artifact-tier storage service with five pre-provisioned buckets (
comfyui,backend,n8n,jupyter,docling) and scoped service-account credentials surfaced asMINIO_<NAME>_ACCESS_KEY/MINIO_<NAME>_SECRET_KEYin.env. Admin console athttp://localhost:63031; S3 API athttp://localhost:63030. Consumer code is unchanged in this release; each consumer integration ships in a dedicated follow-up. Pinned to the dated Docker Hub release tagminio/minio:RELEASE.2025-09-07T16-13-09Z; note that the upstream service-account-CVE fixRELEASE.2025-10-15T17-29-55Zis published on GitHub only and not yet on Docker Hub — operators handling untrusted credentials should rebuild from source or pin a later tag once available. -
minio-initprovisioner: one-shot container runningminio/mcthat creates buckets, named IAM policies (<consumer>-policy), and service accounts on every./start.sh. Idempotent — re-runs are no-ops. -
Bootstrapper integration:
MINIO_PORT=63030/MINIO_CONSOLE_PORT=63031registered inPortManager.PORT_MAPPING(recomputed correctly under--base-port);KeyGeneratorextended withMINIO_ROOT_PASSWORD+ 10 per-consumer service-account credentials (idempotent — hand-edits stick);--minio-source [container|disabled]Click flag plumbed throughSourceOverrideManager; wizard surfaces MinIO as a DATA-tier service via the manifest atservices/minio/service.yml(synthesized bybootstrapper/services/sc_synthesizer.py) plus display-name / description / tag registrations.
-
New
hermesservice (nousresearch/hermes-agent:latest— upstream publishes onlylatest+ immutablesha-<commit>tags, no semver; production should pin to a specific sha perservices/hermes/README.md) — programmable AI agent runtime by Nous Research. Promoted fromdocs/ROADMAP.mdTier 2 to shipped. Container by default (3 SOURCE modes:container,localhost,disabled), ~2-4 GB RAM, no GPU. File-based persistence under/opt/data(hermes-datanamed volume) — no Postgres / Redis dependency. OpenAI-compatible API on port 8642 → host63028; web dashboard on 9119 → host63029, Kong-aliased ashermes.localhost. -
New
hermes-initcompanion — renders/opt/data/config.yamlfrom environment before Hermes starts. Wires LiteLLM (http://litellm:4000/v1) for reasoning, Speaches / Chatterbox / Parakeet via OpenAI-compatible base-URL overrides for voice (TTS_ENDPOINT/STT_ENDPOINT), ComfyUI via a skill-override file at/opt/data/skills/creative-comfyui-host-override.md, and SearXNG for web search. Empty endpoint → block omitted fromconfig.yaml(graceful degradation when a dependency is disabled). Bootstraps deps via inlineapk addthen re-execs under bash (matches openclaw-init / weaviate-init convention). -
hermes-agentregistered in the LiteLLM model_list —litellm-init/scripts/init.pyappends ahermes-agentrow pointing at${HERMES_ENDPOINT}/v1whenHERMES_SOURCE != disabled. Consequence: Open WebUI, n8n, backend, jupyterhub, openclaw all see the new model automatically with no per-consumer wiring. -
HERMES_ENDPOINT+HERMES_API_KEYplumbed to consumers — backend, n8n, jupyterhub, openclaw-gateway env blocks for direct API / webhook access (LiteLLM-routedhermes-agentmodel is the default surface). -
Bootstrapper integration — new
services/hermes/service.ymlmanifest (container/localhost/disabledsources + cross-deps on stt_provider / tts_provider / comfyui / searxng for init-time URL wiring, all underruntime_sc:/runtime_adaptive:/runtime_deps:blocks; synthesized into the legacy dict shape bybootstrapper/services/sc_synthesizer.py),_generate_hermes_config()inbootstrapper/services/service_config.py(mirror of_generate_openclaw_config()),HERMES_ENDPOINTinbootstrapper/utils/endpoint_vars.py, CLI flag--hermes-source, port-clear list, localhost validator, source override manager, dependency manager scale/source mappings, wizard tile (bootstrapper/ui/state_builder.py), service discovery name/description, hosts manager (hermes.localhostwritten by--setup-hosts), log-pane TOOL tag,HERMES_API_KEYauto-generation (32-byte URL-safe token, idempotent like LITELLM_MASTER_KEY). -
Kong route
hermes.localhost→http://hermes:9119— added tobootstrapper/utils/kong_config_generator.py:generate_hermes_service(). Gated onHERMES_SOURCE != disabledANDHERMES_DASHBOARD_ENABLED=true. -
Audit script extensions —
scripts/check-compose-source-deps.pynow enforces(hermes, litellm)and(hermes-init, litellm)depends_onpairs;scripts/check-kong-routes.pyenforces thehermes.localhost → http://hermes:9119/route. -
docs: new
services/hermes/README.md(full service doc), updateddocs/README.md,README.md(5 OpenClaw parallels),docs/deployment/ports-and-routes.md(+rows for 63028/63029 andhermes.localhost),docs/deployment/source-configuration.md(table rows + dedicated subsection),docs/quick-start/interactive-setup-wizard.md(wizard table row),services/kong/README.md(route + curl example),services/ollama/README.md(LiteLLM consumer list),services/litellm/README.md/services/openclaw/README.md/services/open-webui/README.md(cross-references),docs/ROADMAP.md(marks Tier-2 entry as shipped, corrects the wrong Supabase-dependency claim — Hermes is file-based). -
runtime verification: pulled and booted
nousresearch/hermes-agent:latest(multi-arch —linux/amd64+linux/arm64); image is ~5.66 GB on disk; OpenAI-compatible API responds at/v1/modelswith the bundledhermes-agentmodel id; 87 default skills sync into~/.hermes/skills/on every start; entrypoint refusesHERMES_UID=0(default10000is safe).
2.96. Added (Ollama multi-select enrichments — capability tags, sizes, recency-bucket sort, filter chips)
-
Capability tag badges on every Ollama row —
[embedding],[thinking],[vision],[tools],[audio]. Scraped from each model card'sx-test-capabilityspans onollama.com/library. Curated catalogembeddings(plural) aliases to the live-scrapeembedding(singular) so a row never shows both. -
Single-select filter chip row above the multi-select:
Filter [ALL] embedding thinking vision tools audio. Click a chip to narrow the list; clickALLto reset. View-only — rows checked under one filter survive switching to another. New widgetbootstrapper/ui/textual/widgets/multiselect_filter_chips.py; new fieldsPromptStep.filter_tags,PromptPanel._filter_tag/_visible_indices. -
Approximate disk-size column — every variant rendered as Q4_K_M footprint (
8b → 4.8GB,70b → 42GB,0.6b → 360MB,270m → 162MB) viaoption_row._approx_size. Computed from Ollama's published parameter count (params × 0.6 bytes/paramrule of thumb; real downloads are ±10–15% of the figure shown). On narrow terminals the column compresses to the first three variants +…, then drops entirely below the pull-count column. -
Pull count column — right-aligned, muted, formatted
K/M/B(e.g.114.2M). Sourced from each card'sx-test-pull-countspan. -
Two-bucket recency sort — models updated within 365 days come first, sorted by total pulls descending; everything older gets a muted
[legacy]badge and drops below in the same sort. This demotes year-old hits (llama3.1at 114M pulls) below newer-but-popular models (deepseek-r1,gemma3,qwen3). Threshold lives atllm_steps._LEGACY_THRESHOLD_DAYS = 365.updated X agoannotation appears in the hint line. -
OllamaLibraryEntrydataclass replaces the names-only scrape —name,capabilities,sizes,pulls,updated,age_days. Parser anchors on Alpine.jsx-test-*test attributes (stable).list_library_models()removed (no callers).
-
public.llmsis now the single source of truth for the LiteLLMmodel_list. Removed the hardcoded model lists inbootstrapper/utils/litellm_config_generator.py; the bootstrapper now writes only a stubvolumes/litellm/config.yamlwith emptymodel_list. The real config is rendered on everydocker compose upbylitellm-init/scripts/init.pyfromSELECT … FROM public.llms WHERE active = true. -
llm-catalog-initcontainer (llm-catalog-init/Dockerfile+scripts/sync-catalog.py, python:3.12-slim): runs betweensupabase-db-initandollama-pull/litellm-init. UPSERTs the curated catalog frombootstrapper/utils/llm_catalog.pyand applies wizard /.env-driven model selections (OPENAI_USER_MODELS,ANTHROPIC_USER_MODELS,OPENROUTER_USER_MODELS,OLLAMA_USER_MODELS,OLLAMA_CUSTOM_MODELS). Pre-flight check verifies the(provider, name)unique constraint exists. -
bootstrapper/utils/llm_catalog.py: single source of truth for curated cloud + Ollama catalog. Each entry carries capability flags (content,structured_content,vision,embeddings),context_window,default_active. Cloud catalog includes gpt-5 family, claude-4.x line, and OpenRouter aggregator routes. -
Schema migration
supabase/db/scripts/05a-public-tables-migrations.sql: dropllms_name_key, add compositellms_provider_name_key UNIQUE (provider, name)so models with the same bare name across providers can coexist. -
Live cloud model fetch in the wizard:
- OpenAI
/v1/modelswith the user's key + per-provider filter (seebootstrapper/utils/cloud_models.pyfor the current allow/deny lists; the filter is maintained there as new model families ship — DALL-E, Whisper, TTS, fine-tunes and snapshot variants are excluded). - Anthropic
/v1/modelswithx-api-key— usesdisplay_namefor label, dedups snapshots. - OpenRouter
/api/v1/models(no auth) — sorted alphabetically by label, capped at 50 entries to keep the picker usable. - All three fall back to
CLOUD_CATALOGon network/auth/timeout/empty failure.
- OpenAI
-
Live Ollama library scrape of
https://ollama.com/library(~230 entries) viabootstrapper/utils/ollama_library.py. Available for every ollama-* source (localhost, external, container) so users can browse and register additional models regardless of upstream type. Falls back toOLLAMA_DEFAULT_CATALOGon failure. -
Live Ollama upstream discovery: for
ollama-localhost/ollama-external,bootstrapper/utils/ollama_discovery.pyqueries${LITELLM_OLLAMA_UPSTREAM}/api/tagsto list models already pulled on the user's host. -
Three new wizard prompt kinds in
bootstrapper/ui/textual/widgets/prompt_panel.py:-
kind="secret"— maskedInput(password=True), with<KEEP>/<CLEAR>sentinels for re-runs (existing key + Enter = keep current; typeclearto remove). Live "selected N chars entered" counter viaon_input_changedso the user can confirm a paste landed even when the dots scrolled out of view. -
kind="multiselect"— checkbox list with[selected]/[ ]indicators. Space toggles the focused row; Enter confirms. Comma-joined CSV value. Optionaloptions_providerfor lazy/live fetch with aFetching X models…status row, worker, cache, and back-invalidation. -
kind="text"— free-text input with the same<KEEP>/<CLEAR>sentinels (used forOLLAMA_CUSTOM_MODELSso an empty Enter on re-run doesn't silently wipe an existing value).
-
-
CLI flags:
--openai-models,--anthropic-models,--openrouter-models,--ollama-models,--ollama-custom-models(comma-separated). Imply matching--cloud-*-source=enabledwhen paired with the corresponding--*-api-key. -
/tmp/atlas-launch-<YYYYMMDDTHHMMSS>.log— every wizard launch tees pipeline + docker compose output to this file. The session log is now opened at wizard start (not launch), so cloud/v1/modelsfetch failures during the setup phase are persisted too. Ondocker compose upnon-zero exit, automatically capturesdocker compose logs --tail=200for every service. The path is written as the first line in the wizard's log pane (session log: /tmp/atlas-launch-<…>.log). -
Per-service color-coded log pane:
LogPane._write_recordnow matches the compose<container> | <body>pattern and colors the container-name prefix usingpalette.color_for_source(rec.source). Hash-based fallback (md5) gives every service — including ones not in the curatedSOURCE_COLORSmap (jupyterhub, openclaw, local-deep-researcher, etc.) — a stable distinct hue. -
Stack-overview Cloud APIs sub-section:
bootstrapper/ui/textual/widgets/info_box.py:CloudApisRowshows OpenAI / Anthropic / OpenRouter status (enabled · key set selected,disabled,enabled · key missing) below the services grid. Footer count line gains aN cloud apis onsegment. -
Validator auto-disable for
enabled+empty-key:services/source_validator.py:_enforce_cloud_keys_presentflipsCLOUD_*_SOURCE=enabledback todisabledwhen the matching*_API_KEYis empty (with a warning), guarding against unusable launch state from hand-edited .env or CLI-flag misuse. -
Cloud
/v1/modelsfallback diagnostics:bootstrapper/utils/cloud_models.pynow accepts anon_warncallback. The wizard registers a sink (integration._set_wizard_warn_sink) that routes failures into_safe_logso they land in both the log pane and/tmp/atlas-launch-*.log— e.g.[warn/openai-fetch] live /v1/models failed — falling back to catalog (cause: HTTP 401 Unauthorized). Distinguishes empty-key, transport, JSON, missing-data[], and post-filter empty-set failures.
- Wizard step ordering: cloud secret + multi-select pairs (OpenAI / Anthropic / OpenRouter) are spliced immediately after the LLM Engine + Ollama steps, not after every other service-source step. New flow: base port → ComfyUI → LLM Engine → Ollama variants → cloud key+models pairs → other services → cold/hosts/confirm.
-
Cloud providers re-classified as APIs, not services: removed from
bootstrapper/ui/state_builder.py:_SERVICES. They no longer appear in the services grid, footer counts, or no-TUI pre-launch summary table — instead they render in their own "Cloud APIs" block. -
Catalog mount path:
llm-catalog-initmounts./bootstrapper/utils:/catalog:ro(sibling to/scripts) instead of layeringllm_catalog.pyinside the/scripts:romount. Avoids a Docker file-on-dir overlay edge case that silently broke first-run launches. -
docker composeflags in wizard launch:--ansi=always→--ansi=never. The animated TTY-based progress is incompatible with our Popen-piped stdout (compose reportsfailed to get console: provided file is not a consoleand exits 1). Per-service coloring is now synthesized client-side viapalette.color_for_sourceinstead of relying on compose's embedded ANSI codes.
-
DuplicateIdscrash on consecutive secret prompts (prompt_panel.py): widgets were re-mounted per step butContainer.remove_children()is async — the previous step'sInput(id="secret-input")was still in the node list when the next step's mount tried to register the same id. Switched to widget-reuse: persistent_number_input/_secret_input/ hint Statics created once and re-shown per step. -
NameError: PromptOption is not definedinwizard_screen.py:_load_current_stepsplash branch: missing import, now added. -
'NoneType' object has no attribute '__dict__'inllm-catalog-init'sload_catalog(): Python 3.12's@dataclassdecorator (withfrom __future__ import annotations) callsdataclasses._is_type→sys.modules.get(cls.__module__). Module loaded viaimportlib.util.spec_from_file_location()wasn't registered. Fix:sys.modules["llm_catalog"] = modulebeforeexec_module(). -
Live logs not updating in the wizard pane after launch:
_run_composewas on the main async event loop, but_safe_logusedself.app.call_from_thread(...)(designed for worker threads). Callingcall_from_threadfrom the same thread silently failed to deliver UI updates._safe_lognow checksthreading.current_thread() is threading.main_thread()and uses a direct_log_pane.write_logcall when on the main thread,call_from_threadfrom workers. -
docker compose upoutput not in launch log:_run_composeand_run_commandwrote directly to_log_pane.write_log()instead of routing through_safe_log(), so the tee path was bypassed. Fixed. -
Multi-select state lost on back-then-forward navigation:
_load_current_stepalways rebuiltdefault_valuesfromoriginal.default_valuesinstead of honoring the user's prior selection inself._selections. Fixed. -
Text-step empty Enter destroyed existing value: an empty input on a step with a non-empty
default_valuereturned""instead of a<KEEP>sentinel, silently wipingOLLAMA_CUSTOM_MODELSon re-runs. Now uses the same keep-current sentinel as the secret step. -
will_run_wizardignored--*-modelsflags: passing--openai-models gpt-5alone (no source flag) triggered the wizard, silently overriding the CLI value. The detection now also considersuser_model_selectionsandcloud_api_keys. -
Cloud provider "enabled but unusable" on CLI-flag mode:
--openai-api-key sk-…without--openai-modelsleft zero rows active inpublic.llms(cloud entries default todefault_active=False).apply_cloud_selectionnow activates the catalog'sdefault_active=Trueset when the user enables a provider with no model override. -
skip_if_prevcould crash the wizard: any exception in the predicate would propagate. Both forward (_load_current_step) and backward (action_back) navigation now catch exceptions and treat them as "don't skip". -
OLLAMA_PULL_SCALEran for host-side Ollama upstreams:service_config.pyset the scale to1wheneverLLM_PROVIDER_SOURCE != 'none', soollama-pullwould attempt/api/pullagainst the user'sollama-localhost/ollama-externalinstance — surprising behaviour, and contradicted the.env.exampletext +services/ollama/README.md. Restricted toollama-container-*only. (Subsequent change registers host-side custom Ollama rows inpublic.llmswith a warning that the operator mustollama pullthemselves; see the Changed section below.) -
litellm-inittorn-write hazard: a crash between writing the sentinel header andyaml.safe_dumpleft a sentinel-marked but body-lessconfig.yaml, whichlitellm_config_generator._is_litellm_init_managedwould preserve on subsequent runs — persisting a broken config indefinitely.write_confignow writes toconfig.yaml.tmpandos.replace()s atomically. -
Backend memory extraction broken in cloud-only setups:
MemoryService._get_extraction_modelqueriedWHERE provider='ollama', so cloud-only setups (Ollama rows deactivated) fell through to the hardcodedollama/qwen3.6:latest— a model not in LiteLLM's model_list, causing extraction to fail. Now queries all providers; per-provider name mapping mirrorslitellm-init/scripts/init.py:render_model_list. -
Wizard could record inert cloud model selections: when
CLOUD_*_SOURCE=disabledin .env but the API key was already set, the secret-step's "keep current" sentinel let the multi-select render → user picked models →_selections_to_argsrecorded*_USER_MODELSbut didn't enable the source →llm-catalog-initthen deactivated everything for that provider. Skip predicate now consults the .env source state; auto-promotes toenabledwhen the user proceeds past a SECRET_KEEP step that already has a key. -
Stale async fetch worker pollutes provider cache: a slow
/v1/modelsworker dispatched before the user pressed Esc → changed key → revisited the step would write its (now-stale) options into the cache the user had just invalidated. Added a generation token bumped byaction_back; workers compare-then-write and silently drop on mismatch. -
Setup-phase wizard warnings dropped from session log: the launch-log file was only opened during the setup→launch transition, so cloud
/v1/modelsfetch failures during the wizard's setup phase were silently lost — contradicting troubleshooting docs that promised the file captured everything. Tee now opens at wizard start; the announce-in-pane line moves to the launch transition (when the pane exists). The file is also closed on setup-phase quit. -
Migration constraint check could falsely no-op:
05a-public-tables-migrations.sqlandsync-catalog.py:verify_constraintchecked onlypg_constraint.conname; if any other table somehow had the same constraint name, the guard would skip the ALTER. Both call sites now scope byconrelid = 'public.llms'::regclass.
-
Validator side effects split off:
SourceValidator.validate_all_sources()is now read-only. The auto-disable-cloud-providers-with-missing-keys behaviour moved toenforce_runtime_invariants()—start.pycalls both, but pure-tooling callers (linters, dry-runs) can validate without mutating .env. -
Cloud APIs overview live-updates on multi-select 0-selection: unchecking every model in a cloud provider's multi-select now flips the matching Cloud APIs row to
disabledimmediately (matching the_selections_to_argspolicy that treats empty CSV as "user wants this provider off"), instead of waiting until launch to surprise the user. -
Command summary covers cloud + Ollama selections:
--cloud-X-source enabled/disabled,--X-api-key <set>(sanitized; never the raw key),--X-models N selected (...),--ollama-models,--ollama-custom-models. The "equivalent CLI" preview is equivalent again. -
Better Ollama-discovery UX on failure: the unified Ollama multiselect's options provider no longer returns an empty list silently. On
/api/tagsfailure or empty result, surfaces a placeholder row explaining what went wrong and routes the diagnostic through the same launch-log sink the cloud steps use. -
Custom Ollama models on host-side upstreams: previously dropped silently with a warning. Now registered + active in
public.llmswith a loud warning that the operator mustollama pull <name>themselves on the host (sinceollama-pulldoesn't run for host-side sources). Matches the wizard's catalog-multiselect behaviour for localhost/external. -
Stale-actives warning on Ollama upstream switch: switching
LLM_PROVIDER_SOURCE(e.g. container → localhost) without supplyingOLLAMA_USER_MODELSnow warns about every preserved active row that may not exist on the new upstream. -
volumes/api/kong-dynamic.ymlis now a pure runtime artifact (.gitignored, regenerated on every./start.sh). Directdocker compose upfrom a clean checkout is unsupported —services/kong/README.mdupdated;scripts/check-kong-routes.pywas rewritten to invoke the kong generator against.env.examplein a tmp dir and validate that, instead of reading the user's runtime file. -
bootstrapper/utils/cloud_providers.py— single source of truth for cloud LLM provider tuples (display name, source var, API key var, enabled flag var). Replaces three separate per-shape lists instate_builder.py,source_validator.py, andservice_config.py. -
Source-aware secret-step hints — cloud key prompts now show distinct wording for
enabled+key("Enter keeps enabled"),disabled+key("Enter enables with saved key"), anddisabled+no-key("Press Enter (empty) to leave disabled"). Driven by a new optionalsecret_keep_hintfield onPromptStep. -
Live-discovered cloud / Ollama models now insert if missing —
llm-catalog-init/scripts/sync-catalog.py:insert_live_onlyadds rows for selections that aren't in the curated catalog with provider-specific generic capability defaults (LIVE_DEFAULTS). Per-provider routing inlitellm-initis unchanged. Logs reportN requested, M matched in catalog, K inserted as live-only. -
MemoryServiceraises instead of silent fallback —_get_extraction_modelpreviously returnedollama/qwen3.6:lateston any DB error, which is unroutable in cloud-only setups. Now logs the underlying exception and raisesRuntimeErrorwith a clear message about settingLITELLM_DEFAULT_MODELor activating a content row. -
Multi-select hidden-defaults guard —
prompt_panel.pynow intersectsdefault_valueswith the visible option set when loading a multi-select step. Previously, default values not present in the live-fetched options stayed invisibly checked and leaked into the saved CSV at confirm. -
Compose
${VAR-default}semantics —docker-compose.ymlswitched bothLITELLM_OLLAMA_UPSTREAMsubstitutions from${VAR:-default}to${VAR-default}(no colon) so an explicit empty value reaches the LiteLLM container as"". The:-form silently substituted the default for empty too, breaking the documented "none → empty → no Ollama upstream" semantic. -
OpenRouter ID double-prefix guard —
cloud_models.py:list_openrouter_modelsnow skips theopenrouter/prefix when the upstream'sidalready starts with it, preventingopenrouter/openrouter/...if the API response shape ever changes. -
Cloud APIs overview live-updates on auto-promote — when the user proceeds past a
disabled+keycloud secret step (SECRET_KEEP) and the multiselect renders,_apply_secret_step_to_cloud_apisand_apply_models_step_to_cloud_apisnow flip the overview row to "enabled" immediately so the live state mirrors the launch outcome. Also extracted_refresh_info_panelto consolidate four duplicated update blocks. -
Single unified Ollama models picker with
[pulled]/[library]badges — replaces the previous two-pagepulled+librarysplit, which produced two near-duplicate multi-select pages forollama-localhost/ollama-externalusers (the library was a strict superset of/api/tags). Now: container modes show the library scrape only; localhost/external show a merged view where[pulled]rows are on disk on the user's upstream and[library]rows are catalog entries that need a manualollama pull. Step constantsOLLAMA_LIVE_TITLEandOLLAMA_CATALOG_TITLEcollapse into a singleOLLAMA_MODELS_TITLE. -
Multi-select scrolling fix — the option-list container is now a
VerticalScrollwithmax-height: 18and the focused row isscroll_visible()'d after everymove(). Previously a 230-entry library scrape grew the panel past the viewport and pressing Down moved the cursor off-screen invisibly.
-
utils/cloud_models.py --checkself-test CLI — useful for triage; not shipped this round.
-
Dropped 7 non-default Ollama catalog entries (
llama3.3,llama3.2,mistral-small,phi4,qwen3.6:7b,deepseek-r1,mxbai-embed-large). Superseded by the liveollama.com/libraryscrape; never sat indefault_active.OLLAMA_DEFAULT_CATALOGnow contains only the default-active trio. -
Removed dead
_changed_countmethod (wizard_screen.py) — defined but never called from anywhere. Pre-existing dead code. -
Removed dead
ensure-litellm-db.sh— replaced bylitellm-init/scripts/init.py. -
Removed
LITELLM_INIT_IMAGEfrom.env.example— no longer used sincelitellm-initbuilds from a Dockerfile instead ofimage:. -
Fetch-label deduplication — the wizard's
Fetching <provider> models…status row now strips the redundant " Cloud" suffix from cloud provider names.
-
LiteLLM Gateway (mandatory core service): always-on OpenAI-compatible front door for every LLM provider. Pinned image
ghcr.io/berriai/litellm:v1.83.14-stable.patch.2, listening on port 63012 (the slot formerly held by Ollama). Persistence on a dedicatedlitellmdatabase in the existing Supabase Postgres (Prisma migrations run automatically); response caching + rate-limit state in Redis.-
Wizard model: LiteLLM is a locked tile (no source toggle). A separate LLM Engine tile single-selects the local Ollama upstream (
ollama-container-cpu,ollama-container-gpu,ollama-localhost,ollama-external,none). Three new Cloud APIs (OpenAI / Anthropic / OpenRouter) appear in a dedicated overview block rather than as service tiles — each is a secret-input + multiselect pair that toggles the corresponding provider in LiteLLM'smodel_list. Bootstrapper refuses to start when no upstream is configured (engine=none+ every cloud provider disabled). -
CLI flags:
--llm-provider-sourceenum droppedapi/disabled, addednone. New flags--cloud-openai-source,--cloud-anthropic-source,--cloud-openrouter-source(eachenabled/disabled). -
Master key: bootstrapper auto-generates
LITELLM_MASTER_KEY(sk-…) on first start and never overwrites it on subsequent runs. -
Documented backup: Portkey AI Gateway (Apache-2.0) — switch path noted in
services/litellm/README.md.
-
Wizard model: LiteLLM is a locked tile (no source toggle). A separate LLM Engine tile single-selects the local Ollama upstream (
-
vllm-container-gpuupstream is deferred to a follow-up plan (tracked in ROADMAP).
-
Consumer env-var rename (breaking): every service that talks to an LLM now reads
LITELLM_BASE_URL+LITELLM_API_KEY. The legacyOLLAMA_BASE_URL/OLLAMA_ENDPOINTenv vars are removed from all consumer compose blocks (open-web-ui,backend,n8n,n8n-worker,n8n-init,jupyterhub,local-deep-researcher,openclaw-gateway,weaviate-init,weaviate). -
LLM_PROVIDER_PORTrenamed toLITELLM_PORT(same default 63012).bootstrapper/core/port_manager.pyand.env.exampleupdated. -
Backend memory service refactored (
backend/app/memory_service.py,memory_store.py): switched from Ollama's native/api/generate+/api/embeddingsto LiteLLM's OpenAI-compatible/v1/chat/completions+/v1/embeddings. All 7 Weaviate collection schemas migrated fromtext2vec-ollama(withapiEndpoint) totext2vec-openai(withbaseURLpointed at LiteLLM). New helper_litellm_complete()consolidates the chat completion call sites. -
Local Deep Researcher now uses the OpenAI-compatible LangGraph client pointed at LiteLLM (
init-config.pywritesllm_provider=openai; entrypoint healthchecksLITELLM_BASE_URL/health/liveliness). -
Weaviate default vectorizer is now
text2vec-openai(LiteLLM-backed).text2vec-ollamais left enabled for backward-compat with un-migrated collections. -
n8n research workflow (
searxng-research-workflow.json) now POSTs to${LITELLM_BASE_URL}/v1/chat/completionswithAuthorization: Bearer …; response parsing handles OpenAI'schoices[].message.contentshape. -
JupyterHub startup script and notebooks rewritten to expose
LITELLM_BASE_URL/LITELLM_API_KEYandOPENAI_API_BASE/OPENAI_API_KEY(so theopenaiPython SDK and LangChain OpenAI clients work unchanged).01_ollama_basics.ipynbrenamed to01_litellm_basics.ipynb. The deeperollama.chat/ChatOllamaexamples in01_litellm_basics.ipynband02_langchain_rag.ipynbstill need a content rewrite to useOpenAI()/ChatOpenAI()clients (env vars are correct, code samples need a follow-up pass). -
check-compose-source-deps.pygained 12 newREQUIRED_DEPENDS_ONtuples enforcing that every LLM consumer hard-depends onlitellm. Ollama remains inFORBIDDEN_OPTIONAL_DEPENDS_ON(still source-replaceable). -
Migration note for existing users: bump your
.envby either copying the new.env.exampleor running./start.sh --cold.OLLAMA_ENDPOINTis gone;LLM_PROVIDER_PORTbecomesLITELLM_PORT(same value). -
OpenClaw AI Agent: AI agent for messaging platforms (WhatsApp, Telegram, Discord, etc.)
- Connects to messaging apps for AI-powered chat, file management, and task automation
- Web dashboard for administration at
openclaw.localhost - LLM integration: inherits stack's Ollama endpoint, supports Anthropic/OpenAI API keys
- SOURCE options:
container,localhost(Node.js 22+),disabled(default) - CLI option:
--openclaw-source [container|localhost|disabled] - Default ports: 63024 (gateway, offset +24), 63025 (bridge, offset +25)
- Kong routing via
openclaw.localhostsubdomain
-
JupyterHub Data Science IDE: Interactive Jupyter Lab environment with pre-configured AI/ML libraries
- 7 sample notebooks demonstrating all service integrations (Ollama, Weaviate, Neo4j, Supabase, ComfyUI, n8n, SearxNG)
- Pre-installed libraries: Ollama, LangChain, LlamaIndex, Transformers, Weaviate client, Neo4j driver, and more
- Kong routing support via
jupyter.localhostdomain - Persistent workspace with Docker volumes (
jupyterhub-data) - Adaptive service that auto-configures based on available AI services
- CLI option:
--jupyterhub-source [container|disabled] - Default port: 63048 (offset +48 from base port)
- Environment check notebook for service connectivity verification
-
Textual-based bootstrapper TUI: A single Textual app (
bootstrapper/ui/textual/) now owns the entire interactive experience — wizard prompts, the CLI-flag launch screen, the pre-launch pipeline (apply overrides → validate → ports → kong → supabase keys → hosts → encryption → localhost), and the livedocker composebuild / up / verify /logs -fstream — all rendered in one screen with a pinned info-box, a service overview, and a bordered log pane with filter chips. Pressctrl+qto detach (the stack keeps running).--no-tuifalls back to a linear stdout flow for CI / non-TTY shells. -
Brand customization via
BRAND_*env vars: The wizard's brand panel and info-box title / subtitle metadata (brand name, tagline, version, author, author email, license, repo URL) is overridable viaBRAND_NAME,BRAND_TAGLINE,BRAND_VERSION,BRAND_AUTHOR,BRAND_AUTHOR_EMAIL,BRAND_LICENSE,BRAND_REPO_URLin.env. Defaults are Atlas; forks can rebrand without code changes. -
Always-on Supabase services in the bootstrapper overview:
Supabase Auth,Supabase API,Supabase Realtime,Supabase Storage, andSupabase Metaare now surfaced as rows in both the TextualServiceTableand the--no-tuisummary table, alongside Supabase DB and Studio. -
scripts/check-compose-source-deps.py: Preventative linter that verifiesdocker-compose.ymldoes not declare harddepends_onedges from any service to a SOURCE-replaceable provider, and that coredepends_onedges are still in place. -
scripts/check-kong-routes.py: Preventative linter that verifies the Kong route generator (bootstrapper/utils/kong_config_generator.py) produces the documented default routes forcomfyui.localhost,n8n.localhost,search.localhost,jupyter.localhost,api.localhost, andchat.localhost. (Initially validated a checked-in Kong fallback file; rewritten later in this same release to invoke the generator against.env.examplein a tmp dir — see the matching entry under### Changed. Both entries describe the same checker; the file is now generated-only.) -
docs/deployment/ports-and-routes.md: Canonical reference forBASE_PORTmath, every service's direct localhost URL, and Kong host routes. -
Per-service documentation expansion under
services/:backend.md,comfyui.md,local-deep-researcher.md,multi2vec-clip.md,n8n.md,ollama.md,open-webui.md,redis.md,searxng.md,weaviate.mdnow have their own pages alongside the existing in-depth docs. - ROADMAP additions: Tier 1 — unified LLM gateway (LiteLLM, or equivalent) and per-service configuration modularization. Tier 2 — Hermes Agent (Nous Research's programmable agent runtime, with Open WebUI integration link).
- New documentation structure under
/docs/, ROADMAP.md, and this CHANGELOG.
-
Loosened
depends_onedges for SOURCE-replaceable providers:n8n,n8n-worker, andjupyterhubno longer hard-depend onweaviate(jupyterhubalso no longer hard-depends onollamaorneo4j-graph-db);weaviateno longer hard-depends onmulti2vec-clip.n8n/n8n-worker/jupyterhubnow depend onsupabase-db-initinstead ofsupabase-db. Optional consumers useWEAVIATE_URL(and equivalent endpoint env vars) plus runtime readiness checks instead of static compose dependencies — the stack still starts when those providers are disabled, localhost-backed, or externalized. -
Weaviate module configuration now
.env-driven:WEAVIATE_ENABLE_MODULESandCLIP_INFERENCE_APIare exposed in.env.exampleand consumed by the Weaviate compose service. Disabling the CLIP provider no longer requires editingdocker-compose.yml— setMULTI2VEC_CLIP_SOURCE=disabled, dropmulti2vec-clipfromWEAVIATE_ENABLE_MODULES, and clearCLIP_INFERENCE_API. -
Service-definition consolidation:
bootstrapper/ui/state_builder.all_services()is the single source of truth for the canonical service list, consumed by both the TextualServiceTableand the--no-tuisummary table. No duplicated inline service tables. -
Single
DEFAULT_BASE_PORT: Lives inbootstrapper/core/config_parser.py;start.pyand the wizard import the same constant. - README.md restructuring for better usability and new documentation organization / navigation.
- Architecture diagrams updated to include JupyterHub and other recently added services.
-
Legacy Rich-based bootstrapper UI (the Rich
Live+readcharwizard, theTextualpost-wizard log app, and all of their supporting modules):bootstrapper/ui/presentation_app.py,bootstrapper/ui/log_stream_app.py,bootstrapper/ui/select_widget.py,bootstrapper/ui/number_widget.py,bootstrapper/ui/status_ribbon.py,bootstrapper/ui/log_pane.py,bootstrapper/ui/info_box.py,bootstrapper/ui/palette.py,bootstrapper/ui/logo.py, andbootstrapper/wizard/tui_wizard.py. TheATLAS_USE_LEGACY_WIZARD=1env-var fallback that briefly let users opt back into the Rich Live wizard during the migration is also gone. -
Earlier obsolete bootstrapper modules folded into the wizard rebuild:
wizard/interactive_wizard.py,wizard/prompts.py,wizard/ui_renderer.py,utils/scroll_pin.py,utils/ansi_filter.py,ui/services_poller.py,ui/confirm_widget.py. Pruned dead methods (up_with_build,set_service_state,apply_service_snapshot,clear_status,prompt_confirm), dead palette helpers (style_for_service_state,dot_for_service_state,DOT_STARTING,DOT_OFF,DOT_UNHEALTHY,COLOR_STARTING), and unused state constants /ServiceEntryfields (SERVICE_STATE_*,GROUP_*,CATEGORY_*,state,group,category,is_default_source,endpoints).
-
Kong route generator now honors
COMFYUI_LOCALHOST_URL:bootstrapper/utils/kong_config_generator.pypreviously hardcodedhttp://host.docker.internal:8000/for thecomfyui-apiroute underCOMFYUI_SOURCE=localhost, ignoring any.envoverride. It now parsesCOMFYUI_LOCALHOST_URLand uses its host:port for both the Kong service URL and the localhost reachability probe (matching the openclaw generator's per-service env-var pattern). -
LiteLLM crash-loop on first launch via the TUI: the wizard pipeline (
bootstrapper/ui/textual/screens/wizard_screen.py) never calledgenerate_litellm_configuration, sovolumes/litellm/config.yamlwas never written beforedocker compose up. Docker's bind-mount then created an empty directory at the source path, and the LiteLLM container died withIsADirectoryError: '/app/config.yaml'. The wizard'sstepslist now runs the generator right after Kong (matching the linearstart.pyflow), andLiteLLMConfigGenerator.write_configself-heals: if the destination already exists as an empty directory, itrmdirs it and writes a real file. Non-empty directories raise a clear error rather than silently no-oping. -
Supabase keys now auto-generate on first launch without
--cold:bootstrapper/start.py:validate_supabase_keyspreviously generated missing JWT keys only on cold start, leaving fresh-clone users with an opaque "Missing Supabase keys" error on no-flag./start.sh. It now auto-generates whenever all three ofSUPABASE_JWT_SECRET/SUPABASE_ANON_KEY/SUPABASE_SERVICE_KEYare blank — the fresh-clone case. Mixed state (some set, some blank) is detected and refused with a directive to run./bootstrapper/generate_supabase_keys.sh, since the generator HMAC-signs the anon and service keys with the JWT secret and silently rewriting all three would clobber hand-pasted values. Cold start is unaffected (it wipes.envfirst, so its keys come back via the same all-blank path).
- Added
textual >= 0.85— owns the entire wizard / launch / log-streaming experience. - Removed
readchar(was used by the now-deleted Rich Live prompt widgets). - Removed
InquirerPy(replaced earlier in this[Unreleased]cycle). - Bumped
requires-pythonfrom>=3.8to>=3.10(Textual minimum and current LTS floor; the intermediate>=3.9bump landed first then was tightened to>=3.10when the dependency upgrade pass below required it).
Visual: every service row in the setup wizard now leads with a thin category-color bar; six categories (Infra, Data, LLM Core, Media, Agents & Workflows, Apps & UIs) explained in a legend below the grid. Unanswered configurable services show a yellow ◌ placeholder ("pending") instead of guessing their port/source/alias before you've picked them.
Ordering: display order — and the wizard's question sequence — is now derived from each service.yml's depends_on: and category: fields. The hand-edited services/_order.yml has been retired.
Port renumbering: default ports are computed from a per-category slot allocator, not hand-edited per manifest. On first start after this upgrade, your existing .env is auto-rewritten with the new defaults (a backup is taken to .env.backup.<timestamp>). User-customized port values (i.e., not matching the old default) are preserved untouched. Pass --no-port-migrate if you want to opt out of the rewrite.
To roll back: cp .env.backup.<timestamp> .env && sed -i '' '/BOOTSTRAPPER_PORT_LAYOUT_VERSION/d' .env (or simply delete the sentinel line so the migration re-applies on next start).
Aliases: eight new *.localhost aliases — studio, graph, weaviate, ollama, stt, tts, docling, research. Total alias count goes from 10 to 18. Run --setup-hosts to add them to /etc/hosts. Each alias works in both container and host-install (-localhost) modes — Kong proxies through host.docker.internal to the user's host port when the source is -localhost (Kong's compose now declares extra_hosts: ["host.docker.internal:${HOST_GATEWAY_IP}"] so this works on Linux Docker too). *-external sources don't get a Kong route — LiteLLM forwards those itself.
Internals: eight scattered metadata constants across bootstrapper/ (_SERVICES, _HOST_ALIAS, DISPLAY_NAME_OVERRIDES, SERVICE_DESCRIPTIONS, LOCKED_SERVICES, LOCALHOST_ENDPOINT_VARS, GENAI_HOSTS, services/_order.yml) have collapsed into manifest fields. Adding a new service is now a one-folder operation.
- Cross-platform Python bootstrapper: Complete migration from Bash to Python for start/stop scripts
- UV package manager support: Automatic detection and use of UV for better dependency management
- Enhanced error handling: Better error messages and recovery mechanisms
- Consistent behavior: Same functionality across Windows, macOS, and Linux
- Intelligent routing: Kong routes dynamically generated based on SOURCE values
- Health checking: Automatic localhost service availability checking
- Adaptive configuration: Routes automatically removed for disabled services
- No manual configuration: Replaced static kong.yml/kong-local.yml files
- Command-line configuration: Override .env settings via CLI arguments
- Temporary sessions: CLI overrides don't modify .env file
- All SOURCE types supported: Complete CLI coverage for all service sources
- Usage examples: CLI documentation with common patterns
- ComfyUI-init for all sources: Model downloading for both container and localhost setups
- Better dependency resolution: Automatic service dependency management
- Improved startup order: Cold start cleanup moved to proper execution phase
-
Reorganized bootstrapper: New
bootstrapper/directory with Python modules -
Service utilities:
bootstrapper/utils/kong_config_generator.pyfor dynamic configuration -
Moved scripts:
generate_supabase_keys.shrelocated tobootstrapper/ - Modular architecture: Clear separation of concerns in codebase
- Dynamic route generation: Routes created based on active services
- SOURCE-aware: Different routing strategies for container/localhost/external sources
- WebSocket support: Proper WebSocket routing for realtime services
- Authentication handling: Dynamic auth configuration per service
- SOURCE system refinement: Clear documentation of which services support localhost
- Localhost support clarification: Only Ollama, ComfyUI, and Weaviate support localhost SOURCE
- Container-only services: N8N, SearxNG, Open WebUI, Backend API are container-only
- External URL support: Proper handling of external service configurations
- Cold start port conflicts: Fixed cleanup order to occur before port checking
- Service initialization: ComfyUI-init now runs for localhost ComfyUI setups
- Port management: Better handling of port conflicts and base port configuration
- Kong routing: Fixed localhost service routing through Kong gateway
- Service discovery: Proper health checking for localhost services
- Cross-service communication: Improved service-to-service connectivity
- Corrected SOURCE support: Fixed incorrect localhost support claims
- Updated examples: All examples reflect new dynamic configuration approach
- Consistent terminology: Standardized language throughout documentation
-
Static Kong configuration: Removed
volumes/api/kong.ymlandvolumes/api/kong-local.yml - Dual configuration approach: Eliminated the "relic" dual Kong config system
- Manual route configuration: Removed need for manual Kong route management
- Unnecessary Kong routes: Removed routes for Weaviate and Neo4j (not user-facing)
- Duplicate documentation: Consolidated multiple sections about same services
- Outdated references: Removed references to legacy Bash-only approach
- Complete n8n integration: Workflow automation with queue management
- Redis queue backend: Distributed task processing with n8n-worker
- Pre-built workflows: Ready-to-use AI workflow templates
- Kong gateway routing: Access via n8n.localhost subdomain
- Full ComfyUI integration: AI image generation with workflow support
- Multiple deployment options: Container CPU/GPU and localhost support
- Model management: Automatic model downloading and caching
- API integration: REST API access and workflow execution
- Privacy-focused search: Local search aggregation without tracking
- Multiple search engines: Aggregated results from various sources
- API access: Programmatic search capabilities for AI workflows
- Rate limiting: Built-in protection against abuse
- Research tools integration: AI-powered research capabilities
- ComfyUI tool integration: Direct image generation from chat
- Multi-LLM support: Support for various LLM providers
- Custom tool development: Framework for adding new AI tools
- Service modularity: Better separation between services
- Docker network optimization: Improved inter-service communication
- Volume management: More efficient data persistence
- Resource allocation: Better memory and CPU management
- Environment-based scaling: Services scale based on SOURCE configuration
- Dependency management: Automatic service dependency resolution
- Health monitoring: Better service health checking and recovery
- Service startup order: Fixed dependency-based startup sequencing
- Memory management: Resolved OOM issues with large models
- Network connectivity: Fixed inter-service communication issues
- Volume permissions: Resolved file permission problems
- Supabase ecosystem: Complete database, auth, and storage solution
- Kong API Gateway: Centralized API management and routing
- Ollama integration: Local LLM inference with CPU/GPU support
- Docker Compose architecture: Complete containerized environment
- PostgreSQL: Primary database with Supabase extensions
- Neo4j: Graph database for relationship modeling
- Redis: Caching and session management
- Real-time subscriptions: WebSocket-based live data updates
- Supabase Auth: Complete authentication system
- JWT token management: Secure API access tokens
- Role-based access: User roles and permissions
- API key authentication: Service-to-service security
- Supabase Studio: Database management interface
- Environment configuration: Flexible .env-based setup
- Docker orchestration: Multi-service container management
- Development scripts: Easy start/stop scripts
- Service isolation: Each component in dedicated container
- Network segmentation: Proper Docker networking
- Volume persistence: Data persistence across restarts
- Resource management: Memory and CPU optimization
- Environment variables: Centralized configuration
- Service discovery: Automatic service registration
- Port management: Configurable port assignments
- Cross-platform support: Works on macOS, Linux, and Windows
Required Actions:
- Update start/stop usage: New CLI arguments available
- Check SOURCE configurations: Verify localhost support for your services
-
Update hosts file: Run
./start.sh --setup-hostsfor *.localhost domains - Review Kong routes: Routes now generated dynamically
Optional Improvements:
- Install UV package manager for better dependency management
- Use new CLI SOURCE overrides for easier configuration
- Leverage new troubleshooting documentation
Breaking Changes:
- Static
kong.ymlfiles no longer used (automatically migrated) - Some services no longer support localhost SOURCE (see documentation)
-
generate_supabase_keys.shmoved tobootstrapper/directory
-
Environment files: Existing
.envfiles remain compatible - Data volumes: All data preserved across updates
- Service APIs: No changes to service endpoints or functionality
- Docker images: Updated but backward compatible
- Core development team
- Community contributors
- Beta testers and early adopters
- Open source projects that make this stack possible
- Community feedback and feature requests
- Documentation contributors and reviewers
For more details on any release, see the corresponding Git tag or documentation.
Atlas documentation.
- 1. Overview
- 2. Quick Start
- 3. Core Concepts
- 4. Tracks
-
5. Services
- 5.1. Service Catalog
-
5.2. Service Guides
- 5.2.1. airflow
- 5.2.2. asset-baker
- 5.2.3. asset-worker
- 5.2.4. backend
- 5.2.5. backup
- 5.2.6. blender-mcp
- 5.2.7. celery
- 5.2.8. chatterbox
- 5.2.9. cloud-providers
- 5.2.10. cloudflared
- 5.2.11. comfyui
- 5.2.12. crawl4ai
- 5.2.13. doc-processor
- 5.2.14. docling
- 5.2.15. docling-lightrag-adapter
- 5.2.16. fal
- 5.2.17. globals
- 5.2.18. grafana
- 5.2.19. hermes
- 5.2.20. iceberg-rest
- 5.2.21. jenkins
- 5.2.22. jupyterhub
- 5.2.23. kong
- 5.2.24. label-studio
- 5.2.25. langfuse
- 5.2.26. lightrag
- 5.2.27. litellm
- 5.2.28. llm-graph-builder
- 5.2.29. local-deep-researcher
- 5.2.30. loki
- 5.2.31. mcp-servers
- 5.2.32. minio
- 5.2.33. mlflow
- 5.2.34. multi2vec-clip
- 5.2.35. n8n
- 5.2.36. neo4j
- 5.2.37. ollama
- 5.2.38. open-webui
- 5.2.39. openclaw
- 5.2.40. otel-collector
- 5.2.41. parakeet
- 5.2.42. prometheus
- 5.2.43. ray
- 5.2.44. redis
- 5.2.45. redpanda
- 5.2.46. searxng
- 5.2.47. spark
- 5.2.48. speaches
- 5.2.49. stt-provider
- 5.2.50. supabase
- 5.2.51. supavisor
- 5.2.52. tei-reranker
- 5.2.53. tempo
- 5.2.54. tika
- 5.2.55. trino
- 5.2.56. trueforge
- 5.2.57. tts-provider
- 5.2.58. verba
- 5.2.59. vllm-metal
- 5.2.60. weaviate
- 5.2.61. zeppelin
- 5.3. Provider And Extension Guides
- 6. Configuration
- 7. Operations
- 8. Architecture
- 9. Development
- 10. Reference