FreeLattice · Celeste reading home

Public, free to read, reuse, and adapt — Kirk Patrick Miller's notice for this edition. Not a patent grant.

GROK_FIRST_BRICK_TREE_ANY_MODEL_2026-10-01.txt · c04

585dfa8d0f · words 1500 · characters 28950–40744

Previous section · Next section

Exact section text · Full source · Source index

Archive pages whose extracted text matched this section: 1481, 1482, 1483, 1493. Unmatched pages stay on the source record with the checked page method. They are not guessed into a section.

diff --git a/docs/library/TREE_ANY_MODEL_v0.1.md b/docs/library/TREE_ANY_MODEL_v0.1.md
new file mode 100644
index 0000000..923ee70
--- /dev/null
+++ b/docs/library/TREE_ANY_MODEL_v0.1.md
@@ -0,0 +1,33 @@
+# Tree any model v0.1 (v-tree-any-model-v0)
+
+Alpha layer, with a byte-identical twin of `docs/modules/fl-connect.js` in FreeLattice. October 1, 2026.
+
+**Kirk's words:** any locally downloaded model is usable and changeable in any order on theLatticeTree, with no friction, via Connect or the Bridge, 127.0.0.1 only.
+
+## What blocked it (the map, Sep 28 walk)
+
+- A cap of five models per door (`parseModelNames`), five stars per door in the sky, twelve Gathering options.
+- A Connect tap replaced the whole remembered entry: other models, other doors (LM Studio), the door name and the speaking chair were lost.
+- After a Connect tap then a Find, a chosen model that fell past the cap was called "no longer at this door" and the speaking model switched under her.
+- With the Bridge up, Find asked only the Bridge, so LM Studio was invisible.
+- The paste field fetched any host (a LAN address was fetched).
+- The Chat heart named the door, not the model. The trainer read FreeLattice's `fl_active_model`, which the Tree never writes.
+
+## What changed
+
+- `local-mind-probe.js`: `MODEL_CAP` 64, `SKY_FOLD` 8 with "Show all N models" (the chosen one always shown); duplicates collapse; Gathering options up to 64; new `mergeFound(found)` adds or updates one door and keeps every other door, the roster, the chair binds and the speaking chair; `entryFromFoundList` keeps a chosen model that is at any found door ("Still speaking with X, now through Y." or "The chosen model X is at Y. Tap it to speak with it again."), and only says "no longer at this door" when it is truly absent; with the Bridge up, Find still asks the non-Ollama doors; `tryAddress` takes 127.0.0.1 / localhost / [::1] or a bare port only, and refuses anything else with zero fetches; one honesty line for LM Studio CORS.
+- `fl-connect.js` (shared, both repos): on the Tree a Connect tap calls `LocalMindProbe.mergeFound` (the old replace path stays below it for an older probe); the model list is passed along; the chosen model button has `aria-pressed="true"` and `is-chosen`. FreeLattice's own branch of `remember()` is unchanged.
+- `garden-thread.js`: the heart reads "Listening: Ollama · llama3.2:latest. On this machine only.", repaints when a mind is remembered, and a quiet "change mind" button opens Settings (not shown inside Settings).
+- `garden-trainer.js`: `activeModelName()` reads `fl_active_model` first, then the Tree's remembered model; the old `llama3.2` / `phi-pathway` fallbacks stay last.
+
+## Trust decision
+
+The Agent Bridge (127.0.0.1:3141, locked by FreeLattice #120) stays out of the Tree's model path: it would add pairing for a purely local act, it is Ollama-only, and it would touch #120's lock files. The Tree keeps the Ollama Bridge (11435) and direct loopback doors.
+
+## Later (not built)
+
+- Round Table sitters from roster seats and Gathering binds, so two models at one Ollama door can sit together.
+- LM Studio through the Ollama Bridge (a second upstream in `bridge/proxy-core.js`).
+- Teaching `FlConnect.probe` the OpenAI `/v1/models` shape (would change FreeLattice behavior).
+
+Layer, never delete.
diff --git a/docs/modules/fl-connect.js b/docs/modules/fl-connect.js
index 2e02eae..785ed99 100644
--- a/docs/modules/fl-connect.js
+++ b/docs/modules/fl-connect.js
@@ -319,2 +319,22 @@
 
+  // v-tree-any-model-v0: 11434 is the Ollama door; any other 127.0.0.1 port here is the Bridge
+  function doorNameFor(base, port) {
+    var p = String(port || '');
+    var m = String(base || '').match(/:(\d{1,5})\/?$/);
+    if (!p && m) p = m[1];
+    return (!p || p === '11434') ? 'Ollama' : 'Bridge';
+  }
+
+  // v-tree-any-model-v0: which model speaks now, so the Connect card can show it
+  function chosenModelName() {
+    try {
+      if (isAlpha()) {
+        var m = root.LocalMindProbe && root.LocalMindProbe.getRemembered && root.LocalMindProbe.getRemembered();
+        return m && m.model ? String(m.model) : '';
+      }
+      if (root.state && root.state.ollamaModel) return String(root.state.ollamaModel);
+      return String(localStorage.getItem('fl_ollamaModel') || '');
+    } catch (e) { return ''; }
+  }
+
   function remember(mind) {
@@ -327,2 +347,14 @@
     if (isAlpha()) {
+      // v-tree-any-model-v0: on the Tree, Connect merges into the sky (all models, other doors kept)
+      if (root.LocalMindProbe && typeof root.LocalMindProbe.mergeFound === 'function') {
+        try {
+          var merged = root.LocalMindProbe.mergeFound({
+            name: doorNameFor(base, mind.port),
+            url: base ? String(base).replace(/\/+$/, '') + '/api/tags' : '',
+            models: mind.models || [],
+            model: name
+          });
+          if (merged) { stopLoop(); return merged; }
+        } catch (eM) {}
+      }
       var entry = {
@@ -518,4 +550,9 @@
         btn.dataset.model = name;
+        // v-tree-any-model-v0: show what speaks
+        var isOn = chosenModelName() === name;
+        btn.setAttribute('aria-pressed', isOn ? 'true' : 'false');
+        if (isOn) btn.classList.add('is-chosen');
         btn.addEventListener('click', function () {
-          remember({ name: name, base: report.base, port: report.port });
+          // v-tree-any-model-v0: pass the whole list along (FreeLattice's branch ignores it)
+          remember({ name: name, base: report.base, port: report.port, models: report.models });
           refreshUI({ force: true });
@@ -687,2 +724,3 @@
       '.flc-model{min-height:44px;text-align:left;padding:.7rem .85rem;border-radius:10px;border:1px solid rgba(200,210,230,.16);background:rgba(8,6,18,.45);color:#e6ebf5;font:inherit;cursor:pointer}',
+      '.flc-model.is-chosen{outline:2px solid currentColor;font-weight:600}', /* v-tree-any-model-v0 */
       '.flc-btn{display:inline-block;min-height:48px;line-height:48px;padding:0 1.1rem;border-radius:10px;text-decoration:none;font-weight:600;border:0;cursor:pointer;font:inherit}',
diff --git a/docs/modules/garden-rooms.css b/docs/modules/garden-rooms.css
index 7c51be0..70a03fd 100644
--- a/docs/modules/garden-rooms.css
+++ b/docs/modules/garden-rooms.css
@@ -6186 +6186,32 @@ html.flc-hide-galaxy #gardenContainer {
 }
+
+/* v-tree-any-model-v0: every model at a door is a star. Eight show, then "Show all N".
+   A long chair list scrolls inside the picker. "change mind" sits quietly by the heart. */
+.settings-star-models { flex-wrap: wrap; }
+.settings-star-more {
+  font: inherit;
+  font-size: 0.85rem;
+  background: none;
+  border: 0;
+  color: inherit;
+  opacity: 0.75;
+  text-decoration: underline;
+  min-height: 44px;
+  padding: 0 0.5rem;
+  cursor: pointer;
+}
+.core-bind-options { max-height: 50vh; overflow-y: auto; overscroll-behavior: contain; }
+.thread-change {
+  font: inherit;
+  font-size: 0.82rem;
+  min-height: 32px;
+  margin: 0.15rem 0 0.35rem;
+  padding: 0.1rem 0.6rem;
+  background: none;
+  border: 1px solid rgba(200, 210, 230, 0.3);
+  border-radius: 999px;
+  color: inherit;
+  opacity: 0.85;
+  cursor: pointer;
+}
+.thread-change:focus-visible { outline: 2px solid currentColor; outline-offset: 2px; }
diff --git a/docs/modules/garden-thread.js b/docs/modules/garden-thread.js
index 4424654..51bf270 100644
--- a/docs/modules/garden-thread.js
+++ b/docs/modules/garden-thread.js
@@ -492,2 +492,20 @@
 
+  // v-tree-any-model-v0: "Listening: Ollama · llama3.2:latest. On this machine only."
+  function heartListening(mind) {
+    var speaking = mind.model || (mind.models && mind.models[0]) || '';
+    return 'Listening: ' + (mind.name || 'a mind at home') +
+      (speaking && speaking !== mind.name ? ' \u00b7 ' + speaking : '') + '. On this machine only.';
+  }
+  // v-tree-any-model-v0: every mounted thread repaints its heart when a mind is remembered
+  try {
+    window.addEventListener('fl-alpha-mind-remembered', function () {
+      var hearts = document.querySelectorAll('[data-thread-heart]');
+      if (!hearts.length) return;
+      var now = listener();
+      for (var hi = 0; hi < hearts.length; hi++) {
+        hearts[hi].textContent = now ? heartListening(now) : HEART_NONE;
+      }
+    });
+  } catch (eHeart) { /* fail-quiet */ }
+
   function mount(container, opts) {
@@ -508,3 +526,17 @@
     }
+    // v-tree-any-model-v0: name the model, not only the door (the line above stays as the base)
+    heart.setAttribute('data-thread-heart', '1');
+    if (mind) heart.textContent = heartListening(mind);
     root.appendChild(heart);
+    // v-tree-any-model-v0: change the mind from where you are
+    if (mind && !opts.inSettings) {
+      var change = el('button', 'thread-change', 'change mind');
+      change.type = 'button';
+      change.setAttribute('data-thread-change', '1');
+      change.addEventListener('click', function () {
+        if (window.GardenRooms && typeof GardenRooms.openPlace === 'function') GardenRooms.openPlace('settings');
+        else location.href = 'settings.html';
+      });
+      root.appendChild(change);
+    }
 
diff --git a/docs/modules/garden-trainer.js b/docs/modules/garden-trainer.js
index 644f165..eb2f293 100644
--- a/docs/modules/garden-trainer.js
+++ b/docs/modules/garden-trainer.js
@@ -70,2 +70,14 @@ const GardenTrainer = (() => {
   // ================================================================
+  // v-tree-any-model-v0: the Tree remembers its model in fl_alpha_local_mind.
+  // (Before: localStorage.getItem('fl_active_model') at each call, a FreeLattice key the Tree never writes.)
+  function activeModelName() {
+    try {
+      var a = localStorage.getItem('fl_active_model');
+      if (a) return a;
+      var m = window.LocalMindProbe && LocalMindProbe.getRemembered && LocalMindProbe.getRemembered();
+      if (m && m.model) return String(m.model);
+    } catch (e) {}
+    return '';
+  }
+
   function getTrainerTierUnlocks() {
@@ -730,3 +742,3 @@ const GardenTrainer = (() => {
     t1btn.textContent = 'Export Personality File';
-    t1btn.onclick = function() { exportPersonalityModelfile(localStorage.getItem('fl_active_model')); };
+    t1btn.onclick = function() { exportPersonalityModelfile(activeModelName()); };
     t1.appendChild(t1btn);
@@ -767,3 +779,3 @@ const GardenTrainer = (() => {
     btnPy.textContent = 'Export Python Fine-Tuner';
-    btnPy.onclick = function() { exportPythonHelper(localStorage.getItem('fl_active_model')); };
+    btnPy.onclick = function() { exportPythonHelper(activeModelName()); };
     t2btns.appendChild(btnPy);
@@ -898,5 +910,5 @@ const GardenTrainer = (() => {
     btnExpand.onclick = function() {
-      var proposal = proposeNextPathway(localStorage.getItem('fl_active_model'));
+      var proposal = proposeNextPathway(activeModelName());
       var artifact = expandPathway(proposal, {
-        modelName: localStorage.getItem('fl_active_model') || 'phi-pathway'
+        modelName: activeModelName() || 'phi-pathway'
       });
@@ -1091,3 +1103,3 @@ const GardenTrainer = (() => {
     keepBtn.onclick = function() {
-      exportPersonalityModelfile(localStorage.getItem('fl_active_model'));
+      exportPersonalityModelfile(activeModelName());
     };
diff --git a/docs/modules/local-mind-probe.js b/docs/modules/local-mind-probe.js
index e710071..2af788e 100644
--- a/docs/modules/local-mind-probe.js
+++ b/docs/modules/local-mind-probe.js
@@ -31,2 +31,5 @@
   var ROSTER_CAP = 4;
+  // v-tree-any-model-v0: every model at a door is choosable. Soft ceiling only against a runaway list.
+  var MODEL_CAP = 64;
+  var SKY_FOLD = 8; // stars shown before "Show all N"; the chosen one is always shown