60 KiB
Executable File
AI-LSC Changelog
v3.3 — Registry git-URL sync + bandwidth-aware git-pull + ComfyUI
Patch release over v3.2. No schema changes, no UI reorganisation, no
wiring-graph churn — only the 10-layer registry's installer URLs are
touched, plus the git install/update logic in runtime/installer.py,
plus a new ComfyUI registry entry, plus version strings and a per-file
change log.
What changed
The 10-layer registry layer files had drifted from the systems-architect
reference (ai-stack-guide-v12.md, "Approaching the Local AI Stack Like
a Systems Architect" by Jeremy Anderson). 52 installer URLs were either
pointing at placeholder repos (github.com/nicely-done/<tool_id>,
clearly never intended to ship) or at wrong upstreams (e.g. airllm
pointed at liguodongiot/llm-airforce; llamacpp pointed at
ggerganov/llama.cpp instead of the current ggml-org/llama.cpp).
Three tools the v12 md lists as git / git_node (pm_skills, agno,
openhands) were registered as uv package-manager installs — wrong,
since the user manually tested every entry in the md by cloning the
github repo. Four registry-only tools (algory, atlas_os, eagle_eye,
glassmind) had only placeholder URLs because web search could not
find their official upstreams; the user provided the URLs directly.
ComfyUI was referenced in the ai-image-gen-local.json stack template
but had no registry entry.
This release:
- Brings every git-ish installer into agreement with the v12 reference.
- Adds
pkgfields to the 6 script-type installers that reference github release tarballs. - Resolves 10 registry-only git tools to their official upstreams via web search.
- Switches the 3 type-mismatch tools to proper git/git_node installs.
- Resolves the 4 previously-unresolved registry-only tools to the user-provided official upstreams.
- Adds a ComfyUI registry entry (Level 10,
git:comfy-org/comfyui). - Hardens the git installer runtime to be bandwidth-aware.
Five categories of patches (52 total)
- A (28) —
git/git_node/custominstallerpkgURL corrected to match the v12 markdown reference. Seedocs/REGISTRY-URLS-v3.3.0.mdfor the per-file old → new URL table. - B (6) —
script-type installers (fabric, grafana_alloy, llamafile, meilisearch, ollama, qdrant) get a newpkgfield referencing the github repo, while the existing workingcmd(curl release tarball, etc.) is left untouched. - C (10) — registry-only git tools with no v12 markdown entry
resolved via web search:
agent_reach→Panniantong/Agent-Reacheveros_memory→EverMind-AI/EverOSheadroom→headroomlabs-ai/headroommnemo_cortex→GuyMannDude/mnemo-cortexnightshift→johndaskovsky/nightshiftopenbrain→NateBJones-Projects/OB1turbovec→ryancodrai/turboveccrossplane→crossplane/crossplanekeycloak→keycloak/keycloakdma→distcc/distcc(closest parent project; DMA has no separate public repo)
- D (3) — type-mismatch fixes (md says git / git_node, registry had
uvwith package name):pm_skills→git:https://github.com/product-on-purpose/pm-skillsagno→git:https://github.com/agno-agi/agnoopenhands→git_node:https://github.com/All-Hands-AI/OpenHands.git
- E (4) — user-provided URLs for the 4 previously-unresolved
registry-only tools (web search could not find these; the user
provided the official upstream URLs directly):
algory→aryaghan-mutum/algoryatlas_os→atlas-os/atlaseagle_eye→thoughtfuldev/eagleeyeglassmind→khodges42/glassMind
New tool entry (1)
- ComfyUI (
user_interfaces.py): ComfyUI was referenced in theai-image-gen-local.jsonstack template but had no registry entry before. Added as a Level 10 (Human Interface & System Operations) tool with installergit:https://github.com/comfy-org/comfyui, launcherpython main.py --listen 0.0.0.0 --port 8188, deps[cuda], license GPL-3.0. Total registry size is now 187 tools (was 186).
Runtime: bandwidth-aware git-pull-on-update
runtime/installer.py's install_git and install_git_node already
ran git pull --ff-only if the destination dir existed, but the
existence check was os.path.exists(dest) — True for any dir, not
just a git working tree. An empty or non-git dir at the destination
would cause git pull to fail and abort the install. Hardened:
- Check
os.path.isdir(dest/.git)to confirm a real git repo. - On pull failure (diverged branches, network errors, corrupted
index, etc.), move the old dir aside to
<dest>.bak.<unix_ts>and re-clone fresh — so the install always ends in a usable state instead of leaving a half-broken tree. - If the dest dir exists but is not a git repo, back it up and clone fresh.
- Log clearly which path was taken (pulled / re-cloned / new clone / non-git-dir backed up).
Effect: re-running install on a tool that's already cloned only fetches the diff (a few KB / MB), not the entire repo. This is the bandwidth-saving behaviour the user asked for.
Intentionally NOT touched
apex(md sayspip — https://github.com/NVIDIA/apex): kept asuv:apex(pip-installed). The md'spipinstaller type is authoritative and the user did not call this one out.- 12 git-ish installers that already matched the v12 reference
(
anythingllm,dify,heretic,homelab,invokeai,koboldcpp,librechat,mcp_drift_state_tracker,openjarvis,paperlessngx,synapscli,wayland_ai) — no change needed. - 3 registry-only git tools with real-looking URLs already in place
(
goose→block/goose,nvidia_agent_skills→NVIDIA/agent-skills,picode→jasonjmcghee/picode.git) — no change needed.
Versioning & docs
pyproject.toml: 3.2.0 → 3.3.0src/ai_lsc/constants.py:APP_VERSION3.2.0 → 3.3.0 (APP_CODENAMEunchanged:"Decalogue")- New file:
docs/REGISTRY-URLS-v3.3.0.md(per-file old → new URL table, also serves as a release audit trail). gitcommitrewritten for v3.3.0.
Verification
- All 94 Python files under
src/AST-parse cleanly. - Registry extractor re-imports every patched layer module and confirms all 52 patches landed + the new ComfyUI entry. Total registry size: 187 tools (was 186).
- Each old
pkgURL string was unique within its file and matched exactly once — no blind global replaces. - Each script-type pkg-field injection matched exactly one installer block (Python AST-parses after each injection).
install_git/install_git_nodehardened: the new logic branches onos.path.isdir(.git), falls back to re-clone on pull failure, and usesimport timefor backup-dir timestamps. All confirmed present inruntime/installer.py.- 0
nicely-doneplaceholder URLs remain in the registry.
v3.2 — 10-Layer Systems Architecture Taxonomy completed
Finishes the taxonomy re-org that was started (but stalled) on top of
v3.1.1b: the whole codebase now speaks the unified 10-layer taxonomy
from the systems-architecture review (POSIX/Unix philosophy, bare-metal
process hierarchy). The partial migration had already rewritten
constants.py (NAV_LAYER_ORDER) and db_manager.py (CATEGORY_MAP); this
release completes every remaining surface, repairs the damage the
interim tooling caused, and reconciles the registries. Validator
reports 0 errors on both the 108-tool defaults seed and the 186-tool
merged layer registry; wiring graph validates with 0 errors.
New layer ladder (11 → 10)
L1 Host Platform & Infrastructure L6 Multi-Agent Orchestration Runtimes
L2 Development Runtime & Environment L7 Agentic Software Engineering & Sandboxes
L3 GPU Acceleration & Optimization L8 Decentralized Knowledge & Vector Stores
L4 Local Inference Engines L9 Data Extraction & Pipeline Harvest
L5 Intelligent API Routers & Proxies L10 Human Interface & System Operations
The 11-layer model's Security and Observability strata dissolve: security daemons (keycloak, vault, fail2ban, edge/proxy daemons) are host platform infrastructure (L1); scanners/policy/audit tooling (trivy, clamav, opa) and telemetry (btop, grafana, prometheus, opik) are system operations (L10). Routing merges into Intelligent API Routers & Proxies (L5). Vector/graph stores and agent memory move out of Knowledge Management into L8; document/crawl/transcode pipelines become L9; IaC and cluster automation land in L10.
Registry
registry/defaults.pyrebuilt as the 108-tool 10-layer seed from the master-target registry: newlevel/layer/role/categoryplus the richer descriptions. Operational metadata (real installer pkgs/URLs and cmds,post_install/update_cmd/env_overrides, launchers with correct binary names and systemd-for-daemons, dependency edges, curated flags, SPDX licenses,filesystemblocks) is preserved from the previous registry — the master-target file had systematically replaced installers with placeholder URLs, dropped script cmds (validator errors), wiped all dep edges, and renamed binaries (rg→ripgrep, systemd → tmuxservestubs). curl|sh installer policy markers are untouched (see whatremains.txt, C-05).- 11 modular layer files realigned: 185 tools migrated to the 10-layer
taxonomy (107 shared tools take their structural fields from the
master registry; the other 78 classified explicitly — see
scripts/apply_10layer_taxonomy.pyfor the classification table). kanbanreconciled: it existed only indefaults.py(the canonical source is the layer files), so it is now declared inlayers/user_interfaces.pyas well (L10, Sprints Manager). Merged registry: 185 → 186 tools.n8nkeeps its explicit L10 placement (visual flow canvas) and moves to theWorkflow(Visual Builder) category so the categorisation cascade agrees with the registry.n8n/odysseusfilesystemblocks (previously only in the old defaults) are preserved inlayers/orchestrators.py.- The
open_webui/openwebuiorphan-flagging gap from the TODO is confirmed moot: onlyopenwebuiexists and it is wired.
Stack wiring
stack/connections.py: 92 staticStackWiring(layer=...)values migrated to the 10-layer names; the 5 loop-based bulk allocations now resolve layers dynamically via_wiring_layer(_tid)(DEFAULT_REGISTRY lookup +_WIRING_LAYER_SUPPLEMENTfor wired tools outside the seed registry), so the wiring graph can never drift from the taxonomy again. Section header comments map the old L1–L13 sections to their new homes. Wiring-vs-registry layer mismatches: 0 across 137 wirings.
UI
ui/pages/db_manager.pyrepaired and migrated: the interim migration had truncated the file from 1300 to 222 lines (syntax error at the CATEGORY_MAP tail; the entire DB-manager dialog/table implementation was lost). Restored in full from the v3.1.1b routing tarball, then its CATEGORY_MAP was migrated to the 10-layer taxonomy.- Categorisation cascade extended to 221 categories: the master map (105) + 43 categories for the classified tools + 73 preserved categories from the v3.1.1b cascade, translated to the 10-layer taxonomy with their curated roles. Every category used by the 186 registry tools is covered; cascade and registry agree on layer for every tool.
ui/pages/db_manager_category_map.pyregenerated to match.registry/validator.pynow enforces level range 1–10.
Docs & tooling
- README layer table + tool counts updated to the 10-layer taxonomy (186 tools); quickstart's "13-layer architecture" reference fixed.
- Layer-file module docstrings note the 10-layer realignment.
scripts/apply_10layer_taxonomy.pyadded (the corrected, completed migration utility — supersedes the rootapply_taxonomy_migration.py, whose regexes could not match multi-word layer names and whose defaults handling was lossy; kept for history).
Verification
scripts-style checks all pass: 100 Python files AST-parse; validator
0 errors (defaults + merged + wiring graph); level/layer consistency
holds across defaults, layer files, CATEGORY_MAP, and STACK_WIRINGS;
NAV_LAYER_ORDER matches the taxonomy; guardrails clean; headless
package import + RegistryManager first-boot/second-run bootstrap
verified (186 tools seeded to ecosystem.json, structural sync clean).
v3.1.1b — taxonomy re-org: Routing restored (11 layers), canonical 24-dir /mnt/AI layout
Restores the Routing layer that was lost when the 13-layer model was
compressed to 10 (both "L6 AI Endpoints" and "L11 Intelligent Routing"
had been folded into Orchestrators), fixes the mis-categorized tools,
and aligns the backend to the revised canonical /mnt/AI/ directory
tree. No tool count change (185 tools); validator still reports 0 errors.
New layer ladder (10 → 11)
L1 Host Platform L7 Security
L2 Development Env L8 Observability
L3 GPU Runtimes L9 User Interfaces
L4 Engines L10 DevOps
L5 Routing (restored) L11 Knowledge Management
L6 Orchestrators
NAV_LAYER_ORDER inserts Routing between Engines and Orchestrators —
engines serve weights, routing proxies/load-balances/meshes them into
one OpenAI-compat endpoint, orchestrators build agent workflows on top.
Tool moves (18 tools, all layers/levels updated)
| tool_id | Old | New |
|---|---|---|
litellm |
L5 Orchestrators (Proxy / API Gateway) | L5 Routing |
9router_proxy |
L5 Orchestrators (LLM Router) | L5 Routing |
meshllm |
L5 Orchestrators (LLM Mesh) | L5 Routing |
dify |
L5 Orchestrators (Pipeline) | L5 Routing |
picode |
L5 Orchestrators (AI Coding Agent) | L5 Routing (category → Mesh Client) |
vllm |
L5 Orchestrators (Scaling) | L4 Engines (role → Engine; ADR-001 always listed vLLM under Inference Engines) |
sglang |
L5 Orchestrators (Scaling) | L4 Engines (role → Engine) |
eagle_eye |
L5 Orchestrators (category literally "Observability") | L8 Observability |
dma |
L5 Orchestrators (Build Monitoring) | L8 Observability |
aider, claude_code, codex, openhands, opencode, gemini_cli, qwen_code, goose, zcoder |
L5 Orchestrators (AI Coding Agent) | L10 DevOps — matches the DB editor's AI Coding Agent → DevOps mapping, which previously conflicted with the registry |
Remaining tools in the affected layers were renumbered (+1): 38 stay in
Orchestrators (L6), Security L7, Observability L8, User Interfaces L9,
DevOps L10, Knowledge Management L11. Per-layer level uniformity
verified. picode's category changed AI Coding Agent → Mesh Client
so the category cascade cannot silently flip it back to DevOps.
Tool DB editor (CATEGORY_MAP)
- All entries renumbered to the new ladder; map is now fully consistent with the live registry (0 layer/level mismatches).
- 13 new categories so every registry category auto-fills correctly:
LLM Mesh,Mesh Client(Routing);Observability(Observability);Build,Debugging,Shell,Claude Code Skill,Container Ops,Infrastructure,Memory System,Model Surgery,Networking,Virtualization(previously missing entirely). - Fixed conflicts:
AI Coding Agent→ DevOps/L10 (roleCoding Agent, wasAutonomous Coderat L9),Build Monitoring→ Observability/L8,LLM Serving→ Engines/L4,LLM Router/Proxy/Pipeline→ Routing/L5.
STACK_WIRINGS (stack/connections.py)
- 29 wiring
layer=labels synced to the registry: the 13 moved tools with wirings, plusdeep_eye/luxtts(UI tools mislabeled Orchestrators) and 14 pre-existing drifts (heretic,unsloth,parakeet,fabric,n8n,nightshift,hivemind,hermes_agent,agno,hermes_dashboard_page,mnemo_cortex,everos_memory,langflow,opensandbox). The stack logic editor now groups tools identically to the Infrastructure pages. - Stale section comments annotated:
L6: AI Endpoints→ the restored Routing layer;L10: Intelligent Routing→ folded into Orchestrators.
Canonical /mnt/AI/ layout (26 → 24 dirs)
REQUIRED_DIRS now matches the target tree exactly:
- Added
configs/— app configs templated for native runtime plus app state.main_window.config_rootmoves from legacyconfig/toconfigs/, takingpipeline_state.json,pipeline.json,license_approvals.jsonwith it;controller_config.jsonmoves off the/mnt/AI/root intoconfigs/. A one-time_migrate_legacy_state_files()pass moves old files on startup (newer copies win; emptied legacy dirs are removed). - Dropped from the skeleton:
bootstraps/ai-lsc,staging,registry/manifests.registry/remains as app-internal storage forecosystem.jsonand manifests — auto-created on demand byRegistryManager, no longer part of the canonical tree. paths.py:configs_rootadded;staging_root/bootstraps_rootremoved; the deprecatedconfig_root → runtimealias now points atconfigs/. Per-tool config subdirs (configs/<tool>/) are still created on demand byInstallerManager.license_gate.py/licenses.pydoc references updated toconfigs/license_approvals.json.
Existing installs pick up all taxonomy changes automatically:
RegistryManager._sync_with_upstream() re-syncs structural fields
(layer, level, role, category) from the layer files on every start.
v3.1.1a — registry validator fixes (5 errors → 0)
Fixes all 5 validate_registry() errors plus latent bugs surfaced
during the fix pass. No tool count change (185 tools).
Installer fixes (validator errors)
All five failed the "script installer cmd should reference {tools_root}" check — they installed into system dirs or the cwd:
| tool_id | Before | After |
|---|---|---|
firecracker |
extracted into /usr/local/bin/ (and silently broken: versioned tarball dirs never landed on PATH) |
extracts into {tools_root}/firecracker/, symlinks firecracker + jailer into {tools_root}/bin/ |
cloudflared |
downloaded to /usr/local/bin/cloudflared |
downloads to {tools_root}/bin/cloudflared |
llamafile |
downloaded to cwd (mismatched launcher, which already expected {tools_root}/bin/llamafile) |
downloads to {tools_root}/bin/llamafile |
meilisearch |
curl | sh dropped binary in cwd |
runs installer inside {tools_root}/bin/ (official installer places the binary in the cwd) |
grafana_alloy |
install.sh | sh — URL dead (404): upstream dropped the script |
direct release asset alloy-linux-amd64.zip, extracted via python3 -m zipfile (no unzip dependency) |
Launcher fixes (latent bugs)
firecracker,cloudflared,grafana_alloylaunchers now use absolute{tools_root}/bin/<name>paths. Rationale:tools_root/binis on PATH at install time (installer._env()) but not at launch time (enriched_env()builds PATH frombase_bin_dir= uv/npm bins only), so bare-name launcher cmds would pass preflight then fail with "command not found". Absolute paths are immune to the gap.- firecracker version discovery uses the Location header
(
curl -sIL … | grep -i '^location:') instead of-w '%{url_effective}'— launcher/installer cmds are rendered withstr.format(), and the{url_effective}braces would raiseKeyErrorat render time.
Dependency fixes (phantom missing-dep warnings)
difydeps:node→nodejs(the actual registry tool_id).RegistryManager.check_dependencies()now allows system-level deps (kubectl,java) via aSYSTEM_DEPSfrozenset, mirroring the existing_system_depspattern instack/connections.py. Previouslycrossplane(kubectl) andkeycloak(java) produced permanent "missing dependency" warnings that could never be satisfied.
Not fixed (observations, no behavior change)
- 9 duplicate
default_portdefaults across tools (e.g. 3000 shared by grafana/opik/openhands/flowise). These are overridable per service row; leaving as-is unless the stack compiler should auto-assign. TODO.md'sopen_webui/openwebuisplit no longer reproduces: onlyopenwebuiexists in both the registry andSTACK_WIRINGS.
v3.1.1 — local-coder-mesh integration (this build)
Adds 4 new tools, corrects 1 existing tool, adds 4 new STACK_WIRINGS
entries (and rewires 1), adds 1 new stack template, and aligns all
hardcoded /mnt/AI/ paths to the canonical 26-directory layout. No
containers in the dev path — every tool installs natively. ai-lsc's
Podman/Docker/LXC/Firecracker export is reserved for total-stack
deployment exports only, as before.
New tools (181 → 185)
| tool_id | Layer | What | Install |
|---|---|---|---|
picode |
L5 Orchestrators | PiCode (jasonjmcghee/picode) — local code-tinker agent | git clone |
meshllm |
L5 Orchestrators | MeshLLM (Mesh-LLM/mesh-llm) — native binary, pools GPUs/memory across machines, exposes OpenAI-compat API at :9337, web console at :3131. NOT a LiteLLM derivative. | script (official curl installer) |
zcoder |
L5 Orchestrators | Zhipu AI Z-Coder CLI coding agent | npm zcoder-cli |
hermes_webui |
L8 User Interfaces | Hermes-themed Open-WebUI instance on :8081 with its own data volume; backend points at hermes_agent (:17051) instead of Ollama direct |
uv open-webui |
Corrected tool
graphify was already in the registry but had wrong metadata. Fixed in place:
| Field | Old | New |
|---|---|---|
role |
Graph Builder |
Knowledge Graph Builder |
category |
AI Agent |
Claude Code Skill |
installer |
git: nicely-done/graphify |
uv: graphifyy (PyPI; CLI is graphify) |
license |
Proprietary |
MIT (verified from pyproject.toml) |
flags.has_web |
False |
True (graph.html output) |
flags.is_mcp |
False |
True (graphify --mcp stdio server) |
description |
1 line generic | 10 lines accurate (CLI + Claude Code skill + MCP server + LLM backend options) |
Graphify's wiring also changed — see below.
New STACK_WIRINGS entries (133 → 137)
| tool_id | Exposes | Consumes |
|---|---|---|
picode |
(none — CLI agent) | meshllm (primary), litellm (fallback), ollama (direct fallback) |
meshllm |
openai_api (:9337) + mesh_web_console (:3131) |
ollama (optional, for mesh-llm client --auto mode) |
zcoder |
(none — CLI agent) | meshllm (primary), litellm (fallback), ollama (direct fallback) |
hermes_webui |
hermes_webui_http (:8081) |
hermes_agent (required, primary backend), ollama (optional, for RAG embeddings) |
Rewired entry
graphify removed from the L8 passive/CLI list and given a proper
_reg(StackWiring(...)) block:
- Exposes:
graphify_mcp(stdio MCP server, no port) — start withgraphify --mcp. Other MCP-aware agents can query the knowledge graph. - Consumes (all optional, fallback chain for the extraction LLM):
meshllm(:9337/v1),litellm(:4000/v1),ollama(:11434/v1). Graphify defaults to Claude (Anthropic API) but can be configured for fully-local extraction by settingOPENAI_API_BASEto any of the above.
New stack template (13 → 14)
local-coder-mesh.json — "Local Coder Mesh — All-Ollama Coding Stack".
17 tools: ollama, litellm, meshllm, picode, aider, odysseus, opencode,
zcoder, graphify, hermes, hermes_agent, hermes_webui, hermes_desktop,
openwebui, ripgrep, fd, tree_sitter.
Topology: coding agents prefer MeshLLM (:9337) for mesh-pooled inference, fall back to LiteLLM (:4000) for proxy routing, then Ollama direct (:11434). Graphify builds knowledge graphs from the codebase and exposes an MCP server that the coding agents query. Hermes WebUI talks to hermes_agent (NOT Ollama direct) so every Hermes conversation flows through the agent runtime's tool-use layer. OpenWebUI talks to Ollama direct. ripgrep + fd + tree_sitter are passive filesystem tools used by the coding agents for repo-map / symbol navigation.
Recommended models: qwen2.5-coder:7b (fast coding),
qwen2.5-coder:32b (heavy coding), hermes3:8b (Hermes stack),
nomic-embed-text (openwebui RAG, corpus indexing, graphify embeddings).
MeshLLM auto-downloads a suitable model on first serve --auto if none
is specified.
Path alignment to canonical /mnt/AI/ layout
Three files updated so ai-lsc's hardcoded paths match the 26-directory canonical layout:
src/ai_lsc/constants.py—REQUIRED_DIRSreplaced with the 26 canonical entries (bootstraps/ai-lsc,staging,backends,distfiles,runtime,models/hot,models/cold,corpus/hot,corpus/cold,datasets/wordlists,datasets/huggingface,datasets/github,pipelines,registry/manifests,agents,skills,projects/active,projects/labs,projects/vault,blueprints,workspaces,dashboards,tools,exports/oci-images,scripts,logs). Old layout dirs (config,cache,data,containers,bin,tmp,backups,models/ollama,models/chroma,datasets/raw,workspaces/hermes,workspaces/openwebui,workspaces/n8n) are NOT removed — they just become orphans. Clean them up manually if desired.src/ai_lsc/utils/paths.py—build_path_tree()expanded from 10 keys to 24 keys. Old keys kept and repointed at canonical subdirs. New keys added:runtime_root,models_hot,models_cold,corpus_root,pipelines_root,agents_root,projects_root,blueprints_root,dashboards_root,scripts_root,backends_root,distfiles_root,staging_root,bootstraps_root. Theconfig_rootkey is kept as a deprecated alias pointing at/mnt/AI/runtime/so existing callers don't break — new code should usetools_root / <tool_id> / "config"orruntime_root / <tool>explicitly.src/ai_lsc/agents/litellm_config.pyandsrc/ai_lsc/agents/librechat_config.py— hardcoded save paths moved from/mnt/AI/config/litellm_config.yamland/mnt/AI/tools/librechat/librechat.yamlto/mnt/AI/runtime/litellm/config.yamland/mnt/AI/runtime/librechat/config.yamlrespectively. Per-tool configs for long-running services belong underruntime/<tool>/next to their venv / cloned repo, since the spec has no top-level/mnt/AI/config/.
Backfill script path fixes
Three helper scripts in scripts/ had stale hardcoded absolute paths to
/home/z/my-project/workspace/ai-lsc (a developer machine path that
leaked into the v3.1 release). Replaced with
Path(__file__).resolve().parent.parent so they resolve to the project
root regardless of where the tarball is extracted:
scripts/backfill_default_licenses.pyscripts/backfill_layer_flags.pyscripts/backfill_tool_licenses.py
Verification (run against this build)
Registry: 185 tools (was 181)
Wirings: 137 entries (was 133)
Wiring validation errors: 0
Registry validation errors: 5 (all pre-existing in upstream v3.1 —
firecracker, cloudflared, llamafile, meilisearch, grafana_alloy —
none introduced by this build)
meshllm installer: uses {tools_root}/meshllm/bin (passes validator)
Templates: 14 (was 13) — local-coder-mesh added
Template tool resolution: 17/17 tools resolve in registry
build_path_tree() keys: 24 (was 10)
REQUIRED_DIRS entries: 26 (was 22)
graphify installer: uv:graphifyy (was git:nicely-done/graphify)
graphify license: MIT (was Proprietary)
graphify is_mcp flag: True (was False)
meshllm interfaces: ['openai_api' on :9337, 'mesh_web_console' on :3131]
graphify interfaces: ['graphify_mcp' stdio]
Native-only install policy
Every tool in the new local-coder-mesh template installs natively into
/mnt/AI/runtime/<tool_id>/ (venv via uv/pipx) or via pacman/AUR/curl-script.
ai-lsc's container export feature (Podman / Docker / LXC / Firecracker)
is intentionally NOT used at install time — it's reserved for total-stack
deployment exports via the Stack Editor, exactly as in v3.1.
Rollback
To revert this entire build to upstream v3.1, restore from git or re-extract the original tarball. There is no separate "patch pack" to unapply — this is the integrated project.
v3.1 — registry expansion: 2026-era coding agents, serving, and runtimes
Adds 11 tools to the layer registry (170 → 181) and 10 STACK_WIRINGS entries (123 → 133), and backfills 6 missing OSI licenses into the catalog. All facts (npm/PyPI package names, default ports, licenses) were verified against current upstream docs.
New tools
| tool_id | Layer | What | Install |
|---|---|---|---|
deno |
L2 Development | JavaScript/TypeScript/WASM runtime; runs many MCP servers via deno run |
pacman deno |
uv |
L2 Development | Astral's Python package/project manager (AI-LSC's own install backend) | pacman uv |
tinygrad |
L3 GPU Runtimes | Minimalist autograd tensor library (CUDA/AMD/CPU backends) | uv tinygrad |
opencode |
L5 Orchestrators | SST's open-source terminal AI coding agent (TUI, LSP, 75+ providers) | npm opencode-ai |
gemini_cli |
L5 Orchestrators | Google's open-source terminal AI agent | npm @google/gemini-cli |
qwen_code |
L5 Orchestrators | Qwen's agentic terminal coding tool (Gemini CLI fork) | npm @qwen-code/qwen-code |
goose |
L5 Orchestrators | Block's extensible AI agent with MCP extensions | manual (opens releases page) |
letta |
L5 Orchestrators | Stateful agent framework (MemGPT) with persistent memory; letta server on :8283 |
uv letta |
sglang |
L5 Orchestrators | Fast LLM serving with RadixAttention; OpenAI-compat API on :30000 | uv sglang |
jan |
L8 User Interfaces | Offline ChatGPT-alternative desktop app; OpenAI-compat local API on :1337 | npm @janhq/jan |
mem0 |
L10 Knowledge Mgmt | Memory layer for AI apps/agents; pluggable vector backends | uv mem0ai |
goose uses installer type custom deliberately: its official install path is a curl | sh one-liner, which conflicts with the v3.1 no-remote-code-execution policy. The custom installer opens the GitHub releases page for a manual, download-first install instead. deno and uv are intentionally left unwired (language runtimes, consistent with nodejs/python).
Wiring topology (Pipeline Ticker)
- Terminal coding agents wired:
opencode,gemini_cli,qwen_code,goose, and the pre-existingcodexeach gained connections to Ollama (direct) and LiteLLM (proxied) — staging either backend now prevents orphan-flagging.codexhad been an orphan since v3.1 because it had noSTACK_WIRINGSentry. sglangmirrors the vLLM wiring: exposesopenai_apion :30000, consumescuda_driver.lettaexposes its REST API on :8283 and optionally consumes PostgreSQL (LETTA_PG_URI) + Ollama.janexposesopenai_apion :1337 (bundled llama.cpp engine).mem0optionally consumes Ollama (LLM + embeddings) and Qdrant (vector storage).tinygradoptionally consumescuda_driver(it also runs on CPU/AMD).validate_wiring()reports 0 errors across all 133 wirings.
License catalog backfill (fixes 8 pre-existing validator errors)
The following SPDX IDs were referenced by layer files but missing from registry/licenses.py, producing license is not in the license catalog validation errors: LGPL-2.1 (strace, lxc, libvirt), GPL-1.0 (perl), PHP-3.01 (php), Ruby (ruby), MirOS (mksh), MIT/Apache-2.0 (rust). All six are OSI-approved and are now catalog entries (auto-approvable). The merged registry validates with 0 errors — the only remaining messages are the 5 documented script installer cmd should reference {{tools_root}} warnings tied to the curl|sh policy decision (llamafile, meilisearch, grafana_alloy + the newer firecracker, cloudflared).
Docs
README tool counts and the layer table were refreshed to the real merged-registry numbers (they had been stale since the DevOps→Orchestrators reorg moved the coding agents).
v3.1 — 2026-07-07
Codename: Ankh of Jah (continuation)
The v3.1 release is a hardening + polish pass on top of v3.0. It applies the full master code critique (91 of 93 findings addressed), introduces two new UI widgets (Pipeline Ticker + Workspace Tab), and ships three latent-bug fixes that were caught during the post-pass double-check.
New features
Pipeline Ticker
A horizontally scrolling status bar at the top of every workspace tab. Reads the active-tools set from pipeline_state.json, joins it against the STACK_WIRINGS topology in stack/connections.py, and renders:
ollama ──openai_api──▶ litellm ──openai_api──▶ aider ❗ open_webui
- Color-coded arrows by interface type — blue for
openai_api, green forvector/vector_search/embedding, orange forredis_pubsub/redis_cache, purple forpostgresql/mariadb/mysql, teal forhttp_api/websocket/grpc, slate forfilesystem/tmux_socket/systemd_unit, red forcuda_driver. - Orphan detection — any active tool that has no in-stack wiring to another active tool is flagged red with an
❗prefix. This surfaces real registry gaps (e.g. theopen_webuivsopenwebuitool_id collision). - Running-state coloring — pills are blue when the tool is running, white when stopped. The ticker refreshes on every 2 s service-status poll so state changes propagate immediately.
- Hover-pause — mouse over the ticker to stop the scroll so you can read a long flow.
- Click-to-jump — click any tool pill to switch to the Tools tab; the matching row is selected, scrolled to center, and flashed amber for 1.5 s.
Files: src/ai_lsc/ui/widgets/pipeline_ticker.py (new, ~330 lines), src/ai_lsc/ui/main_window.py (wiring), src/ai_lsc/ui/pages/tools_tab.py (new highlight_tool() method), src/ai_lsc/ui/pages/service_row.py (new is_running_now() cache method).
Workspace Tab
A new Workspace nav entry (between Chat and Git Sources) that provides virt-manager / aqemu–style peek orchestration. Every active tool gets its own sub-tab:
- 🌐 Web tools (
has_web=True) embed viaQWebEngineViewathttp://127.0.0.1:{port}. A URL bar at the top shows the loaded URL; ⟳ reloads, ↗ opens in an external browser. No need to leave the app for a browser. - ⌨ CLI tools (
has_cli=True, no web) attach to their tmux session viatmux capture-pane -t <session>::<tool_id> -p -S -200polled at 4 Hz. The terminal is read-only — use a real terminal app for interactive input. - 📦 Passive / library tools get a placeholder explaining they have no interactive surface.
- ⏸ Not-yet-running tools show a Start tool button that wires back to the existing
ServiceRow.start_service()flow. After 1.5 s the workspace tab auto-refreshes so the placeholder → live view switch happens automatically. - Sub-tabs are closable; closing a sub-tab stops the polling but does NOT stop the underlying tool (use the Stack Editor for that).
Servo note: Web embedding uses PySide6.QtWebEngineWidgets.QWebEngineView by default. Swapping in Mozilla's servo engine later is a one-line change to _make_web_view() in src/ai_lsc/ui/widgets/workspace_tab.py — the rest of the WorkspaceTab code only depends on the setUrl() / url() / load() API.
Files: src/ai_lsc/ui/widgets/workspace_tab.py (new, ~310 lines), src/ai_lsc/ui/main_window.py (new _build_workspace_orchestration_page + nav entry + refresh hooks).
Security & reliability hardening (the critique pass)
91 of 93 findings from the master code critique were addressed. See whatremains.txt for the two intentionally-skipped items and deferred polish. Highlights:
CRITICAL (6 of 7 fixed; C-05 skipped per user)
- C-01
runtime/process.py—shell=Trueremoved fromlaunch_desktop,launch_terminal,kill_by_name,docker_compose_down. New_to_arg_list()helper accepts either a pre-split argv list or a single shell-style string (split viashlex.split). - C-02
runtime/systemd.py—shell=Trueremoved fromstart,stop,is_active.is_activenow has a 5 s timeout. - C-03
runtime/tmux.py— 5shell=Truesites converted to list-form argv. New_validate_name()rejects shell-metacharacter session/window names. New_socket_path_for()moves tmux sockets out of/tmpinto$XDG_RUNTIME_DIR/ai-lsc/.SESSIONis now user-scoped:ai_lsc_<uid>. - C-04
runtime/installer.py— 15+shell=Truesites converted to list-form argv. Post-install hooks and script-type installers now run via["bash", "-c", cmd](still a shell, but the registry string is passed verbatim as a single argv element so it can't break out of the subprocess call itself). - C-05
curl|shremote installers — SKIPPED per user instruction. The Ollama / Grafana Alloy / Meilisearchcurl … | shpatterns remain inruntime/installer.py:361,registry/layers/inference.py:30,registry/layers/observability.py:122,registry/layers/data_knowledge.py:164, andregistry/defaults.py:642. - C-06
agents/librechat_config.py— hardcodedsk-ai-lsc-localandsk-localAPI keys removed. Keys now read fromAI_LSC_LITELLM_KEY/AI_LSC_OPENWEBUI_KEYenvironment variables via a new_env_api_key()helper. - C-07
agents/orchestrator.py— missingimport osadded (was a guaranteedNameErroron everyAgentOrchestratorinstantiation).
HIGH (24 of 24 fixed)
- H-01 Path-traversal via unsanitized
tool_id— new_validate_tool_id()inruntime/executor.pyrejects..,.,/, and shell metacharacters. - H-02
os.getcwd()for config —_load_configandsave_configinui/main_window.pynow resolve againstself.base_dirinstead of the cwd the app was launched from. - H-03 Atomic JSON writes — new
_atomic_write_json()helper inui/main_window.pyusestempfile.mkstemp+os.fsync+os.replace. Applied to stack export, config save, and stack-wizard state save. - H-04 Popen reference tracking —
ProcessManagernow tracks every launched child inself._launchedand exposesreap()+shutdown()methods. Zombie accumulation in long-lived GUI sessions is fixed. - H-05
pull_modelPopen not killed on thread crash —agents/dispatcher.py:_pull_modelnow wrapsproc.communicate()intry/exceptwithproc.kill()in the handler. - H-06 JSON parse crash from malformed LLM output —
agents/agent_loop.pynow wrapsjson.loads(func.get("arguments", "{}"))intry/except json.JSONDecodeError. The parse error is fed back to the LLM as a tool result so the model can self-correct on the next round. - H-07
_pull_modelNone guard —dispatcher._pull_modelnow handles the case whereruntime.pull_model()returnsNone. - H-08 Duplicate
list_available_toolsschema —agents/tool_bridge.py:generate_all_schemasnow filters out the staticlist_available_toolsschema before appending the annotated version. - H-09 Broad
except Exception— 12+ sites acrossguardrails.py,loader.py,manifest/support.py,agent_loop.py,ollama_tools.py,service_row.py,chat/api.py,model_pool.py,qdrant_bridge.pynow catch specific exceptions (OSError,ValueError,json.JSONDecodeError,subprocess.SubprocessError,urllib.error.URLError, etc.). - H-10 Exception messages expose internal details —
chat/api.pynow sanitizes error bodies via_short_reason()and_categorize_url_error(). Full detail is logged server-side. - H-11 LXC container name from unsanitized
tool_id— new_validate_lxc_name()and_validate_tool_id()inruntime/lxc.py. - H-12 LXC
attach_execdestroys quoting —command.split()replaced withshlex.split(command). - H-13 Redis lock silently bypassed when down —
agents/redis_bridge.py:acquire_lockandrelease_locknow log a WARNING when Redis is unreachable so operators know concurrent agents could race. - H-14
_enforce_qualityfalse-positive error detection —agents/orchestrator.pynow uses a word-boundary regex with a negative lookahead(?![\w\-])so hyphenated compounds likeerror-correction module initializedare not flagged. - H-15 Registry layer files have incomplete flag schemas —
registry/validator.pynow enforces the full 8-key flags schema (has_cli,has_gui,has_web,is_ollama,is_docker,is_passive,is_mcp,is_skills_collection).scripts/backfill_layer_flags.pybackfilled 123 flag blocks across all 13 layer files. - H-16
_inject_skill_stubreturns fake success —agents/dispatcher.pynow validates the skill exists before reporting success. - H-17 Placeholder resolution triple-copy in
export.py— new_resolve_placeholders()helper replaces three duplicated 6-line.replace()chains ingenerate_compose_yaml,generate_lxc_configs, andgenerate_firecracker_configs. - H-18 Ollama
/api/toolsendpoint does not exist —agents/ollama_tools.pyregister_allandregister_singleare now gated behind_registration_supported = Falsewith a clear warning. Tool schemas are passed inline to/api/chatinstead. - H-19 No port validation on user input — new
_validate_port()inruntime/executor.pyenforces1 ≤ port ≤ 65535. UI surfaces a cleanValueErrormessage instead of a cryptic URLError. - H-20 No URL scheme validation in
install_custom— new_validate_url()inruntime/installer.pyrejects non-http(s) schemes. - H-21 No signal handling on parent exit —
ui/main_window.py:closeEventnow callsruntime._process.shutdown()to terminate every tracked child. - H-22
install_customopens arbitrary URLs — same fix as H-20. - H-23 Thread-unsafe pull lock in model pool —
agents/model_pool.pyself._pull_lock = False(plain boolean) replaced withthreading.Lock(). Non-blocking acquire so concurrent agent threads don't both enter the pull branch. - H-24 Qdrant collection dimension hardcoded —
agents/qdrant_bridge.pycreate_collectionnow probes the live embedding dimension via_probe_embedding_dimension()instead of hardcoding768. Existing collections with a different dimension are surfaced (not silently hidden).
MEDIUM (42 of 42 fixed)
- M-01 Missing
encoding="utf-8"onopen()— fixed in 15+ sites acrossui/main_window.py,runtime/lxc.py,ui/dialogs/stack_wizard.py. - M-02
os.path.joinmixed withpathlib—utils/filesystem.py:walk_treenow usesPath.rglob. - M-03 TOCTOU race in log file operations —
ui/main_window.pylog readers now wrap stat + read in a singletry/except OSError. - M-04 No file locking on shared JSON files —
_atomic_write_json()now usesfcntl.flockfor cross-process serialization. - M-05 No file lock / fsync on LXC config append —
runtime/lxc.py:_apply_confignow flushes + fsyncs. - M-06
/tmpsocket path without cleanup —runtime/tmux.py:_socket_path_formoves sockets under$XDG_RUNTIME_DIR/ai-lsc/with sanitized tool_id. - M-07 through M-13 Nested-if flattening — applied via guard clauses, dict dispatch, reverse-lookup dicts, and predicate extraction across
installer.py,orchestrator.py,model_pool.py,guardrails.py,main_window.py. - M-14 through M-17 Duplicate code extraction —
_iter_py_files/_read_sourceinguardrails.py,_scan_and_import/_match_tagspatterns,_cache_set/_cache_getinredis_bridge.py,_resolve_placeholdersinexport.py. - M-18 through M-23 Loop → comprehension / builtin —
dict.fromkeys()for dedup, dict comprehension forpreflight_batch,next()fordetect_terminal, list comprehension for LXC config lines,extend()for skills_loaded. - M-24 Dead variable
exposedinget_consumers— removed. - M-25
Anytype annotations —ui/protocol.pynow usesTYPE_CHECKINGimports;agents/orchestrator.pydispatcherparam now typed as"AgentDispatcher". - M-26 Variable shadowing in
generate_env_file—linesrenamed toollama_ep. - M-27 Dead if/else block in
guardrails.py— removed (PARENT_ALLOWED_DIRS was always empty). - M-28 Redundant
import jsonin orchestrator method — removed. - M-29 Sorted-set member collision in task queue —
redis_bridge.pynow usestask_idas the sorted-set member and stores the payload in a separate hash. - M-30
_LAYERS_DIRdefined but never used — removed fromregistry/loader.py. - M-31
use_modeltautological assignment — cleaned up. - M-32 Missing error handling on
install_pip— addedtry/except subprocess.CalledProcessError. - M-33 / M-34 Timeouts on
systemctl is-activeandpkill— both now havetimeout=5. - M-35 File handle leak in
verify_and_watch—open(log_file, "a").close()replaced withPath(log_file).touch(). - M-36 Recursive directory traversal —
utils/filesystem.py:walk_treerewritten to usePath.rglob. - M-37 Model pool pull timeout applies to entire stream —
for line in resp: passreplaced withresp.read(). - M-38 Embed batch is sequential —
qdrant_bridge.py:embed_batchnow usesThreadPoolExecutor.map. - M-39 Hardcoded
pacmaninstall hint —_install_hintinruntime/lxc.pynow lists pacman + apt + dnf. - M-40 Nested ternary in
_build_payload_history— deferred (chatbot_console.py), see whatremains.txt. - M-41 Duplicate
OpenEngineerImporterimport — removed fromstack_templates/manager.py. - M-42
_load_skillsdocuments unimplemented Qdrant feature — converted to explicitTODO(security)comment.
LOW (19 of 20 fixed; 1 deferred)
See whatremains.txt for the full list. Highlights:
- L-02 Line-ending normalization on log reads —
rstrip("\r\n")applied. - L-03 TOCTOU on symlink creation —
os.symlinkwrapped intry/except FileExistsError. - L-05 Tmux session name uniqueness —
SESSION = f"ai_lsc_{os.getuid()}". - L-06 Port range check in
chat/api.py—_validate_portapplied toport_idup-front. - L-08
create_collectionreports success for existing collection — now checks the existing collection's dimension and surfaces mismatches.
SaaS-only tool blocklist (v3.1 hardening follow-up)
After the v3.1 release, the user identified a policy gap: SaaS-only tools (closed-source desktop apps with restrictive ToS, hosted LLM routers with no local binary, managed inference services) must not be addable to the registry. The audit found that the actual code registry was already clean — the SaaS names (OpenRouter, LM Studio, Groq, Codestral) only appeared as stale references in README.md and docs/ADR-001-capability-architecture.md. They were removed from the code at some earlier point but the docs were never updated.
What changed:
-
Stale doc references fixed.
README.mdL6 row now listsLiteLLM Proxy, 9Router Proxy, Odysseus, LangChain, LangFlow, OpenAI Swarm, Agno(matching the actual registry).README.mdL8 row replacesCodestral(Mistral SaaS model) withOpenHandsandCodex.docs/ADR-001-capability-architecture.mdLLM Gateway providers now listLiteLLM · 9Router Proxy · Local proxy(wasLiteLLM · OpenRouter · Local proxy); Inference Engine providers replaceLM StudiowithSGlang. -
SaaS blocklist added to the registry validator (
src/ai_lsc/registry/validator.py). The following tool_ids are now rejected at validation time with a clear error message pointing to this section:openrouter,lm_studio,lmstudio,groq,together_ai,together,fireworks_ai,fireworks,replicate,runpod,modal,anyscale,perplexity,cohere,mistral_api,deepseek_api,openai_api,huggingface_inference -
SaaS host regex added to the validator. Even if a tool_id isn't on the blocklist, the validator now rejects any launcher cmd or installer cmd that references a known SaaS provider hostname (api.openai.com, api.anthropic.com, api.openrouter.ai, api.groq.com, api.together.xyz, api.fireworks.ai, api.replicate.com, api.perplexity.ai, api.cohere.ai, api.mistral.ai, api.deepseek.com, generativelanguage.googleapis.com, api.lmstudio.ai, endpoint.huggingface.com). Localhost URLs (127.0.0.1, localhost, 0.0.0.0) are always allowed.
-
Localhost-only env forced for CLI tools that CAN call SaaS.
claude_code,aider,openhands,fabric, and the newcodexentry now have their launcher cmds prepended with the appropriate localhost env vars:Tool Env vars forced claude_codeANTHROPIC_BASE_URL=http://127.0.0.1:4000 ANTHROPIC_API_KEY=sk-ai-lsc-localaiderOPENAI_API_BASE=http://127.0.0.1:4000/v1 OPENAI_API_KEY=sk-ai-lsc-localopenhandsOPENAI_API_BASE=http://127.0.0.1:4000/v1 OPENAI_API_KEY=sk-ai-lsc-localfabricOPENAI_API_BASE=http://127.0.0.1:4000/v1 OPENAI_API_KEY=sk-ai-lsc-localcodex(new)OPENAI_BASE_URL=http://127.0.0.1:4000/v1 OPENAI_API_KEY=sk-ai-lsc-localThis breaks SaaS routing by default — the user must explicitly override the env var (via the service row port or a custom launcher cmd) to call a SaaS endpoint. Each tool's
depsnow includeslitellmso the Stack Editor flags a missing local proxy if the user hasn't staged one. -
New
codextool entry added toregistry/defaults.pyandregistry/layers/endpoints.py— OpenAI's open-source Codex CLI (@openai/codexnpm package), classified as L6 AI Endpoints, with the localhost-only launcher shown above. The user explicitly approved this addition with the caveat: "you can add codex and claude code but only if mapped by force to a localhost port for the engine or endpoint."
Verification: defaults.py validates with 0 errors (125 tools); layer files validate with 0 non-curl|sh errors (124 tools); all 5 localhost-mapped tools confirmed to have 127.0.0.1 in their launcher cmd; the SaaS blocklist correctly rejects openrouter and lm_studio with the documented error message; the SaaS host regex correctly rejects https://api.openai.com/v1 in a launcher cmd while allowing http://127.0.0.1:4000/v1.
License acceptance gate (v3.1 hardening follow-up #2)
After the SaaS blocklist landed, the user asked for a layered license-acceptance system: ToS/disclaimer warnings for proprietary tool pulls, per-tool license acceptance for open-source tools, and a license auto-approval registry where the user can pre-approve entire license types (accept all GPL, all AGPL, all MIT, all BSD, etc.) so they don't have to accept each tool individually. The auto-approval registry must NOT contain any SaaS or proprietary tools. lmstudio stays on the blocklist for aggressive ToS.
What changed:
-
New license catalog (
src/ai_lsc/registry/licenses.py) — 20 SPDX IDs across three categories:Category Auto-approvable? Disclaimer? Licenses OSI (open-source) ✅ Yes ❌ No MIT, Apache-2.0, GPL-2.0, GPL-3.0, AGPL-3.0, LGPL-3.0, BSD-2-Clause, BSD-3-Clause, MPL-2.0, ISC, PostgreSQL, Python SOURCE_AVAILABLE (fair-code) ❌ No ✅ Yes BSL-1.1, SSPL, RSALv2, Sustainable-Use, Dify-OSL PROPRIETARY (ToS-governed) ❌ No ✅ Yes (prominent) Proprietary, Anthropic-ToS, LMStudio-ToS (blocked) Each license entry includes: SPDX ID, human-readable name, category, URL to full text, one-paragraph summary, and an optional disclaimer shown in the acceptance dialog.
-
New
LicenseGateclass (src/ai_lsc/registry/license_gate.py) — sits between the user's "Install" click and the installer dispatch. For every tool installation, the gate checks (in order):- SaaS blocklist — if the tool_id is blocked, raises
LicenseBlockedimmediately (no dialog, no acceptance, no install). - Auto-approval registry (
config/license_approvals.json) — user-editable list of OSI-approved SPDX IDs. If the tool's license is in this list AND the license category is OSI, install proceeds without a dialog. Non-OSI licenses in the file are ignored + logged (defensive against hand-editing). - Per-tool acceptance registry (
config/license_acceptances.json) — auto-managed; records every per-tool acceptance so the user isn't prompted twice. - If none of the three cover the tool, raises
LicenseAcceptanceRequired(carrying theLicenseInfofor the dialog).
LicenseGate.add_auto_approval(spdx)rejects non-OSI licenses with a clearValueError— source-available and proprietary licenses cannot be auto-approved, period. - SaaS blocklist — if the tool_id is blocked, raises
-
New
LicenseAcceptanceDialog(src/ai_lsc/ui/dialogs/license_dialog.py) — Qt dialog shown when the gate raisesLicenseAcceptanceRequired. Shows:- Tool name + tool_id + license name + SPDX ID
- Category banner (green ✓ for OSI, orange ⚠ for source-available, red ⛔ for proprietary)
- License summary (one paragraph)
- Disclaimer (only for source-available + proprietary — prominent red box)
- "Open in browser ↗" button linking to the full license text
- Confirmation checkbox (pre-checked for OSI, unchecked for non-OSI — the user must explicitly check it before Accept is enabled)
- Three buttons: Accept & Install (records per-tool acceptance), Accept all <license> (only for OSI — adds the SPDX to the auto-approval registry), Cancel
Also includes a
LicenseBlockedDialogfor the blocked case (just an OK button + suggestion to use a local alternative). -
licensefield added to the registry schema — every tool entry must now declare its license SPDX ID. The validator (registry/validator.py) enforces this:- Missing/empty
licensefield → error - Unknown SPDX ID (not in the license catalog) → error
- The
_REQUIRED_FIELDSset now includes"license"
Two backfill scripts added:
scripts/backfill_tool_licenses.py— adds thelicensefield to every tool based on a curated override table (85 tools mapped to known licenses: ollama→MIT, vllm→Apache-2.0, grafana→AGPL-3.0, redis→RSALv2, terraform→BSL-1.1, n8n→Sustainable-Use, claude_code→Anthropic-ToS, etc.)scripts/backfill_default_licenses.py— fills any remaining tools with"Proprietary"as the defensive default (83 tools defaulted — the user can review and update these to their actual licenses later)
- Missing/empty
-
License gate wired into the installer —
InstallerManager.__init__now accepts alicense_gateparameter.InstallerManager.run()andinstall_with_preflight()callself._check_license(tool_id, license_spdx)before any subprocess dispatch. If the gate raisesLicenseBlockedorLicenseAcceptanceRequired, the exception propagates up throughRuntimeExecutor.install_tool()to the UI. -
ServiceRowcatches license exceptions — the install thread'sexcept Exceptionhandler (which runs on the main thread viaQTimer.singleShot) calls_handle_license_exception()which:- For
LicenseBlocked→ showsLicenseBlockedDialog(just an OK button) - For
LicenseAcceptanceRequired→ showsLicenseAcceptanceDialogand connects the dialog'saccepted_individual/accepted_all_of_typesignals to handlers that record the acceptance and retry the install
- For
-
lmstudioblocklist comment — theSAAS_BLOCKLISTdefinition invalidator.pynow includes an explicit comment: "lm_studio / lmstudio: BLOCKED for aggressive ToS — the user considers LM Studio's Terms of Service restrictive enough to be equivalent to a SaaS offering, so it is auto-banned regardless of any per-tool acceptance the user might try to grant."
License distribution after backfill (135 unique tool_ids across defaults.py + layer files):
Proprietary 50 tools (defensive default for tools whose license is unknown)
MIT 49 tools
Apache-2.0 17 tools
AGPL-3.0 5 tools
GPL-3.0 3 tools
MPL-2.0 2 tools
Anthropic-ToS 1 tool (claude_code)
Python 1 tool (python)
BSL-1.1 1 tool (terraform)
PostgreSQL 1 tool (postgresql)
GPL-2.0 1 tool (mariadb)
RSALv2 1 tool (redis)
LGPL-3.0 1 tool (glances)
Dify-OSL 1 tool (dify)
Sustainable-Use 1 tool (n8n)
The 50 tools defaulted to "Proprietary" include some that are actually open-source (tmux, git, podman, docker) — the user can review and update these in defaults.py / the layer files. The gate will require individual acceptance for each until the license is corrected.
Verification:
- defaults.py: 125 tools, 0 validation errors
- Layer files: 124 tools, 0 non-curl|sh validation errors
- All 135 unique tool_ids have a
licensefield LicenseGate.check()correctly returnsneeds_acceptancefor fresh tools,acceptedafteraccept(),blockedfor SaaS-blocklist tool_idsLicenseGate.add_auto_approval()correctly rejects BSL-1.1 (source-available) and Proprietary withValueErrorlmstudioandlm_studioboth onSAAS_BLOCKLISTclaude_code(Anthropic-ToS) andn8n(Sustainable-Use) both surfaceneeds_disclaimer=Truevia their category- Auto-approval + acceptance files are created in
config/with the expected JSON structure
Bugs caught during the post-pass double-check (fixed)
Three latent bugs were introduced during the initial fix wave and caught by the double-check's 19 functional spot-checks. All three are now fixed:
installer._validate_tool_idregex allowed/— the regex wasr"^[A-Za-z0-9_.:\-/]+$"(with trailing/), so../../etc/passwdwould have passed. Tightened tor"^[A-Za-z0-9_.:\-]+$"(no/).- All three
_validate_tool_idvalidators accepted bare..and.—os.path.normpath('..')returns'..'unchanged, so the normpath check missed these. Added explicittool_id in {".", ".."}rejection in installer.py, executor.py, and lxc.py. - H-14
_ERROR_REstill flaggederror-correction—\b(word boundary) treats-as a non-word char, soerror-correctionhad a boundary betweenerrorandcorrection. Changed regex to\b(?:error|failed|not found|timeout|exception|traceback)(?![\w\-])— the negative lookahead rejects matches where the next character is a word char OR a hyphen.
Verification
- All 89 Python source files parse cleanly via
ast.parse. - All 124 tools in
defaults.pypass the strengthened validator (0 errors). - All 123 tools across the 13 layer files pass the strengthened validator (3 expected curl|sh-related warnings only).
- All 134 unique tool_ids (defaults + layers merged) pass every
_validate_tool_idvalidator (installer, executor, LXC) and every_validate_lxc_namecheck. - 19/19 functional spot-checks pass (after the 3 double-check fixes).
- Ticker edge + orphan detection simulated against the real
STACK_WIRINGSdata across 6 test scenarios — all behaved as expected.
Known issues
- PySide6 not available in some test environments. The new widget modules (
pipeline_ticker.py,workspace_tab.py) AST-parse cleanly and pass structural checks, but were not exercised by a live Qt instantiation test in the sandbox where v3.1 was finalized. Verify by running the app. open_webuivsopenwebuitool_id collision — the Pipeline Ticker surfaces this real registry inconsistency. Recommend a follow-up pass to reconcile.docs/screenshots/still reflects v3.0 — a screenshot refresh pass to capture the new Pipeline Ticker + Workspace Tab is overdue.
Upgrade notes
- If you have a v3.0
pipeline_state.json, it will continue to work — the schema is unchanged. - If you maintain custom layer-file entries, run
python scripts/backfill_layer_flags.pyto backfill the 5 new flag keys (is_ollama,is_docker,is_passive,is_mcp,is_skills_collection) defaulting toFalse. The validator will reject entries missing these keys. - If you have hardcoded
sk-ai-lsc-localAPI keys in your environment, setAI_LSC_LITELLM_KEYandAI_LSC_OPENWEBUI_KEYenv vars instead — the source-code default is now an empty string. - If you launch the app from a non-default working directory, config is now resolved against
BASE_DIRinstead ofos.getcwd()— this may move yourconfig.jsonto a new location on first v3.1 launch.
Artifacts
- Master tarball:
ai-lsc-master-2026-07-07.tar.gz - New scripts:
scripts/backfill_layer_flags.py - New widgets:
src/ai_lsc/ui/widgets/pipeline_ticker.py,src/ai_lsc/ui/widgets/workspace_tab.py - New docs:
CHANGES.md(this file),docs/ADR-002-pipeline-ticker.md,docs/ADR-003-workspace-tab.md - Skipped-items register:
whatremains.txt
v3.0 — earlier
See git history for the v3.0 release notes. v3.0 introduced the 13-layer architecture, the capability model, the OpenEngineer importer, and the Firecracker microVM export backend.