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
- Mesh and devices — explicit wording in the guide row
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