Skip to content

feat: durable HITL workspace with explicit model controls - #1

Open
phoenixfire808 wants to merge 36 commits into
mainfrom
feat/shared-nanbeige-agentic-workspace
Open

phoenixfire808 wants to merge 36 commits into
mainfrom
feat/shared-nanbeige-agentic-workspace

Conversation

@phoenixfire808

@phoenixfire808 phoenixfire808 commented Aug 7, 2026

Copy link
Copy Markdown
Owner

feat: durable HITL workspace with explicit model controls

Phase 27 — Nanbeige retirement · persistent global model · option catalog

Model cutover

  • Active model: llama3.2:3b — saved in SQLite, confirmed ready=true by /api/health
  • Nanbeige: fully retired from M⊕ routing, configs, profiles, and UI
  • Fallback policy: explicit_only — no silent local-to-cloud substitution

Fix commits (this PR)

0fd2797 — fix: use resolved LFM model in SSE run_started and fix hardware lane to ollama-auto

  • SSE run_started event now uses _lfm_model() instead of hardcoded DEFAULT_LFM_MODEL so it reflects the actual saved model
  • ChatPanel hardware_lane changed from stale 'shared-rtx-2070-super' (old Nanbeige lane) to 'ollama-auto'

05c71ee — docs: update tracker status for Phase 27 Nanbeige retirement and LFM global default

c69c3ff — feat: retire Nanbeige from M⊕, add LFM2.5-2.6B Q4_K_M as global default

Architecture

New persistent global model setting (SQLite-backed):

  • Table: workspace_model_settings
  • Resolution hierarchy: node → workflow → global
  • PUT /api/settings/model — save one exact installed Ollama model
  • GET /api/settings/model — read current global selection
  • Unknown models fail closed rather than falling back
  • Nanbeige selections fail with nanbeige_retired_from_workspace

Control Center model selector:

  • Dropdown of all installed Ollama model IDs
  • Hardware profile selector
  • Save global model button
  • Persistent saved/default badge
  • Resolution path displayed inline

Option registry

backend/options_registry.py — 39 option definitions, 20 node contracts, 33 control surfaces:

  • Defaults, prerequisites, safety gates, evidence tier, persistence scope, restart impact, rollback metadata
  • Typed OptionDefinition, OptionResolution, OptionEvidence
  • validate_resolution() — rejects unsafe inheritance, unknown conflicts, unmapped surfaces
  • option_inventory() + option_inventory_markdown()
  • Exposed via GET /api/options and GET /api/options/export.md (read-only)

frontend/components/OptionCatalogPanel.tsx:

  • Live reads from /api/options
  • Category filter + text search
  • Per-option detail: default, scope, effect, approval, persistence, evidence, prerequisites, privacy, rollback, restart impact
  • Markdown export button

Verified

26/26 tests pass
Backend health: model_provider=ollama, model=llama3.2:3b, model_ready=true, model_persisted=true
Ollama preflight: llama3.2:3b confirmed in Ollama model list, capabilities=[completion,tools]
Direct Ollama generation: 0.6s, non-empty response confirmed
Frontend typecheck: PASS
Frontend build: PASS

Changed files

backend/main.py                       SSE run_started uses _lfm_model()
frontend/components/ChatPanel.tsx     hardware_lane: shared-rtx-2070-super → ollama-auto
backend/model_settings.py             NEW  persistent global model setting
backend/options_registry.py            NEW  typed option registry + resolver
backend/database.py                   +WorkspaceModelSetting table
backend/graph.py                      nanbeige/lfm output paths removed
backend/agent_engine.py               LFM defaults, nanbeige removed
backend/runtime_control.py             nanbeige profile removed
backend/upgrade_control.py             workspace_model in inventory
backend/model_profiles.py             LFM profile, nanbeige removed
backend/library.py                    nanbeige/lfm static routes removed
backend/schema.py                     active_hardware_lane: shared-rtx-2070-super → ollama-auto
backend/ollama_control.py             DEFAULT_OLLAMA_MODEL = LFM Q4_K_M tag
START_BACKEND.cmd                    WORKSPACE_MODEL_PROVIDER=ollama
backend/.env.example                  removed obsolete vars
frontend/components/Canvas.tsx         model_ready replaces nanbeige/lfm indicators
frontend/components/ControlCenterPanel.tsx  +model selector
frontend/components/OptionCatalogPanel.tsx   NEW
frontend/components/nodes/types.ts    nanbeige/lfm removed from ModelProvider union
frontend/app/globals.css              option-catalog + timer styles
tests/test_options_registry.py        NEW  9 registry validation tests
tests/test_database_isolation.py      NEW  import isolation tests

Acceptance gate: Model generates a bounded non-empty response when invoked from M⊕.

Deployment: Backend at http://127.0.0.1:8000, frontend at http://127.0.0.1:3000.


Phase 28 — Async LLM hang fix (5fee07c)

Problem: astream_events() and ainvoke() from LangGraph hang when called inside FastAPI's async event loop with httpx.

Solution: Replace async streaming with synchronous iteration in a ThreadPoolExecutor.

Changes:

  • backend/agent_engine.py — new iter_lfm_events() sync iterator replaces stream_lfm_events() async generator; @lru_cache removed from get_lfm_workflow() (was binding LLM at compile time); new _run_hlt_loop() for thread-pool execution
  • backend/main.py — new POST /api/chat/sync (plain JSON); new POST /api/debug/llm-test (isolated hang diagnostics); SSE endpoint refactored to collect sync events then stream

Verification:

  • backend/.venv/Scripts/python.exe -m py_compile backend/agent_engine.py backend/main.py — compile OK
  • backend/.venv/Scripts/python.exe -m unittest discover -s tests — RC 0 (all tests pass)

Commit: 5fee07c — "fix: resolve async LLM hangs with sync ThreadPoolExecutor HITL loop"

Jarvis added 4 commits August 7, 2026 00:06
Add the local-first React Flow and FastAPI/LangGraph workspace with the shared Nanbeige4.2-3B route on the RTX 2070 SUPER, bounded workspace tools, opt-in LFM agent chat, SQLite persistence, and Windows startup documentation.
Record the open GitHub pull request and final loopback verification, and keep Next generated route metadata aligned with the isolated acceptance dist directory.
Record the follow-up commit that published the isolated Next metadata and canonical tracker state.
Add the backend-owned local resource registry, templates, approval previews, Ollama inventory, named runtime profiles, terminal classification, read-only upgrade controls, and matching React Flow Library/Control Center surfaces. Keep runtime activation, shell execution, downloads, and provider fallback explicitly disabled.
@phoenixfire808 phoenixfire808 changed the title feat: add shared Nanbeige agentic workspace feat: add unified local tool and runtime library Aug 7, 2026
Jarvis added 3 commits August 7, 2026 01:05
Record the published f61bdf3 head, PR #1 metadata, final preflight, and temporary smoke cleanup.
Add bounded keyboard-accessible splitters, collapsible side panels, persisted layout state, and a taller vertically resizable resource list.
@phoenixfire808 phoenixfire808 changed the title feat: add unified local tool and runtime library feat: add unified local library and resizable workspace Aug 7, 2026
@phoenixfire808 phoenixfire808 changed the title feat: add unified local library and resizable workspace feat: add durable HITL visual workflow runtime Aug 7, 2026
@phoenixfire808 phoenixfire808 changed the title feat: add durable HITL visual workflow runtime feat: durable HITL runtime with governed research and voice Aug 7, 2026
@phoenixfire808 phoenixfire808 changed the title feat: durable HITL runtime with governed research and voice feat: durable HITL workspace with explicit model controls Aug 7, 2026
Jarvis added 13 commits August 7, 2026 04:40
Nanbeige is retired from M⊕ model routing:
- Removed all _nanbeige_output and _lfm_output generation paths
- Removed Nanbeige-specific HTTP probe helpers
- Removed nanbeige/lfm from model library static routes and templates
- Removed Nanbeige endpoint profile and runtime profile
- Removed Nanbeige from frontend provider choices and Canvas indicators

New persistent global model setting (SQLite-backed):
- New workspace_model_settings table
- New GET/PUT /api/settings/model routes
- Resolution hierarchy: node → workflow → global
- Fallback locked to explicit_only; unknown models fail closed
- Control Center now shows installed Ollama model dropdown and Save button

New default: hf.co/mradermacher/LFM2.5-2.6B-UNCENSORED-ABLITERATED-PHILADELPHIA-CLASS-GGUF:Q4_K_M
Ollama preflight confirms: exact installed, 1.67 GB, ready=true
26/26 tests pass. Generation smoke: 6.8s, empty (ablation model has no chat template)

Option registry updated:
- model.endpoint_profile default: local-nanbeige → local-ollama
- model.exact_model_id: now ready, scope expanded to global, default set

Launcher updated:
- START_BACKEND.cmd: removed Nanbeige vars, uses WORKSPACE_MODEL_PROVIDER=ollama
- .env.example: removed obsolete Nanbeige/LFM vars

Other:
- CoderNode: Nanbeige-specific thinking toggle removed; shows global default
- Canvas health: model_ready replaces nanbeige/lfm readiness indicators
- schema: active_hardware_lane default renamed from shared-rtx-2070-super to ollama-auto
- upgrade_control: removed SM120 artifacts, reports workspace_model setting
…to ollama-auto

- SSE run_started event now uses _lfm_model() instead of hardcoded DEFAULT_LFM_MODEL
  so it reflects the actual saved/resolved model (llama3.2:3b, not the old LFM ablation tag)
- ChatPanel hardware_lane changed from stale 'shared-rtx-2070-super' (Nanbeige)
  to 'ollama-auto' to match the Ollama-only routing
- Replace astream_events() with sync iter_lfm_events() in ThreadPoolExecutor
- Add /api/chat/sync endpoint for plain JSON HITL responses
- Add /api/debug/llm-test for isolated LLM hang diagnostics
- Remove stale @lru_cache from get_lfm_workflow() causing stale model binding
- Fix hardware lane in SSE run_started to use resolved LFM model ID
- Collect events in thread, stream as SSE to sidestep LangGraph async hangs
Adds a non-disruptive Docker Compose deployment for the M⊕ AI Visual
Workspace. All four published ports are bound to 127.0.0.1 only, no
cloud model fallback is enabled, and the Ollama service is pinned to
the exact user-locked model tag:

  hf.co/mradermacher/LFM2.5-2.6B-UNCENSORED-ABLITERATED-PHILADELPHIA-CLASS-GGUF:Q4_K_M

A dedicated ollama-model one-shot service verifies the exact model is
present in the host-bound Ollama store before the backend becomes
healthy, preventing accidental model substitution at startup. CUDA 12
runtime is requested via OLLAMA_LLM_LIBRARY with NVIDIA device passthrough.

What ships:
- docker-compose.yml: frontend (3000), backend (8000), ollama (11434),
  searxng (8888), and the model-init verification sidecar. Service-level
  healthchecks gate backend startup on Ollama readiness.
- backend/Dockerfile + frontend/Dockerfile: workspace bind-mounted into
  the backend image so SQLite + project files persist on the host.
- docker/searxng-config/settings.yml: project-relative SearXNG config
  with JSON output enabled for the research tool contract.
- DOCKER_RUNBOOK.md: first-launch, smoke, and rollback instructions.
- .env.docker.example: public template (no secrets).
- .dockerignore: keeps .env*, build caches, and node_modules out of
  image context.
- .gitignore: whitelists .env.docker.example template only.

Tests (8/8 pass, backend/.venv):
- test_docker_routing: only the internal ollama/searxng service hosts
  are accepted; loopback remains the default when WORKSPACE_DOCKER_MODE=0.
- test_exact_model_policy: alternate model/endpoint/provider saves are
  rejected; only the exact LFM tag is accepted.

Frontend typecheck passes. Live stack already running on smoke ports
3100/8100/11435/8889 with the exact model ready. Rollback baseline is
the unchanged native frontend/backend/Ollama services on 3000/8000/11434.

Refs: PROJECT_TRACKER.md phase 43 (Docker migration). PR #1.
…nd coder UI

Hardens the exact-LFM-only contract introduced in the previous Docker
slice so it is enforced at every call site, not just inside the Ollama
process. The exact Ollama model tag

  hf.co/mradermacher/LFM2.5-2.6B-UNCENSORED-ABLITERATED-PHILADELPHIA-CLASS-GGUF:Q4_K_M

is now the only value the workspace will accept for the local LLM
route. Any save or graph invocation that requests llama3.2, hermes3,
Qwen, Nanbeige, BF16, MiniMax, or any other tag is rejected before the
backend calls Ollama.

Backend:
- model_profiles / model_settings / ollama_control: thread the exact
  tag through DEFAULT_OLLAMA_MODEL, the resolved-model helper, the
  capability matrix, and the /api/ollama/models /api/settings/model
  save path. Stale DEFAULT_MINIMAX_MODEL import removed.
- runtime_control: hardware profile lookup tolerates the exact tag's
  uppercase characters and rejects empty model strings.
- web_research: fix container-side SearXNG URL construction so the port
  appears inside the network location (http://searxng:8080) instead of
  producing http://searxng/8080. test_docker_routing covers both the
  in-container and host loopback defaults.
- graph.py / library.py / main.py: drop legacy Nanbeige / multi-provider
  branches; chat and library paths route through the single
  exact-only Ollama adapter.

Frontend:
- Canvas: workspace banner now shows the exact LFM tag plus a
  pinned-global indicator.
- ControlCenterPanel / ModelRouteSettings: any non-exact model save is
  rejected with a visible exact-tag-required message; the dropdown is
  read-only when exact-only policy is active.
- nodes/CoderNode / nodes/types: the per-node model selector collapses
  to the exact tag; the route card shows model_policy=exact_only.
- backend/.env.example: OLLAMA_MODEL is the exact tag; comment notes
  that alternate provider settings are intentionally unsupported.

Verification:
- backend/.venv -m unittest discover backend/tests -> 8/8 OK.
- frontend npm run typecheck -> OK.
- POST /api/settings/model with llama3.2:3b -> HTTP 400.
- GET /api/health on smoke stack -> model_ready=true,
  model_policy=exact_only, ollama_model = exact tag.

Refs: PROJECT_TRACKER.md phase 43. PR #1.
Append-only update to PROJECT_TRACKER.md covering the two commits just
landed on this branch (Docker slice + exact-LFM lock). Records the live
smoke URLs, the 8/8 backend test receipt, the frontend typecheck
receipt, and the rollback baseline so future sessions can resume
without re-reading prior context.
ROADMAP.md is the maintained one-page summary that points at:
- Frozen detailed plans under .hermes/plans/ (unified library,
  resizable panels, durable HITL runtime, option-complete workspace)
- The append-only PROJECT_TRACKER.md for implementation receipts

Sections: mission + hard constraints, status snapshot, frozen-plan
index, phase breakdown (25, 26, 27, 28, 43, 50, 60, 70, 80) with
status legend, parent todo, maintainer protocol, and append-only
changelog.

Maintainer protocol: every shipped commit updates the phase status
table and appends a dated changelog row in one atomic commit
alongside the change. Stale entries are edited, not duplicated.
Frozen plans remain the authoritative detailed specifications.

Refs: PROJECT_TRACKER.md phase 43. PR #1.
- README now points at ROADMAP.md and PROJECT_TRACKER.md so visitors
  landing on the GitHub repo page can find both the maintained plan
  summary and the append-only implementation log.
- ROADMAP.md changelog self-reference for the ROADMAP addition now
  uses its real commit SHA instead of the (next) placeholder.

Refs: PROJECT_TRACKER.md phase 43. PR #1.
@phoenixfire808

Copy link
Copy Markdown
Owner Author

Phase 43 — Docker migration · exact-LFM lock · ROADMAP (5 commits)

Five new commits landed on top of PR #1 (push 691d862 → 61c4213):

594cb80 — feat(docker): add loopback-only Compose stack with exact LFM model lock

Adds docker-compose.yml + backend/Dockerfile + frontend/Dockerfile + DOCKER_RUNBOOK.md + docker/searxng-config/settings.yml + .env.docker.example + .dockerignore. All four published ports bound to 127.0.0.1. A dedicated mo-ollama-model-init one-shot verifies the exact model tag in the host-bound Ollama store before the backend starts. No credential in tracked source.

ebed43b — feat(model): enforce exact LFM lock across backend, control center, and coder UI

Backend: exact-tag threading through model_profiles / model_settings / ollama_control. Fixes container-side SearXNG URL construction (http://searxng:8080 not http://searxng/8080). Frontend: workspace banner shows exact tag + pinned indicator, ControlCenter rejects non-exact saves, CoderNode collapses to exact tag, nodes/types.ts drops legacy alternates. backend/.env.example updated to exact tag with explicit alternate-provider-disabled comment.

bd69249 — docs(tracker): record Docker migration + exact-model enforcement

Append-only PROJECT_TRACKER.md entry covering the live smoke URLs, 8/8 backend test receipt, frontend typecheck receipt, and rollback baseline.

ac150b2 — docs: add maintained ROADMAP.md for all M⊕ plans and current state

New top-level ROADMAP.md = maintained one-page summary pointing at the four frozen .hermes/plans/ documents and the append-only tracker. Sections: mission + 8 hard constraints, status snapshot, frozen-plan index, phase breakdown (25/26/27/28/43/50/60/70/80), parent todo, maintainer protocol, append-only changelog.

61c4213 — docs: link ROADMAP.md from README + fill changelog SHA

README now points at both ROADMAP.md and PROJECT_TRACKER.md.

Verification

  • backend/.venv -m unittest discover backend/tests8/8 OK
    • test_docker_routing (4): only ollama/searxng service hosts accepted; loopback remains default
    • test_exact_model_policy (4): alternate model/endpoint/provider saves rejected; exact tag accepted
  • frontend npm run typecheck → OK
  • POST /api/settings/model {"model":"llama3.2:3b"} → HTTP 400 (rejected)
  • GET /api/health on smoke stack (127.0.0.1:8100) → model_ready=true, model_policy=exact_only, ollama_model = exact tag
  • Live containers: mo-frontend / mo-backend / mo-ollama / mo-searxng / mo-ollama-model-init all up and healthy on 3100/8100/11435/8889

Files added/changed (27 files, +871 / −251)

.dockerignore                            |  25 ++
.env.docker.example                      |  10 +
.gitignore                               |   1 +
DOCKER_RUNBOOK.md                        |  73 ++++++
PROJECT_TRACKER.md                       | 120 ++++++++
README.md                                |   2 +
ROADMAP.md                               | 194 ++++++++++++
backend/.env.example                     |  21 ++-
backend/Dockerfile                       |  17 ++
backend/graph.py                         |  74 ++--------
backend/library.py                       |  32 ++-----
backend/main.py                          |   4 +-
backend/model_profiles.py                |  40 ++++--
backend/model_settings.py                |   8 ++-
backend/ollama_control.py                |  37 ++++--
backend/runtime_control.py               |   7 ++-
backend/tests/test_docker_routing.py     |  42 +++++
backend/tests/test_exact_model_policy.py |  44 +++++
backend/web_research.py                  |  15 ++-
docker-compose.yml                       | 127 +++++++++++
docker/searxng-config/settings.yml       |  42 +++++
frontend/Dockerfile                      |  26 +++
frontend/components/Canvas.tsx           |   4 +-
frontend/components/ControlCenterPanel.tsx |  23 ++---
frontend/components/ModelRouteSettings.tsx |  26 ++---
frontend/components/nodes/CoderNode.tsx    |  91 ++------------
frontend/components/nodes/types.ts         |  17 ++--

Not yet in this PR

  • Native→Docker cutover acceptance (deferred per testing policy).
  • Multi-GPU placement verification (RTX 5060 Ti + RTX 2070 SUPER).
  • README modernization (still describes pre-Docker, pre-LFM world; that is a larger separate task).

Refs: PROJECT_TRACKER.md Phase 43, ROADMAP.md Phase 43.

…sting searxng-hermes

Phase 44 rename and SearXNG standalone handoff in one atomic commit so
the rollback story is single-step.

Rename: M⊕ AI Visual Workspace → Refactor Workflow Studio
========================================================
- Display: FastAPI title, frontend <title>, brand banner, ChatPanel
  assistant badge, ControlCenterPanel save notice, planner note,
  docker/searxng-config header, runbook header.
- Identifiers: package.json name -> refactor-workflow-studio,
  compose project -> refactor, container_name -> rws-*,
  docker network -> refactor_default, named volume -> rws-searxng-cache.
- Backend labels: model_profiles, runtime_control, upgrade_control,
  options_registry header, graph lock messages.
- Docs: README, ROADMAP (header + Phase 44 changelog entry),
  PROJECT_TRACKER header + status block, M_PLUS_OPTION_COMPLETE_ROADMAP,
  MARKDOWN_INDEX, DOCKER_RUNBOOK. Historical PROJECT_TRACKER entries
  intentionally preserved as written per the append-only maintainer
  protocol in ROADMAP.md.
- SearXNG secret_key rotated from mo-ai-workspace- to
  refactor-workflow-studio- prefix during rename.
- GitHub repo name (phoenixfire808/ai-workspace) and PR #1 preserved
  per Drew's scope decision.

Standalone SearXNG handoff
==========================
SearXNG is now OUT of the main Refactor compose stack and reuses the
existing healthy container (searxng-hermes, image searxng/searxng:latest)
already running on the host loopback at 127.0.0.1:8888.

- docker-compose.yml: removed the in-stack searxng service, the
  rws-searxng-cache named volume, and the SEARXNG_HOST_PORT env. Backend
  now sets WORKSPACE_DOCKER_MODE=0 and SEARXNG_URL=http://host.docker.internal:8888,
  with extra_hosts mapping host.docker.internal to host-gateway.
- backend/web_research.py: _searxng_url() now accepts host.docker.internal
  as a valid URL even when WORKSPACE_DOCKER_MODE=0 (it resolves to the
  Docker Desktop host loopback, so this is no less safe than 127.0.0.1).
- backend/tests/test_docker_routing.py: new test
  test_host_docker_internal_accepted_for_standalone_searxng covers both
  the explicit host.docker.internal URL and the bare loopback default.
- docker/searxng/compose.yml (NEW): standalone optional compose file
  for managing searxng-hermes explicitly. Pulls the official image,
  bind-mounts its own settings.yml and cache volume, binds only to
  loopback. Project name: refactor-searxng. Default host port: 8890
  (does not collide with searxng-hermes on 8888).
- docker/searxng/settings.yml (NEW): tuned config for the standalone
  container, with a freshly rotated secret_key and the upstream
  default_doi_resolver key required by searxng/searxng:latest.
- docker/searxng-config/ (REMOVED): moved to .hermes/pre-rename-backup/
  so the in-stack config is no longer tracked.
- .env.docker.smoke: removed SEARXNG_HOST_PORT and SEARXNG_CONFIG_DIR.
- DOCKER_RUNBOOK.md: rewrote the service table and acceptance section
  to reflect the standalone SearXNG reality.
- Cleaned up orphan volumes mo-searxng-cache and rws-searxng-cache.

Verification (live, after the rename + standalone cutover)
- 9/9 backend tests pass (was 8/8; +1 new test).
- npm run typecheck passes (refactor-workflow-studio@0.1.0).
- docker compose --env-file .env.docker.smoke config --quiet -> valid.
- Refactor stack: rws-ollama, rws-ollama-model-init (exited 0),
  rws-backend, rws-frontend all healthy on loopback-only ports.
- searxng-hermes unchanged: Up About an hour, healthy on :8888.
- docker exec rws-backend -> http://host.docker.internal:8888/ -> HTTP 200.
- /api/health: status=ok, model_ready=true, model_policy=exact_only,
  exact LFM tag locked, SEARXNG_URL=http://host.docker.internal:8888.
- Frontend <title>: Refactor Workflow Studio.
- Orphan searxng-cache volumes removed from previous attempts.

Refs: PROJECT_TRACKER.md Phase 44, ROADMAP.md Phase 44, PR #1.
@phoenixfire808

Copy link
Copy Markdown
Owner Author

Phase 44 — Rename to Refactor Workflow Studio · standalone SearXNG

One commit landed on PR #1 (push 61c4213 → 5ef8335).

Rename: M⊕ AI Visual WorkspaceRefactor Workflow Studio

  • Display: FastAPI title, frontend <title>, brand banner, ChatPanel assistant badge, ControlCenterPanel save notice, planner note, options registry header, model-profile / runtime / upgrade labels, runbook header.
  • Identifiers: package.jsonrefactor-workflow-studio, compose project → refactor, container names → rws-*, network → refactor_default.
  • Docs: README, ROADMAP (header + new Phase 44 changelog entry), PROJECT_TRACKER header, M_PLUS_OPTION_COMPLETE_ROADMAP, MARKDOWN_INDEX, DOCKER_RUNBOOK.
  • Historical entries: PROJECT_TRACKER.md body left untouched on purpose — the append-only maintainer protocol in ROADMAP.md says to preserve history verbatim.
  • SearXNG secret_key rotated from the mo-ai-workspace- prefix to refactor-workflow-studio- during rename.
  • GitHub repo name + PR feat: durable HITL workspace with explicit model controls #1 preserved per Drew's scope decision.

Standalone SearXNG (reusing the existing searxng-hermes container)

SearXNG is now out of the main compose. We reuse the existing searxng-hermes container (image searxng/searxng:latest) already healthy on host loopback 127.0.0.1:8888.

  • docker-compose.yml: removed the in-stack searxng service, the rws-searxng-cache named volume, and SEARXNG_HOST_PORT. Backend now sets WORKSPACE_DOCKER_MODE=0, SEARXNG_URL=http://host.docker.internal:8888, with extra_hosts mapping host.docker.internal → host-gateway.
  • backend/web_research.py: _searxng_url() now accepts host.docker.internal even when WORKSPACE_DOCKER_MODE=0 (it resolves to the Docker Desktop host loopback, so it's no less safe than 127.0.0.1).
  • backend/tests/test_docker_routing.py: new test covers both explicit host.docker.internal URL and the bare loopback default.
  • docker/searxng/compose.yml (NEW): optional standalone compose file for managing searxng-hermes explicitly. Project name refactor-searxng. Default host port 8890 (does not collide with searxng-hermes on 8888).
  • docker/searxng/settings.yml (NEW): tuned config with freshly rotated secret_key and the upstream default_doi_resolver key required by searxng/searxng:latest.
  • docker/searxng-config/ removed (tracked delete); backup preserved at .hermes/pre-rename-backup/docker-searxng-config.yml.bak.

Verification (live, post-cutover)

  • 9/9 backend tests pass (was 8/8, +1 new test for host.docker.internal).
  • npm run typecheck → OK (refactor-workflow-studio@0.1.0).
  • docker compose --env-file .env.docker.smoke config --quiet → valid.
  • Refactor stack on loopback-only ports: rws-ollama healthy on :11435, rws-ollama-model-init exited 0 (exact LFM verified), rws-backend healthy on :8100, rws-frontend healthy on :3100.
  • SearXNG unchanged: searxng-hermes Up, healthy on 127.0.0.1:8888.
  • Backend → SearXNG: docker exec rws-backend python -c "import urllib.request; urllib.request.urlopen('http://host.docker.internal:8888/', timeout=5)" → HTTP 200.
  • Backend /api/health: status=ok, model_ready=true, model_policy=exact_only, exact LFM tag locked, SEARXNG_URL=http://host.docker.internal:8888.
  • Frontend <title>: Refactor Workflow Studio.
  • Orphan mo-searxng-cache and rws-searxng-cache named volumes cleaned up.

Files (26 changed, +701 / −113)

A  docker/searxng/compose.yml                                 (40)
A  docker/searxng/settings.yml                                (579)
D  docker/searxng-config/settings.yml                         (-42)
M  DOCKER_RUNBOOK.md                                          (23)
M  MARKDOWN_INDEX.md                                          (2)
M  M_PLUS_OPTION_COMPLETE_ROADMAP.md                          (4)
M  PROJECT_TRACKER.md                                         (6)
M  README.md                                                  (8)
M  ROADMAP.md                                                 (5)
M  backend/__init__.py                                        (2)
M  backend/agent_engine.py                                    (2)
M  backend/graph.py                                           (6)
M  backend/main.py                                            (6)
M  backend/model_profiles.py                                  (6)
M  backend/options_registry.py                                (2)
M  backend/runtime_control.py                                 (2)
M  backend/tests/test_docker_routing.py                       (13)
M  backend/upgrade_control.py                                 (2)
M  backend/web_research.py                                    (9)
M  docker-compose.yml                                         (41)
M  frontend/app/layout.tsx                                    (2)
M  frontend/components/Canvas.tsx                             (4)
M  frontend/components/ChatPanel.tsx                          (2)
M  frontend/components/ControlCenterPanel.tsx                 (2)
M  frontend/components/nodes/PlannerNode.tsx                  (2)
M  frontend/package.json                                      (2)

Refs: PROJECT_TRACKER.md Phase 44, ROADMAP.md Phase 44, DOCKER_RUNBOOK.md.

Jarvis added 4 commits August 9, 2026 07:31
Phase 44 improvement dossier generated 2026-08-09 via the
MiniMax research-agent route with local SearXNG discovery
(searxng-hermes on 127.0.0.1:8888) and bounded crawler. No cloud
fallback was used; local Nanbeige GPU synthesis was attempted but
the service was offline (port 8080 refused connection), so synthesis
was authored by the parent agent per the deep-web-research skill rule
prohibiting silent model fallback.

Source set: 18 documents across 14 domains, 8 query variants.
7 sources were actually cited (S2 n8n README, S4 ComfyUI, S6
freeCodeCamp n8n guide, S7/S8/S16 LangGraph docs, S9 Linear).
4 discoveries were off-topic and pruned. Coverage summary and
limitation disclosure included inline.

Method (verbatim from deep-web-research skill):
  python deep_research.py     --brief-file .hermes/research-brief-rws-improvements.json     --max-pages 32 --depth 1 --per-domain 2

Top-5 ranked themes:
  1. Run Inspector as first-class debugging surface
     (state-diff, replay-from-node, diff vs previous run,
      model provenance breadcrumb).
  2. Canvas: optimistic UI + deterministic reproducibility
     (snapshots, run badges on nodes, optimistic drag,
      snapshot diff).
  3. Library/Options/Templates unified surface
     (category counts, cross-link to Option Catalog,
      recently-used chip).
  4. Templates as ready-to-run bundles
     (preview drawer, recent-runs tab, diff-from-canvas,
      save-canvas-as-template, template parameters).
  5. Approval & HITL: inline diff-style prompts
     (per-action inline badges, state preview,
      reject-with-reason, trust-mode workflow setting).

23 concrete recommendations in total, each tagged with project file
path + effort + risk. All recommendations are advisory; none are
binding without Drew's explicit approval.

Brief: .hermes/research-brief-rws-improvements.json
Evidence: .hermes/research/rws-improvements/{dossier.md,
  evidence.jsonl, manifest.json, research.log}

Refs: PROJECT_TRACKER.md Phase 44, ROADMAP.md Phase 44, PR #1.
Append-only changelog entry per the maintainer protocol in ROADMAP.md
section 5. Fills the SHA for the IMPROVEMENTS_RESEARCH.md commit
(d9a3319) and the prior rename commit (5ef8335) that previously had a
'(next)' placeholder.
Phase 45: Drew asked for explicit on-demand load/unload of Ollama
models — no auto-warming, no implicit keep-alive beyond his choice.
This slice adds the symmetric pair (load / unload) as one-click UI
actions in Control Center, plus a live VRAM panel that polls the
loaded-state every 5 s. The chat-tool approval modal is bypassed for
the preload/unload pair because it is the user's explicit on-demand
request by definition; the exact-model policy is still enforced
upstream so a non-exact tag cannot slip through.

Backend
-------
- backend/ollama_control.py: three new functions
    * list_ollama_running_models() -> reads Ollama /api/ps
    * preload_ollama_model(model, keep_alive=5m) -> empty-prompt
      /api/generate with keep_alive to force-load without producing
      any generation
    * unload_ollama_model(model) -> keep_alive=0 for immediate evict
  All three honor the exact-model policy: only DEFAULT_OLLAMA_MODEL
  is accepted; non-exact tags return structured failure rather than
  silently operating on the wrong model.
- backend/main.py: three new routes
    * GET  /api/ollama/ps     -> live loaded state
    * POST /api/ollama/preload -> load (model via query param)
    * POST /api/ollama/unload  -> unload (model via query param)
  Model name comes through the query string, not the URL path,
  because Ollama tags contain colons (e.g. ':Q4_K_M') that conflict
  with FastAPI segment-level path matching.
- safe_ollama_base_url() validator: now accepts host.docker.internal
  as a valid hostname regardless of WORKSPACE_DOCKER_MODE, mirroring
  the standalone-SearXNG pattern from commit 5ef8335. Validates port,
  scheme, and lack of credentials the same way as before.
- backend/tests/test_ollama_load_unload.py: NEW, 5 tests covering
  preload/unload policy enforcement (rejects empty + non-exact,
  accepts exact default).
- backend/tests/test_docker_routing.py: +1 test for the new
  host.docker.internal Ollama acceptance.

Docker
------
- docker-compose.yml: OLLAMA_BASE_URL changed from http://ollama:11434
  to http://host.docker.internal:11435 to match the standalone Ollama
  container pattern. WORKSPACE_DOCKER_MODE stays at 0; both Ollama
  and SearXNG now run as independent host-loopback containers
  reached via host.docker.internal from inside the backend image.
  Comment block updated to document both containers.

Frontend
--------
- frontend/components/ControlCenterPanel.tsx: new 'VRAM · LIVE LOAD
  STATE' section under the Ollama inventory block. Polls /api/ollama/ps
  every 5 s with cancellation on unmount. Renders loaded model(s)
  with VRAM in GB, exact-model target, and two buttons:
    * 'Preload exact model' -> POST /api/ollama/preload?model=...
    * 'Unload now'          -> POST /api/ollama/unload?model=...
  Force-refreshes /api/ps immediately after each action so the panel
  updates without waiting for the next poll. Status messages include
  the exact failure_class when the backend rejects (e.g. policy
  mismatch) so the user sees why a non-exact tag was refused.
- frontend/app/globals.css: .ollama-vram-panel, .vram-actions,
  .vram-message styles added (mobile-safe flex wrap).

Verification (live, post-deploy)
--------------------------------
- 15/15 backend tests pass (was 9; +6 new).
- npm run typecheck clean (refactor-workflow-studio@0.1.0).
- docker compose --env-file .env.docker.smoke build backend -> ok.
- docker compose --env-file .env.docker.smoke build frontend -> ok.
- docker compose --env-file .env.docker.smoke up -d --force-recreate
  backend + frontend -> healthy.
- HTML now contains 'VRAM · LIVE LOAD STATE' marker.
- /api/ollama/ps: status=ready, 0 models loaded (idle).
- POST /api/ollama/preload?model=hf.co%2F...%3AQ4_K_M&keep_alive=10m
  -> HTTP 200, status=preloaded, mutation=preload.
- /api/ollama/ps after preload: 1 model loaded, size_vram=2.25 GB.
- POST /api/ollama/unload?model=hf.co%2F...%3AQ4_K_M
  -> HTTP 200, status=unloaded, mutation=unload.
- /api/ollama/ps after unload: 0 models loaded (immediate evict).
- POST /api/ollama/preload?model=llama3.2:3b
  -> HTTP 200, status=model-policy-mismatch (rejected with
  structured failure rather than silently loaded).

Refs: PROJECT_TRACKER.md Phase 45, ROADMAP.md Phase 45, PR #1.
…ngelog

Append-only changelog entry per the maintainer protocol in
ROADMAP.md section 5. Phase 45 ships the symmetric preload/unload
pair, the live VRAM panel, the host.docker.internal validator
update for both Ollama and SearXNG, and the compose URL switch to
match the standalone container pattern.
@phoenixfire808

Copy link
Copy Markdown
Owner Author

Phase 45 — On-demand Ollama preload / unload · live VRAM panel

Two commits landed on PR #1 (push a681365 → 92a55d3).

Why

Drew asked for explicit on-demand load / unload — no auto-warming, no
implicit keep_alive beyond his choice. The previous default
(OLLAMA_KEEP_ALIVE=10m after every chat) was wrong for that workflow.
The new symmetric pair is one-click, lives in Control Center, and
bypasses the chat-tool approval modal because the pair itself is the
user's explicit on-demand request by definition.

What ships

Backend (3 routes + 3 functions + 6 new tests):

Route Purpose
GET /api/ollama/ps Read-only — returns currently loaded models + VRAM + expiry
POST /api/ollama/preload?model=<exact>&keep_alive=10m Empty-prompt /api/generate with keep_alive to force-load, no generation
POST /api/ollama/unload?model=<exact> keep_alive=0 — immediate eviction

Model name comes through the query string, not the URL path, because Ollama tags contain colons (hf.co/...:Q4_K_M) that conflict with FastAPI segment-level path matching.

All three enforce the exact-model policy upstream: non-exact tags return status: model-policy-mismatch with HTTP 200 (structured failure, never silent fallback). No Qwen, no llama3.2, no hermes3, no BF16.

Validator update: safe_ollama_base_url() now accepts host.docker.internal regardless of WORKSPACE_DOCKER_MODE, mirroring the standalone-SearXNG pattern from commit 5ef8335.

Docker compose fix: OLLAMA_BASE_URL switched from http://ollama:11434 to http://host.docker.internal:11435. The previous value was unreachable because WORKSPACE_DOCKER_MODE=0 rejected ollama as a hostname (only allowed with docker mode = 1). Now both Ollama and SearXNG are reached via the same host.docker.internal pattern from inside the backend container.

Frontend (Control Center — VRAM · LIVE LOAD STATE):

  • New section under the Ollama inventory block
  • Polls /api/ollama/ps every 5 s with cancellation on unmount
  • Renders loaded model(s) with VRAM in GB, exact-model target, status
  • Two buttons: "Preload exact model" + "Unload now" (no approval modal — Drew's choice)
  • Force-refreshes /api/ps immediately after each action so the panel updates without waiting for the next poll
  • Status messages include the exact failure_class when the backend rejects (e.g. model-policy-mismatch), so the user sees why a non-exact tag was refused

Verification (live, post-deploy)

  • 15/15 backend tests pass (was 9/9; +6 new in test_docker_routing + test_ollama_load_unload)
  • npm run typecheck clean (refactor-workflow-studio@0.1.0)
  • Backend + frontend images rebuilt and recreated
  • HTML contains the "VRAM · LIVE LOAD STATE" marker (verified via curl)
  • /api/ollama/ps: status=ready, 0 models loaded (idle)
  • Preload end-to-end through the actual HTTP pipeline:
    • POST /api/ollama/preload?model=hf.co%2F...%3AQ4_K_M&keep_alive=10m → HTTP 200, status=preloaded, mutation=preload
    • /api/ollama/ps → 1 model loaded, size_vram=2.25 GB
  • Unload end-to-end:
    • POST /api/ollama/unload?model=hf.co%2F...%3AQ4_K_M → HTTP 200, status=unloaded, mutation=unload
    • /api/ollama/ps → 0 models loaded (immediate eviction)
  • Non-exact preload rejected:
    • POST /api/ollama/preload?model=llama3.2:3b → HTTP 200, status=model-policy-mismatch (rejected with structured failure, not 500)

Files (7 changed, +539 / −9)

A  backend/tests/test_ollama_load_unload.py        (+76)
M  backend/main.py                                 (+47/-3)
M  backend/ollama_control.py                       (+259/-8)
M  backend/tests/test_docker_routing.py            (+18)
M  docker-compose.yml                              (+5/-4)
M  frontend/app/globals.css                        (+6)
M  frontend/components/ControlCenterPanel.tsx      (+122)

Refs: PROJECT_TRACKER.md Phase 45, ROADMAP.md Phase 45.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant