9.5. 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.
1. [Unreleased]¶
1.1. Added — 2026-07-19 — Managed headless Blender MCP source¶
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.
1.2. Added — 2026-07-19 — Host model pulls for ollama-localhost¶
- 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.
1.3. Added — 2026-07-19 — Endpoint exports for host media directories and the Blender bridge¶
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.
1.4. Added — 2026-07-19 — Managed-MPS ComfyUI model provisioning¶
- 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.
1.5. Added — 2026-07-19 — Declarative deployment profiles¶
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.
1.6. Added — 2026-07-19 — Durable host-adaptive <SVC>_SOURCE: auto¶
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.
1.7. Fixed — 2026-07-15 — Startup and API safety boundaries¶
- 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, ignore and do not advertise byte ranges, and are deleted when transmission completes or its finite timeout elapses. - 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.
1.8. Added — 2026-07-13 — ComfyUI media-gateway provider¶
- 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.
1.9. Fixed — 2026-07-13 — Synchronized three-surface documentation¶
- 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.
1.10. Added — 2026-07-10 — Consumer manifest registration¶
- 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.
1.11. Added — 2026-07-10 — Consumer overlay doctor¶
- 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.
1.12. Added — 2026-07-10 — Headless consumer validation commands¶
- 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.
1.13. Added — 2026-07-10 — Non-following start for automation¶
- 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.
1.14. Added — 2026-07-10 — MinIO parent-owned consumer buckets¶
- 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.
1.15. Documentation — 2026-07-10 — Consumer reference layout¶
- 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.
1.16. Added — 2026-07-10 — Ragas RAG evaluation surface¶
- 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.
1.17. Added — 2026-07-10 — Chonkie RAG chunking surface¶
- 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.
1.18. Added — 2026-07-10 — External consumer env overlay¶
- 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.
1.19. Added — 2026-07-09 — Asset worker glTF post-processing service¶
- 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.
1.20. Added — 2026-07-09 — Hosted media gateway foundation¶
- 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.
1.21. Added — 2026-07-09 — ComfyUI pinned core + custom-node provisioning¶
- 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.
1.22. Added — 2026-07-09 — ComfyUI model-bundle catalog foundation¶
- 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.23. 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.24. 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.25. 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.
1.26. Added — 2026-07-03 — Atlas root dashboard¶
- 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.
1.27. Fixed — 2026-07-02 — overnight maintenance hardening¶
- 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.28. 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.29. 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.30. 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.
1.31. Fixed — 2026-06-28 — Cold start now regenerates the volume-baked DB passwords¶
- 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.32. 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.)
1.33. Fixed — 2026-06-28 — Wizard "default for chat" no longer lists embedding models¶
- 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.34. 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).
1.35. Changed — 2026-06-28 — Batched safe Dependabot dependency-floor bumps (#167)¶
- 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.
1.36. Added — 2026-06-27 — Brand-overridable block-art logo (BRAND_LOGO_FILE)¶
- 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.
1.37. Changed — 2026-06-27 — Model catalog source-of-truth: Supabase DB → per-service YAML¶
- 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/).
1.38. Fixed — 2026-06-27 — Model-SoT follow-ups¶
- 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.
1.39. Added — 2026-06-21 — Phase 1 reuse mechanics (services/_user/ auto-launch + release tags)¶
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.
1.40. Added — 2026-06-20 — Cloudflare Tunnel service (cloudflared)¶
- 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.
1.41. Added — 2026-06-20 — On-demand backup runner (backup)¶
- 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.
1.42. Added — 2026-06-20 — Secrets hygiene guard + cross-OS doc accuracy¶
- 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).
1.43. Added — 2026-06-20 — Production-hardening profile (--profile prod)¶
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.
1.44. Changed — 2026-06-20 — Atlas startup artwork gated behind a master switch (off by default)¶
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.
1.45. Added — 2026-06-18 — Atlas brand identity: logo, wizard splash, poster¶
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.
1.46. Changed — 2026-06-16 — Project renamed: GenAI Vanilla → Atlas¶
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_ROWS in block_logo.py redrawn; _GRADIENT and
render pipeline unchanged. Linear get_ascii_art_full() is now a
single 6-row ATLAS strip; the 15-color gradient applies per-character.
- BRAND_NAME default in services/globals/service.yml →
"Atlas"; .env.example regenerated.
- Architecture HTML template <title> flips (genai-vanilla) →
(atlas); 33 services/*/architecture.html regenerated.
- JSON Schema $id URLs https://genai-vanilla/schemas/... →
https://atlas/schemas/... (internal identifiers; never resolved
over HTTP).
- Test baseline bootstrapper/tests/fixtures/rendered_config_baseline.yml
regenerated to reflect the new atlas-* container/volume/network names.
- Historical CHANGELOG entries below this section deliberately preserved
(they describe the project as it was named at the time).
1.47. Fixed — 2026-06-16 overnight maintenance pass¶
./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).
1.48. Fixed — 2026-06-15¶
- 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.
1.49. Changed — 2026-06-15¶
- 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.
1.50. Fixed — 2026-06-14 overnight maintenance pass (18 commits, passes 1-42)¶
- 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.
1.51. Fixed — 2026-06-13 overnight maintenance pass (15 commits, passes 1-50)¶
- 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.
1.52. Fixed — 2026-06-11 overnight maintenance passes 56-62 (7 commits)¶
- 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.
1.53. Fixed — 2026-06-11 overnight maintenance passes 50-55 (5 commits)¶
- 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).
1.54. Fixed — 2026-06-11 overnight maintenance passes 46-49 (5 commits)¶
- 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.
1.55. Fixed — 2026-06-11 overnight maintenance passes 40-45 (6 commits)¶
- 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.
1.56. Fixed — 2026-06-10 overnight maintenance passes 35-39 (6 commits)¶
- 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.
1.57. Fixed — 2026-06-10 overnight maintenance pass 34 (1 commit)¶
- 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.
1.58. Fixed — 2026-06-10 overnight maintenance passes 32-33 (2 commits)¶
- 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.
1.59. Fixed — 2026-06-10 overnight maintenance passes 27-31 (5 commits)¶
- 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.
1.60. Fixed — 2026-06-10 overnight maintenance pass 26 (1 commit)¶
- 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.
1.61. Fixed — 2026-06-10 overnight maintenance passes 21-25 (4 commits)¶
- 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).
1.62. Fixed — 2026-06-10 overnight maintenance pass 20 (1 commit)¶
- 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.
1.63. Fixed — 2026-06-10 overnight maintenance pass 19 (1 commit)¶
- 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.
1.64. Fixed — 2026-06-10 overnight maintenance passes 16-18 (3 commits)¶
- 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.
1.65. Fixed — 2026-06-10 overnight maintenance pass 13 (1 commit)¶
- 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.
1.66. Fixed — 2026-06-10 overnight maintenance pass 12 (1 commit)¶
- 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.
1.67. Fixed — 2026-06-10 overnight maintenance pass 11 (1 commit)¶
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.
1.68. Fixed — 2026-06-10 overnight maintenance pass 9 (1 commit)¶
- 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.
1.69. Fixed — 2026-06-10 overnight maintenance pass 8 (1 commit)¶
- 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).
1.70. Fixed — 2026-06-10 overnight maintenance pass 7 (1 commit)¶
- 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.
1.71. Fixed — 2026-06-10 overnight maintenance pass 6 (1 commit)¶
- 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.
1.72. Fixed — 2026-06-10 overnight maintenance pass 5 (2 commits)¶
- 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.
1.73. Fixed — 2026-06-10 overnight maintenance pass 4 (2 commits)¶
--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).
1.74. Fixed — 2026-06-10 overnight maintenance pass 3 (5 commits)¶
- 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.
1.75. Fixed — 2026-06-10 overnight maintenance pass 2 (6 commits)¶
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.
1.76. Fixed — 2026-06-10 overnight maintenance pass 1 (18 commits)¶
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).
1.77. Changed — 2026-06-10 overnight maintenance pass 1¶
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+.
1.78. Fixed — Critical bugs caught by the 2026-06-08 overnight audit¶
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). 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".
1.79. Fixed — Init container resilience (7 unbounded loops)¶
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 generic
run_command()helper gained explicittimeout=(10s / 60s) withsubprocess.TimeoutExpiredadded to the except clauses.
1.80. Fixed — Documentation drift (MinIO ports, TEI memory guide)¶
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.
1.81. Fixed — Build & supply-chain hygiene¶
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.
1.82. Tests — Structural regression guards¶
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.
1.83. Docs — Top-level architecture diagram refreshed¶
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.
1.84. Security — Auto-rotate 8 weak credential placeholders on first launch¶
.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.
1.85. Fixed — start.py cold-start port-clear + TUI launch flag pass-through¶
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.
1.86. Fixed — LightRAG init resilience + open-webui init timeouts¶
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.
1.87. Fixed — Documentation post-migration drift sweep¶
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.
1.88. Fixed — Manifest data_flow.calls gap for LightRAG / TEI Reranker¶
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.
1.89. Fixed — LightRAG three small drift bugs¶
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.
1.90. Fixed — Narrow broad except clauses in 3 bootstrapper modules¶
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.
1.91. Tests — Regression-guard additions¶
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.
1.92. Fixed — CI hygiene¶
- 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).
1.93. Added (LightRAG service)¶
- 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.
1.94. Added (TEI Reranker service)¶
- 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.
1.95. Added — Apache Airflow + Apache Spark cluster + Apache Zeppelin (data / apps / agents bands)¶
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 at
airflow.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 - a
jdbc: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.
1.96. Added — Scala sample notebook + VS Code remote-Jupyter verification flow¶
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.
1.97. Assessed — OmniVoice TTS engine (skipped pending upstream readiness)¶
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).
1.98. Fixed — Drop unreachable JupyterHub + Hermes Prometheus scrape jobs¶
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.
1.99. Fixed — Wrong access ports in top-level README¶
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.
1.100. Fixed — Observability follow-ups: cAdvisor socket, Grafana provisioning + 11.4 bump¶
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.
1.101. Fixed — Prometheus + Grafana bind-mount paths produced doubled sources¶
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.
1.102. Added — Scala kernels in JupyterHub + VS Code remote-Jupyter wiring¶
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) and scala3 (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_VERSION Dockerfile build args so future bumps are
explicit and rebuild predictably.
VS Code remote-Jupyter:
- services/jupyterhub/compose.yml adds 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 new JUPYTER_ALLOW_ORIGIN
env var.
- Full operator walkthrough lives at
services/jupyterhub/README.md § 10 (Connecting from VS Code). The
flow: install Microsoft's Jupyter extension, copy JUPYTERHUB_TOKEN
from .env, paste http://localhost:63081/?token=<TOKEN> into the
"Existing Jupyter Server" prompt. VS Code then offers the new
kernels via its kernel-picker.
1.103. Changed — JupyterHub requirements.txt¶
- 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.
1.104. Added — observability bundle (Prometheus + Grafana)¶
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 bundled node-exporter
(host metrics) and cAdvisor (container metrics) as co-lifecycled containers.
Default retention: 7 days, user-configurable at wizard time via the new inline
secondary_number row 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 via
generate_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 (in services/supabase/) — reads pg_stat_* views,
auto-discovers every Supabase database.
- redis-exporter (in services/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 to litellm_settings.callbacks (shared
via bootstrapper/utils/litellm_settings.py so 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-only expose).
- n8n — N8N_METRICS=true + prefix / workflow-id labels.
- MinIO — MINIO_PROMETHEUS_AUTH_TYPE=public (no JWT needed).
- Backend — prometheus-fastapi-instrumentator>=7.0.0 middleware; emits
standard http_request_duration_seconds / http_requests_total series.
- JupyterHub — originally shipped expecting built-in /hub/metrics, but the
image is single-user jupyter/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_SOURCE CLI flags + source_mapping entries.
- --prometheus-retention-days CLI flag (default 7; wizard prompts via the
new secondary_number row-schema field).
- _generate_prometheus_config() — cross-manifest scale arithmetic hook that
writes PROMETHEUS_SCALE, NODE_EXPORTER_SCALE, CADVISOR_SCALE,
POSTGRES_EXPORTER_SCALE, and REDIS_EXPORTER_SCALE from a single SOURCE
value (matches the _generate_stt_provider_config pattern).
- _generate_grafana_config() — scale + endpoint resolution.
- generate_prometheus_service() / generate_grafana_service() in the Kong
route generator. Both routes use preserve_host: True (Grafana is an SPA
that builds redirects from the Host header).
- TUI _TAG_BY_KEY entries for prometheus, node-exporter, cadvisor,
grafana, postgres-exporter, redis-exporter.
- services.schema.json extended with optional secondary_number on rows.
Audit + tests:
- check-compose-source-deps.py::REQUIRED_DEPENDS_ON adds
(postgres-exporter, supabase-db), (redis-exporter, redis),
(grafana, prometheus).
- test_wizard_app_discovery::EXPECTED_DISCOVERED adds Prometheus and
Grafana; the source_mapping flag assertion adds the matching CLI keys.
- test_deps_resolver::test_kong_fronted_services_in_upstream updated —
Kong's downstream is now {prometheus} because Prom scrapes Kong's Status
API.
- rendered_config_baseline.yml regenerated for the new compose shape
(~825 lines .env.example; +275 lines baseline).
- Per-service docs (READMEs + architecture diagrams) regenerated via
bootstrapper.docs.regen --all.
1.105. Removed (breaking) — external source variants stack-wide¶
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.
1.106. ComfyUI model picker — localhost/external coverage (follow-up to PR #17)¶
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.
1.107. ComfyUI model picker¶
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.
1.108. 2026-05-28 third-pass audit (follow-up to PR #12 / #13)¶
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).
1.109. Architecture diagrams — skill-driven rewrite¶
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.
1.110. 2026-05-27 overnight audit (second pass — follow-up to PR #11)¶
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.
1.111. Known follow-ups (deferred from the 2026-05-27 repo-wide audit pass)¶
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.
1.112. Added — Ray distributed-compute cluster¶
- 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).
1.113. Changed — Localhost port override (URL → PORT migration)¶
- 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.
1.114. 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/README.md §1.7),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.
1.115. Changed (Architecture diagrams — data-flow model + clustered layout)¶
- 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.
1.116. Added (Cross-service deps + diagrams — Phase B research)¶
- 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.
1.117. Added (Cross-service deps + diagrams — Phase A foundations)¶
- 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.
1.118. Added (Dependency vulnerability monitoring)¶
.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).
1.119. Added (LiteLLM Kong alias for the admin dashboard)¶
- 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.
1.120. Added (MinIO Kong alias for the admin console)¶
- 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.
1.121. Added (Tests for Ollama-LiteLLM-wizard catalog-sync invariants)¶
- 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).
1.122. Fixed (Wizard Ollama-models pre-check + per-variant pulled badge)¶
- 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.
1.123. Added (Ollama auto-import for host-side sources)¶
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.
1.124. Fixed (log-stream cleanup)¶
- 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.
1.125. Fixed (LiteLLM gateway: empty chat responses, broken tool calls, duplicate Hermes provider)¶
- 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).
1.126. Changed (Per-service configuration modularization)¶
- 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.
1.127. Added (config modularization safety net)¶
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.
1.128. 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.
1.129. Added (Ollama wizard step — search box, MLX badge, cloud-only filter, tag column alignment)¶
- 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.
1.130. Added (env-file backfill helper)¶
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).
1.131. Fixed (service startup)¶
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.
1.132. Added (MinIO artifact-tier object storage)¶
- 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.
1.133. Added (Hermes Agent runtime)¶
- 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).
1.134. 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).
1.135. Added (wizard rework — DB-driven model_list, live model lists, multi-select prompts)¶
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. - 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.
1.136. Changed (wizard rework)¶
- 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.
1.137. Fixed (wizard regressions discovered + fixed during this round)¶
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.
1.138. Changed¶
- 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.
1.139. Deferred / known limitations¶
utils/cloud_models.py --checkself-test CLI — useful for triage; not shipped this round.
1.140. Cleanup¶
- 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.
1.141. Added¶
- 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. vllm-container-gpuupstream is deferred to a follow-up plan (tracked in ROADMAP).
1.142. Changed (LiteLLM migration)¶
- 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.
1.143. Changed¶
- 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.
1.144. Removed¶
- 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).
1.145. Fixed¶
- 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).
1.146. Dependencies¶
- 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).
1.147. Fixed — 2026-08-05 — Provider startup, bounded-subprocess, and supply-chain hardening¶
- 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.
1.148. Fixed — 2026-08-05 — JupyterHub empty-notebook-dir crash-loop guard¶
- 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.
1.149. Fixed — 2026-08-05 — Research-sources cascade-delete index¶
- 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.
1.150. Fixed — 2026-08-05 — Backend memory/research resilience¶
- 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"".
1.151. Fixed — 2026-08-05 — Wizard Ctrl+Q detach after a launch failure¶
- 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.
1.152. Added — 2026-08-05 — Consumer-declared ComfyUI custom nodes + managed-MPS install (#905)¶
- 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, which §1.157 adds to the catalog — see #909.)
1.153. Fixed — 2026-08-08 — Runtime-lock advisory refresh (pypdf, GitPython, lightning)¶
- 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.
1.154. Fixed — 2026-08-07 — Wizard command summary overflowed with many services¶
- 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.
1.155. Changed — 2026-08-07 — Wizard splits into Setup and Logs tabs¶
- 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.
1.156. Fixed — 2026-08-08 — Wizard UX pass from a live run (tabs, prompts, stop, base port)¶
- 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 runtimes are host-global singletons shared across consumers, so they are deliberately left running — and the completion notice says so, rather than reporting "stopped" while a GPU-holding process is still up. 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.
1.157. Fixed — 2026-08-09 — comfyui-krea2edit was provisionable but inert (#909)¶
- 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.
1.158. Fixed — 2026-08-09 — Wizard tab follow-ups from the #911 review (#912)¶
- 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.
1.159. Added — 2026-08-09 — Ollama parallel-serving doctor lint for host daemons (#849)¶
./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.
1.160. Changed — 2026-08-09 — Track matrix collapsed to one generated home (#838)¶
- 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.
1.161. 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.
1.162. 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 now sheds cache instead of taking the queue down. The policy moves from
noevictiontovolatile-lru, which evicts only keys carrying a TTL — on this stack, the response cache. n8n's BullMQ queue, Kong's rate-limit counters, Langfuse's queue and the backend's media store are written without a TTL and are therefore never eviction candidates. Undernoevictiona full instance rejected writes, so an oversized cache could break the queue; the worst case is unchanged, since with nothing volatile leftvolatile-lrureturns the same OOM error.REDIS_MAXMEMORYis now settable and defaults to0(unlimited), so no existing deployment's working set shrinks on upgrade — but a cap is now one variable away, and without one the container still OOM-kills rather than evicting. - 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 §1.159's 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.163. Changed — 2026-08-11 — Default Ollama content/vision model moves to qwen3.8¶
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— see §1.162'sOLLAMA_KV_CACHE_TYPE=q8_0, which exists to offset exactly that.
1.164. 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 §1.159 parallel-serving 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.
1.165. Added — 2026-08-14 — The Logs tab says when it is holding an error (#912)¶
- 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 §1.155 split never covered.
1.166. Added — 2026-08-15 — Consumers can declare their own managed host process (#795)¶
- 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.
1.167. Fixed — 2026-08-15 — blender-mcp stop reported failure for a process it had just killed¶
- 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 generic managed-host framework (§1.166), 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.
1.168. Fixed — 2026-08-15 — The docs CI check failed on transient asset fetches, and never said so¶
- 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 job now caches
.cache(actions/cache, keyed onmkdocs.yml+docs/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.
1.169. Fixed — 2026-08-15 — blender-mcp stop could SIGKILL an unrelated process¶
- 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, the same guardcomfyui_mps_managerandvllm_metal_managerhave carried. It reads the process command line viapsand refuses to signal a pid whose argv shows no Blender binary, launcher, or state dir. An unavailable or ambiguouspsreturns "not a stranger" and proceeds — teardown is never blocked on an unknowable probe.- The
psprobe now asks for unlimited width (-ww) in all three managers. Linux procps truncatespsoutput to the terminal width — 80 columns when there is no tty, which is every CI job and every daemon — so a real bridge command line (binary +--python+ launcher path) had its path markers cut off the end and the manager's own process read as a stranger, meaningstop()would refuse to stop it. macOSpsdoes not truncate, which is exactly why this passed locally and failed on the Linux runner.comfyui_mpsandvllm_metalcarried the same latent weakness and were fixed alongside; they were saved only by luck, having short markers (main.py,vllm) that appear early in the argv. - The §1.167 zombie-reap test was re-armed in the same change. Adding this guard silently defanged it: its child also read as a stranger, so
stop()short-circuited and the test passed without ever reaching the reap it exists to cover. The child's argv now carries the state dir so it registers as ours, with an explicit setup assertion, and removing the reap makes it fail again.
1.170. 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.
2. [3.0.0] - 2026-05-15 (Topology-Driven Ordering & Port Layout v1)¶
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.
3. [2.0.0] - 2025-08-31 (Python Migration & Modular Architecture)¶
3.1. Added¶
3.1.1. Python migration¶
- 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
3.1.2. Dynamic Kong configuration¶
- 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
3.1.3. CLI SOURCE overrides¶
- 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
3.1.4. Enhanced service management¶
- 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
3.2. Changed¶
3.2.1. Project structure¶
- 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
3.2.2. Kong gateway¶
- 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
3.2.3. Service configuration¶
- 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
3.3. Fixed¶
3.3.1. Startup issues¶
- 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
3.3.2. Integration issues¶
- 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
3.3.3. Documentation¶
- Corrected SOURCE support: Fixed incorrect localhost support claims
- Updated examples: All examples reflect new dynamic configuration approach
- Consistent terminology: Standardized language throughout documentation
3.4. Removed¶
3.4.1. Obsolete files¶
- 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
3.4.2. Cleanup¶
- 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
4. [1.5.0] - 2025-07-29 (Service Integration & Workflow Enhancement)¶
4.1. Added¶
4.1.1. n8n workflow automation¶
- 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
4.1.2. ComfyUI image generation¶
- 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
4.1.3. SearxNG privacy search¶
- 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
4.1.4. Open WebUI enhancement¶
- 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
4.2. Changed¶
4.2.1. Architecture improvements¶
- 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
4.2.2. Configuration enhancement¶
- Environment-based scaling: Services scale based on SOURCE configuration
- Dependency management: Automatic service dependency resolution
- Health monitoring: Better service health checking and recovery
4.3. Fixed¶
4.3.1. Bug fixes¶
- 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
5. [1.0.0] - 2025-04-26 (Initial Release)¶
5.1. Added¶
5.1.1. Core foundation¶
- 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
5.1.2. Database services¶
- 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
5.1.3. Authentication and security¶
- 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
5.1.4. Development tools¶
- Supabase Studio: Database management interface
- Environment configuration: Flexible .env-based setup
- Docker orchestration: Multi-service container management
- Development scripts: Easy start/stop scripts
5.2. Infrastructure¶
5.2.1. Container architecture¶
- Service isolation: Each component in dedicated container
- Network segmentation: Proper Docker networking
- Volume persistence: Data persistence across restarts
- Resource management: Memory and CPU optimization
5.2.2. Configuration management¶
- Environment variables: Centralized configuration
- Service discovery: Automatic service registration
- Port management: Configurable port assignments
- Cross-platform support: Works on macOS, Linux, and Windows
6. Migration Guide¶
6.1. From 1.x to 2.0 (Python Migration)¶
Required Actions:
1. Update start/stop usage: New CLI arguments available
2. Check SOURCE configurations: Verify localhost support for your services
3. Update hosts file: Run ./start.sh --setup-hosts for .localhost domains
4. 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.yml files no longer used (automatically migrated)
- Some services no longer support localhost SOURCE (see documentation)
- generate_supabase_keys.sh moved to bootstrapper/ directory
6.2. Compatibility Notes¶
- 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
7. Acknowledgments¶
7.1. Contributors¶
- Core development team
- Community contributors
- Beta testers and early adopters
7.2. Special Thanks¶
- 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.